229 lines
8.2 KiB
JavaScript
229 lines
8.2 KiB
JavaScript
/**
|
|
* Agent workspace loader (VM, same preamble order as /bin/agent).
|
|
*/
|
|
import test from 'brittle'
|
|
import { readFileSync } from 'node:fs'
|
|
import vm from 'node:vm'
|
|
|
|
const CODE = readFileSync(
|
|
new URL('../lib/agent-workspace.js', import.meta.url),
|
|
'utf8'
|
|
)
|
|
|
|
/** @returns {Record<string, unknown>} */
|
|
function load(extra = {}) {
|
|
const sandbox = {
|
|
TextDecoder,
|
|
TextEncoder,
|
|
Uint8Array,
|
|
console,
|
|
...extra
|
|
}
|
|
vm.createContext(sandbox)
|
|
vm.runInContext(CODE, sandbox, { filename: 'agent-workspace.js' })
|
|
return sandbox
|
|
}
|
|
|
|
test('bareAgentLoadWorkspacePrompt concatenates files', async (t) => {
|
|
const files = new Map()
|
|
files.set('/home/x/.agent/workspace/SOUL.md', '# Hi')
|
|
files.set('/home/x/.agent/workspace/AGENTS.md', '# Rules')
|
|
for (const n of [
|
|
'IDENTITY.md',
|
|
'USER.md',
|
|
'TOOLS.md',
|
|
'MEMORY.md',
|
|
'BOOTSTRAP.md',
|
|
'HEARTBEAT.md',
|
|
'PROMPT.md'
|
|
])
|
|
files.set('/home/x/.agent/workspace/' + n, '# ' + n)
|
|
const vfs = {
|
|
readFile: async (p) => {
|
|
const u = files.get(p)
|
|
if (!u) throw new Error('enoent')
|
|
return new TextEncoder().encode(u)
|
|
}
|
|
}
|
|
const s = load()
|
|
const loadFn = /** @type {(ctx: object, paths: object, cap?: number) => Promise<string>} */ (
|
|
s.bareAgentLoadWorkspacePrompt
|
|
)
|
|
const block = await loadFn(
|
|
{ vfs, b4a: null },
|
|
{ workspace: '/home/x/.agent/workspace', workspaceMemory: '/home/x/.agent/workspace/memory' },
|
|
50000
|
|
)
|
|
t.ok(block.includes('=== SOUL.md ==='))
|
|
t.ok(block.includes('# Hi'))
|
|
t.ok(block.includes('=== AGENTS.md ==='))
|
|
})
|
|
|
|
test('bareAgentEnsureWorkspace seeds from share', async (t) => {
|
|
const written = []
|
|
const files = new Map()
|
|
const set = (p, txt) => files.set(p, txt)
|
|
set('/share/agent-workspace/SOUL.md', '# Seeded soul')
|
|
set('/share/agent-workspace/AGENTS.md', '# A')
|
|
set('/share/agent-workspace/IDENTITY.md', '# I')
|
|
set('/share/agent-workspace/USER.md', '# U')
|
|
set('/share/agent-workspace/TOOLS.md', '# T')
|
|
set('/share/agent-workspace/MEMORY.md', '# M')
|
|
set('/share/agent-workspace/BOOTSTRAP.md', '# B')
|
|
set('/share/agent-workspace/HEARTBEAT.md', '# H')
|
|
set('/share/agent-workspace/PROMPT.md', '# P')
|
|
set('/share/agent-workspace/memory/.gitkeep', '')
|
|
set('/share/agent-workspace/loader.stub.js', '// l')
|
|
set('/share/agent-workspace/index.stub.js', '// i')
|
|
set('/share/agent-workspace/skill-loader.stub.js', '// sl')
|
|
set('/share/agent-workspace/README-agent.md', '# R')
|
|
const vfs = {
|
|
async mkdir(_p, _o) {},
|
|
async readFile(p) {
|
|
if (files.has(p)) return new TextEncoder().encode(files.get(p))
|
|
throw new Error('enoent')
|
|
},
|
|
async writeFile(p, buf) {
|
|
const txt =
|
|
buf instanceof Uint8Array ? new TextDecoder().decode(buf) : String(buf)
|
|
written.push([p, txt])
|
|
files.set(p, txt)
|
|
}
|
|
}
|
|
const s = load()
|
|
const ensure = /** @type {(ctx: object, paths: object) => Promise<void>} */ (
|
|
s.bareAgentEnsureWorkspace
|
|
)
|
|
await ensure(
|
|
{ vfs, b4a: null },
|
|
{
|
|
dir: '/home/x/.agent',
|
|
workspace: '/home/x/.agent/workspace',
|
|
workspaceMemory: '/home/x/.agent/workspace/memory',
|
|
workspaceSkills: '/home/x/.agent/workspace/skills'
|
|
}
|
|
)
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/SOUL.md'))
|
|
const soul = written.find(([p]) => p === '/home/x/.agent/workspace/SOUL.md')
|
|
t.ok(soul && soul[1].includes('Seeded soul'))
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/skill-loader.js'))
|
|
})
|
|
|
|
test('bareAgentEnsureSkillTemplates copies skill seeds when missing for xai provider', async (t) => {
|
|
const written = []
|
|
const files = new Map()
|
|
const set = (p, txt) => files.set(p, txt)
|
|
set('/share/agent-workspace/skills/.gitkeep', '')
|
|
set('/share/agent-workspace/skills/p2p-os-status/SKILL.md', '# Skill')
|
|
set('/share/agent-workspace/skills/bare-os-kernel-proc/SKILL.md', '# Proc skill')
|
|
set('/share/agent-workspace/skills/bare-os-super-developer/SKILL.md', '# Dev skill')
|
|
set('/share/agent-workspace/skills/agent-ops/SKILL.md', '# Ops skill')
|
|
set('/share/agent-workspace/skills/xai-compat/SKILL.md', '# xAI skill')
|
|
set('/share/agent-workspace/skills/holesail/SKILL.md', '# Holesail skill')
|
|
set('/share/agent-workspace/skills/hdms/SKILL.md', '# Hdms skill')
|
|
set('/share/agent-workspace/skill-loader.stub.js', '// stub')
|
|
const vfs = {
|
|
async mkdir(_p, _o) {},
|
|
async readFile(p) {
|
|
if (files.has(p)) return new TextEncoder().encode(files.get(p))
|
|
throw new Error('enoent')
|
|
},
|
|
async writeFile(p, buf) {
|
|
const txt =
|
|
buf instanceof Uint8Array ? new TextDecoder().decode(buf) : String(buf)
|
|
written.push([p, txt])
|
|
files.set(p, txt)
|
|
}
|
|
}
|
|
const s = load()
|
|
const fn = /** @type {(ctx: object, paths: object) => Promise<void>} */ (
|
|
s.bareAgentEnsureSkillTemplates
|
|
)
|
|
await fn(
|
|
{ vfs, b4a: null },
|
|
{
|
|
dir: '/home/x/.agent',
|
|
workspace: '/home/x/.agent/workspace',
|
|
workspaceSkills: '/home/x/.agent/workspace/skills'
|
|
},
|
|
{ provider: 'xai' }
|
|
)
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/p2p-os-status/SKILL.md'))
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/bare-os-kernel-proc/SKILL.md'))
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/bare-os-super-developer/SKILL.md'))
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/agent-ops/SKILL.md'))
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/xai-compat/SKILL.md'))
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/holesail/SKILL.md'))
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/hdms/SKILL.md'))
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/skill-loader.js'))
|
|
})
|
|
|
|
test('bareAgentEnsureSkillTemplates skips xai skill for non-xai provider', async (t) => {
|
|
const written = []
|
|
const files = new Map()
|
|
const set = (p, txt) => files.set(p, txt)
|
|
set('/share/agent-workspace/skills/.gitkeep', '')
|
|
set('/share/agent-workspace/skills/p2p-os-status/SKILL.md', '# Skill')
|
|
set('/share/agent-workspace/skills/bare-os-kernel-proc/SKILL.md', '# Proc skill')
|
|
set('/share/agent-workspace/skills/bare-os-super-developer/SKILL.md', '# Dev skill')
|
|
set('/share/agent-workspace/skills/agent-ops/SKILL.md', '# Ops skill')
|
|
set('/share/agent-workspace/skills/xai-compat/SKILL.md', '# xAI skill')
|
|
set('/share/agent-workspace/skills/holesail/SKILL.md', '# Holesail skill')
|
|
set('/share/agent-workspace/skills/hdms/SKILL.md', '# Hdms skill')
|
|
set('/share/agent-workspace/skill-loader.stub.js', '// stub')
|
|
const vfs = {
|
|
async mkdir(_p, _o) {},
|
|
async readFile(p) {
|
|
if (files.has(p)) return new TextEncoder().encode(files.get(p))
|
|
throw new Error('enoent')
|
|
},
|
|
async writeFile(p, buf) {
|
|
const txt =
|
|
buf instanceof Uint8Array ? new TextDecoder().decode(buf) : String(buf)
|
|
written.push([p, txt])
|
|
files.set(p, txt)
|
|
}
|
|
}
|
|
const s = load()
|
|
const fn = /** @type {(ctx: object, paths: object, cfg?: object) => Promise<void>} */ (
|
|
s.bareAgentEnsureSkillTemplates
|
|
)
|
|
await fn(
|
|
{ vfs, b4a: null },
|
|
{
|
|
dir: '/home/x/.agent',
|
|
workspace: '/home/x/.agent/workspace',
|
|
workspaceSkills: '/home/x/.agent/workspace/skills'
|
|
},
|
|
{ provider: 'groq' }
|
|
)
|
|
t.ok(written.some(([p]) => p === '/home/x/.agent/workspace/skills/p2p-os-status/SKILL.md'))
|
|
t.ok(!written.some(([p]) => p === '/home/x/.agent/workspace/skills/xai-compat/SKILL.md'))
|
|
})
|
|
|
|
test('bareAgentSyncWorkspaceFromConfig writes IDENTITY and USER from config', async (t) => {
|
|
const written = []
|
|
const vfs = {
|
|
async mkdir(_p, _o) {},
|
|
async writeFile(p, buf) {
|
|
const txt =
|
|
buf instanceof Uint8Array ? new TextDecoder().decode(buf) : String(buf)
|
|
written.push([p, txt])
|
|
}
|
|
}
|
|
const s = load()
|
|
const sync = /** @type {(ctx: object, paths: object, cfg: object) => Promise<void>} */ (
|
|
s.bareAgentSyncWorkspaceFromConfig
|
|
)
|
|
await sync(
|
|
{ vfs, b4a: null },
|
|
{ workspace: '/home/x/.agent/workspace' },
|
|
{ owner_name: 'Raven', agent_label: 'Bear' }
|
|
)
|
|
const id = written.find(([p]) => p === '/home/x/.agent/workspace/IDENTITY.md')
|
|
t.ok(id && id[1].includes('**Name:** Bear'))
|
|
t.ok(id && id[1].includes('Raven'))
|
|
const user = written.find(([p]) => p === '/home/x/.agent/workspace/USER.md')
|
|
t.ok(user && user[1].includes('Raven'))
|
|
})
|