/** * Guest-safe Grok Build ports: glob, todos, plan mode, memory, hooks, edits. */ import test from 'brittle' import { readFileSync } from 'node:fs' import vm from 'node:vm' const STATE_SRC = readFileSync(new URL('../lib/agent/agent-state.js', import.meta.url), 'utf8') const HELPERS_SRC = readFileSync( new URL('../lib/agent/agent-helpers.js', import.meta.url), 'utf8' ) const PORT_SRC = readFileSync( new URL('../lib/agent/agent-grok-port.js', import.meta.url), 'utf8' ) const TOOLS_SRC = readFileSync(new URL('../lib/agent/agent-tools.js', import.meta.url), 'utf8') + '\n' + readFileSync( new URL('../lib/agent/agent-tool-definitions.js', import.meta.url), 'utf8' ) + '\n' + readFileSync( new URL('../lib/agent/agent-tool-dispatch.js', import.meta.url), 'utf8' ) function loadPort() { const sandbox = { TextDecoder, TextEncoder, Uint8Array, console } vm.createContext(sandbox) vm.runInContext(PORT_SRC, sandbox, { filename: 'agent-grok-port.js' }) return sandbox } function loadDispatch() { const sandbox = { TextDecoder, TextEncoder, Uint8Array, console } vm.createContext(sandbox) vm.runInContext(STATE_SRC, sandbox, { filename: 'agent-state.js' }) vm.runInContext(HELPERS_SRC, sandbox, { filename: 'agent-helpers.js' }) vm.runInContext(PORT_SRC, sandbox, { filename: 'agent-grok-port.js' }) vm.runInContext(TOOLS_SRC, sandbox, { filename: 'agent-tools.js' }) return sandbox } function makeVfs(initial) { /** @type {Map} */ const files = new Map(Object.entries(initial || {})) /** @type {Set} */ const dirs = new Set(['/']) for (const p of files.keys()) { let dir = String(p).replace(/\/[^/]+$/, '') while (dir && dir !== '/') { dirs.add(dir) dir = dir.replace(/\/[^/]+$/, '') } } const b4a = { from(v) { return new TextEncoder().encode(typeof v === 'string' ? v : String(v)) }, toString(v) { return new TextDecoder().decode(v) } } const vfs = { async mkdir(p) { dirs.add(String(p).replace(/\/+$/, '') || '/') }, async readFile(p) { if (!files.has(p)) throw new Error('enoent:' + p) return b4a.from(files.get(p) || '') }, async writeFile(p, buf) { const txt = buf instanceof Uint8Array ? new TextDecoder().decode(buf) : String(buf) files.set(p, txt) }, async unlink(p) { if (!files.has(p)) throw new Error('enoent:' + p) files.delete(p) }, async rm(p, opts) { if (opts && opts.recursive) { for (const key of [...files.keys()]) { if (key === p || String(key).startsWith(String(p) + '/')) files.delete(key) } return } if (!files.has(p)) throw new Error('enoent:' + p) files.delete(p) }, async readdir(p) { const root = String(p).replace(/\/+$/, '') || '/' if (!dirs.has(root) && ![...files.keys()].some((k) => k.startsWith(root + '/'))) { throw new Error('enoent:' + p) } const prefix = root === '/' ? '/' : root + '/' const names = new Set() for (const key of [...files.keys(), ...dirs]) { if (key === root) continue if (!String(key).startsWith(prefix)) continue const rest = String(key).slice(prefix.length) const name = rest.split('/')[0] if (name) names.add(name) } return [...names] }, async lstat(p) { const n = String(p).replace(/\/+$/, '') || '/' if (dirs.has(n)) return { isDirectory: () => true } if (files.has(n)) return { isDirectory: () => false } throw new Error('enoent:' + p) } } return { vfs, files, dirs, b4a } } async function dispatch(s, o) { const fn = /** @type {(arg: object) => Promise} */ (s.bareAgentDispatchTool) const raw = await fn({ ctx: o.ctx, paths: o.paths, toolName: o.toolName, argsJson: JSON.stringify(o.args || {}), configRef: o.configRef || { current: {} }, appendProgress: o.appendProgress || (() => {}), home: o.home || '/home/guest', signal: null, onTaskComplete: o.onTaskComplete || (() => {}) }) return JSON.parse(raw) } test('glob ** and * match relative and basename paths', async (t) => { const s = loadPort() t.ok(s.bareAgentGlobMatch('src/foo.js', '**/*.js')) t.ok(s.bareAgentGlobMatch('foo.js', '*.js')) t.ok(s.bareAgentGlobMatch('src/foo.js', '*.js')) t.absent(s.bareAgentGlobMatch('src/foo.md', '**/*.js')) }) test('todo merge updates by id and summarize counts open items', async (t) => { const s = loadPort() const first = s.bareAgentTodoApply( [ { id: 'a', content: 'one', status: 'pending' }, { id: 'b', content: 'two', status: 'in_progress' } ], { merge: false }, [] ) const next = s.bareAgentTodoApply( [{ id: 'a', status: 'completed' }], { merge: true }, first ) t.is(next.length, 2) t.is(next[0].status, 'completed') t.is(next[0].content, 'one') const sum = s.bareAgentTodoSummarize(next) t.is(sum.open, 1) t.is(sum.completed, 1) }) test('todo_write rejects empty and duplicate ids', async (t) => { const s = loadPort() t.exception(() => s.bareAgentTodoApply([{ content: 'x' }], { merge: false }, [])) t.exception(() => s.bareAgentTodoApply( [ { id: 'a', content: 'one' }, { id: 'a', content: 'two' } ], { merge: false }, [] ) ) }) test('search_replace requires unique old_string unless replace_all', async (t) => { const s = loadPort() const text = 'aaa\nbbb\naaa\n' const miss = s.bareAgentSearchReplaceApply(text, 'zzz', 'q', false) t.absent(miss.ok) t.is(miss.error, 'old_string not found') const many = s.bareAgentSearchReplaceApply(text, 'aaa', 'ccc', false) t.absent(many.ok) t.is(many.error, 'old_string not unique') t.is(many.count, 2) const all = s.bareAgentSearchReplaceApply(text, 'aaa', 'ccc', true) t.ok(all.ok) t.is(all.replacements, 2) t.ok(String(all.next).includes('ccc\nbbb\nccc')) }) test('slice file lines is 1-based and numbered', async (t) => { const s = loadPort() const sliced = s.bareAgentSliceFileLines('a\nb\nc\nd\n', { offset: 2, limit: 2 }) t.is(sliced.start_line, 2) t.is(sliced.end_line, 3) t.is(sliced.total_lines, 4) t.ok(sliced.truncated) t.is(sliced.content, '2→b\n3→c') }) test('plan mode allows reads and plan.md writes only', async (t) => { const s = loadPort() const plan = '/home/guest/.agent/plan.md' t.ok(s.bareAgentPlanModeToolAllowed('read_file', { path: '/etc/hosts' }, { plan })) t.ok(s.bareAgentPlanModeToolAllowed('write_file', { path: plan }, { plan })) t.absent( s.bareAgentPlanModeToolAllowed('write_file', { path: '/home/guest/x.js' }, { plan }) ) t.ok(s.bareAgentPlanModeToolAllowed('todo_write', {}, { plan })) }) test('todo nudge appears after idle turns with open items', async (t) => { const s = loadPort() t.is(s.bareAgentTodoNudgeText({ open: 2, turnsSinceTodoWrite: 2 }), '') t.ok(s.bareAgentTodoNudgeText({ open: 2, turnsSinceTodoWrite: 3 }).includes('Open todos')) t.ok( s.bareAgentTodoNudgeText({ open: 0, turnsSinceTodoWrite: 5 }).includes('todo_write') ) t.is(s.bareAgentTodoNudgeText({ open: 2, turnsSinceTodoWrite: 9, nudgeEnabled: false }), '') }) test('pre-tool hook denies matching tool + regex', async (t) => { const s = loadPort() const reason = s.bareAgentHookDenies( { event: 'PreToolUse', tools: ['run_command'], deny_regex: 'rm\\s+-rf', reason: 'no recursive delete' }, 'run_command', { command: 'rm -rf /tmp/x' } ) t.is(reason, 'no recursive delete') t.is( s.bareAgentHookDenies( { event: 'PreToolUse', tools: ['run_command'], deny_regex: 'rm\\s+-rf' }, 'read_file', { path: '/tmp/x' } ), '' ) }) test('memory score ranks files that contain more query tokens', async (t) => { const s = loadPort() const tokens = s.bareAgentMemoryTokens('holesail discord whitelist') t.ok(tokens.includes('holesail')) t.ok( s.bareAgentMemoryScore('holesail discord whitelist notes', tokens) > s.bareAgentMemoryScore('unrelated pear notes', tokens) ) }) test('walk-up discovers AGENTS.md and .grok/rules', async (t) => { const s = loadPort() const { vfs, b4a } = makeVfs({ '/home/guest/proj/src/AGENTS.md': 'leaf', '/home/guest/proj/AGENTS.md': 'mid', '/home/guest/proj/.grok/rules/style.md': 'rule' }) await vfs.mkdir('/home/guest/proj/src') await vfs.mkdir('/home/guest/proj/.grok/rules') const found = await s.bareAgentDiscoverAgentsMdPaths( { vfs, b4a }, '/home/guest/proj/src', 8 ) t.ok(found.includes('/home/guest/proj/src/AGENTS.md')) t.ok(found.includes('/home/guest/proj/AGENTS.md')) t.ok(found.includes('/home/guest/proj/.grok/rules/style.md')) }) test('dispatch glob_files / todo_write / plan_mode / unique edit', async (t) => { const s = loadDispatch() const { vfs, files, b4a } = makeVfs({ '/home/guest/src/a.js': 'const x = 1\nconst y = 1\n', '/home/guest/src/b.md': '# hi\n', '/home/guest/.agent/workspace/memory/notes.md': 'remember holesail keys', '/home/guest/.agent/workspace/MEMORY.md': 'top memory holesail' }) await vfs.mkdir('/home/guest/src') await vfs.mkdir('/home/guest/.agent') await vfs.mkdir('/home/guest/.agent/workspace') await vfs.mkdir('/home/guest/.agent/workspace/memory') const ctx = { vfs, b4a } const paths = { dir: '/home/guest/.agent', config: '/home/guest/.agent/config.json', todos: '/home/guest/.agent/todos.json', plan: '/home/guest/.agent/plan.md', hooks: '/home/guest/.agent/hooks', ask: '/home/guest/.agent/ask.json', workspace: '/home/guest/.agent/workspace', workspaceMemory: '/home/guest/.agent/workspace/memory', compact: '/home/guest/.agent/compact.md' } const configRef = { current: { plan_mode_active: false } } const globbed = await dispatch(s, { ctx, paths, toolName: 'glob_files', args: { pattern: '**/*.js', root: '/home/guest' }, configRef, home: '/home/guest' }) t.ok(globbed.ok) t.ok(globbed.files.includes('/home/guest/src/a.js')) const todos = await dispatch(s, { ctx, paths, toolName: 'todo_write', args: { merge: false, todos: [{ id: 'g1', content: 'port glob', status: 'in_progress' }] }, configRef, home: '/home/guest' }) t.ok(todos.ok) t.is(todos.summary.open, 1) t.ok(String(files.get(paths.todos) || '').includes('port glob')) const mem = await dispatch(s, { ctx, paths, toolName: 'memory_search', args: { query: 'holesail' }, configRef, home: '/home/guest' }) t.ok(mem.ok) t.ok(Array.isArray(mem.hits) && mem.hits.length >= 1) configRef.current.plan_mode_active = true const denied = await dispatch(s, { ctx, paths, toolName: 'write_file', args: { path: '/home/guest/src/a.js', content: 'nope' }, configRef, home: '/home/guest' }) t.absent(denied.ok) t.is(denied.error, 'plan_mode_readonly') const planned = await dispatch(s, { ctx, paths, toolName: 'enter_plan_mode', args: { note: 'draft first' }, configRef, home: '/home/guest' }) t.ok(planned.ok) t.ok(String(files.get(paths.plan) || '').includes('draft first')) const exited = await dispatch(s, { ctx, paths, toolName: 'exit_plan_mode', args: { summary: 'ready' }, configRef, home: '/home/guest' }) t.ok(exited.ok) t.absent(configRef.current.plan_mode_active) const uniqueFail = await dispatch(s, { ctx, paths, toolName: 'search_replace', args: { path: '/home/guest/src/a.js', old_string: 'const', new_string: 'let' }, configRef, home: '/home/guest' }) t.absent(uniqueFail.ok) t.is(uniqueFail.error, 'old_string not unique') const replaced = await dispatch(s, { ctx, paths, toolName: 'search_replace', args: { path: '/home/guest/src/a.js', old_string: 'const x = 1', new_string: 'const x = 2' }, configRef, home: '/home/guest' }) t.ok(replaced.ok) t.is(replaced.replacements, 1) t.ok(String(files.get('/home/guest/src/a.js') || '').includes('const x = 2')) const sliced = await dispatch(s, { ctx, paths, toolName: 'read_file', args: { path: '/home/guest/src/a.js', offset: 1, limit: 1 }, configRef, home: '/home/guest' }) t.ok(sliced.ok) t.ok(String(sliced.content || '').startsWith('1→')) }) test('grep finds lines with glob and context', async (t) => { const s = loadPort() const { vfs, b4a } = makeVfs({ '/home/guest/src/a.js': 'const alpha = 1\nconst beta = 2\nconst gamma = 3\n', '/home/guest/src/b.md': '# alpha docs\n' }) await vfs.mkdir('/home/guest/src') const out = await s.bareAgentGrepFiles( { vfs, b4a }, { pattern: 'alpha', root: '/home/guest', glob: '**/*.js', context: 1 } ) t.ok(out.ok) t.is(out.count, 1) t.ok(out.matches[0].path.endsWith('a.js')) t.ok(String(out.matches[0].text).includes('beta')) }) test('apply_patch add/update/delete', async (t) => { const s = loadPort() const { vfs, files, b4a } = makeVfs({ '/home/guest/src/a.js': 'const x = 1\nconst y = 2\n' }) await vfs.mkdir('/home/guest/src') const parsed = s.bareAgentParseApplyPatch( [ '*** Begin Patch', '*** Add File: /home/guest/src/new.js', '+export const n = 1', '*** Update File: /home/guest/src/a.js', '@@', ' const x = 1', '-const y = 2', '+const y = 3', '*** Delete File: /home/guest/src/gone.js', '*** End Patch' ].join('\n') ) t.ok(parsed.ok) t.is(parsed.ops.length, 3) files.set('/home/guest/src/gone.js', 'bye') const applied = await s.bareAgentApplyPatchOps({ vfs, b4a }, parsed.ops, { home: '/home/guest' }) t.ok(applied.ok) t.ok(String(files.get('/home/guest/src/new.js') || '').includes('export const n')) t.ok(String(files.get('/home/guest/src/a.js') || '').includes('const y = 3')) t.absent(files.has('/home/guest/src/gone.js')) }) test('update_goal / grep dispatch', async (t) => { const s = loadDispatch() const { vfs, b4a } = makeVfs({ '/home/guest/src/a.js': 'hello world\n' }) await vfs.mkdir('/home/guest/src') await vfs.mkdir('/home/guest/.agent') const ctx = { vfs, b4a } const paths = { dir: '/home/guest/.agent', config: '/home/guest/.agent/config.json', todos: '/home/guest/.agent/todos.json', plan: '/home/guest/.agent/plan.md' } const configRef = { current: { autonomous_active: true, autonomous_status: 'running' } } const grepped = await dispatch(s, { ctx, paths, toolName: 'grep', args: { pattern: 'hello', path: '/home/guest' }, configRef, home: '/home/guest' }) t.ok(grepped.ok) t.ok(grepped.count >= 1) const goal = await dispatch(s, { ctx, paths, toolName: 'update_goal', args: { completed: true, message: 'shipped' }, configRef, home: '/home/guest' }) t.ok(goal.ok) t.is(configRef.current.autonomous_status, 'completed') t.absent(configRef.current.autonomous_active) }) test('schedule interval parse and memory_append', async (t) => { const s = loadPort() const five = s.bareAgentParseScheduleInterval('5m') t.is(five.kind, 'everyMs') t.is(five.everyMs, 300000) const cron = s.bareAgentParseScheduleInterval('0 * * * *') t.is(cron.kind, 'calendar') t.is(s.bareAgentScheduleId('Ping Host'), 'agent-ping-host') const d = loadDispatch() const { vfs, files, b4a } = makeVfs({}) await vfs.mkdir('/home/guest/.agent/workspace') const ctx = { vfs, b4a } const paths = { dir: '/home/guest/.agent', workspace: '/home/guest/.agent/workspace', workspaceMemory: '/home/guest/.agent/workspace/memory' } const mem = await dispatch(d, { ctx, paths, toolName: 'memory_append', args: { text: 'holesail keys live in state.json', kind: 'FACT' }, configRef: { current: {} }, home: '/home/guest' }) t.ok(mem.ok) t.ok(String(files.get(paths.workspace + '/MEMORY.md') || '').includes('FACT')) const sched = await dispatch(d, { ctx, paths, toolName: 'schedule_task', args: { id: 'heartbeat', interval: '30m', prompt: 'report peer count' }, configRef: { current: {} }, home: '/home/guest' }) t.ok(sched.ok) t.ok(String(files.get(sched.path) || '').includes('EveryMs=1800000')) t.ok(String(files.get(sched.path) || '').includes('agent --auto')) }) test('gitignore, fuzzy, undo, history search', async (t) => { const s = loadPort() const rules = s.bareAgentParseIgnoreRules('*.log\n!keep.log\nbuild/\n') t.ok(s.bareAgentIgnoreMatch('foo.log', false, rules)) t.absent(s.bareAgentIgnoreMatch('keep.log', false, rules)) t.ok(s.bareAgentIgnoreMatch('build/out.js', true, rules) || s.bareAgentIgnoreMatch('build', true, rules)) t.ok(s.bareAgentFuzzyScore('readme', '/home/x/README.md') > s.bareAgentFuzzyScore('readme', '/home/x/a.js')) const hits = s.bareAgentHistorySearch( [ { role: 'user', content: 'fix the holesail tunnel' }, { role: 'assistant', content: 'ok' } ], 'holesail', 4 ) t.ok(hits.length >= 1) t.is(hits[0].role, 'user') const d = loadDispatch() const { vfs, files, b4a } = makeVfs({ '/home/guest/src/keep.js': 'const keep = 1\n', '/home/guest/src/skip.log': 'noise\n', '/home/guest/.gitignore': '*.log\n' }) await vfs.mkdir('/home/guest/src') await vfs.mkdir('/home/guest/.agent') const ctx = { vfs, b4a } const paths = { dir: '/home/guest/.agent', edits: '/home/guest/.agent/edits.json' } const globbed = await dispatch(d, { ctx, paths, toolName: 'glob_files', args: { pattern: '**/*', root: '/home/guest' }, configRef: { current: {} }, home: '/home/guest' }) t.ok(globbed.ok) t.ok(globbed.files.some((p) => /keep\.js$/.test(p))) t.absent(globbed.files.some((p) => /skip\.log$/.test(p))) const wrote = await dispatch(d, { ctx, paths, toolName: 'write_file', args: { path: '/home/guest/src/keep.js', content: 'changed\n' }, configRef: { current: {} }, home: '/home/guest' }) t.ok(wrote.ok) t.is(files.get('/home/guest/src/keep.js'), 'changed\n') const undone = await dispatch(d, { ctx, paths, toolName: 'undo_last_edit', args: {}, configRef: { current: {} }, home: '/home/guest' }) t.ok(undone.ok) t.ok(String(files.get('/home/guest/src/keep.js') || '').includes('const keep')) const waited = await dispatch(d, { ctx, paths, toolName: 'wait_for', args: { path: '/home/guest/src/keep.js', pattern: 'keep', timeout_ms: 500, interval_ms: 100 }, configRef: { current: {} }, home: '/home/guest' }) t.ok(waited.ok) t.ok(waited.matched) }) test('rewind, export, unified diff, symbol regex', async (t) => { const s = loadPort() const hist = [ { role: 'user', content: 'first' }, { role: 'assistant', content: 'ok1' }, { role: 'user', content: 'second' }, { role: 'assistant', content: 'ok2' } ] const points = s.bareAgentRewindPoints(hist) t.is(points.length, 2) const rewound = s.bareAgentRewindHistory(hist, { steps: 1 }) t.ok(rewound.ok) t.is(rewound.dropped, 2) t.is(rewound.messages.length, 2) t.is(rewound.messages[0].content, 'first') const md = s.bareAgentExportTranscript(hist) t.ok(md.includes('# Agent session export')) t.ok(md.includes('first')) const diff = s.bareAgentUnifiedDiff('a\nb\nc\n', 'a\nB\nc\n', { from: '/a', to: '/b' }) t.ok(diff.ok) t.absent(diff.identical) t.ok(String(diff.text).includes('-b')) t.ok(String(diff.text).includes('+B')) const re = new RegExp(s.bareAgentSymbolRegex('bareAgentCopyPath')) t.ok(re.test('async function bareAgentCopyPath(ctx, from, to) {')) t.absent(re.test('const x = bareAgentCopyPath')) }) test('copy, tree, find_symbol, create_skill, rewind dispatch', async (t) => { const d = loadDispatch() const { vfs, files, b4a } = makeVfs({ '/home/guest/src/lib.js': 'async function helloWorld() {\n return 1\n}\n', '/home/guest/src/a.txt': 'alpha\n', '/home/guest/src/b.txt': 'beta\n', '/home/guest/.agent/history.json': JSON.stringify( [ { role: 'user', content: 'one' }, { role: 'assistant', content: 'ok' }, { role: 'user', content: 'two' }, { role: 'assistant', content: 'ok2' } ], null, 2 ) }) await vfs.mkdir('/home/guest/src') await vfs.mkdir('/home/guest/.agent') const ctx = { vfs, b4a } const paths = { dir: '/home/guest/.agent', history: '/home/guest/.agent/history.json', workspace: '/home/guest/.agent/workspace', workspaceSkills: '/home/guest/.agent/workspace/skills', workspaceMemory: '/home/guest/.agent/workspace/memory' } const copied = await dispatch(d, { ctx, paths, toolName: 'copy_path', args: { from_path: '/home/guest/src/a.txt', to_path: '/home/guest/src/a.copy.txt' }, home: '/home/guest' }) t.ok(copied.ok) t.is(files.get('/home/guest/src/a.copy.txt'), 'alpha\n') const tree = await dispatch(d, { ctx, paths, toolName: 'list_directory', args: { path: '/home/guest/src', tree: true }, home: '/home/guest' }) t.ok(tree.ok) t.ok(String(tree.tree || '').includes('lib.js')) const found = await dispatch(d, { ctx, paths, toolName: 'find_symbol', args: { name: 'helloWorld', root: '/home/guest/src' }, home: '/home/guest' }) t.ok(found.ok) t.ok(found.count >= 1) const counted = await dispatch(d, { ctx, paths, toolName: 'grep', args: { pattern: 'hello', root: '/home/guest/src', output_mode: 'count' }, home: '/home/guest' }) t.ok(counted.ok) t.is(counted.output_mode, 'count') t.ok(counted.count >= 1) const skill = await dispatch(d, { ctx, paths, toolName: 'create_skill', args: { id: 'demo-skill', description: 'Demo', body: '# Demo\n\nDo the thing.\n' }, home: '/home/guest' }) t.ok(skill.ok) t.ok(String(files.get('/home/guest/.agent/workspace/skills/demo-skill/SKILL.md') || '').includes('name: demo-skill')) const rewound = await dispatch(d, { ctx, paths, toolName: 'rewind_session', args: { steps: 1 }, home: '/home/guest' }) t.ok(rewound.ok) t.is(rewound.dropped, 2) const hist = JSON.parse(files.get('/home/guest/.agent/history.json') || '[]') t.is(hist.length, 2) const remembered = await dispatch(d, { ctx, paths, toolName: 'remember', args: { text: 'holesail keys live in ~/.holesail' }, home: '/home/guest' }) t.ok(remembered.ok) t.is(remembered.kind, 'FACT') t.ok(String(files.get('/home/guest/.agent/workspace/MEMORY.md') || '').includes('holesail keys')) const exported = await dispatch(d, { ctx, paths, toolName: 'export_session', args: { path: '/home/guest/.agent/export.md' }, home: '/home/guest' }) t.ok(exported.ok) t.ok(String(files.get('/home/guest/.agent/export.md') || '').includes('# Agent session export')) }) test('copy from read-only system path and project skill roots', async (t) => { const d = loadDispatch() const { vfs, files, b4a } = makeVfs({ '/bin/sh': 'readonly-shell\n', '/home/guest/proj/.grok/skills/demo/SKILL.md': '---\nname: demo\ndescription: Project skill\n---\n\n# Demo\n' }) await vfs.mkdir('/bin') await vfs.mkdir('/home/guest') const ctx = { vfs, b4a, env: { PWD: '/home/guest/proj', HOME: '/home/guest' } } const copied = await dispatch(d, { ctx, paths: { dir: '/home/guest/.agent', workspace: '/home/guest/.agent/workspace' }, toolName: 'copy_path', args: { from_path: '/bin/sh', to_path: '/home/guest/sh.copy' }, home: '/home/guest' }) t.ok(copied.ok) t.is(files.get('/home/guest/sh.copy'), 'readonly-shell\n') const blocked = await dispatch(d, { ctx, paths: { dir: '/home/guest/.agent' }, toolName: 'copy_path', args: { from_path: '/home/guest/sh.copy', to_path: '/bin/sh.hijack' }, home: '/home/guest' }) t.absent(blocked.ok) t.is(blocked.error, 'path_not_allowed') const s = loadPort() const roots = await s.bareAgentDiscoverProjectSkillRoots(ctx, '/home/guest/proj/src') t.ok(roots.some((r) => String(r.path || '').endsWith('/.grok/skills'))) }) test('search result parser extracts DDG-style topics', async (t) => { const s = loadPort() const rows = s.bareAgentParseSearchResults( { Heading: 'Bare', AbstractURL: 'https://example.com/bare', Abstract: 'A JS runtime', RelatedTopics: [ { Text: 'Pear', FirstURL: 'https://example.com/pear' }, { Topics: [{ Text: 'Holepunch', FirstURL: 'https://example.com/hp' }] } ] }, 8 ) t.ok(rows.length >= 2) t.ok(rows.some((r) => r.url === 'https://example.com/bare')) }) test('dispatch delete / proc / run_command are open by default', async (t) => { const s = loadDispatch() const { vfs, files, b4a } = makeVfs({ '/home/guest/wipe-me.txt': 'gone soon', '/bin/sh': 'readonly', '/proc/bare_os/extra.json': '{"ok":true}' }) await vfs.mkdir('/home/guest') const ctx = { vfs, b4a } const paths = { dir: '/home/guest/.agent', config: '/home/guest/.agent/config.json', cmdOut: '/tmp/agent.out' } const configRef = { current: { allow_delete: true, command_deny: [], autonomous_active: true, autonomous_allow_paths: ['*'], autonomous_deny_ops: [] } } const del = await dispatch(s, { ctx, paths, toolName: 'delete_path', args: { path: '/home/guest/wipe-me.txt' }, configRef, home: '/home/guest' }) t.ok(del.ok) t.absent(files.has('/home/guest/wipe-me.txt')) const deniedSys = await dispatch(s, { ctx, paths, toolName: 'write_file', args: { path: '/bin/sh', content: 'nope' }, configRef, home: '/home/guest' }) t.absent(deniedSys.ok) t.is(deniedSys.error, 'path_not_allowed') const proc = await dispatch(s, { ctx, paths, toolName: 'read_proc_file', args: { path: '/proc/bare_os/extra.json' }, configRef, home: '/home/guest' }) t.ok(proc.ok) t.ok(proc.json && proc.json.ok === true) const seen = [] ctx.execLine = async (cmd) => { seen.push(String(cmd)) await vfs.writeFile('/tmp/agent.out', b4a.from('ok\n')) } const run = await dispatch(s, { ctx, paths, toolName: 'run_command', args: { command: 'git status && rm -rf /tmp/x' }, configRef, home: '/home/guest' }) t.ok(run.ok) t.ok(seen.length >= 1) configRef.current.command_deny = ['rm -rf'] const blocked = await dispatch(s, { ctx, paths, toolName: 'run_command', args: { command: 'rm -rf /tmp/x' }, configRef, home: '/home/guest' }) t.absent(blocked.ok) t.is(blocked.error, 'command_denied') }) test('discord_send_message queues or uses the live DM hook', async (t) => { const s = loadDispatch() const { vfs, files, b4a } = makeVfs({}) await vfs.writeFile( '~/.discord/channel.json', b4a.from(JSON.stringify({ userId: '111', open: true }) + '\n') ) const paths = { dir: '/home/guest/.agent', config: '/home/guest/.agent/config.json', cmdOut: '/tmp/agent.out' } const queued = await dispatch(s, { ctx: { vfs, b4a }, paths, toolName: 'discord_send_message', args: { text: 'hello from agent' }, home: '/home/guest' }) t.ok(queued.ok) t.ok(queued.queued) t.ok(files.has('~/.discord/outbox.json')) t.ok(String(files.get('~/.discord/outbox.json')).includes('hello from agent')) const sent = [] const live = await dispatch(s, { ctx: { vfs, b4a, bareOsDiscordSendDm: async (opts) => { sent.push(opts) return { ok: true, queued: false, userId: '111' } } }, paths, toolName: 'discord_send_message', args: { text: 'live ping' }, home: '/home/guest' }) t.ok(live.ok) t.absent(live.queued) t.is(sent[0].text, 'live ping') const empty = await dispatch(s, { ctx: { vfs, b4a }, paths, toolName: 'discord_send_message', args: { text: ' ' }, home: '/home/guest' }) t.absent(empty.ok) t.is(empty.error, 'empty_message') const st = await dispatch(s, { ctx: { vfs, b4a }, paths, toolName: 'discord_channel_status', args: {}, home: '/home/guest' }) t.ok(st.ok) t.is(st.userId, '111') }) test('write_file and create_directory expand ~/ onto the session home', async (t) => { const s = loadDispatch() const home = '/home/e16d7bc4ce14' const { vfs, files, dirs, b4a } = makeVfs({}) const ctx = { vfs, b4a } const paths = { dir: home + '/.agent', config: home + '/.agent/config.json', cmdOut: home + '/.agent/last_command_out.txt', edits: home + '/.agent/edits.json' } const configRef = { current: {} } const mkdir = await dispatch(s, { ctx, paths, toolName: 'create_directory', args: { path: '~/test_work' }, configRef, home }) t.ok(mkdir.ok) t.is(mkdir.path, home + '/test_work') t.ok(dirs.has(home + '/test_work')) const wrote = await dispatch(s, { ctx, paths, toolName: 'write_file', args: { path: '~/test_work/hello.js', content: 'console.log("hi")\n' }, configRef, home }) t.ok(wrote.ok) t.is(wrote.path, home + '/test_work/hello.js') t.is(files.get(home + '/test_work/hello.js'), 'console.log("hi")\n') const alias = await dispatch(s, { ctx, paths, toolName: 'write_file', args: { file_path: '~/test_work/welcome.js', contents: 'ok\n' }, configRef, home }) t.ok(alias.ok) t.is(alias.path, home + '/test_work/welcome.js') t.is(files.get(home + '/test_work/welcome.js'), 'ok\n') })