// @vitest-environment node import { readFile } from 'node:fs/promises'; import path from 'node:path'; import { describe, expect, it } from 'vitest'; import { BUNDLED_CODING_PLUGIN_ROOTS, CodingPluginManifestError, loadBundledCodingPluginDefinitions, loadBundledCodingPluginDefinitionsSync, loadCodingPluginDefinition, parseCodingPluginManifest, parseAgentPluginsRootManifest, resolveBundledCodingPluginRootPaths, } from '../../electron/coding-plugins/manifest'; import { AGENT_PLUGINS_SCHEMA_URL, DATA_SERVICE_PLUGIN_DEFINITION, DATA_SERVICE_TOOL_NAMES, } from '../../shared/coding-plugins'; const PACKAGE_ROOT = path.resolve('resources/coding-plugins/data-service'); const GAME_RESOURCE_ROOT = path.resolve('resources/coding-plugins/game-resource'); const WEB_SEARCH_ROOT = path.resolve('resources/coding-plugins/web-search'); async function packageManifests(): Promise<{ root: Record; capability: Record }> { return { root: JSON.parse(await readFile(path.join(PACKAGE_ROOT, 'plugin.json'), 'utf8')) as Record, capability: JSON.parse(await readFile(path.join(PACKAGE_ROOT, 'com.makelore/capability.json'), 'utf8')) as Record, }; } describe('bundled coding plugin manifests', () => { it('loads the fixed Data Service package and immutable declarations', async () => { const definitions = await loadBundledCodingPluginDefinitions(path.resolve('resources/coding-plugins')); const startupDefinitions = loadBundledCodingPluginDefinitionsSync(path.resolve('resources/coding-plugins')); expect(BUNDLED_CODING_PLUGIN_ROOTS).toEqual(['data-service', 'game-resource', 'web-search']); expect(resolveBundledCodingPluginRootPaths(path.resolve('resources/coding-plugins'))).toEqual([ PACKAGE_ROOT, GAME_RESOURCE_ROOT, WEB_SEARCH_ROOT, ]); expect(definitions).toHaveLength(3); expect(definitions[0]).toMatchObject({ id: 'makelore.data-service', adapterId: 'data-service', contractVersion: 1, skills: [{ id: 'data-service', entryPath: 'skills/data-service/SKILL.md' }], }); expect(definitions[0]?.tools.map(({ name }) => name)).toEqual(DATA_SERVICE_TOOL_NAMES); expect(definitions[0]).toEqual(DATA_SERVICE_PLUGIN_DEFINITION); expect(definitions.slice(1)).toMatchObject([ { id: 'makelore.game-resource', version: '1.0.0', runtimeKind: 'platform_hosted', acquisitionMode: 'user_acquired', releaseId: '00000000-0000-4000-8000-000000000105', provenance: { source: 'bundled', packageRoot: 'game-resource' }, skills: [{ id: 'game-resource', entryPath: 'skills/game-resource/SKILL.md' }], }, { id: 'makelore.web-search', version: '1.0.0', runtimeKind: 'platform_hosted', acquisitionMode: 'user_acquired', releaseId: '00000000-0000-4000-8000-000000000204', provenance: { source: 'bundled', packageRoot: 'web-search' }, skills: [{ id: 'makelore-web-search', entryPath: 'skills/makelore-web-search/SKILL.md' }], }, ]); expect(startupDefinitions).toEqual(definitions); expect(Object.isFrozen(startupDefinitions)).toBe(true); expect(Object.isFrozen(startupDefinitions[0]?.operations)).toBe(true); expect(definitions[0]?.operations).toHaveLength(14); expect(Object.isFrozen(definitions[0])).toBe(true); expect(Object.isFrozen(definitions[0]?.tools)).toBe(true); expect(DATA_SERVICE_PLUGIN_DEFINITION.tools).toHaveLength(10); }); it('accepts exact root and capability manifests and freezes the projection', async () => { const { root, capability } = await packageManifests(); expect(parseAgentPluginsRootManifest(root, 'plugin.json').$schema).toBe(AGENT_PLUGINS_SCHEMA_URL); const parsed = parseCodingPluginManifest(root, capability, { packageRoot: PACKAGE_ROOT, capabilityManifestPath: path.join(PACKAGE_ROOT, 'com.makelore/capability.json'), }); expect(parsed.id).toBe('makelore.data-service'); expect(Object.isFrozen(parsed)).toBe(true); expect(Object.isFrozen(parsed.tools[0]?.inputSchema)).toBe(true); }); it.each([ ['price field', (root: Record) => { root.price = 1; }], ['non-empty MCP', (root: Record) => { root.mcp = [{ name: 'unsupported' }]; }], ['non-empty scripts', (root: Record) => { root.scripts = ['run.js']; }], ['unknown adapter', (_root: Record, capability: Record) => { capability.adapterId = 'other'; }], ['unknown surface', (_root: Record, capability: Record) => { capability.surfaces = { projectSettings: 'other' }; }], ])('rejects %s', async (_label, mutate) => { const { root, capability } = await packageManifests(); mutate(root, capability); expect(() => parseCodingPluginManifest(root, capability, { packageRoot: PACKAGE_ROOT, capabilityManifestPath: path.join(PACKAGE_ROOT, 'com.makelore/capability.json'), })).toThrow(CodingPluginManifestError); }); it('rejects duplicate tools, escaping Skill entries and unconfirmed destructive tools', async () => { const { root, capability } = await packageManifests(); const duplicate = structuredClone(capability) as Record; duplicate.tools = [...(capability.tools as unknown[]), (capability.tools as unknown[])[0]]; expect(() => parseCodingPluginManifest(root, duplicate, { packageRoot: PACKAGE_ROOT, capabilityManifestPath: path.join(PACKAGE_ROOT, 'com.makelore/capability.json'), })).toThrow('duplicate tool identifier'); const escaping = structuredClone(capability) as Record; const skill = (escaping.skills as Array>)[0]; skill.entry = '../../outside/SKILL.md'; expect(() => parseCodingPluginManifest(root, escaping, { packageRoot: PACKAGE_ROOT, capabilityManifestPath: path.join(PACKAGE_ROOT, 'com.makelore/capability.json'), })).toThrow('escapes the package root'); const unconfirmed = structuredClone(capability) as Record; const tools = unconfirmed.tools as Array>; const destructive = tools.find((tool) => tool.mutation === 'destructive'); expect(destructive).toBeDefined(); const schema = destructive?.inputSchema as Record; const properties = schema.properties as Record; delete properties.confirmed; schema.required = (schema.required as string[]).filter((required) => required !== 'confirmed'); expect(() => parseCodingPluginManifest(root, unconfirmed, { packageRoot: PACKAGE_ROOT, capabilityManifestPath: path.join(PACKAGE_ROOT, 'com.makelore/capability.json'), })).toThrow('confirmed: true'); }); it('does not infer package roots from arbitrary directories', async () => { expect(resolveBundledCodingPluginRootPaths(path.resolve('tmp'))).toEqual([ path.resolve('tmp/data-service'), path.resolve('tmp/game-resource'), path.resolve('tmp/web-search'), ]); }); it('loads a single package directly through its exact capability path', async () => { await expect(loadCodingPluginDefinition(PACKAGE_ROOT)).resolves.toMatchObject({ id: 'makelore.data-service', tools: expect.arrayContaining([ expect.objectContaining({ name: 'data_service_configure' }), ]), }); }); });