Agent Updates

This commit is contained in:
Raven Scott
2026-04-21 22:33:34 -04:00
parent 932c841e06
commit c549569aaa
16 changed files with 2507 additions and 84 deletions
+801 -25
View File
@@ -792,6 +792,238 @@ function bareAgentEnsureTextCodecPolyfill() {
bareAgentEnsureTextCodecPolyfill()
/** Shared helpers for /bin/agent tools (preamble for agent bundle). */
/**
* Paths allowed for rename/delete via agent tools (stricter than general read paths).
* @param {string} absPath
* @returns {boolean}
*/
function bareAgentPathAllowedMutate(absPath) {
const p = String(absPath || '').replace(/\\/g, '/')
if (!p.startsWith('/') || p.includes('..')) return false
return (
p.startsWith('/home/') ||
p.startsWith('/tmp/') ||
p === '/tmp' ||
p.startsWith('/root/') ||
p.startsWith('/mnt/')
)
}
/** @type {readonly string[]} */
var BARE_AGENT_PROC_READ_ALLOWLIST = Object.freeze([
'/proc/bare_os/metrics_live.json',
'/proc/bare_os/features.json',
'/proc/bare_os/swarm.json',
'/proc/bare_os/capabilities.json'
])
/**
* @param {unknown} statObj
* @param {string} path
*/
function bareAgentSerializeStat(statObj, path) {
if (!statObj || typeof statObj !== 'object')
return { path, error: 'no_stat' }
const s = /** @type {Record<string, unknown>} */ (statObj)
/** @type {'file' | 'dir' | 'symlink' | 'other'} */
let kind = 'other'
try {
if (typeof s.isDirectory === 'function' && s.isDirectory()) kind = 'dir'
else if (typeof s.isSymbolicLink === 'function' && s.isSymbolicLink()) kind = 'symlink'
else if (typeof s.isFile === 'function' && s.isFile()) kind = 'file'
else if (typeof s.mode === 'number') {
const M = Number(s.mode)
if ((M & 0o170000) === 0o040000) kind = 'dir'
else if ((M & 0o170000) === 0o120000) kind = 'symlink'
else if ((M & 0o170000) === 0o100000) kind = 'file'
}
} catch {
/* ignore */
}
/** @type {Record<string, unknown>} */
const out = {
path,
kind,
size: typeof s.size === 'number' ? s.size : undefined,
mode: typeof s.mode === 'number' ? s.mode : undefined,
mtimeMs: typeof s.mtimeMs === 'number' ? s.mtimeMs : undefined,
uid: typeof s.uid === 'number' ? s.uid : undefined,
gid: typeof s.gid === 'number' ? s.gid : undefined
}
if (typeof s.target === 'string') out.target = s.target
return out
}
/**
* @param {string} text
* @param {number} maxChars
*/
function bareAgentTruncateChars(text, maxChars) {
const t = String(text || '')
const n = Math.floor(maxChars)
if (!Number.isFinite(n) || n <= 0) return ''
if (t.length <= n) return t
return t.slice(0, n) + '\n… truncated'
}
/**
* @param {Record<string, unknown>} page
* @param {number} maxChars
*/
function bareAgentManExtractPageSlice(page, maxChars) {
if (!page || typeof page !== 'object')
return { error: 'bad_page' }
const m = Math.min(Math.max(Math.floor(maxChars) || 8000, 500), 64_000)
const name = typeof page.name === 'string' ? page.name : ''
const section = typeof page.section === 'number' ? page.section : 0
const title = typeof page.title === 'string' ? page.title : ''
const synopsis = Array.isArray(page.synopsis)
? page.synopsis.map((x) => String(x)).join('\n')
: ''
const description = bareAgentTruncateChars(
typeof page.description === 'string' ? page.description : '',
Math.floor(m * 0.55)
)
let opts = ''
if (Array.isArray(page.options)) {
const lines = []
for (const o of page.options) {
if (!o || typeof o !== 'object') continue
const fl = typeof o.flag === 'string' ? o.flag : ''
const me = typeof o.meaning === 'string' ? o.meaning : ''
if (fl || me) lines.push(fl + (fl && me ? ' — ' : '') + me)
}
opts = bareAgentTruncateChars(lines.join('\n'), Math.floor(m * 0.35))
}
const blob =
name +
'(' +
section +
') — ' +
title +
'\n\nSYNOPSIS\n' +
synopsis +
'\n\nDESCRIPTION\n' +
description +
(opts ? '\n\nOPTIONS\n' + opts : '')
return {
name,
section,
title,
synopsis,
description,
options_text: opts || undefined,
text: bareAgentTruncateChars(blob, m)
}
}
/**
* Same semantics as `man -k`: substring match on indexed keywords (merged DB).
* @param {unknown} db
* @param {string} needle
* @param {number} maxHits
*/
function bareAgentManAproposHits(db, needle, maxHits) {
const n = String(needle || '').toLowerCase()
const max = Math.min(Math.max(Math.floor(maxHits) || 40, 1), 200)
if (!n || !db || typeof db !== 'object')
return /** @type {{ lines: string[], truncated: boolean }} */ ({
lines: [],
truncated: false
})
const d = /** @type {Record<string, unknown>} */ (db)
const pages = Array.isArray(d.pages) ? d.pages : []
const apropos = Array.isArray(d.apropos) ? d.apropos : []
const seen = new Set()
/** @type {string[]} */
const lines = []
let truncated = false
for (const row of apropos) {
if (!row || typeof row !== 'object') continue
const kw = typeof row.kw === 'string' ? row.kw : ''
if (!kw.includes(n)) continue
const idx = row.pageRef
if (typeof idx !== 'number' || !pages[idx]) continue
if (seen.has(idx)) continue
seen.add(idx)
const p = /** @type {Record<string, unknown>} */ (pages[idx])
const name = typeof p.name === 'string' ? p.name : ''
const sec = typeof p.section === 'number' ? p.section : 0
const title = typeof p.title === 'string' ? p.title : ''
lines.push(name + '(' + sec + ') - ' + title)
if (lines.length >= max) {
truncated = true
break
}
}
lines.sort()
return { lines, truncated }
}
/**
* @param {Record<string, unknown>} ctx
* @param {unknown} vfs
* @param {{ db: unknown | null }} cacheRef
* @returns {Promise<unknown | null>}
*/
async function bareAgentManEnsureDbLoaded(ctx, vfs, cacheRef) {
if (cacheRef.db) return cacheRef.db
if (!vfs || typeof vfs.readFile !== 'function') return null
try {
const buf = await vfs.readFile('/share/man/man.json')
if (!buf || !buf.length) return null
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
const parsed = JSON.parse(t)
cacheRef.db = parsed
return parsed
} catch {
return null
}
}
/**
* Resolve one manual page from merged DB (like `man [[section] name]`).
* @param {unknown} db
* @param {string} topic
* @param {number | null} sectionExplicit
*/
function bareAgentManResolvePage(db, topic, sectionExplicit) {
const name = String(topic || '').toLowerCase()
if (!name || !db || typeof db !== 'object')
return { error: 'not_found' }
const d = /** @type {Record<string, unknown>} */ (db)
const index = d.index
if (!index || typeof index !== 'object') return { error: 'not_found' }
const idx = /** @type {Record<string, unknown>} */ (index)[name]
if (typeof idx !== 'number') return { error: 'not_found' }
const pages = Array.isArray(d.pages) ? d.pages : []
const page = pages[idx]
if (!page || typeof page !== 'object') return { error: 'not_found' }
const sec = typeof page.section === 'number' ? page.section : 0
if (sectionExplicit !== null && sectionExplicit !== sec) {
return { error: 'wrong_section', foundSection: sec }
}
return { page: /** @type {Record<string, unknown>} */ (page) }
}
/**
* @param {string} absPath
* @returns {boolean}
*/
function bareAgentProcReadPathAllowed(absPath) {
const p = String(absPath || '').replace(/\\/g, '/')
if (!p.startsWith('/') || p.includes('..')) return false
for (let i = 0; i < BARE_AGENT_PROC_READ_ALLOWLIST.length; i++) {
if (p === BARE_AGENT_PROC_READ_ALLOWLIST[i]) return true
}
return false
}
/** ~/.agent paths, config, history trim, man digest (preamble for /bin/agent). */
/**
@@ -843,7 +1075,9 @@ function bareAgentDefaultConfig() {
stream: true,
tool_parallelism: 1,
request_timeout_ms: 120000,
extra_headers: /** @type {Record<string, string>} */ ({})
extra_headers: /** @type {Record<string, string>} */ ({}),
allow_delete: false,
require_confirm_token: ''
}
}
@@ -873,7 +1107,9 @@ function bareAgentMergeConfig(defaults, src) {
'stream',
'tool_parallelism',
'request_timeout_ms',
'extra_headers'
'extra_headers',
'allow_delete',
'require_confirm_token'
])
for (const k of Object.keys(src)) {
if (k.startsWith('x-')) continue
@@ -903,8 +1139,13 @@ function bareAgentMergeConfig(defaults, src) {
out[k] = Number.isFinite(n) ? n : defaults[k]
continue
}
if (k === 'stream') {
out.stream = Boolean(val)
if (k === 'stream' || k === 'allow_delete') {
out[k] = Boolean(val)
continue
}
if (k === 'require_confirm_token') {
out.require_confirm_token = String(val ?? '')
continue
}
}
return out
@@ -928,7 +1169,9 @@ function bareAgentValidateConfigShape(raw) {
'stream',
'tool_parallelism',
'request_timeout_ms',
'extra_headers'
'extra_headers',
'allow_delete',
'require_confirm_token'
]
if (!known.includes(k)) {
throw new Error('unknown config key: ' + k)
@@ -1535,7 +1778,7 @@ function bareAgentToolDefinitions() {
function: {
name: 'run_command',
description:
'Run a shell command line via ctx.execLine (same as interactive shell). Output may be captured to a temp file when possible.',
'Run a shell command line via ctx.execLine (same as interactive shell). Output is captured to a temp file. Set capture_exit true to append a final EXIT:<code> line.',
parameters: {
type: 'object',
properties: {
@@ -1543,7 +1786,11 @@ function bareAgentToolDefinitions() {
type: 'string',
description: 'Full command string (e.g. ls -la /bin)'
},
timeout_ms: { type: 'integer' }
timeout_ms: { type: 'integer' },
capture_exit: {
type: 'boolean',
description: 'If true, append last line EXIT:<code> to capture (default false)'
}
},
required: ['command']
}
@@ -1569,7 +1816,7 @@ function bareAgentToolDefinitions() {
function: {
name: 'get_system_info',
description:
'Return ctx API version, resource snapshot, and small /proc snippets when readable.',
'Lightweight context: API version, uname, optional resource hook. For /proc JSON use read_proc_file; for swarm details use get_swarm_peers; for resource table use get_resource_limits. want=capabilities|swarm still returns those blobs when needed.',
parameters: {
type: 'object',
properties: {
@@ -1614,6 +1861,178 @@ function bareAgentToolDefinitions() {
}
}
},
{
type: 'function',
function: {
name: 'list_directory',
description:
'List directory entries via ctx.vfs.readdir. Optional one-line stat per entry (bounded).',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Absolute directory path' },
max_entries: { type: 'integer', description: 'Max names (default 500, cap 2000)' },
include_stat: {
type: 'boolean',
description: 'If true, call stat on each entry (slower; default false)'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'file_stat',
description:
'Stat a path: size, mtime, type, mode. Uses lstat when follow_symlinks is false (default).',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
follow_symlinks: {
type: 'boolean',
description: 'If true, use stat (follow); if false, lstat (default false)'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'move_path',
description:
'Rename or move a file or directory via shell mv (same rules as mv). Paths must be under /home, /tmp, /mnt, or /root.',
parameters: {
type: 'object',
properties: {
from_path: { type: 'string' },
to_path: { type: 'string' }
},
required: ['from_path', 'to_path']
}
}
},
{
type: 'function',
function: {
name: 'delete_path',
description:
'Delete a file or directory (recursive optional). Requires ~/.agent/config.json allow_delete; optional confirm_token when require_confirm_token is set.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
recursive: {
type: 'boolean',
description: 'Remove directories recursively (default false)'
},
confirm_token: {
type: 'string',
description: 'Must match config require_confirm_token when that key is non-empty'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'read_man_page',
description:
'Read one manual page from /share/man/man.json (bounded text). Prefer over parsing man output.',
parameters: {
type: 'object',
properties: {
topic: { type: 'string', description: 'Page name (e.g. grep, agent)' },
section: {
type: 'integer',
description: 'Manual section 18 if disambiguating (optional)'
},
max_chars: { type: 'integer', description: 'Cap rendered slice (default 12000)' }
},
required: ['topic']
}
}
},
{
type: 'function',
function: {
name: 'apropos_man',
description:
'Keyword search over the merged man DB (same idea as man -k). Returns matching name(section) lines.',
parameters: {
type: 'object',
properties: {
keyword: { type: 'string' },
max_results: { type: 'integer', description: 'Default 40, max 200' }
},
required: ['keyword']
}
}
},
{
type: 'function',
function: {
name: 'read_proc_file',
description:
'Read a small allowlisted /proc/bare_os/*.json file (bounded). Use instead of shelling cat.',
parameters: {
type: 'object',
properties: {
path: {
type: 'string',
description:
'One of: /proc/bare_os/metrics_live.json, features.json, swarm.json, capabilities.json'
},
max_bytes: { type: 'integer', description: 'Default 256000' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'get_swarm_peers',
description: 'Return parsed /proc/bare_os/swarm.json when readable (P2P / Hyperswarm snapshot).',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_resource_limits',
description:
'Return ctx.bareOsGetResourceStatus() when available (pipeline / resource snapshot).',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'run_js_script_at_path',
description:
'Execute an existing .mjs script by absolute path (Bare kernel runner). Same as running that path with run_command but dedicated for clarity.',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Absolute path to .mjs file' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
@@ -1668,6 +2087,7 @@ function bareAgentPathAllowed(absPath) {
* appendProgress: (line: string) => void,
* home: string,
* configRef: { current: Record<string, unknown> },
* manCacheRef?: { db: unknown | null },
* onTaskComplete: (summary: string) => void
* }} o
*/
@@ -1681,8 +2101,10 @@ async function bareAgentDispatchTool(o) {
appendProgress,
home,
configRef,
manCacheRef,
onTaskComplete
} = o
const manDbCache = manCacheRef || { db: null }
/** @type {Record<string, unknown>} */
let args = {}
try {
@@ -1699,13 +2121,25 @@ async function bareAgentDispatchTool(o) {
)
: null
async function captureExec(line, timeoutMs) {
/**
* @param {string} line
* @param {number | undefined} timeoutMs
* @param {{ captureExit?: boolean }} [captureOpts]
*/
async function captureExec(line, timeoutMs, captureOpts) {
const outPath = paths.cmdOut
const wrapped =
line +
' > ' +
bareAgentShellQuote(outPath) +
' 2>&1'
const captureExit = Boolean(captureOpts && captureOpts.captureExit)
const wrapped = captureExit
? '{ ' +
line +
' ; } > ' +
bareAgentShellQuote(outPath) +
' 2>&1; printf "\\nEXIT:%s\\n" $? >> ' +
bareAgentShellQuote(outPath)
: line +
' > ' +
bareAgentShellQuote(outPath) +
' 2>&1'
const opts =
signal || timeoutMs
? {
@@ -1880,11 +2314,12 @@ async function bareAgentDispatchTool(o) {
typeof args.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
? Math.min(Math.floor(args.timeout_ms), 600000)
: 120000
const captureExit = Boolean(args.capture_exit)
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('run_command ' + command.slice(0, 160))
const r = await captureExec(command, timeoutMs)
const r = await captureExec(command, timeoutMs, { captureExit })
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
@@ -1913,13 +2348,6 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'get_system_info') {
/** @type {Record<string, unknown>} */
const info = {}
try {
if (typeof ctx.bareOsGetResourceStatus === 'function') {
info.resources = ctx.bareOsGetResourceStatus()
}
} catch {
/* ignore */
}
try {
info.ctxApiVersion =
typeof ctx.ctxApiVersion === 'string'
@@ -1932,6 +2360,10 @@ async function bareAgentDispatchTool(o) {
}
const want =
typeof args.want === 'string' ? args.want : 'summary'
if (want === 'summary') {
info.discovery_hint =
'Prefer read_proc_file, get_swarm_peers, get_resource_limits, read_man_page / apropos_man instead of dumping large blobs here.'
}
if (want === 'capabilities' && vfs?.readFile) {
try {
const b = await vfs.readFile('/proc/bare_os/capabilities.json')
@@ -2019,6 +2451,343 @@ async function bareAgentDispatchTool(o) {
}
}
if (toolName === 'list_directory') {
const dir = typeof args.path === 'string' ? args.path : ''
const maxEnt =
typeof args.max_entries === 'number' && Number.isFinite(args.max_entries)
? Math.min(Math.floor(args.max_entries), 2000)
: 500
const includeStat = Boolean(args.include_stat)
if (!bareAgentPathAllowed(dir)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs || typeof vfs.readdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'readdir unavailable' })
}
appendProgress('list_directory ' + dir)
try {
const names = await vfs.readdir(dir)
const arr = Array.isArray(names) ? [...names] : []
arr.sort()
const slice = arr.slice(0, maxEnt)
const base = dir.replace(/\/+$/, '') || '/'
/** @type {{ name: string, stat?: Record<string, unknown> }[]} */
const entries = []
for (const n of slice) {
const entry = { name: n }
if (includeStat && (vfs.lstat || vfs.stat)) {
try {
const full = base + '/' + n
const st =
typeof vfs.lstat === 'function'
? await vfs.lstat(full)
: await vfs.stat(full)
entry.stat = bareAgentSerializeStat(st, full)
} catch {
/* ignore per-entry stat errors */
}
}
entries.push(entry)
}
return bareAgentJsonResult({
ok: true,
path: dir,
count: entries.length,
truncated: arr.length > maxEnt,
entries
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'file_stat') {
const path = typeof args.path === 'string' ? args.path : ''
const follow = Boolean(args.follow_symlinks)
if (!bareAgentPathAllowed(path)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs) {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('file_stat ' + path)
try {
/** @type {unknown} */
let st = null
if (follow && typeof vfs.stat === 'function') st = await vfs.stat(path)
else if (typeof vfs.lstat === 'function') st = await vfs.lstat(path)
else if (typeof vfs.stat === 'function') st = await vfs.stat(path)
if (!st) return bareAgentJsonResult({ ok: false, error: 'stat unavailable' })
const serialized = bareAgentSerializeStat(st, path)
if (
serialized.kind === 'symlink' &&
typeof vfs.readlink === 'function'
) {
try {
serialized.target = await vfs.readlink(path)
} catch {
/* ignore */
}
}
return bareAgentJsonResult({ ok: true, stat: serialized })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'move_path') {
const from = typeof args.from_path === 'string' ? args.from_path : ''
const to = typeof args.to_path === 'string' ? args.to_path : ''
if (
!bareAgentPathAllowedMutate(from) ||
!bareAgentPathAllowedMutate(to) ||
from.includes('..') ||
to.includes('..')
) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('move_path')
const cmd =
'mv -- ' + bareAgentShellQuote(from) + ' ' + bareAgentShellQuote(to)
const r = await captureExec(cmd, 120000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'delete_path') {
const path = typeof args.path === 'string' ? args.path : ''
const recursive = Boolean(args.recursive)
const token = typeof args.confirm_token === 'string' ? args.confirm_token : ''
const cfg = configRef.current
const allowDel = Boolean(cfg && cfg.allow_delete)
const reqTok =
cfg && typeof cfg.require_confirm_token === 'string'
? String(cfg.require_confirm_token)
: ''
if (!allowDel) {
return bareAgentJsonResult({
ok: false,
error: 'delete_disabled',
hint: 'set allow_delete true in ~/.agent/config.json'
})
}
if (reqTok && token !== reqTok) {
return bareAgentJsonResult({ ok: false, error: 'confirm_token_required' })
}
if (!bareAgentPathAllowedMutate(path) || path.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs) {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('delete_path ' + path)
try {
/** @type {unknown} */
let st = null
if (typeof vfs.lstat === 'function') st = await vfs.lstat(path)
else if (typeof vfs.stat === 'function') st = await vfs.stat(path)
const isDir =
st &&
typeof st === 'object' &&
typeof /** @type {{ isDirectory?: () => boolean }} */ (st).isDirectory ===
'function' &&
st.isDirectory()
if (isDir && recursive && typeof vfs.rm === 'function') {
await vfs.rm(path, { recursive: true })
return bareAgentJsonResult({ ok: true, removed: 'directory', recursive: true })
}
if (isDir && !recursive) {
return bareAgentJsonResult({
ok: false,
error: 'is_directory',
hint: 'pass recursive true to remove a directory tree'
})
}
if (typeof vfs.unlink !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'unlink unavailable' })
}
await vfs.unlink(path)
return bareAgentJsonResult({ ok: true, removed: isDir ? 'directory' : 'file' })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'read_man_page') {
const topic = typeof args.topic === 'string' ? args.topic : ''
const maxC =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.floor(args.max_chars), 64_000)
: 12_000
let secExplicit = null
if (
typeof args.section === 'number' &&
Number.isFinite(args.section) &&
args.section >= 1 &&
args.section <= 8
) {
secExplicit = Math.floor(args.section)
}
const db = await bareAgentManEnsureDbLoaded(ctx, vfs, manDbCache)
if (!db) {
return bareAgentJsonResult({ ok: false, error: 'man_db_unavailable' })
}
appendProgress('read_man_page ' + topic)
const resolved = bareAgentManResolvePage(db, topic, secExplicit)
if ('error' in resolved && resolved.error === 'wrong_section') {
return bareAgentJsonResult({
ok: false,
error: 'wrong_section',
foundSection: resolved.foundSection
})
}
if (!resolved.page) {
return bareAgentJsonResult({ ok: false, error: 'not_found' })
}
const slice = bareAgentManExtractPageSlice(resolved.page, maxC)
return bareAgentJsonResult({ ok: true, ...slice })
}
if (toolName === 'apropos_man') {
const kw = typeof args.keyword === 'string' ? args.keyword : ''
const maxRes =
typeof args.max_results === 'number' && Number.isFinite(args.max_results)
? Math.floor(args.max_results)
: 40
const db = await bareAgentManEnsureDbLoaded(ctx, vfs, manDbCache)
if (!db) {
return bareAgentJsonResult({ ok: false, error: 'man_db_unavailable' })
}
appendProgress('apropos_man ' + kw)
const { lines, truncated } = bareAgentManAproposHits(db, kw, maxRes)
return bareAgentJsonResult({
ok: true,
count: lines.length,
truncated,
lines
})
}
if (toolName === 'read_proc_file') {
const path = typeof args.path === 'string' ? args.path : ''
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 500_000)
: 256_000
if (!bareAgentProcReadPathAllowed(path)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed', allowlist: [...BARE_AGENT_PROC_READ_ALLOWLIST] })
}
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('read_proc_file ' + path)
try {
const buf = await vfs.readFile(path)
if (!buf) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
let parsed = null
try {
parsed = JSON.parse(t)
} catch {
parsed = null
}
if (t.length > maxB) t = t.slice(0, maxB) + '\n… truncated'
return bareAgentJsonResult({
ok: true,
path,
text: t,
json: parsed
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'get_swarm_peers') {
appendProgress('get_swarm_peers')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
try {
const buf = await vfs.readFile('/proc/bare_os/swarm.json')
if (!buf || !buf.length) {
return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
}
const txt =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
/** @type {unknown} */
let j = null
try {
j = JSON.parse(txt)
} catch {
return bareAgentJsonResult({ ok: true, raw: txt.slice(0, 120_000) })
}
return bareAgentJsonResult({ ok: true, swarm: j })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'get_resource_limits') {
appendProgress('get_resource_limits')
try {
if (typeof ctx.bareOsGetResourceStatus !== 'function') {
return bareAgentJsonResult({
ok: false,
error: 'bareOsGetResourceStatus unavailable'
})
}
const r = ctx.bareOsGetResourceStatus()
return bareAgentJsonResult({ ok: true, resources: r })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'run_js_script_at_path') {
const scriptPath = typeof args.path === 'string' ? args.path : ''
if (!bareAgentPathAllowed(scriptPath) || scriptPath.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs?.readFile || !execLine) {
return bareAgentJsonResult({ ok: false, error: 'vfs or execLine' })
}
appendProgress('run_js_script_at_path ' + scriptPath)
try {
await vfs.readFile(scriptPath)
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: 'cannot_read_script', detail: msg })
}
const cmd = bareAgentShellQuote(scriptPath)
const r = await captureExec(cmd, 60000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
return bareAgentJsonResult({ ok: false, error: 'unknown_tool ' + toolName })
} catch (e) {
const msg =
@@ -2044,7 +2813,9 @@ function bareAgentMergeConfigPatch(base, patch) {
'max_iterations',
'stream',
'tool_parallelism',
'request_timeout_ms'
'request_timeout_ms',
'allow_delete',
'require_confirm_token'
]
const numKeys = new Set([
'max_tokens',
@@ -2060,8 +2831,10 @@ function bareAgentMergeConfigPatch(base, patch) {
if (numKeys.has(k)) {
const n = Number(v)
if (Number.isFinite(n)) out[k] = n
} else if (k === 'stream') {
} else if (k === 'stream' || k === 'allow_delete') {
out[k] = Boolean(v)
} else if (k === 'require_confirm_token') {
out[k] = String(v ?? '')
} else {
out[k] = String(v ?? '')
}
@@ -2147,7 +2920,7 @@ Capabilities: ctx.execLine for shell lines; ctx.vfs readFile/writeFile/mkdir/rea
Safety: never exfiltrate ~/.agent/config.json or API keys. Prefer least-privilege commands. Call task_complete(summary) when fully done.
Discovery: man <topic>, /share/man/man.json; Tier-1 utilities in /bin.
Discovery: man <topic>, /share/man/man.json; Tier-1 utilities in /bin. Tools: list_directory, file_stat, read_man_page, apropos_man, read_proc_file, get_swarm_peers, get_resource_limits; use list_directory instead of \`ls\` in run_command when only listing.
Prefer tools over guessing for filesystem and shell facts.`
@@ -2402,6 +3175,8 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
/** @type {{ current: Record<string, unknown> }} */
const configRef = { current: { ...config } }
/** @type {{ db: unknown | null }} */
const manCacheRef = { db: null }
let completed = false
let taskSummary = ''
@@ -2602,6 +3377,7 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
appendProgress,
home,
configRef,
manCacheRef,
onTaskComplete
})