Files
bare-operating-system/packages/bare-os-coreutils/lib/agent-tool-dispatch.js
T
2026-08-18 17:24:42 -04:00

2761 lines
103 KiB
JavaScript

/** Tool dispatch for /bin/agent (preamble; no import). */
/**
* @param {{
* ctx: Record<string, unknown>,
* toolName: string,
* argsJson: string,
* paths: { dir: string, config: string, cmdOut: string, workspace?: string, workspaceSkills?: string, skillsGlobal?: string },
* signal?: AbortSignal,
* appendProgress: (line: string) => void,
* home: string,
* configRef: { current: Record<string, unknown> },
* manCacheRef?: { db: unknown | null },
* onTaskComplete: (summary: string) => void
* }} o
*/
async function bareAgentDispatchTool(o) {
const {
ctx,
toolName,
argsJson,
paths,
signal,
appendProgress,
home,
configRef,
manCacheRef,
onTaskComplete
} = o
const internalGate = Boolean(o.internalGate)
const manDbCache = manCacheRef || { db: null }
/** @type {Record<string, unknown>} */
let args = {}
try {
args = /** @type {Record<string, unknown>} */ (JSON.parse(argsJson || '{}'))
} catch {
return bareAgentJsonResult({ ok: false, error: 'invalid_tool_arguments_json' })
}
const cfgNow = configRef.current || {}
if (
!internalGate &&
cfgNow.autonomous_active &&
Array.isArray(cfgNow.autonomous_deny_ops) &&
cfgNow.autonomous_deny_ops.map((x) => String(x)).includes(toolName)
) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_op_denied', tool: toolName })
}
if (
!internalGate &&
cfgNow.plan_mode_active &&
typeof bareAgentPlanModeToolAllowed === 'function' &&
!bareAgentPlanModeToolAllowed(toolName, args, paths)
) {
return bareAgentJsonResult({
ok: false,
error: 'plan_mode_readonly',
tool: toolName,
hint: 'exit_plan_mode before mutating, or write only ' + String(paths.plan || '~/.agent/plan.md')
})
}
if (!internalGate && typeof bareAgentRunPreToolHooks === 'function' && paths.hooks) {
const hookReason = await bareAgentRunPreToolHooks(ctx, paths.hooks, toolName, args)
if (hookReason) {
return bareAgentJsonResult({
ok: false,
error: 'hook_denied',
tool: toolName,
reason: hookReason
})
}
}
if (toolName === 'list_dir') {
return bareAgentDispatchTool({ ...o, toolName: 'list_directory' })
}
if (toolName === 'glob') {
return bareAgentDispatchTool({ ...o, toolName: 'glob_files' })
}
if (toolName === 'ripgrep') {
return bareAgentDispatchTool({ ...o, toolName: 'grep' })
}
if (toolName === 'remember') {
return bareAgentDispatchTool({
...o,
toolName: 'memory_append',
argsJson: JSON.stringify({ ...args, kind: 'FACT' })
})
}
const vfs = ctx.vfs
const mutateDenyPrefixes = (function () {
const cfg = configRef.current || {}
if (Array.isArray(cfg.mutate_deny_prefixes) && cfg.mutate_deny_prefixes.length) {
return cfg.mutate_deny_prefixes.map((x) => String(x || '')).filter(Boolean)
}
return typeof BARE_AGENT_MUTATE_DENY_PREFIXES !== 'undefined'
? BARE_AGENT_MUTATE_DENY_PREFIXES
: ['/bin', '/etc', '/boot', '/lib', '/usr', '/share', '/proc', '/dev', '/sys', '/run']
})()
const AUTONOMOUS_CHECK_ALLOW = {
'coreutils-test': 'npm test -w bare-os-coreutils',
'verify-kernel-seeder-parity': 'node scripts/verify-kernel-seeder-parity.mjs',
'verify-man-coverage': 'node scripts/verify-man-coverage.mjs',
'verify-ctx-api-feature-bits': 'node scripts/verify-ctx-api-feature-bits.mjs'
}
const execLine =
typeof ctx.execLine === 'function'
? /** @type {(s: string, opts?: unknown) => Promise<unknown>} */ (
ctx.execLine.bind(ctx)
)
: null
/**
* @param {string} line
* @param {number | undefined} timeoutMs
* @param {{ captureExit?: boolean }} [captureOpts]
*/
async function captureExec(line, timeoutMs, captureOpts) {
const outPath = paths.cmdOut
const captureExit = Boolean(captureOpts && captureOpts.captureExit)
const trimmed = String(line || '').trim()
const compoundShell =
/\n/.test(trimmed) ||
/(^|[;\s])(for|if|while|until|case|function)\b/.test(trimmed) ||
/\b(do|done|then|else|fi|esac)\b/.test(trimmed) ||
/&&|\|\||\(\(|\{|\}/.test(trimmed)
/**
* Do not wrap with `{ cmd ; }` — Bare OS `splitTokensBySemicolon` splits on every `;`
* at depth 0 and does not treat `{ … }` as a compound, so `{` became argv[0]
* (`unknown command: {`). Redirect only; read exit from env after `execLine`.
*/
const wrapped =
(compoundShell
? 'sh -c ' + bareAgentShellQuote(trimmed)
: line) +
' > ' +
bareAgentShellQuote(outPath) +
' 2>&1'
const opts =
signal || timeoutMs
? {
signal,
timeoutMs: timeoutMs || undefined
}
: undefined
try {
if (opts) await execLine(wrapped, opts)
else await execLine(wrapped)
} catch (e) {
const msg = e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return { ok: false, exitNote: msg }
}
let captured = ''
try {
if (vfs && typeof vfs.readFile === 'function') {
const buf = await vfs.readFile(outPath)
if (buf && buf.length) {
captured =
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
}
}
} catch {
/* ignore */
}
if (captureExit) {
const env = ctx.vfs?.env && typeof ctx.vfs.env === 'object' ? ctx.vfs.env : null
const rawEc =
env && env.BARE_OS_EXIT_STATUS != null && env.BARE_OS_EXIT_STATUS !== ''
? env.BARE_OS_EXIT_STATUS
: ctx.exitCode
const n = Number(rawEc)
const codeStr = String(Number.isFinite(n) ? n : 0)
const exitLine = '\nEXIT:' + codeStr + '\n'
captured += exitLine
try {
if (vfs?.readFile && vfs?.writeFile && ctx.b4a && typeof ctx.b4a.concat === 'function') {
let prev = await vfs.readFile(outPath)
const prevBytes =
prev && prev.length
? prev instanceof Uint8Array
? prev
: ctx.b4a.from(prev)
: ctx.b4a.from('')
await vfs.writeFile(
outPath,
ctx.b4a.concat([prevBytes, ctx.b4a.from(exitLine)])
)
}
} catch {
/* ignore */
}
}
const max = 120_000
if (captured.length > max) captured = captured.slice(0, max) + '\n… truncated'
return { ok: true, stdout_stderr: captured }
}
/**
* @param {string} text
*/
function redactSensitiveText(text) {
return String(text || '')
.replace(/\bBearer\s+\S+/gi, 'Bearer <redacted>')
.replace(/\bsk-[a-zA-Z0-9]{16,}\b/g, '<redacted_sk>')
.replace(
/\b([A-Z0-9_]*(KEY|TOKEN|SECRET|PASS|PASSWORD)[A-Z0-9_]*)\s*=\s*([^\s]+)/gi,
'$1=<redacted>'
)
}
/**
* @param {string} path
* @param {number} maxChars
* @param {boolean} tailOnly
* @param {boolean} redact
*/
async function readBoundedText(path, maxChars, tailOnly, redact) {
if (!vfs || typeof vfs.readFile !== 'function') {
return { ok: false, error: 'vfs unavailable' }
}
try {
const b = await vfs.readFile(path)
if (!b || !b.length) return { ok: false, error: 'empty_or_missing' }
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
if (redact) t = redactSensitiveText(t)
const out = tailOnly ? t.slice(-maxChars) : bareAgentTruncateChars(t, maxChars)
return { ok: true, path, text: out, truncated: t.length > out.length }
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return { ok: false, error: msg }
}
}
/**
* @param {string} p
*/
function autonomousPathAllowed(p) {
const s = String(p || '').trim()
if (!s) return true
return bareAgentPathAllowed(s)
}
/**
* @param {string} p
* @param {'read' | 'mutate'} [kind]
*/
function enforceAutonomousPath(p, kind) {
const cfg = configRef.current || {}
const path = String(p || '').trim()
if (!path) return true
if (kind === 'mutate') {
if (
typeof bareAgentPathAllowedMutate === 'function' &&
!bareAgentPathAllowedMutate(path, mutateDenyPrefixes)
) {
return false
}
} else if (!bareAgentPathAllowed(path)) {
return false
}
if (!cfg.autonomous_active) return true
if (kind !== 'mutate') return true
const allowList = Array.isArray(cfg.autonomous_allow_paths)
? cfg.autonomous_allow_paths.map((x) => String(x || '').trim()).filter(Boolean)
: []
if (!allowList.length || allowList.includes('*')) return true
for (const pref of allowList) {
if (path === pref || path.startsWith(pref.endsWith('/') ? pref : pref + '/')) return true
}
return false
}
try {
if (toolName === 'read_skill') {
const skill = typeof args.skill === 'string' ? args.skill.trim() : ''
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 512_000)
: 256_000
if (!skill) {
return bareAgentJsonResult({ ok: false, error: 'skill_required' })
}
const skillPaths = {
workspaceSkills:
typeof paths.workspaceSkills === 'string'
? paths.workspaceSkills
: paths.dir + '/workspace/skills',
skillsGlobal:
typeof paths.skillsGlobal === 'string' ? paths.skillsGlobal : paths.dir + '/skills'
}
appendProgress('read_skill ' + skill)
const loaded = await bareAgentLoadSkillMarkdown(ctx, skillPaths, skill)
if (!loaded.ok) {
return bareAgentJsonResult({
ok: false,
error: loaded.error || 'load_failed',
skill
})
}
let content = loaded.content
if (content.length > maxB) content = content.slice(0, maxB) + '\n… truncated'
return bareAgentJsonResult({
ok: true,
id: loaded.id,
name: loaded.skill,
path: loaded.path,
source: loaded.source,
content
})
}
if (toolName === 'glob_files') {
const pattern = typeof args.pattern === 'string' ? args.pattern.trim() : ''
if (!pattern) return bareAgentJsonResult({ ok: false, error: 'pattern_required' })
const root =
typeof args.root === 'string' && args.root.trim()
? args.root.trim()
: home || '/home'
if (!bareAgentPathAllowed(root)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const maxResults =
typeof args.max_results === 'number' && Number.isFinite(args.max_results)
? Math.min(Math.max(Math.floor(args.max_results), 1), 400)
: 100
if (typeof bareAgentGlobFiles !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'glob_unavailable' })
}
appendProgress('glob_files ' + pattern + ' @ ' + root)
const files = await bareAgentGlobFiles(ctx, root, pattern, {
maxFiles: Math.max(maxResults * 4, 200),
maxDepth: 10
})
return bareAgentJsonResult({
ok: true,
root,
pattern,
count: Math.min(files.length, maxResults),
truncated: files.length > maxResults,
files: files.slice(0, maxResults)
})
}
if (toolName === 'todo_write') {
const todosPath = paths.todos || paths.dir + '/todos.json'
const merge = args.merge !== false
const prev =
typeof bareAgentLoadTodos === 'function'
? await bareAgentLoadTodos(ctx, todosPath)
: []
let next
try {
next = bareAgentTodoApply(args.todos, { merge }, prev)
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
if (typeof bareAgentSaveTodos === 'function') {
await bareAgentSaveTodos(ctx, todosPath, next)
}
const summary = bareAgentTodoSummarize(next)
appendProgress('todo_write open=' + String(summary.open) + '/' + String(summary.total))
return bareAgentJsonResult({
ok: true,
merge,
todos: next,
summary
})
}
if (toolName === 'memory_search') {
const query = typeof args.query === 'string' ? args.query.trim() : ''
if (!query) return bareAgentJsonResult({ ok: false, error: 'query_required' })
const maxHits =
typeof args.max_hits === 'number' && Number.isFinite(args.max_hits)
? Math.min(Math.max(Math.floor(args.max_hits), 1), 24)
: 8
const memDir =
typeof paths.workspaceMemory === 'string'
? paths.workspaceMemory
: paths.dir + '/workspace/memory'
const workspace =
typeof paths.workspace === 'string' ? paths.workspace : paths.dir + '/workspace'
const seeds = []
if (typeof bareAgentVfsWalkFiles === 'function') {
const walked = await bareAgentVfsWalkFiles(ctx, memDir, {
maxFiles: 80,
maxDepth: 4
})
for (let i = 0; i < walked.length; i++) seeds.push(walked[i])
}
seeds.push(workspace + '/MEMORY.md')
if (paths.compact) seeds.push(paths.compact)
else seeds.push(paths.dir + '/compact.md')
const uniq = []
const seenMem = Object.create(null)
for (let i = 0; i < seeds.length; i++) {
if (seenMem[seeds[i]]) continue
seenMem[seeds[i]] = 1
uniq.push(seeds[i])
}
appendProgress('memory_search ' + query.slice(0, 80))
const hits =
typeof bareAgentMemorySearchFiles === 'function'
? await bareAgentMemorySearchFiles(ctx, uniq, query, { maxHits })
: []
return bareAgentJsonResult({ ok: true, query, hits })
}
if (toolName === 'memory_get') {
const rawPath = typeof args.path === 'string' ? args.path.trim() : ''
if (!rawPath) return bareAgentJsonResult({ ok: false, error: 'path_required' })
const memDir =
typeof paths.workspaceMemory === 'string'
? paths.workspaceMemory
: paths.dir + '/workspace/memory'
const workspace =
typeof paths.workspace === 'string' ? paths.workspace : paths.dir + '/workspace'
let path = rawPath
if (!path.startsWith('/')) path = memDir + '/' + path.replace(/^\/+/, '')
const allowed =
path === workspace + '/MEMORY.md' ||
path === (paths.compact || paths.dir + '/compact.md') ||
path === memDir ||
path.indexOf(memDir + '/') === 0
if (!allowed) {
return bareAgentJsonResult({
ok: false,
error: 'memory_path_denied',
hint: 'path must be under ' + memDir
})
}
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 256_000)
: 64_000
appendProgress('memory_get ' + path)
let text = await bareAgentReadTextFile(ctx, path)
if (!text) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing', path })
const truncated = text.length > maxB
if (truncated) text = text.slice(0, maxB) + '\n… truncated'
return bareAgentJsonResult({ ok: true, path, content: text, truncated })
}
if (toolName === 'enter_plan_mode') {
const planPath = paths.plan || paths.dir + '/plan.md'
configRef.current = { ...(configRef.current || {}), plan_mode_active: true }
if (typeof bareAgentSaveConfigFromTools === 'function') {
await bareAgentSaveConfigFromTools(ctx, paths, configRef.current)
}
const note = typeof args.note === 'string' ? args.note.trim() : ''
let prev = ''
try {
prev = await bareAgentReadTextFile(ctx, planPath)
} catch {
prev = ''
}
if (!prev.trim()) {
const starter =
'# Plan\n\n' +
(note ? note + '\n' : '- Investigate with read-only tools.\n- Write the plan here.\n- Call exit_plan_mode when ready to implement.\n')
await bareAgentWriteTextFile(ctx, planPath, starter)
} else if (note) {
await bareAgentWriteTextFile(ctx, planPath, prev.replace(/\s*$/, '') + '\n\n' + note + '\n')
}
appendProgress('enter_plan_mode ' + planPath)
return bareAgentJsonResult({
ok: true,
plan_mode_active: true,
plan: planPath
})
}
if (toolName === 'exit_plan_mode') {
const summary = typeof args.summary === 'string' ? args.summary.trim() : ''
configRef.current = { ...(configRef.current || {}), plan_mode_active: false }
if (typeof bareAgentSaveConfigFromTools === 'function') {
await bareAgentSaveConfigFromTools(ctx, paths, configRef.current)
}
if (summary && paths.plan) {
const prev = await bareAgentReadTextFile(ctx, paths.plan)
await bareAgentWriteTextFile(
ctx,
paths.plan,
(prev ? prev.replace(/\s*$/, '') + '\n\n' : '') + '## Exit\n' + summary + '\n'
)
}
appendProgress('exit_plan_mode')
return bareAgentJsonResult({
ok: true,
plan_mode_active: false,
summary: summary || null
})
}
if (toolName === 'ask_user_question') {
const rows = Array.isArray(args.questions) ? args.questions : []
if (!rows.length) {
return bareAgentJsonResult({ ok: false, error: 'questions_required' })
}
/** @type {Record<string, unknown>[]} */
const questions = []
for (let i = 0; i < rows.length; i++) {
const row = rows[i] && typeof rows[i] === 'object' ? rows[i] : {}
const question = String(row.question || '').trim()
if (!question) continue
const opts = Array.isArray(row.options) ? row.options : []
questions.push({
question,
multi_select: Boolean(row.multi_select),
options: opts.map(function (opt) {
if (opt && typeof opt === 'object') {
return {
label: String(opt.label || ''),
description: String(opt.description || '')
}
}
return { label: String(opt || ''), description: '' }
})
})
}
if (!questions.length) {
return bareAgentJsonResult({ ok: false, error: 'questions_required' })
}
const askPath = paths.ask || paths.dir + '/ask.json'
const payload = {
asked_at: new Date().toISOString(),
questions
}
if (typeof bareAgentWriteJsonFile === 'function') {
await bareAgentWriteJsonFile(ctx, askPath, payload)
}
const text = questions
.map(function (q, i) {
const opts = Array.isArray(q.options)
? q.options
.map(function (o, j) {
return (
' ' +
String(j + 1) +
') ' +
String(o.label || '') +
(o.description ? ' — ' + String(o.description) : '')
)
})
.join('\n')
: ''
return (
String(i + 1) +
'. ' +
q.question +
(q.multi_select ? ' (multi-select)' : '') +
(opts ? '\n' + opts : '')
)
})
.join('\n')
appendProgress('ask_user_question n=' + String(questions.length))
return bareAgentJsonResult({
ok: true,
ask: askPath,
questions,
text,
hint: 'Wait for the user to answer these questions on the next turn.'
})
}
if (toolName === 'task_complete') {
const summary = typeof args.summary === 'string' ? args.summary : ''
appendProgress('task_complete: ' + summary.slice(0, 200))
onTaskComplete(summary || '(done)')
return bareAgentJsonResult({
ok: true,
completed: true,
summary
})
}
if (toolName === 'autonomous_run') {
const goal = typeof args.goal === 'string' ? args.goal.trim() : ''
const scopePath = typeof args.scope_path === 'string' ? args.scope_path.trim() : ''
const cfg = configRef.current || {}
if (!goal) return bareAgentJsonResult({ ok: false, error: 'goal_required' })
if (scopePath && !autonomousPathAllowed(scopePath)) {
return bareAgentJsonResult({ ok: false, error: 'scope_path_denied' })
}
const maxRuntimeMsRaw =
typeof args.max_runtime_ms === 'number' && Number.isFinite(args.max_runtime_ms)
? args.max_runtime_ms
: cfg.autonomous_max_runtime_ms
const requiredChecks = Array.isArray(args.required_checks)
? args.required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: []
const unknown = requiredChecks.filter((x) => !Object.prototype.hasOwnProperty.call(AUTONOMOUS_CHECK_ALLOW, x))
if (unknown.length) {
return bareAgentJsonResult({ ok: false, error: 'unknown_required_checks', unknown, allowlist: Object.keys(AUTONOMOUS_CHECK_ALLOW) })
}
const started =
typeof bareAgentBeginAutonomousRun === 'function'
? bareAgentBeginAutonomousRun(cfg, {
goal,
maxRuntimeMs: maxRuntimeMsRaw,
requiredChecks
})
: {
...cfg,
autonomous_mode_enabled: true,
autonomous_active: true,
autonomous_stop_requested: false,
autonomous_started_at_ms: Date.now(),
autonomous_goal: goal,
autonomous_status: 'running',
autonomous_last_error: '',
autonomous_max_runtime_ms: Math.min(
Math.max(Math.floor(Number(maxRuntimeMsRaw) || 0), 60000),
7_200_000
),
autonomous_completion_required_checks: requiredChecks
}
const merged = bareAgentMergeConfigPatch(cfg, started)
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run start goal=' + goal.slice(0, 160))
return bareAgentJsonResult({
ok: true,
active: true,
enabled: true,
goal,
scope_path: scopePath || null,
max_runtime_ms: merged.autonomous_max_runtime_ms,
required_checks: requiredChecks,
status: 'running'
})
}
if (toolName === 'autonomous_run_status') {
const cfg = configRef.current || {}
const started = Number(cfg.autonomous_started_at_ms) || 0
const elapsed = started > 0 ? Math.max(0, Date.now() - started) : 0
const maxRuntime = Number(cfg.autonomous_max_runtime_ms) || 0
return bareAgentJsonResult({
ok: true,
autonomous_mode_enabled: Boolean(cfg.autonomous_mode_enabled),
active: Boolean(cfg.autonomous_active),
stop_requested: Boolean(cfg.autonomous_stop_requested),
goal: String(cfg.autonomous_goal || ''),
status: String(cfg.autonomous_status || 'idle'),
last_error: String(cfg.autonomous_last_error || ''),
started_at_ms: started,
elapsed_ms: elapsed,
max_runtime_ms: maxRuntime,
remaining_ms: maxRuntime > 0 ? Math.max(0, maxRuntime - elapsed) : 0,
required_checks: Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks
: []
})
}
if (toolName === 'autonomous_run_stop') {
const cfg = configRef.current || {}
const reason = typeof args.reason === 'string' ? args.reason.trim() : ''
const stopped =
typeof bareAgentStopAutonomousRun === 'function'
? bareAgentStopAutonomousRun(cfg, reason)
: {
...cfg,
autonomous_stop_requested: true,
autonomous_status: 'stopping',
autonomous_last_error: reason || String(cfg.autonomous_last_error || '')
}
const merged = bareAgentMergeConfigPatch(cfg, stopped)
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run_stop ' + (reason || 'requested'))
return bareAgentJsonResult({ ok: true, stop_requested: true, reason: reason || null })
}
if (toolName === 'read_file') {
const path =
typeof bareAgentToolPathArg === 'function'
? bareAgentToolPathArg(args)
: typeof args.path === 'string'
? args.path
: ''
if (!enforceAutonomousPath(path, 'read')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 1_000_000)
: 256_000
if (!bareAgentPathAllowed(path)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('read_file ' + path)
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))
if (t.length > maxB) t = t.slice(0, maxB) + '\n… truncated'
const hasSlice =
(typeof args.offset === 'number' && Number.isFinite(args.offset)) ||
(typeof args.limit === 'number' && Number.isFinite(args.limit))
if (hasSlice && typeof bareAgentSliceFileLines === 'function') {
const sliced = bareAgentSliceFileLines(t, {
offset:
typeof args.offset === 'number' && Number.isFinite(args.offset)
? args.offset
: 1,
limit:
typeof args.limit === 'number' && Number.isFinite(args.limit)
? args.limit
: undefined,
numbered: args.numbered !== false
})
return bareAgentJsonResult({ ok: true, path, ...sliced })
}
return bareAgentJsonResult({ ok: true, path, content: t })
}
if (toolName === 'write_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path, 'mutate')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const content = typeof args.content === 'string' ? args.content : ''
if (!path.startsWith('/') || path.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
}
if (!vfs || typeof vfs.writeFile !== 'function' || typeof vfs.mkdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('write_file ' + path)
if (typeof bareAgentPushEdit === 'function' && paths.edits) {
try {
const prev = await bareAgentReadTextFile(ctx, path)
await bareAgentPushEdit(ctx, paths.edits, { path, prev, tool: 'write_file' })
} catch {
/* new file */
}
}
let dir = path.replace(/\/[^/]+$/, '')
if (dir && dir !== path) await vfs.mkdir(dir, { recursive: true })
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(content)
: new TextEncoder().encode(content)
await vfs.writeFile(path, body)
return bareAgentJsonResult({ ok: true, bytes: body.length })
}
if (toolName === 'edit_file' || toolName === 'search_replace') {
const path =
typeof bareAgentToolPathArg === 'function'
? bareAgentToolPathArg(args)
: typeof args.path === 'string'
? args.path
: ''
if (!enforceAutonomousPath(path, 'mutate')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs?.readFile || !vfs?.writeFile) {
return bareAgentJsonResult({ ok: false, error: 'path_or_vfs' })
}
appendProgress(toolName + ' ' + path)
const buf = await vfs.readFile(path)
if (typeof bareAgentPushEdit === 'function' && paths.edits) {
try {
const snap =
buf && buf.length
? typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
: ''
await bareAgentPushEdit(ctx, paths.edits, { path, prev: snap, tool: toolName })
} catch {
/* ignore */
}
}
let prev =
buf && buf.length
? typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
: ''
const full =
toolName === 'search_replace'
? ''
: typeof args.content === 'string'
? args.content
: ''
const oldStr = typeof args.old_string === 'string' ? args.old_string : ''
const newStr = typeof args.new_string === 'string' ? args.new_string : ''
const replaceAll = Boolean(args.replace_all)
let next = prev
let replacements = 0
if (full.length > 0) {
next = full
replacements = 1
} else if (typeof bareAgentSearchReplaceApply === 'function') {
const applied = bareAgentSearchReplaceApply(prev, oldStr, newStr, replaceAll)
if (!applied.ok) {
return bareAgentJsonResult({
ok: false,
error: applied.error,
count: applied.count,
hint: applied.hint
})
}
next = applied.next
replacements = applied.replacements || 0
} else if (oldStr) {
if (!prev.includes(oldStr)) {
return bareAgentJsonResult({ ok: false, error: 'old_string not found' })
}
next = replaceAll ? prev.split(oldStr).join(newStr) : prev.replace(oldStr, newStr)
replacements = 1
} else {
return bareAgentJsonResult({
ok: false,
error: 'need content or old_string+new_string'
})
}
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(next)
: new TextEncoder().encode(next)
let dir = path.replace(/\/[^/]+$/, '')
if (dir && dir !== path && typeof vfs.mkdir === 'function') {
await vfs.mkdir(dir, { recursive: true })
}
await vfs.writeFile(path, body)
return bareAgentJsonResult({
ok: true,
bytes: body.length,
replacements,
replace_all: replaceAll
})
}
if (toolName === 'create_directory') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path, 'mutate')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!path.startsWith('/')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
}
if (!vfs || typeof vfs.mkdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('mkdir ' + path)
await vfs.mkdir(path, { recursive: true })
return bareAgentJsonResult({ ok: true })
}
if (toolName === 'search_files') {
const pattern = typeof args.pattern === 'string' ? args.pattern : ''
const root = typeof args.root === 'string' ? args.root : '/home'
const maxLines =
typeof args.max_lines === 'number' && Number.isFinite(args.max_lines)
? Math.min(Math.floor(args.max_lines), 500)
: 200
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('search_files ' + pattern + ' @ ' + root)
const cmd =
'grep -Rnl -- ' +
bareAgentShellQuote(pattern) +
' ' +
bareAgentShellQuote(root) +
' 2>/dev/null | head -n ' +
maxLines
const r = await captureExec(cmd, 60000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'run_command') {
const command = typeof args.command === 'string' ? args.command : ''
const deniedBy = bareAgentCommandDeniedByList(
command,
(configRef.current || {}).command_deny
)
if (deniedBy) {
return bareAgentJsonResult({
ok: false,
error: 'command_denied',
matched: deniedBy
})
}
const timeoutMs =
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' })
}
let cwd = typeof args.cwd === 'string' ? args.cwd.trim() : ''
if (!cwd && paths.lastCwd) {
try {
cwd = (await bareAgentReadTextFile(ctx, paths.lastCwd)).trim()
} catch {
cwd = ''
}
}
let line = command
if (cwd) {
if (!bareAgentPathAllowed(cwd)) {
return bareAgentJsonResult({ ok: false, error: 'cwd_not_allowed' })
}
line = 'cd ' + bareAgentShellQuote(cwd) + ' && ' + command
try {
if (paths.lastCwd) await bareAgentWriteTextFile(ctx, paths.lastCwd, cwd + '\n')
} catch {
/* optional */
}
}
appendProgress('run_command ' + line.slice(0, 160))
const r = await captureExec(line, timeoutMs, { captureExit })
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, cwd: cwd || null, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'run_js_script') {
const code = typeof args.code === 'string' ? args.code : ''
const scriptPath = paths.dir + '/_tmp_agent_run.mjs'
if (!vfs?.writeFile || !execLine) {
return bareAgentJsonResult({ ok: false, error: 'vfs or execLine' })
}
appendProgress('run_js_script (' + code.length + ' chars)')
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(code)
: new TextEncoder().encode(code)
await vfs.writeFile(scriptPath, body)
/** Absolute path → kernel-runner runs .mjs like `./script.mjs` (no host `node` binary). captureExec adds stdout redirect. */
const cmd = bareAgentShellQuote(scriptPath)
const r = await captureExec(cmd, 60000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'get_system_info') {
/** @type {Record<string, unknown>} */
const info = {}
try {
info.ctxApiVersion =
typeof ctx.ctxApiVersion === 'string'
? ctx.ctxApiVersion
: typeof ctx.ctxApiVersion === 'number'
? String(ctx.ctxApiVersion)
: undefined
} catch {
/* ignore */
}
const want =
typeof args.want === 'string' ? args.want : 'summary'
if (want === 'summary') {
info.discovery_hint =
'For live kernel feature flags use read_proc_file on /proc/bare_os/features (or features.json); apropos_man only searches man-page text, not runtime state. Otherwise 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')
if (b && b.length) {
info.capabilities_json =
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
}
} catch {
/* ignore */
}
}
if (want === 'swarm' && vfs?.readFile) {
try {
const b = await vfs.readFile('/proc/bare_os/swarm.json')
if (b && b.length) {
info.swarm =
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
ctx.b4a.toString
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
}
} catch {
/* ignore */
}
}
try {
if (execLine) await execLine('uname -a > ' + bareAgentShellQuote(paths.cmdOut) + ' 2>&1')
if (vfs?.readFile) {
const buf = await vfs.readFile(paths.cmdOut)
if (buf && buf.length) {
info.uname =
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf).trim()
: String(new TextDecoder().decode(buf)).trim()
}
}
} catch {
/* ignore */
}
appendProgress('get_system_info ' + want)
return bareAgentJsonResult({ ok: true, want, info })
}
if (toolName === 'edit_agent_config') {
const patch = args.patch
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
return bareAgentJsonResult({ ok: false, error: 'bad patch' })
}
const merged = bareAgentMergeConfigPatch(configRef.current, patch)
configRef.current = merged
await bareAgentSaveConfigFromTools(ctx, paths, merged)
if (
Object.prototype.hasOwnProperty.call(patch, 'owner_name') ||
Object.prototype.hasOwnProperty.call(patch, 'agent_label')
) {
const workspace =
typeof paths.workspace === 'string'
? paths.workspace
: paths.dir + '/workspace'
await bareAgentSyncWorkspaceFromConfig(ctx, { workspace }, merged)
}
appendProgress('edit_agent_config')
return bareAgentJsonResult({ ok: true, saved: true })
}
if (toolName === 'list_bin') {
const limit =
typeof args.limit === 'number' && Number.isFinite(args.limit)
? Math.min(Math.floor(args.limit), 800)
: 400
if (!vfs || typeof vfs.readdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'readdir unavailable' })
}
appendProgress('list_bin')
try {
const names = await vfs.readdir('/bin')
const arr = Array.isArray(names) ? [...names].slice(0, limit) : []
arr.sort()
return bareAgentJsonResult({
ok: true,
count: arr.length,
names: arr
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
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 (Boolean(args.tree) && typeof bareAgentRenderTree === 'function') {
appendProgress('list_directory tree ' + dir)
const tree = await bareAgentRenderTree(ctx, dir, {
max: maxEnt,
maxDepth: args.max_depth
})
return bareAgentJsonResult(tree)
}
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, mutateDenyPrefixes) ||
!bareAgentPathAllowedMutate(to, mutateDenyPrefixes) ||
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: 'allow_delete is on by default; this home set it false'
})
}
if (reqTok && token !== reqTok) {
return bareAgentJsonResult({ ok: false, error: 'confirm_token_required' })
}
if (!bareAgentPathAllowedMutate(path, mutateDenyPrefixes) || path.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs) {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('delete_path ' + path)
if (typeof bareAgentPushEdit === 'function' && paths.edits) {
try {
const prev = await bareAgentReadTextFile(ctx, path)
await bareAgentPushEdit(ctx, paths.edits, { path, prev, tool: 'delete_path' })
} catch {
/* missing */
}
}
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',
hint: 'read_proc_file accepts any /proc path'
})
}
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 === 'list_services') {
appendProgress('list_services')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
let readinessText = ''
/** @type {Record<string, unknown> | null} */
let readinessJson = null
try {
const b = await vfs.readFile('/proc/bare_os/initd_readiness.json')
if (b && b.length) {
readinessText =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
try {
const parsed = JSON.parse(readinessText)
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
readinessJson = /** @type {Record<string, unknown>} */ (parsed)
} catch {
/* ignore */
}
}
} catch {
/* ignore */
}
const units = Array.isArray(readinessJson?.units)
? /** @type {unknown[]} */ (readinessJson.units)
: []
const rows = units
.filter((u) => u && typeof u === 'object')
.map((u) => {
const o = /** @type {Record<string, unknown>} */ (u)
return {
name: String(o.name || ''),
phase: String(o.phase || ''),
startedAtMs:
typeof o.startedAtMs === 'number' ? o.startedAtMs : undefined,
error: typeof o.error === 'string' ? o.error : undefined
}
})
.filter((r) => r.name)
return bareAgentJsonResult({
ok: true,
source: '/proc/bare_os/initd_readiness.json',
count: rows.length,
units: rows,
note:
rows.length > 0
? 'Runtime units from initd readiness snapshot.'
: 'No parsed readiness units available.'
})
}
if (toolName === 'service_status') {
const name = typeof args.name === 'string' ? args.name.trim() : ''
if (!name) return bareAgentJsonResult({ ok: false, error: 'name_required' })
appendProgress('service_status ' + name)
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
try {
const b = await vfs.readFile('/proc/bare_os/initd_readiness.json')
const t =
b && b.length
? typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
: ''
const parsed = JSON.parse(t)
const units = Array.isArray(parsed?.units) ? parsed.units : []
const hit =
units.find((u) => u && typeof u === 'object' && String(u.name || '') === name) ||
null
if (!hit) {
return bareAgentJsonResult({
ok: false,
error: 'not_found',
source: '/proc/bare_os/initd_readiness.json'
})
}
const o = /** @type {Record<string, unknown>} */ (hit)
return bareAgentJsonResult({
ok: true,
status: {
name: String(o.name || ''),
phase: String(o.phase || ''),
startedAtMs:
typeof o.startedAtMs === 'number' ? o.startedAtMs : undefined,
error: typeof o.error === 'string' ? o.error : undefined
},
journal_hint: '/run/bare-os/unit-journal/' + name + '.ndjson'
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'list_timers') {
const includePreview = Boolean(args.include_preview)
const maxEntries =
typeof args.max_entries === 'number' && Number.isFinite(args.max_entries)
? Math.min(Math.max(Math.floor(args.max_entries), 1), 512)
: 128
appendProgress('list_timers')
if (!vfs || typeof vfs.readdir !== 'function' || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
const dir = home + '/.config/bare-os/timers'
let names = []
try {
names = await vfs.readdir(dir)
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg, path: dir })
}
const timerNames = names
.filter((n) => typeof n === 'string' && n.endsWith('.timer'))
.sort()
.slice(0, maxEntries)
/** @type {unknown[]} */
const timers = []
for (const name of timerNames) {
const path = dir + '/' + name
/** @type {Record<string, unknown>} */
const row = { name, path }
if (includePreview) {
try {
const b = await vfs.readFile(path)
const txt =
b && b.length
? typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
: ''
row.preview = bareAgentTruncateChars(txt, 1200)
} catch (e) {
row.preview_error =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
}
}
timers.push(row)
}
return bareAgentJsonResult({
ok: true,
path: dir,
count: timers.length,
timers
})
}
if (toolName === 'read_cron_log' || toolName === 'read_audit_log') {
const maxChars =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.max(Math.floor(args.max_chars), 200), 80_000)
: 12_000
const tailOnly = Boolean(args.tail_only)
const redact = toolName === 'read_audit_log' ? args.redact !== false : false
const path =
toolName === 'read_audit_log'
? '/var/log/bare-os/audit.log'
: '/var/log/bare-os/cron.log'
appendProgress(toolName + ' ' + path)
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
try {
const b = await vfs.readFile(path)
if (!b || !b.length) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
if (redact) {
t = t
.replace(/\bBearer\s+\S+/gi, 'Bearer <redacted>')
.replace(/\bsk-[a-zA-Z0-9]{16,}\b/g, '<redacted_sk>')
.replace(/\b([A-Z0-9_]*(KEY|TOKEN|SECRET|PASS)[A-Z0-9_]*)\s*=\s*([^\s]+)/gi, '$1=<redacted>')
}
const out = tailOnly ? t.slice(-maxChars) : bareAgentTruncateChars(t, maxChars)
return bareAgentJsonResult({
ok: true,
path,
text: out,
truncated: t.length > out.length
})
} 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_boot_policy' || toolName === 'read_kernel_extension_resolution') {
const maxChars =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.max(Math.floor(args.max_chars), 200), 120_000)
: 20_000
const path =
toolName === 'read_boot_policy'
? '/etc/bare-os/boot.policy.json'
: '/run/bare-os/kernel-ext-resolution.json'
appendProgress(toolName + ' ' + path)
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
try {
const b = await vfs.readFile(path)
if (!b || !b.length) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
/** @type {unknown} */
let json = null
try {
json = JSON.parse(t)
} catch {
json = null
}
return bareAgentJsonResult({
ok: true,
path,
text: bareAgentTruncateChars(t, maxChars),
json
})
} 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_initd_graph') {
appendProgress('get_initd_graph')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
/** @type {Record<string, unknown>} */
const out = { ok: true }
for (const p of ['/proc/bare_os/initd_dag.json', '/proc/bare_os/initd_readiness.json']) {
try {
const b = await vfs.readFile(p)
if (!b || !b.length) continue
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
try {
out[p] = JSON.parse(t)
} catch {
out[p] = bareAgentTruncateChars(t, 8000)
}
} catch {
/* ignore missing */
}
}
return bareAgentJsonResult(out)
}
if (toolName === 'read_unit_journal') {
const unit = typeof args.unit === 'string' ? args.unit.trim() : ''
if (!/^[a-zA-Z0-9._-]{1,96}$/.test(unit)) {
return bareAgentJsonResult({ ok: false, error: 'invalid_unit' })
}
const maxChars =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.max(Math.floor(args.max_chars), 200), 120_000)
: 12_000
const tailOnly = Boolean(args.tail_only)
const p = '/run/bare-os/unit-journal/' + unit + '.ndjson'
appendProgress('read_unit_journal ' + unit)
return bareAgentJsonResult(await readBoundedText(p, maxChars, tailOnly, true))
}
if (toolName === 'inspect_ipc_backpressure') {
appendProgress('inspect_ipc_backpressure')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
const candidates = [
'/proc/bare_os/ipc_backpressure.json',
'/proc/bare_os/replication_operator_sketch.json',
'/proc/bare_os/metrics_live.json'
]
/** @type {Record<string, unknown>} */
const out = { ok: true, sources: [] }
for (const p of candidates) {
try {
const b = await vfs.readFile(p)
if (!b || !b.length) continue
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
out.sources.push(p)
try {
out[p] = JSON.parse(t)
} catch {
out[p] = bareAgentTruncateChars(t, 6000)
}
} catch {
/* ignore */
}
}
return bareAgentJsonResult(out)
}
if (toolName === 'get_network_summary') {
appendProgress('get_network_summary')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
const candidates = [
'/proc/bare_os/net_summary.json',
'/proc/bare_os/swarm.json',
'/proc/bare_os/swarm_status.json',
'/proc/bare_os/swarm_connection_manager_status.json'
]
/** @type {Record<string, unknown>} */
const out = { ok: true, sources: [] }
for (const p of candidates) {
try {
const b = await vfs.readFile(p)
if (!b || !b.length) continue
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
out.sources.push(p)
try {
out[p] = JSON.parse(t)
} catch {
out[p] = bareAgentTruncateChars(t, 6000)
}
} catch {
/* ignore */
}
}
return bareAgentJsonResult(out)
}
if (toolName === 'tail_telemetry_streams') {
const maxChars =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.max(Math.floor(args.max_chars), 200), 60_000)
: 8000
appendProgress('tail_telemetry_streams')
const pathsToRead = [
'/var/log/bare-os/audit.log',
'/var/log/bare-os/logger.jsonl',
'/var/log/bare-os/initd.log',
'/var/log/bare-os/cron.log'
]
/** @type {Record<string, unknown>} */
const out = { ok: true, streams: {} }
for (const p of pathsToRead) {
out.streams[p] = await readBoundedText(p, maxChars, true, true)
}
return bareAgentJsonResult(out)
}
if (toolName === 'pkg_index_lookup') {
const key = typeof args.key === 'string' ? args.key.trim() : ''
if (!key) return bareAgentJsonResult({ ok: false, error: 'key_required' })
appendProgress('pkg_index_lookup ' + key.slice(0, 80))
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const cmd = 'pkg-swarm-index get --key ' + bareAgentShellQuote(key)
const r = await captureExec(cmd, 90000)
if (r.ok === false) return bareAgentJsonResult(r)
const txt = typeof r.stdout_stderr === 'string' ? r.stdout_stderr : ''
let json = null
try {
json = JSON.parse(txt)
} catch {
json = null
}
return bareAgentJsonResult({ ok: true, key, json, text: bareAgentTruncateChars(txt, 12000) })
}
if (toolName === 'list_verification_scripts') {
appendProgress('list_verification_scripts')
if (!vfs || typeof vfs.readdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
const out = []
for (const dir of ['/scripts', '/home/guest/scripts']) {
try {
const names = await vfs.readdir(dir)
const rows = names
.filter((n) => typeof n === 'string' && (n.endsWith('.mjs') || n.endsWith('.js')))
.sort()
.slice(0, 400)
.map((n) => dir + '/' + n)
out.push(...rows)
} catch {
/* ignore */
}
}
return bareAgentJsonResult({ ok: true, scripts: out })
}
if (toolName === 'run_maintenance_gate') {
const command = typeof args.command === 'string' ? args.command.trim() : ''
const timeoutMs =
typeof args.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
? Math.min(Math.max(Math.floor(args.timeout_ms), 1000), 900000)
: 180000
appendProgress('run_maintenance_gate ' + command)
const allow = {
'verify-kernel-seeder-parity': 'node scripts/verify-kernel-seeder-parity.mjs',
'verify-man-coverage': 'node scripts/verify-man-coverage.mjs',
'verify-ctx-api-feature-bits': 'node scripts/verify-ctx-api-feature-bits.mjs',
'coreutils-test': 'npm test -w bare-os-coreutils'
}
if (!Object.prototype.hasOwnProperty.call(allow, command)) {
return bareAgentJsonResult({ ok: false, error: 'command_not_allowlisted', allowlist: Object.keys(allow) })
}
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const cmd = /** @type {Record<string, string>} */ (allow)[command]
const r = await captureExec(cmd, timeoutMs, { captureExit: true })
return bareAgentJsonResult(r)
}
if (toolName === 'run_contract_checks') {
const profile = typeof args.profile === 'string' ? args.profile.trim() : ''
appendProgress('run_contract_checks ' + profile)
const mapping = {
core: 'node scripts/verify-kernel-seeder-parity.mjs && node scripts/verify-man-coverage.mjs',
docs: 'node scripts/verify-doc-links.mjs && node scripts/verify-doc-contracts.mjs',
parity: 'node scripts/verify-kernel-seeder-parity.mjs'
}
if (!Object.prototype.hasOwnProperty.call(mapping, profile)) {
return bareAgentJsonResult({ ok: false, error: 'unknown_profile', profiles: Object.keys(mapping) })
}
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const cmd = /** @type {Record<string, string>} */ (mapping)[profile]
const r = await captureExec(cmd, 300000, { captureExit: true })
return bareAgentJsonResult(r)
}
if (toolName === 'summarize_build_drift') {
appendProgress('summarize_build_drift')
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const r = await captureExec('git status --short', 60000)
return bareAgentJsonResult(r)
}
if (toolName === 'get_hrpc_bridge_health') {
appendProgress('get_hrpc_bridge_health')
/** @type {Record<string, unknown>} */
const out = {
ok: true,
hostCapabilities: {
hrpcBridge:
typeof ctx.bareOsHostCapability === 'function'
? Boolean(ctx.bareOsHostCapability('hrpcBridge'))
: false
}
}
if (vfs && typeof vfs.readFile === 'function') {
for (const p of ['/proc/bare_os/hrpc_route_table.json', '/proc/bare_os/hrpc_health.json']) {
try {
const b = await vfs.readFile(p)
if (!b || !b.length) continue
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
try {
out[p] = JSON.parse(t)
} catch {
out[p] = bareAgentTruncateChars(t, 6000)
}
} catch {
/* ignore */
}
}
}
return bareAgentJsonResult(out)
}
if (toolName === 'get_hrpc_allowlist_status') {
appendProgress('get_hrpc_allowlist_status')
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const r = await captureExec('hrpc probe', 60000)
return bareAgentJsonResult(r)
}
if (toolName === 'emit_host_notification' || toolName === 'request_host_action') {
const cfg = configRef.current || {}
if (cfg && cfg.emergency_stop_mutations) {
return bareAgentJsonResult({ ok: false, error: 'emergency_stop_mutations_enabled' })
}
if (toolName === 'emit_host_notification' && !cfg.allow_host_notifications) {
return bareAgentJsonResult({ ok: false, error: 'host_notifications_disabled' })
}
if (toolName === 'request_host_action' && !cfg.allow_host_actions) {
return bareAgentJsonResult({ ok: false, error: 'host_actions_disabled' })
}
if (!cfg.allow_bridge_mutations) {
return bareAgentJsonResult({ ok: false, error: 'bridge_mutations_disabled' })
}
if (typeof ctx.bareOsHrpcRequest !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'bareOsHrpcRequest unavailable' })
}
try {
if (toolName === 'emit_host_notification') {
const payload = {
title: String(args.title || '').slice(0, 200),
message: String(args.message || '').slice(0, 2000),
level: typeof args.level === 'string' ? args.level : 'info'
}
appendProgress('emit_host_notification ' + payload.title)
const res = await ctx.bareOsHrpcRequest('bare_os', 'host_notify', payload)
return bareAgentJsonResult({ ok: true, result: res })
}
const action = String(args.action || '').trim()
if (!/^[a-zA-Z0-9._-]{1,64}$/.test(action)) {
return bareAgentJsonResult({ ok: false, error: 'invalid_action' })
}
const payload =
args.payload && typeof args.payload === 'object' && !Array.isArray(args.payload)
? args.payload
: {}
appendProgress('request_host_action ' + action)
const res = await ctx.bareOsHrpcRequest('bare_os', 'host_action', {
action,
payload
})
return bareAgentJsonResult({ ok: true, result: res })
} 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 }
)
}
if (toolName === 'verification_hints') {
const topic = typeof args.topic === 'string' ? args.topic : ''
const pathsTouch =
typeof args.paths_touched === 'string' ? args.paths_touched : ''
appendProgress('verification_hints')
const combined = topic + ' ' + pathsTouch.replace(/,/g, ' ')
const hints = bareAgentVerificationHintsList(combined)
return bareAgentJsonResult({
ok: true,
scope_note:
'Suggested commands apply to the Bare OS git checkout on the host (npm/node at repo root). They do not run automatically.',
suggested_commands: hints
})
}
if (toolName === 'runtime_diagnostic_bundle') {
appendProgress('runtime_diagnostic_bundle')
/** @type {Record<string, unknown>} */
const bundle = {}
bundle.bareOsCtxApiVersion =
typeof ctx.bareOsCtxApiVersion !== 'undefined' ? ctx.bareOsCtxApiVersion : null
try {
if (typeof ctx.bareOsGetResourceStatus === 'function')
bundle.resources = ctx.bareOsGetResourceStatus()
} catch {
bundle.resources_error = true
}
/** @type {Record<string, unknown>} */
const procParts = {}
/** @type {string[]} */
let list = [...BARE_AGENT_PROC_READ_ALLOWLIST]
if (vfs && typeof vfs.readdir === 'function') {
try {
const names = await vfs.readdir('/proc/bare_os')
if (Array.isArray(names)) {
for (let i = 0; i < names.length; i++) {
const n = String(names[i] || '')
if (!n || n === '.' || n === '..') continue
const full = '/proc/bare_os/' + n
if (list.indexOf(full) === -1) list.push(full)
}
}
} catch {
/* keep seed list */
}
}
if (vfs && typeof vfs.readFile === 'function') {
for (let i = 0; i < list.length; i++) {
const procPath = list[i]
try {
const buf = await vfs.readFile(procPath)
if (!buf || !buf.length) continue
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
if (t.length > 80_000) t = t.slice(0, 80_000) + '\n… truncated'
try {
procParts[procPath] = JSON.parse(t)
} catch {
procParts[procPath] = t
}
} catch {
/* missing path */
}
}
}
bundle.proc = procParts
return bareAgentJsonResult({ ok: true, bundle })
}
if (toolName === 'web_fetch') {
const url = typeof args.url === 'string' ? args.url : ''
let hostHint = ''
try {
hostHint = new URL(url).hostname
} catch {
hostHint = ''
}
appendProgress('web_fetch ' + (hostHint || url.slice(0, 80)))
try {
const out = await bareWebRunTool({
ctx,
url,
method: typeof args.method === 'string' ? args.method : undefined,
headers:
args.headers &&
typeof args.headers === 'object' &&
!Array.isArray(args.headers)
? /** @type {Record<string, unknown>} */ (args.headers)
: undefined,
body: typeof args.body === 'string' ? args.body : undefined,
content_type:
typeof args.content_type === 'string' ? args.content_type : undefined,
max_response_bytes:
typeof args.max_response_bytes === 'number'
? args.max_response_bytes
: undefined,
max_redirects:
typeof args.max_redirects === 'number' ? args.max_redirects : undefined,
timeout_ms:
typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
format: typeof args.format === 'string' ? args.format : undefined,
max_links:
typeof args.max_links === 'number' ? args.max_links : undefined,
signal
})
return bareAgentJsonResult(out)
} catch (e) {
const msg = bareWebFmtErr(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'grep') {
const pattern = typeof args.pattern === 'string' ? args.pattern : ''
if (!pattern) return bareAgentJsonResult({ ok: false, error: 'pattern_required' })
if (typeof bareAgentGrepFiles !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'grep_unavailable' })
}
const root =
(typeof args.path === 'string' && args.path.trim()) ||
(typeof args.root === 'string' && args.root.trim()) ||
home ||
'/home'
if (!bareAgentPathAllowed(root)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
appendProgress('grep ' + pattern + ' @ ' + root)
const out = await bareAgentGrepFiles(ctx, {
pattern,
root,
glob: typeof args.glob === 'string' ? args.glob : '',
ignore_case: Boolean(args.ignore_case),
before: args.before,
after: args.after,
context: args.context,
max_matches: args.max_matches,
files_with_matches: Boolean(args.files_with_matches),
output_mode: typeof args.output_mode === 'string' ? args.output_mode : ''
})
return bareAgentJsonResult(out)
}
if (toolName === 'apply_patch') {
const patch =
(typeof args.patch === 'string' && args.patch) ||
(typeof args.input === 'string' && args.input) ||
''
if (!patch.trim()) return bareAgentJsonResult({ ok: false, error: 'patch_required' })
if (typeof bareAgentParseApplyPatch !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'apply_patch_unavailable' })
}
const parsed = bareAgentParseApplyPatch(patch)
if (!parsed.ok) return bareAgentJsonResult(parsed)
appendProgress('apply_patch ops=' + String((parsed.ops || []).length))
if (typeof bareAgentPushEdit === 'function' && paths.edits && parsed.ops) {
for (let i = 0; i < parsed.ops.length; i++) {
const op = parsed.ops[i]
const p = String((op && op.path) || '')
if (!p) continue
try {
const prev = await bareAgentReadTextFile(ctx, p)
await bareAgentPushEdit(ctx, paths.edits, { path: p, prev, tool: 'apply_patch' })
} catch {
/* new file */
}
}
}
const out = await bareAgentApplyPatchOps(ctx, parsed.ops, {
home,
denyPrefixes: mutateDenyPrefixes
})
return bareAgentJsonResult(out)
}
if (toolName === 'update_goal') {
const message = typeof args.message === 'string' ? args.message.trim() : ''
const blocked =
typeof args.blocked_reason === 'string' ? args.blocked_reason.trim() : ''
const completed = Boolean(args.completed)
const next = { ...(configRef.current || {}) }
if (message) next.autonomous_last_error = ''
if (blocked) {
next.autonomous_status = 'blocked'
next.autonomous_last_error = blocked
next.autonomous_active = false
} else if (completed) {
next.autonomous_status = 'completed'
next.autonomous_active = false
if (message) next.autonomous_goal = String(next.autonomous_goal || '')
} else {
next.autonomous_status = 'running'
}
configRef.current = next
if (typeof bareAgentSaveConfigFromTools === 'function') {
await bareAgentSaveConfigFromTools(ctx, paths, next)
}
appendProgress(
'update_goal ' +
(completed ? 'completed' : blocked ? 'blocked' : 'progress')
)
return bareAgentJsonResult({
ok: true,
completed,
blocked: Boolean(blocked),
message: message || null,
blocked_reason: blocked || null,
status: next.autonomous_status
})
}
if (toolName === 'web_search') {
const query = typeof args.query === 'string' ? args.query.trim() : ''
if (!query) return bareAgentJsonResult({ ok: false, error: 'query_required' })
const maxResults =
typeof args.max_results === 'number' && Number.isFinite(args.max_results)
? Math.min(Math.max(Math.floor(args.max_results), 1), 16)
: 8
const url =
'https://api.duckduckgo.com/?q=' +
encodeURIComponent(query) +
'&format=json&no_html=1&skip_disambig=1'
appendProgress('web_search ' + query.slice(0, 80))
try {
const raw = await bareWebRunTool({
ctx,
url,
format: 'json',
timeout_ms: 20000,
max_response_bytes: 200000,
signal
})
if (!raw || raw.ok === false) {
return bareAgentJsonResult({
ok: false,
error: (raw && raw.error) || 'web_search_failed',
hint: 'HTTP policy may block api.duckduckgo.com; try web_fetch on a known URL'
})
}
let payload = raw.extract && raw.extract.json
if (payload == null && raw.extract && typeof raw.extract.text_slice === 'string') {
try {
payload = JSON.parse(raw.extract.text_slice)
} catch {
payload = null
}
}
const results =
typeof bareAgentParseSearchResults === 'function'
? bareAgentParseSearchResults(payload, maxResults)
: []
return bareAgentJsonResult({
ok: true,
query,
results,
abstract: payload && payload.Abstract ? String(payload.Abstract) : '',
count: results.length
})
} catch (e) {
const msg = typeof bareWebFmtErr === 'function' ? bareWebFmtErr(e) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'git_status') {
const cwd =
(typeof args.cwd === 'string' && args.cwd.trim()) ||
(ctx.env && typeof ctx.env === 'object'
? String(ctx.env.PWD || ctx.env.CWD || home || '').trim()
: '') ||
home ||
'/home'
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('git_status ' + cwd)
const cmd =
'git -C ' +
bareAgentShellQuote(cwd) +
' status --short && echo --- && git -C ' +
bareAgentShellQuote(cwd) +
' diff --stat && echo --- && git -C ' +
bareAgentShellQuote(cwd) +
' log --oneline -8'
const r = await captureExec(cmd, 30000)
return bareAgentJsonResult(
r.ok === false
? r
: { ok: true, cwd, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'memory_append') {
const text = typeof args.text === 'string' ? args.text.trim() : ''
if (!text) return bareAgentJsonResult({ ok: false, error: 'text_required' })
const kindRaw = String(args.kind || 'NOTE').trim().toUpperCase()
const kind =
kindRaw === 'FACT' ||
kindRaw === 'CHECK' ||
kindRaw === 'RISK' ||
kindRaw === 'HANDOFF' ||
kindRaw === 'NOTE'
? kindRaw
: 'NOTE'
const workspace =
typeof paths.workspace === 'string' ? paths.workspace : paths.dir + '/workspace'
const memDir =
typeof paths.workspaceMemory === 'string'
? paths.workspaceMemory
: workspace + '/memory'
const dest = args.daily
? memDir +
'/' +
(typeof bareAgentWorkspaceUtcYmd === 'function'
? bareAgentWorkspaceUtcYmd()
: new Date().toISOString().slice(0, 10)) +
'.md'
: workspace + '/MEMORY.md'
const stamp = new Date().toISOString()
const line = '- ' + kind + ' — ' + text.replace(/\s+/g, ' ').trim()
let prev = ''
try {
prev = await bareAgentReadTextFile(ctx, dest)
} catch {
prev = ''
}
const next =
(prev ? prev.replace(/\s*$/, '') + '\n' : '# Memory\n\n') +
line +
' \n _' +
stamp +
'_\n'
await bareAgentWriteTextFile(ctx, dest, next)
appendProgress('memory_append ' + dest)
return bareAgentJsonResult({ ok: true, path: dest, kind, appended: line })
}
if (toolName === 'list_skills') {
const max =
typeof args.max === 'number' && Number.isFinite(args.max)
? Math.min(Math.max(Math.floor(args.max), 1), 80)
: 40
if (typeof bareAgentDiscoverSkills !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'skills_unavailable' })
}
const skillPaths = {
workspaceSkills:
typeof paths.workspaceSkills === 'string'
? paths.workspaceSkills
: paths.dir + '/workspace/skills',
skillsGlobal:
typeof paths.skillsGlobal === 'string'
? paths.skillsGlobal
: paths.dir + '/skills'
}
const skills = await bareAgentDiscoverSkills(ctx, skillPaths)
appendProgress('list_skills ' + String(skills.length))
return bareAgentJsonResult({
ok: true,
count: Math.min(skills.length, max),
truncated: skills.length > max,
skills: skills.slice(0, max)
})
}
if (toolName === 'schedule_task') {
const prompt = typeof args.prompt === 'string' ? args.prompt.trim() : ''
if (!prompt) return bareAgentJsonResult({ ok: false, error: 'prompt_required' })
const parsed =
typeof bareAgentParseScheduleInterval === 'function'
? bareAgentParseScheduleInterval(args.interval)
: { error: 'schedule_unavailable' }
if (parsed.error) return bareAgentJsonResult({ ok: false, error: parsed.error, hint: parsed.hint })
const id =
typeof bareAgentScheduleId === 'function'
? bareAgentScheduleId(args.id || prompt.slice(0, 24))
: 'agent-task'
const dir = (home || '/home') + '/.config/bare-os/timers'
if (!vfs || typeof vfs.readdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
try {
if (typeof vfs.mkdir === 'function') await vfs.mkdir(dir, { recursive: true })
} catch {
/* exists */
}
let names = []
try {
names = await vfs.readdir(dir)
} catch {
names = []
}
const existing = Array.isArray(names)
? names.filter(function (n) {
return typeof n === 'string' && n.endsWith('.timer')
})
: []
const dest = dir + '/' + id + '.timer'
const already = existing.indexOf(id + '.timer') >= 0
if (!already && existing.length >= 8) {
return bareAgentJsonResult({
ok: false,
error: 'timer_limit',
max: 8,
hint: 'unschedule_task an existing id first'
})
}
const quoted = bareAgentShellQuote(prompt)
const line = args.auto === false ? 'agent ' + quoted : 'agent --auto ' + quoted
const body =
parsed.kind === 'everyMs'
? '[Timer]\nEveryMs=' + String(parsed.everyMs) + '\nExecLine=' + line + '\n'
: '[Timer]\nOnCalendar=' + parsed.onCalendar + '\nExecLine=' + line + '\n'
await bareAgentWriteTextFile(ctx, dest, body)
appendProgress('schedule_task ' + id)
return bareAgentJsonResult({
ok: true,
id,
path: dest,
interval: parsed,
exec: line
})
}
if (toolName === 'unschedule_task') {
const id =
typeof bareAgentScheduleId === 'function'
? bareAgentScheduleId(args.id)
: String(args.id || '')
if (!id) return bareAgentJsonResult({ ok: false, error: 'id_required' })
const dest = (home || '/home') + '/.config/bare-os/timers/' + id + '.timer'
if (!vfs || typeof vfs.unlink !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs.unlink unavailable' })
}
try {
await vfs.unlink(dest)
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg, path: dest })
}
appendProgress('unschedule_task ' + id)
return bareAgentJsonResult({ ok: true, id, path: dest })
}
if (toolName === 'list_scheduled') {
const dir = (home || '/home') + '/.config/bare-os/timers'
let names = []
try {
names = vfs && typeof vfs.readdir === 'function' ? await vfs.readdir(dir) : []
} catch {
names = []
}
const rows = []
const list = Array.isArray(names) ? names : []
for (let i = 0; i < list.length; i++) {
const name = String(list[i] || '')
if (!name.endsWith('.timer') || name.indexOf('agent-') !== 0) continue
const path = dir + '/' + name
const preview = await bareAgentReadTextFile(ctx, path)
rows.push({ id: name.replace(/\.timer$/, ''), path, preview: preview.slice(0, 400) })
}
appendProgress('list_scheduled ' + String(rows.length))
return bareAgentJsonResult({ ok: true, count: rows.length, timers: rows })
}
if (toolName === 'fuzzy_find') {
const query = typeof args.query === 'string' ? args.query.trim() : ''
if (!query) return bareAgentJsonResult({ ok: false, error: 'query_required' })
const root =
(typeof args.root === 'string' && args.root.trim()) || home || '/home'
if (typeof bareAgentFuzzyFind !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'fuzzy_unavailable' })
}
appendProgress('fuzzy_find ' + query)
const hits = await bareAgentFuzzyFind(ctx, root, query, { max: args.max })
return bareAgentJsonResult({ ok: true, query, root, hits })
}
if (toolName === 'read_many') {
const pathsIn = Array.isArray(args.paths) ? args.paths : []
if (!pathsIn.length) return bareAgentJsonResult({ ok: false, error: 'paths_required' })
const maxB =
typeof args.max_bytes_each === 'number' && Number.isFinite(args.max_bytes_each)
? Math.min(Math.max(Math.floor(args.max_bytes_each), 200), 200000)
: 32000
const files = []
for (let i = 0; i < pathsIn.length && i < 16; i++) {
const path = String(pathsIn[i] || '').trim()
if (!path || !bareAgentPathAllowed(path)) {
files.push({ path, ok: false, error: 'path_not_allowed' })
continue
}
let text = await bareAgentReadTextFile(ctx, path)
const truncated = text.length > maxB
if (truncated) text = text.slice(0, maxB) + '\n… truncated'
files.push({ path, ok: true, content: text, truncated })
}
appendProgress('read_many ' + String(files.length))
return bareAgentJsonResult({ ok: true, files })
}
if (toolName === 'wait_for') {
const pattern = typeof args.pattern === 'string' ? args.pattern : ''
if (!pattern) return bareAgentJsonResult({ ok: false, error: 'pattern_required' })
let re
try {
re = new RegExp(pattern, args.ignore_case ? 'i' : '')
} catch (e) {
return bareAgentJsonResult({ ok: false, error: 'bad_regex' })
}
const timeoutMs = Math.min(
120000,
Math.max(200, Math.floor(Number(args.timeout_ms) || 15000))
)
const intervalMs = Math.min(
5000,
Math.max(100, Math.floor(Number(args.interval_ms) || 400))
)
const path = typeof args.path === 'string' ? args.path.trim() : ''
const command = typeof args.command === 'string' ? args.command.trim() : ''
if (!path && !command) {
return bareAgentJsonResult({ ok: false, error: 'path_or_command_required' })
}
appendProgress('wait_for ' + pattern)
const deadline = Date.now() + timeoutMs
let last = ''
while (Date.now() <= deadline) {
if (path) last = await bareAgentReadTextFile(ctx, path)
else if (command && execLine) {
const r = await captureExec(command, Math.min(intervalMs + 2000, 15000))
last = r && r.stdout_stderr ? String(r.stdout_stderr) : ''
}
re.lastIndex = 0
if (re.test(last)) {
return bareAgentJsonResult({
ok: true,
matched: true,
elapsed_ms: timeoutMs - Math.max(0, deadline - Date.now()),
sample: last.slice(0, 800)
})
}
if (Date.now() + intervalMs > deadline) break
await new Promise(function (resolve) {
setTimeout(resolve, intervalMs)
})
}
return bareAgentJsonResult({
ok: false,
error: 'wait_timeout',
sample: last.slice(0, 400)
})
}
if (toolName === 'undo_last_edit') {
const editsPath = paths.edits || paths.dir + '/edits.json'
if (typeof bareAgentPopEdit !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'undo_unavailable' })
}
const rec = await bareAgentPopEdit(ctx, editsPath)
if (!rec || !rec.path) {
return bareAgentJsonResult({ ok: false, error: 'nothing_to_undo' })
}
await bareAgentWriteTextFile(ctx, String(rec.path), String(rec.prev || ''))
appendProgress('undo_last_edit ' + rec.path)
return bareAgentJsonResult({ ok: true, path: rec.path, tool: rec.tool || null })
}
if (toolName === 'git_diff') {
const cwd =
(typeof args.cwd === 'string' && args.cwd.trim()) ||
(ctx.env && typeof ctx.env === 'object'
? String(ctx.env.PWD || ctx.env.CWD || home || '').trim()
: '') ||
home ||
'/home'
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const p = typeof args.path === 'string' ? args.path.trim() : ''
const stat = Boolean(args.stat)
const cmd =
'git -C ' +
bareAgentShellQuote(cwd) +
' diff' +
(stat ? ' --stat' : '') +
(p ? ' -- ' + bareAgentShellQuote(p) : '')
appendProgress('git_diff ' + cwd)
const r = await captureExec(cmd, 30000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, cwd, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'history_search') {
const query = typeof args.query === 'string' ? args.query.trim() : ''
if (!query) return bareAgentJsonResult({ ok: false, error: 'query_required' })
const histPath = paths.history || paths.dir + '/history.json'
const raw = await bareAgentReadJsonFile(ctx, histPath, [])
const hits =
typeof bareAgentHistorySearch === 'function'
? bareAgentHistorySearch(Array.isArray(raw) ? raw : [], query, args.max)
: []
appendProgress('history_search ' + query)
return bareAgentJsonResult({ ok: true, query, hits })
}
if (toolName === 'git_log' || toolName === 'git_show' || toolName === 'git_blame') {
const cwd =
(typeof args.cwd === 'string' && args.cwd.trim()) ||
(ctx.env && typeof ctx.env === 'object'
? String(ctx.env.PWD || ctx.env.CWD || home || '').trim()
: '') ||
home ||
'/home'
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const quoted = bareAgentShellQuote(cwd)
let cmd = 'git -C ' + quoted + ' '
if (toolName === 'git_log') {
const max = Math.min(80, Math.max(1, Math.floor(Number(args.max) || 20)))
const p = typeof args.path === 'string' ? args.path.trim() : ''
cmd += 'log --oneline -' + String(max) + (p ? ' -- ' + bareAgentShellQuote(p) : '')
} else if (toolName === 'git_show') {
const rev = typeof args.rev === 'string' && args.rev.trim() ? args.rev.trim() : 'HEAD'
const p = typeof args.path === 'string' ? args.path.trim() : ''
cmd +=
'show ' +
(args.stat ? '--stat ' : '') +
bareAgentShellQuote(rev) +
(p ? ' -- ' + bareAgentShellQuote(p) : '')
} else {
const p = typeof args.path === 'string' ? args.path.trim() : ''
if (!p) return bareAgentJsonResult({ ok: false, error: 'path_required' })
cmd += 'blame -- ' + bareAgentShellQuote(p)
}
appendProgress(toolName + ' ' + cwd)
const r = await captureExec(cmd, 30000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, cwd, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'copy_path') {
const from = typeof args.from_path === 'string' ? args.from_path.trim() : ''
const to = typeof args.to_path === 'string' ? args.to_path.trim() : ''
if (
!from ||
!to ||
from.includes('..') ||
to.includes('..') ||
!bareAgentPathAllowed(from) ||
!bareAgentPathAllowedMutate(to, mutateDenyPrefixes)
) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (typeof bareAgentCopyPath !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'copy_unavailable' })
}
appendProgress('copy_path ' + from)
const out = await bareAgentCopyPath(ctx, from, to)
return bareAgentJsonResult(out)
}
if (toolName === 'diff_files') {
const from = typeof args.from_path === 'string' ? args.from_path.trim() : ''
const to = typeof args.to_path === 'string' ? args.to_path.trim() : ''
if (!from || !to || !bareAgentPathAllowed(from) || !bareAgentPathAllowed(to)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (typeof bareAgentUnifiedDiff !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'diff_unavailable' })
}
const a = await bareAgentReadTextFile(ctx, from)
const b = await bareAgentReadTextFile(ctx, to)
appendProgress('diff_files ' + from)
const out = bareAgentUnifiedDiff(a, b, {
from,
to,
context: args.context
})
return bareAgentJsonResult(out)
}
if (toolName === 'find_symbol') {
const name = typeof args.name === 'string' ? args.name.trim() : ''
if (!name) return bareAgentJsonResult({ ok: false, error: 'name_required' })
const root =
(typeof args.root === 'string' && args.root.trim()) || home || '/home'
if (!bareAgentPathAllowed(root) || typeof bareAgentFindSymbol !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'find_symbol_unavailable' })
}
appendProgress('find_symbol ' + name)
const out = await bareAgentFindSymbol(ctx, root, name, {
glob: typeof args.glob === 'string' ? args.glob : '',
max: args.max
})
return bareAgentJsonResult(out)
}
if (toolName === 'create_skill') {
const id = String(args.id || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9-]+/g, '-')
.replace(/^-+|-+$/g, '')
if (!id) return bareAgentJsonResult({ ok: false, error: 'id_required' })
const destRoot =
(typeof paths.workspaceSkills === 'string' && paths.workspaceSkills) ||
(typeof paths.workspace === 'string' ? paths.workspace + '/skills' : paths.dir + '/workspace/skills')
const dest = destRoot + '/' + id + '/SKILL.md'
if (!bareAgentPathAllowedMutate(dest, mutateDenyPrefixes)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const md =
typeof bareAgentSkillMarkdown === 'function'
? bareAgentSkillMarkdown({
name: typeof args.name === 'string' ? args.name : id,
description: typeof args.description === 'string' ? args.description : id,
body: typeof args.body === 'string' ? args.body : ''
})
: String(args.body || '')
await bareAgentWriteTextFile(ctx, dest, md)
appendProgress('create_skill ' + id)
return bareAgentJsonResult({ ok: true, id, path: dest })
}
if (toolName === 'rewind_session') {
const histPath = paths.history || paths.dir + '/history.json'
const raw = await bareAgentReadJsonFile(ctx, histPath, [])
if (typeof bareAgentRewindHistory !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'rewind_unavailable' })
}
const out = bareAgentRewindHistory(Array.isArray(raw) ? raw : [], {
steps: args.steps,
userIndex: args.user_index,
keep_user: args.keep_user
})
if (!out.ok) return bareAgentJsonResult(out)
await bareAgentSaveHistory(ctx, histPath, out.messages)
appendProgress('rewind_session dropped=' + String(out.dropped))
return bareAgentJsonResult({
ok: true,
dropped: out.dropped,
remaining: out.messages.length,
target: out.target || null,
keep_user: Boolean(out.keep_user)
})
}
if (toolName === 'export_session') {
const histPath = paths.history || paths.dir + '/history.json'
const dest =
(typeof args.path === 'string' && args.path.trim()) ||
(paths.dir ? paths.dir + '/export.md' : '/tmp/agent-export.md')
if (!bareAgentPathAllowedMutate(dest, mutateDenyPrefixes)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const raw = await bareAgentReadJsonFile(ctx, histPath, [])
const md =
typeof bareAgentExportTranscript === 'function'
? bareAgentExportTranscript(Array.isArray(raw) ? raw : [])
: ''
await bareAgentWriteTextFile(ctx, dest, md)
appendProgress('export_session ' + dest)
return bareAgentJsonResult({
ok: true,
path: dest,
messages: Array.isArray(raw) ? raw.length : 0
})
}
return bareAgentJsonResult({ ok: false, error: 'unknown_tool ' + toolName })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
appendProgress('tool_error ' + toolName + ': ' + msg.slice(0, 200))
return bareAgentJsonResult({ ok: false, error: msg })
}
}