/** * Agent skills discovery (VM: agent-workspace then agent-skills preamble order). */ import test from 'brittle' import { readFileSync } from 'node:fs' import vm from 'node:vm' const WORKSPACE = readFileSync( new URL('../lib/agent/agent-workspace.js', import.meta.url), 'utf8' ) const SKILLS = readFileSync(new URL('../lib/agent/agent-skills.js', import.meta.url), 'utf8') /** @returns {Record} */ function loadSandbox() { const sandbox = { TextDecoder, Uint8Array, console } vm.createContext(sandbox) vm.runInContext(WORKSPACE, sandbox, { filename: 'agent-workspace.js' }) vm.runInContext(SKILLS, sandbox, { filename: 'agent-skills.js' }) return sandbox } test('bareAgentDiscoverSkills workspace precedes global', async (t) => { const files = new Map() const skillWs = '---\nname: p2p-os-status\ndescription: From workspace\n---\n\n# WS\n' const skillGl = '---\nname: p2p-os-status\ndescription: From global\n---\n\n# GL\n' files.set('/home/x/.agent/workspace/skills/ws-skill/SKILL.md', skillWs) files.set('/home/x/.agent/skills/gl-only/SKILL.md', '---\nname: gl-only\ndescription: Global only\n---\n') files.set('/home/x/.agent/skills/p2p-os-status/SKILL.md', skillGl) const vfs = { readdir: async (p) => { if (p === '/home/x/.agent/workspace/skills') return ['ws-skill'] if (p === '/home/x/.agent/skills') return ['gl-only', 'p2p-os-status'] return [] }, readFile: async (p) => { const u = files.get(p) if (!u) throw new Error('enoent') return new TextEncoder().encode(u) } } const s = loadSandbox() const discover = /** @type {(ctx: object, paths: object) => Promise} */ ( s.bareAgentDiscoverSkills ) const list = await discover( { vfs, b4a: null }, { workspaceSkills: '/home/x/.agent/workspace/skills', skillsGlobal: '/home/x/.agent/skills' } ) t.is(list.length, 2) const ws = list.find((x) => /** @type {{ id: string }} */ (x).id === 'ws-skill') t.ok(ws) const dup = list.filter((x) => /** @type {{ name: string }} */ (x).name === 'p2p-os-status') t.is(dup.length, 1) t.is(dup[0].source, 'workspace') }) test('bareAgentLoadSkillMarkdown returns full SKILL.md', async (t) => { const body = '---\nname: my-skill\ndescription: D\n---\n\n# Full\n' const files = new Map() files.set('/home/x/.agent/workspace/skills/foo/SKILL.md', body) const vfs = { readdir: async () => ['foo'], readFile: async (p) => { const u = files.get(p) if (!u) throw new Error('enoent') return new TextEncoder().encode(u) } } const s = loadSandbox() const load = /** @type {(ctx: object, paths: object, q: string) => Promise} */ ( s.bareAgentLoadSkillMarkdown ) const r = await load( { vfs, b4a: null }, { workspaceSkills: '/home/x/.agent/workspace/skills', skillsGlobal: '/home/x/.agent/skills' }, 'my-skill' ) t.ok(/** @type {{ ok: boolean }} */ (r).ok) t.ok(String(/** @type {{ content: string }} */ (r).content).includes('# Full')) }) test('bareAgentSkillsCompactPrompt includes table row', async (t) => { const files = new Map() files.set( '/home/x/.agent/workspace/skills/p2p-os-status/SKILL.md', '---\nname: p2p-os-status\ndescription: Check P2P health\n---\n\n# X\n' ) const vfs = { readdir: async () => ['p2p-os-status'], readFile: async (p) => { const u = files.get(p) if (!u) throw new Error('enoent') return new TextEncoder().encode(u) } } const s = loadSandbox() const compact = /** @type {(ctx: object, paths: object) => Promise} */ ( s.bareAgentSkillsCompactPrompt ) const block = await compact( { vfs, b4a: null }, { workspaceSkills: '/home/x/.agent/workspace/skills', skillsGlobal: '/home/x/.agent/skills' }, 8000 ) t.ok(block.includes('p2p-os-status')) t.ok(block.includes('Check P2P')) })