120 lines
3.9 KiB
JavaScript
120 lines
3.9 KiB
JavaScript
/**
|
|
* 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<string, unknown>} */
|
|
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<unknown[]>} */ (
|
|
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<unknown>} */ (
|
|
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<string>} */ (
|
|
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'))
|
|
})
|