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, BUNDLED_CODING_PLUGIN_PREVIEW_SURFACES, BUNDLED_CODING_PLUGIN_SETTINGS_SURFACES, CODE_OWNED_PLUGIN_PERMISSION_IDS, DATA_SERVICE_CAPABILITY_IDS, DATA_SERVICE_OPERATION_DEFINITIONS, DATA_SERVICE_PLUGIN_ID, DATA_SERVICE_TOOL_NAMES, type AgentPluginsRootManifest, type CodingPluginDefinition, type CodingPluginAcquisitionMode, type CodingPluginExecutionMode, type CodingPluginPackageProvenance, type CodingPluginRuntimeKind, type CodingPluginSkillDefinition, type CodingPluginToolDefinition, type PluginToolMutation, } from '../../shared/coding-plugins'; /** * P0 deliberately has one statically enumerated package root. Keeping this * list relative makes it impossible for an environment variable or a project * file to add a package to the trusted catalog. */ export const BUNDLED_CODING_PLUGIN_ROOTS = Object.freeze([ 'data-service', ] as const); const CAPABILITY_MANIFEST_RELATIVE_PATH = 'com.makelore/capability.json'; const PACKAGE_MANIFEST_FILE = 'plugin.json'; 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 MAX_DISPLAY_TEXT = 256; const UNSUPPORTED_COMPONENT_KEYS = new Set([ 'commands', 'executables', 'hooks', 'mcp', 'mcpServers', 'scripts', ]); const FORBIDDEN_FIELD_PATTERN = /^(?:price|prices|points|balance|owner|owners|token|tokens|url|urls|entrypoint|entryPoint|react)$/iu; const DATA_SERVICE_OPERATION_CAPABILITIES: Readonly> = Object.freeze({ configure: 'data-service.control', inspect: 'data-service.control', list_projects: 'data-service.control', remove_collection: 'data-service.control', reset: 'data-service.control', remove_project: 'data-service.control', get_document: 'data-service.documents', list_documents: 'data-service.documents', put_document: 'data-service.documents', delete_document: 'data-service.documents', }); const ROOT_KEYS = new Set([ '$schema', 'name', 'version', 'description', 'author', 'extensions', ...UNSUPPORTED_COMPONENT_KEYS, ]); const CAPABILITY_KEYS = new Set([ 'schemaVersion', 'pluginId', 'contractVersion', 'scope', 'adapterId', 'requiresBackend', 'display', 'skills', 'tools', 'surfaces', ...UNSUPPORTED_COMPONENT_KEYS, ]); const AUTHOR_KEYS = new Set(['name']); const EXTENSIONS_KEYS = new Set(['com.makelore']); const MAKELore_EXTENSION_KEYS = new Set(['capabilityManifest']); const DISPLAY_KEYS = new Set(['name', 'description']); const SKILL_KEYS = new Set(['id', 'entry', 'grants']); const TOOL_KEYS = new Set([ 'name', 'label', 'description', 'capabilityId', 'operation', 'roles', 'mutation', 'projectWriteLease', 'permissions', 'inputSchema', ]); 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; export class CodingPluginManifestError extends Error { readonly code = 'plugin_manifest_invalid' as const; constructor( readonly filePath: string, readonly field: string, message: string, ) { super(`Invalid plugin manifest (${filePath}, ${field}): ${message}`); this.name = 'CodingPluginManifestError'; } } function isRecord(value: unknown): value is UnknownRecord { return value !== null && typeof value === 'object' && !Array.isArray(value); } function freezeDeep(value: T): T { if (value && typeof value === 'object' && !Object.isFrozen(value)) { for (const child of Object.values(value as UnknownRecord)) freezeDeep(child); Object.freeze(value); } return value; } function fail(filePath: string, field: string, message: string): never { throw new CodingPluginManifestError(filePath, field, message); } function assertRecord( value: unknown, filePath: string, field: string, ): UnknownRecord { if (!isRecord(value)) fail(filePath, field, 'expected an object'); return value; } function assertExactKeys( value: UnknownRecord, allowed: ReadonlySet, filePath: string, field: string, ): void { for (const key of Object.keys(value)) { if (FORBIDDEN_FIELD_PATTERN.test(key)) { fail(filePath, `${field}.${key}`, 'field is not allowed in a plugin manifest'); } if (!allowed.has(key)) { fail(filePath, `${field}.${key}`, 'field is not part of the exact P0 schema'); } } } function unsupportedComponentIsEmpty(value: unknown): boolean { if (value === undefined || value === null) return true; if (Array.isArray(value)) return value.length === 0; if (isRecord(value)) return Object.keys(value).length === 0; return false; } function rejectUnsupportedComponents(value: UnknownRecord, filePath: string, field: string): void { for (const key of UNSUPPORTED_COMPONENT_KEYS) { if (key in value && !unsupportedComponentIsEmpty(value[key])) { fail(filePath, `${field}.${key}`, 'non-empty component is unsupported in P0'); } } } function text(value: unknown, filePath: string, field: string, maximum = MAX_DISPLAY_TEXT): string { if (typeof value !== 'string' || value.trim().length === 0 || value.length > maximum) { fail(filePath, field, `expected non-empty text of at most ${maximum} characters`); } return value; } function stableId( value: unknown, filePath: string, field: string, pattern: RegExp, ): string { const result = text(value, filePath, field, 64); if (!pattern.test(result)) fail(filePath, field, 'identifier has an invalid shape'); return result; } function bool(value: unknown, filePath: string, field: string): boolean { if (typeof value !== 'boolean') fail(filePath, field, 'expected a boolean'); return value; } function positiveInteger(value: unknown, filePath: string, field: string): number { if (!Number.isSafeInteger(value) || (value as number) < 1) { fail(filePath, field, 'expected a positive safe integer'); } return value as number; } function uniqueStrings( value: unknown, filePath: string, field: string, pattern: RegExp, allowEmpty = false, ): string[] { if (!Array.isArray(value)) fail(filePath, field, 'expected an array'); const result: string[] = []; const seen = new Set(); for (const [index, candidate] of value.entries()) { if (typeof candidate !== 'string' || (!allowEmpty && candidate.trim().length === 0)) { fail(filePath, `${field}[${index}]`, 'expected a non-empty string'); } if (!pattern.test(candidate)) fail(filePath, `${field}[${index}]`, 'identifier has an invalid shape'); if (seen.has(candidate)) fail(filePath, `${field}[${index}]`, 'duplicate identifier'); seen.add(candidate); result.push(candidate); } return result; } function normalizeCandidatePath( packageRoot: string, baseDirectory: string, candidate: unknown, filePath: string, field: string, ): string { const value = text(candidate, filePath, field, 512); const portable = value.replaceAll('\\', '/'); if (portable.startsWith('/') || /^[A-Za-z]:\//u.test(portable) || /^[A-Za-z][A-Za-z0-9+.-]*:/u.test(portable)) { fail(filePath, field, 'path must be relative to the package root'); } const root = path.resolve(packageRoot); const resolved = path.resolve(baseDirectory, value); const relative = path.relative(root, resolved); if (!relative || relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) { fail(filePath, field, 'path escapes the package root'); } 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 ? new Set(['type', 'additionalProperties', 'required', 'properties']) : new Set([ 'type', 'additionalProperties', 'required', 'properties', 'items', 'pattern', 'minLength', 'maxLength', 'minimum', 'maximum', 'maxItems', 'const', ]); assertExactKeys(schema, allowed, filePath, field); if (typeof schema.type !== 'string' || !['array', 'boolean', 'integer', 'object', 'string'].includes(schema.type)) { fail(filePath, `${field}.type`, 'unsupported input schema type'); } if ('additionalProperties' in schema && typeof schema.additionalProperties !== 'boolean') { fail(filePath, `${field}.additionalProperties`, 'must be a boolean'); } if ('required' in schema) { uniqueStrings(schema.required, filePath, `${field}.required`, /^[A-Za-z_][A-Za-z0-9_]*$/u); } if ('properties' in schema) { const properties = assertRecord(schema.properties, filePath, `${field}.properties`); for (const [name, propertySchema] of Object.entries(properties)) { if (!/^[A-Za-z_][A-Za-z0-9_]*$/u.test(name)) { fail(filePath, `${field}.properties.${name}`, 'input property name is invalid'); } validateSchemaNode(propertySchema, filePath, `${field}.properties.${name}`); } if ('required' in schema) { for (const required of schema.required as string[]) { if (!(required in properties)) { fail(filePath, `${field}.required`, `required property is not declared: ${required}`); } } } } else if ('required' in schema && (schema.required as unknown[]).length > 0) { fail(filePath, `${field}.required`, 'required properties need a properties object'); } if ('items' in schema) validateSchemaNode(schema.items, filePath, `${field}.items`); for (const numericKey of ['minLength', 'maxLength', 'minimum', 'maximum', 'maxItems']) { if (numericKey in schema && (!Number.isSafeInteger(schema[numericKey]) || (schema[numericKey] as number) < 0)) { fail(filePath, `${field}.${numericKey}`, 'must be a non-negative safe integer'); } } if ('const' in schema && schema.const !== true && schema.const !== false) { fail(filePath, `${field}.const`, 'only boolean literal constants are supported'); } } 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, field: string, ): void { const required = Array.isArray(schema.required) ? schema.required : []; const properties = isRecord(schema.properties) ? schema.properties : {}; const confirmation = properties.confirmed; if (!required.includes('confirmed') || !isRecord(confirmation) || confirmation.type !== 'boolean' || confirmation.const !== true) { fail(filePath, field, 'destructive tools must require literal confirmed: true'); } } export function parseAgentPluginsRootManifest( value: unknown, filePath = PACKAGE_MANIFEST_FILE, ): AgentPluginsRootManifest { const root = assertRecord(value, filePath, 'root'); assertExactKeys(root, ROOT_KEYS, filePath, 'root'); rejectUnsupportedComponents(root, filePath, 'root'); if (root.$schema !== AGENT_PLUGINS_SCHEMA_URL) { fail(filePath, '$schema', `must equal ${AGENT_PLUGINS_SCHEMA_URL}`); } const name = stableId(root.name, filePath, 'name', /^[a-z][a-z0-9.-]{0,127}$/u); const version = text(root.version, filePath, 'version', 128); 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'); const authorName = text(author.name, filePath, 'author.name', 128); const extensions = assertRecord(root.extensions, filePath, 'extensions'); assertExactKeys(extensions, EXTENSIONS_KEYS, filePath, 'extensions'); const makeLore = assertRecord(extensions['com.makelore'], filePath, 'extensions.com.makelore'); assertExactKeys(makeLore, MAKELore_EXTENSION_KEYS, filePath, 'extensions.com.makelore'); const capabilityManifest = text( makeLore.capabilityManifest, filePath, 'extensions.com.makelore.capabilityManifest', 512, ); return freezeDeep({ $schema: AGENT_PLUGINS_SCHEMA_URL, name, version, description, author: { name: authorName }, extensions: { 'com.makelore': { capabilityManifest } }, }); } function parseSkill( value: unknown, index: number, packageRoot: string, capabilityDirectory: string, filePath: string, ): CodingPluginSkillDefinition { const skill = assertRecord(value, filePath, `skills[${index}]`); assertExactKeys(skill, SKILL_KEYS, filePath, `skills[${index}]`); const id = stableId(skill.id, filePath, `skills[${index}].id`, SKILL_ID_PATTERN); const entryPath = normalizeCandidatePath( packageRoot, capabilityDirectory, skill.entry, filePath, `skills[${index}].entry`, ); const grants = uniqueStrings( skill.grants, filePath, `skills[${index}].grants`, CAPABILITY_ID_PATTERN, ); for (const grant of grants) { if (!DATA_SERVICE_CAPABILITY_IDS.includes(grant as (typeof DATA_SERVICE_CAPABILITY_IDS)[number])) { fail(filePath, `skills[${index}].grants`, `unknown capability grant: ${grant}`); } } return { id, entryPath, grants }; } function parseTool( value: unknown, index: number, filePath: string, ): CodingPluginToolDefinition { const tool = assertRecord(value, filePath, `tools[${index}]`); assertExactKeys(tool, 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); if (!DATA_SERVICE_CAPABILITY_IDS.includes(capabilityId as (typeof DATA_SERVICE_CAPABILITY_IDS)[number])) { fail(filePath, `tools[${index}].capabilityId`, `unknown capability: ${capabilityId}`); } 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`, 'P0 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`); const permissions = uniqueStrings( tool.permissions, filePath, `tools[${index}].permissions`, /^[a-z][a-z0-9._-]{0,63}$/u, ); for (const permission of permissions) { if (!CODE_OWNED_PLUGIN_PERMISSION_IDS.includes(permission as (typeof CODE_OWNED_PLUGIN_PERMISSION_IDS)[number])) { fail(filePath, `tools[${index}].permissions`, `unknown code-owned permission: ${permission}`); } } validateSchemaNode(tool.inputSchema, filePath, `tools[${index}].inputSchema`, true); const schema = tool.inputSchema as UnknownRecord; if (schema.type !== 'object' || schema.additionalProperties !== false) { fail(filePath, `tools[${index}].inputSchema`, 'tool input schema must be an exact object'); } if (mutation === 'destructive') { validateDestructiveConfirmation(schema, filePath, `tools[${index}].inputSchema`); } return { name, label, description, capabilityId, operation, roles: ['parent'], mutation: mutation as PluginToolMutation, projectWriteLease, permissions, inputSchema: freezeDeep(structuredClone(schema)), }; } function validateDataServiceDefinition( definition: CodingPluginDefinition, filePath: string, ): void { const expectedNames = new Set(DATA_SERVICE_TOOL_NAMES); if (definition.id !== DATA_SERVICE_PLUGIN_ID) return; if (definition.tools.length !== DATA_SERVICE_TOOL_NAMES.length) { fail(filePath, 'tools', `Data Service must declare exactly ${DATA_SERVICE_TOOL_NAMES.length} tools`); } for (const tool of definition.tools) { if (!expectedNames.has(tool.name)) fail(filePath, `tools.${tool.name}`, 'unknown Data Service tool'); const expectedCapability = DATA_SERVICE_OPERATION_CAPABILITIES[tool.operation]; if (!expectedCapability || expectedCapability !== tool.capabilityId) { fail(filePath, `tools.${tool.name}.operation`, 'operation does not reference its capability'); } } const operationNames = definition.tools.map((tool) => tool.operation); if (new Set(operationNames).size !== operationNames.length) { fail(filePath, 'tools.operation', 'duplicate operation identifier'); } } function trustedMetadata( options: CodingPluginManifestParseOptions, declaredRuntimeKind: CodingPluginRuntimeKind, packageRoot: string, schemaVersion: 1 | 2, filePath: string, ): Pick { 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: CodingPluginManifestParseOptions = {}, ): CodingPluginDefinition { const packageRoot = path.resolve(options.packageRoot ?? path.dirname(options.capabilityManifestPath ?? '.')); const rootManifestPath = path.resolve( options.rootManifestPath ?? path.join(packageRoot, PACKAGE_MANIFEST_FILE), ); const root = parseAgentPluginsRootManifest(rootValue, rootManifestPath); const capabilityManifestPath = path.resolve( options.capabilityManifestPath ?? path.join(packageRoot, CAPABILITY_MANIFEST_RELATIVE_PATH), ); const capabilityRelativePath = path.relative(packageRoot, capabilityManifestPath); if (!capabilityRelativePath || capabilityRelativePath === '..' || 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'); 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', 'P0 plugins must be project-scoped'); const adapterId = text(capability.adapterId, capabilityManifestPath, 'adapterId', 64); if (!BUNDLED_CODING_PLUGIN_ADAPTER_IDS.includes(adapterId as (typeof BUNDLED_CODING_PLUGIN_ADAPTER_IDS)[number])) { fail(capabilityManifestPath, 'adapterId', `adapter is not code-owned: ${adapterId}`); } const requiresBackend = bool(capability.requiresBackend, capabilityManifestPath, 'requiresBackend'); const display = assertRecord(capability.display, capabilityManifestPath, 'display'); assertExactKeys(display, DISPLAY_KEYS, capabilityManifestPath, 'display'); const displayName = text(display.name, capabilityManifestPath, 'display.name'); const description = text(display.description, capabilityManifestPath, 'display.description'); const capabilityDirectory = path.dirname(capabilityManifestPath); const skillsValue = capability.skills; if (!Array.isArray(skillsValue) || skillsValue.length === 0) { fail(capabilityManifestPath, 'skills', 'must contain at least one Skill'); } const skills = skillsValue.map((value, index) => parseSkill( value, index, packageRoot, capabilityDirectory, capabilityManifestPath, )); if (new Set(skills.map((skill) => skill.id)).size !== skills.length) { fail(capabilityManifestPath, 'skills', 'duplicate Skill identifier'); } if (!Array.isArray(capability.tools) || capability.tools.length === 0) { fail(capabilityManifestPath, 'tools', 'must contain at least one tool'); } const tools = capability.tools.map((value, index) => parseTool(value, index, capabilityManifestPath)); if (new Set(tools.map((tool) => tool.name)).size !== tools.length) { fail(capabilityManifestPath, 'tools.name', 'duplicate tool identifier'); } if (new Set(tools.map((tool) => tool.operation)).size !== tools.length) { fail(capabilityManifestPath, 'tools.operation', 'duplicate operation identifier'); } 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'); } } const surfacesValue = capability.surfaces; const surfaces = assertRecord(surfacesValue, capabilityManifestPath, 'surfaces'); assertExactKeys(surfaces, SURFACE_KEYS, capabilityManifestPath, 'surfaces'); const normalizedSurfaces: { projectSettings?: string; previewRuntime?: string } = {}; if (surfaces.projectSettings !== undefined) { const projectSettings = text(surfaces.projectSettings, capabilityManifestPath, 'surfaces.projectSettings', 64); if (!BUNDLED_CODING_PLUGIN_SETTINGS_SURFACES.includes(projectSettings as (typeof BUNDLED_CODING_PLUGIN_SETTINGS_SURFACES)[number])) { fail(capabilityManifestPath, 'surfaces.projectSettings', `surface is not code-owned: ${projectSettings}`); } normalizedSurfaces.projectSettings = projectSettings; } if (surfaces.previewRuntime !== undefined) { const previewRuntime = text(surfaces.previewRuntime, capabilityManifestPath, 'surfaces.previewRuntime', 64); if (!BUNDLED_CODING_PLUGIN_PREVIEW_SURFACES.includes(previewRuntime as (typeof BUNDLED_CODING_PLUGIN_PREVIEW_SURFACES)[number])) { fail(capabilityManifestPath, 'surfaces.previewRuntime', `surface is not code-owned: ${previewRuntime}`); } normalizedSurfaces.previewRuntime = previewRuntime; } const definition = freezeDeep({ id: pluginId, version: root.version, contractVersion, displayName, description, ...trustedMetadata(options, 'bundled_typed', packageRoot, 1, capabilityManifestPath), scope: 'project' as const, adapterId, requiresBackend, skills, tools, operations: pluginId === DATA_SERVICE_PLUGIN_ID ? DATA_SERVICE_OPERATION_DEFINITIONS : tools.map(({ capabilityId, operation, name }) => ({ capabilityId, operation, toolName: name })), surfaces: normalizedSurfaces, }); validateDataServiceDefinition(definition, capabilityManifestPath); return definition; } function readJson(source: string, filePath: string): unknown { try { return JSON.parse(source) as unknown; } catch (error) { fail(filePath, 'root', `invalid JSON: ${error instanceof Error ? error.message : String(error)}`); } } export async function loadCodingPluginDefinition( packageRoot: string, options: CodingPluginLoadOptions = {}, ): Promise { 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 = normalizeCapabilityManifestPath( root, rootManifest.extensions['com.makelore'].capabilityManifest, pluginManifestPath, 'extensions.com.makelore.capabilityManifest', ); if (capabilityManifest !== CAPABILITY_MANIFEST_RELATIVE_PATH) { fail(pluginManifestPath, 'extensions.com.makelore.capabilityManifest', `must point to ${CAPABILITY_MANIFEST_RELATIVE_PATH}`); } const capabilityManifestPath = path.join(root, capabilityManifest); const capabilityValue = readJson(await readFile(capabilityManifestPath, 'utf8'), capabilityManifestPath); const definition = parseCodingPluginManifest(rootValue, capabilityValue, { packageRoot: root, rootManifestPath: pluginManifestPath, capabilityManifestPath, ...options, }); for (const skill of definition.skills) { try { await readFile(path.join(root, skill.entryPath), 'utf8'); } catch (error) { fail( capabilityManifestPath, `skills.${skill.id}.entry`, `Skill entry could not be read: ${error instanceof Error ? error.message : String(error)}`, ); } } return definition; } 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 = normalizeCapabilityManifestPath( root, rootManifest.extensions['com.makelore'].capabilityManifest, pluginManifestPath, 'extensions.com.makelore.capabilityManifest', ); if (capabilityManifest !== CAPABILITY_MANIFEST_RELATIVE_PATH) { fail(pluginManifestPath, 'extensions.com.makelore.capabilityManifest', `must point to ${CAPABILITY_MANIFEST_RELATIVE_PATH}`); } const capabilityManifestPath = path.join(root, capabilityManifest); const capabilityValue = readJson(readFileSync(capabilityManifestPath, 'utf8'), capabilityManifestPath); const definition = parseCodingPluginManifest(rootValue, capabilityValue, { packageRoot: root, rootManifestPath: pluginManifestPath, capabilityManifestPath, ...options, }); for (const skill of definition.skills) { try { readFileSync(path.join(root, skill.entryPath), 'utf8'); } catch (error) { fail( capabilityManifestPath, `skills.${skill.id}.entry`, `Skill entry could not be read: ${error instanceof Error ? error.message : String(error)}`, ); } } return definition; } export function resolveBundledCodingPluginRootPaths(resourcesRoot: string): string[] { const root = path.resolve(resourcesRoot); return BUNDLED_CODING_PLUGIN_ROOTS.map((relativeRoot) => path.join(root, relativeRoot)); } export const resolveBundledPluginRoots = resolveBundledCodingPluginRootPaths; function validateBundledDefinitions(definitions: readonly CodingPluginDefinition[]): void { const pluginIds = new Set(); const capabilityIds = new Set(); const skillIds = new Set(); const operationIds = new Set(); const toolIds = new Set(); for (const definition of definitions) { if (pluginIds.has(definition.id)) throw new Error(`Duplicate bundled plugin identifier: ${definition.id}`); pluginIds.add(definition.id); for (const skill of definition.skills) { if (skillIds.has(skill.id)) throw new Error(`Duplicate bundled Skill identifier: ${skill.id}`); skillIds.add(skill.id); for (const grant of skill.grants) capabilityIds.add(grant); } for (const tool of definition.tools) { if (toolIds.has(tool.name)) throw new Error(`Duplicate bundled tool identifier: ${tool.name}`); if (operationIds.has(tool.operation)) throw new Error(`Duplicate bundled operation identifier: ${tool.operation}`); toolIds.add(tool.name); operationIds.add(tool.operation); capabilityIds.add(tool.capabilityId); } } } export async function loadBundledCodingPluginDefinitions( resourcesRoot: string, ): Promise { const roots = resolveBundledCodingPluginRootPaths(resourcesRoot); const definitions = await Promise.all(roots.map(async (root, index) => { 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), 'name', `fixed bundle root ${expectedRoot} contains ${definition.id}`, ); } return definition; })); validateBundledDefinitions(definitions); return Object.freeze(definitions); } export function loadBundledCodingPluginDefinitionsSync( resourcesRoot: string, ): readonly CodingPluginDefinition[] { const roots = resolveBundledCodingPluginRootPaths(resourcesRoot); const definitions = roots.map((root, index) => { 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), 'name', `fixed bundle root ${expectedRoot} contains ${definition.id}`, ); } return definition; }); validateBundledDefinitions(definitions); return Object.freeze(definitions); } export const parseBundledCodingPlugins = loadBundledCodingPluginDefinitions; export const loadBundledPluginDefinitions = loadBundledCodingPluginDefinitions;