Files
bare-operating-system/packages/bare-os-coreutils/lib/agent-tools.js
T
Raven Scott 64619d5bc2
Release rolling / release (push) Successful in 8m1s
Updates
2026-07-31 21:08:43 -04:00

2725 lines
94 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** OpenAI-style tool schemas + dispatch (preamble for /bin/agent). */
/**
* Host-checkout verification hints (keep loosely aligned with scripts/lib/agent-check-hints-data.mjs).
* @param {string} combined paths + topic
* @returns {string[]}
*/
function bareAgentVerificationHintsList(combined) {
const c = String(combined || '').toLowerCase()
/** @type {string[]} */
const hints = []
if (/(^|\/)kernel\/|kernel\\|\/boot\/init|lib\/init|lib\\init/.test(c)) {
hints.push('npm run bundle:kernel', 'node scripts/verify-kernel-seeder-parity.mjs')
}
if (/bare-os-coreutils|kernel\/bin|kernel\\bin/.test(c)) {
hints.push('npm run build -w bare-os-coreutils', 'node scripts/verify-man-coverage.mjs')
}
if (/bare-os-booter|bare-os-ctx-api/.test(c)) {
hints.push(
'npm run test -w bare-os-booter',
'node scripts/verify-ctx-api-feature-bits.mjs'
)
}
if (/bare-os-protocol|seed-rpc|channel\.js/.test(c)) {
hints.push('npm run test -w bare-os-protocol')
}
if (/bare-os-seeder/.test(c)) {
hints.push('node scripts/verify-kernel-seeder-parity.mjs')
}
if (/bare-os-bare-libs|kernel\/lib\/bare|kernel\\lib\\bare/.test(c)) {
hints.push('npm run build -w bare-os-bare-libs', 'node scripts/verify-bundle-health.mjs')
}
if (/shell|sh\.js|test\.js/.test(c) && /booter/.test(c)) {
hints.push('npm run test:shell-fast')
}
if (/docs\/|handbook\/|developer-guide\//.test(c)) {
hints.push('npm run pretest', 'node scripts/verify-doc-links.mjs')
}
if (!hints.length) hints.push('npm run pretest', 'npm test')
return [...new Set(hints)]
}
/**
* @param {string} s
*/
function bareAgentShellQuote(s) {
return "'" + String(s).replace(/'/g, "'\\''") + "'"
}
/**
* @param {unknown} v
* @returns {string}
*/
function bareAgentJsonResult(v) {
try {
return JSON.stringify(v)
} catch {
return '{"error":"json_stringify_failed"}'
}
}
/**
* @returns {unknown[]}
*/
function bareAgentToolDefinitions() {
return [
{
type: 'function',
function: {
name: 'read_file',
description:
'Read a UTF-8 text file from the VFS. Path must be absolute (e.g. /home/guest/...).',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Absolute file path' },
max_bytes: {
type: 'integer',
description: 'Max bytes to read (default 256000)'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'write_file',
description:
'Create or overwrite a file. Parent directories are created as needed.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
content: { type: 'string', description: 'Full file contents' }
},
required: ['path', 'content']
}
}
},
{
type: 'function',
function: {
name: 'edit_file',
description:
'Edit a text file: either replace entire content, or replace first occurrence of old_string with new_string.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
content: {
type: 'string',
description: 'If set (non-empty), full file replacement'
},
old_string: {
type: 'string',
description: 'Search string (used with new_string)'
},
new_string: { type: 'string', description: 'Replacement text' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'create_directory',
description: 'Create a directory (recursive).',
parameters: {
type: 'object',
properties: {
path: { type: 'string' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'search_files',
description:
'Run grep -R to list paths matching a pattern (bounded). Uses the shell.',
parameters: {
type: 'object',
properties: {
pattern: { type: 'string' },
root: {
type: 'string',
description: 'Directory to search (default /home)'
},
max_lines: { type: 'integer', description: 'Default 200' }
},
required: ['pattern']
}
}
},
{
type: 'function',
function: {
name: 'run_command',
description:
'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: {
command: {
type: 'string',
description: 'Full command string (e.g. ls -la /bin)'
},
timeout_ms: { type: 'integer' },
capture_exit: {
type: 'boolean',
description: 'If true, append last line EXIT:<code> to capture (default false)'
}
},
required: ['command']
}
}
},
{
type: 'function',
function: {
name: 'run_js_script',
description:
'REQUIRED to run agent-authored JavaScript: Node is not installed. Writes code to ~/.agent/_tmp_agent_run.mjs and runs it by absolute path (Bare kernel — same as /bin scripts). Do not use run_command with node/npm/npx. Prefer async function run(ctx, argv). stdout/stderr captured.',
parameters: {
type: 'object',
properties: {
code: { type: 'string', description: 'Full ESM/CommonJS script body' }
},
required: ['code']
}
}
},
{
type: 'function',
function: {
name: 'get_system_info',
description:
'Lightweight context: API version, uname, optional resource hook. Questions about **which kernel features are on/off in this session** → read_proc_file on /proc/bare_os/features (or features.json) and /proc/bare_os/capabilities.json; not apropos_man. For other /proc JSON use read_proc_file; swarm → get_swarm_peers; resource table → get_resource_limits. want=capabilities|swarm still returns those blobs when needed.',
parameters: {
type: 'object',
properties: {
want: {
type: 'string',
enum: ['summary', 'capabilities', 'swarm'],
description: 'Optional focus (default summary)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'edit_agent_config',
description:
'Merge keys into ~/.agent/config.json (shallow merge for known keys only).',
parameters: {
type: 'object',
properties: {
patch: {
type: 'object',
description:
'Partial config object (rest_base_url, model, temperature, owner_name, agent_label, …)'
}
},
required: ['patch']
}
}
},
{
type: 'function',
function: {
name: 'list_bin',
description: 'List Tier-1 utilities in /bin via VFS.',
parameters: {
type: 'object',
properties: {
limit: { type: 'integer', description: 'Max names (default 400)' }
}
}
}
},
{
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. This is documentation search only—never use it to answer what kernel features are currently enabled or disabled (use read_proc_file on /proc/bare_os/features).',
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 pseudo file (bounded). Canonical live kernel feature state: /proc/bare_os/features or /proc/bare_os/features.json (same content). Use instead of shelling cat.',
parameters: {
type: 'object',
properties: {
path: {
type: 'string',
description:
'Exact allowlisted path (metrics_live.json, features or features.json, capabilities.json, swarm*.json, swarm_*_status.json); bounded read.'
},
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: {
name: 'web_fetch',
description:
'Fetch live HTTP(S) URLs and return structured content for the assistant. Uses ctx.httpFetch (same policy as wget/curl: BARE_OS_HTTP_ALLOWLIST / DENYLIST). For official docs index use read_man_page / apropos_man — they are not web pages. Supports GET/HEAD/POST and extract modes: auto (JSON vs HTML vs text), markdownish plain text from HTML, links (anchor hrefs), meta (title/og:), raw UTF-8 slice, or json parse.',
parameters: {
type: 'object',
properties: {
url: { type: 'string', description: 'Absolute http(s) URL' },
method: {
type: 'string',
description: 'HTTP method (default GET)',
enum: ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
},
headers: {
type: 'object',
description: 'Optional header map (string values only)'
},
body: {
type: 'string',
description: 'Request body for non-GET (e.g. JSON string for APIs)'
},
content_type: {
type: 'string',
description: 'Content-Type when body is set (default application/octet-stream)'
},
format: {
type: 'string',
enum: ['auto', 'json', 'markdownish', 'text', 'links', 'meta', 'raw'],
description:
'auto: sniff Content-Type; json: parse JSON; markdownish/text: strip HTML to readable text; links: absolute http(s) links; meta: title/description/og tags; raw: bounded UTF-8 text'
},
max_response_bytes: {
type: 'integer',
description: 'Cap downloaded bytes (default 524288, max 2MiB)'
},
max_redirects: {
type: 'integer',
description: 'Max redirects to follow (default 5)'
},
timeout_ms: {
type: 'integer',
description: 'Per-request timeout ms (default 30000, max 120000)'
},
max_links: {
type: 'integer',
description: 'Max links when format=links (default 200)'
}
},
required: ['url']
}
}
},
{
type: 'function',
function: {
name: 'read_skill',
description:
'Load the full SKILL.md for a modular agent skill (folder id or frontmatter name, case-insensitive). Workspace ~/.agent/workspace/skills/ overrides ~/.agent/skills/. Use after checking the compact skills index in the system prompt.',
parameters: {
type: 'object',
properties: {
skill: {
type: 'string',
description: 'Skill folder name (e.g. p2p-os-status) or YAML frontmatter name'
},
max_bytes: {
type: 'integer',
description: 'Max bytes of SKILL.md (default 256000)'
}
},
required: ['skill']
}
}
},
{
type: 'function',
function: {
name: 'list_services',
description:
'List initd service definitions and current runtime phases from /proc/bare_os/initd_readiness.json when available.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'service_status',
description:
'Read one initd unit status by name from /proc/bare_os/initd_readiness.json and include journal hint paths when present.',
parameters: {
type: 'object',
properties: {
name: { type: 'string', description: 'Unit name, e.g. bare-cron' }
},
required: ['name']
}
}
},
{
type: 'function',
function: {
name: 'list_timers',
description:
'List user timer drop-ins from ~/.config/bare-os/timers and optionally include short file previews.',
parameters: {
type: 'object',
properties: {
include_preview: {
type: 'boolean',
description: 'Include bounded timer file text previews (default false)'
},
max_entries: {
type: 'integer',
description: 'Maximum timer files to return (default 128, max 512)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_cron_log',
description:
'Read /var/log/bare-os/cron.log with bounded output and optional tail mode.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 12000)'
},
tail_only: {
type: 'boolean',
description: 'When true, return only the trailing max_chars slice'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_audit_log',
description:
'Read /var/log/bare-os/audit.log with bounded output and best-effort secret redaction.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 12000)'
},
tail_only: {
type: 'boolean',
description: 'When true, return only the trailing max_chars slice'
},
redact: {
type: 'boolean',
description: 'Apply lightweight token redaction (default true)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_boot_policy',
description:
'Read /etc/bare-os/boot.policy.json and return text plus parsed JSON when available.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 20000)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_kernel_extension_resolution',
description:
'Read /run/bare-os/kernel-ext-resolution.json for extension ordering, conflicts, and pin outcomes.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 20000)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'get_initd_graph',
description:
'Read initd dependency DAG/readiness graph from /proc/bare_os/initd_dag.json and /proc/bare_os/initd_readiness.json.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'read_unit_journal',
description:
'Read a bounded/redacted tail of /run/bare-os/unit-journal/<unit>.ndjson.',
parameters: {
type: 'object',
properties: {
unit: { type: 'string', description: 'Initd unit name, e.g. bare-cron' },
max_chars: { type: 'integer', description: 'Maximum output chars (default 12000)' },
tail_only: { type: 'boolean', description: 'Return trailing max_chars only' }
},
required: ['unit']
}
}
},
{
type: 'function',
function: {
name: 'inspect_ipc_backpressure',
description:
'Inspect IPC/backpressure operator snapshots from /proc/bare_os JSON surfaces.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_network_summary',
description:
'Read a typed network/swarm summary from /proc/bare_os surfaces with best-effort fallback paths.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'tail_telemetry_streams',
description:
'Read bounded/redacted tails from telemetry logs such as /var/log/bare-os/audit.log, logger.jsonl, and initd logs.',
parameters: {
type: 'object',
properties: {
max_chars: { type: 'integer', description: 'Maximum chars per stream (default 8000)' }
}
}
}
},
{
type: 'function',
function: {
name: 'pkg_index_lookup',
description:
'Run pkg-swarm-index lookup and return parsed output for one package key.',
parameters: {
type: 'object',
properties: {
key: { type: 'string', description: 'Package key to lookup' }
},
required: ['key']
}
}
},
{
type: 'function',
function: {
name: 'list_verification_scripts',
description:
'List known verification scripts from /scripts and summarize likely check families.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'run_maintenance_gate',
description:
'Run one allowlisted maintenance command with bounded capture for automation workflows.',
parameters: {
type: 'object',
properties: {
command: { type: 'string', description: 'Allowlisted command id' },
cwd: { type: 'string', description: 'Optional working directory' },
timeout_ms: { type: 'integer', description: 'Timeout in milliseconds' }
},
required: ['command']
}
}
},
{
type: 'function',
function: {
name: 'run_contract_checks',
description:
'Run a grouped set of contract checks by profile id (allowlisted) with bounded output.',
parameters: {
type: 'object',
properties: {
profile: { type: 'string', description: 'Check profile id, e.g. core, docs, parity' }
},
required: ['profile']
}
}
},
{
type: 'function',
function: {
name: 'summarize_build_drift',
description:
'Summarize build/generated drift by comparing git status and key generated artifacts.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_hrpc_bridge_health',
description:
'Read HRPC bridge/operator health from /proc/bare_os surfaces and include host capability hints when available.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_hrpc_allowlist_status',
description:
'Inspect effective HRPC allowlist/probe status using hrpc probe and operator snapshots.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'emit_host_notification',
description:
'Request an audited host notification via HRPC route (policy-gated; disabled by default).',
parameters: {
type: 'object',
properties: {
title: { type: 'string' },
message: { type: 'string' },
level: { type: 'string', enum: ['info', 'warn', 'error'] }
},
required: ['title', 'message']
}
}
},
{
type: 'function',
function: {
name: 'request_host_action',
description:
'Request a schema-validated host action through HRPC (policy-gated; disabled by default).',
parameters: {
type: 'object',
properties: {
action: { type: 'string', description: 'Host action id' },
payload: { type: 'object', description: 'Action payload object' }
},
required: ['action']
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run',
description:
'Start an autonomous coding run with goal, optional scope path, and runtime cap. This enables autonomous mode in config.',
parameters: {
type: 'object',
properties: {
goal: { type: 'string', description: 'Task goal the agent should complete autonomously' },
scope_path: { type: 'string', description: 'Preferred working scope path (optional)' },
max_runtime_ms: { type: 'integer', description: 'Optional runtime cap override' },
required_checks: {
type: 'array',
items: { type: 'string' },
description: 'Optional quality gates (allowlisted check ids)'
}
},
required: ['goal']
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_status',
description:
'Return current autonomous run state, elapsed/runtime budget, configured checks, and latest status.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_stop',
description:
'Request manual stop for an autonomous run; loop will stop safely on next control checkpoint.',
parameters: {
type: 'object',
properties: {
reason: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
name: 'verification_hints',
description:
'Suggest npm/node verification commands for a developer working at the Bare OS git checkout on the host (paths or topic keywords). Does not run commands.',
parameters: {
type: 'object',
properties: {
topic: {
type: 'string',
description: 'Free-text task or area (e.g. kernel init, seed RPC, shell)'
},
paths_touched: {
type: 'string',
description:
'Optional comma-separated path-like strings from the repo (forward slashes ok)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'runtime_diagnostic_bundle',
description:
'Non-secret snapshot: ctx API version, optional resource status, and allowlisted /proc/bare_os files that exist (features, swarm, metrics). Prefer over many separate read_proc_file calls.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'task_complete',
description:
'Call when the user task is fully done. Provide a concise summary.',
parameters: {
type: 'object',
properties: {
summary: { type: 'string' }
},
required: ['summary']
}
}
}
]
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} absPath
*/
function bareAgentPathAllowed(absPath) {
const p = String(absPath || '').replace(/\\/g, '/')
if (!p.startsWith('/')) return false
const ok =
p.startsWith('/home/') ||
p.startsWith('/tmp/') ||
p === '/tmp' ||
p.startsWith('/root/') ||
p.startsWith('/mnt/') ||
p.startsWith('/bin/') ||
p === '/bin' ||
p.startsWith('/etc/') ||
p.startsWith('/share/') ||
p.startsWith('/usr/') ||
p.startsWith('/var/') ||
p.startsWith('/proc/') ||
p.startsWith('/boot/') ||
p.startsWith('/lib/') ||
p.startsWith('/dev/')
return ok
}
/**
* @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 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 (
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 })
}
const vfs = ctx.vfs
const AUTONOMOUS_DENY_PATH_PREFIXES = [
'/.git',
'/proc',
'/dev',
'/sys',
'/run',
'/boot',
'/lib',
'/usr/lib'
]
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
if (!s.startsWith('/')) return false
for (const pref of AUTONOMOUS_DENY_PATH_PREFIXES) {
if (s === pref || s.startsWith(pref + '/')) return false
}
return true
}
/**
* @param {string} p
*/
function enforceAutonomousPath(p) {
const cfg = configRef.current || {}
if (!cfg.autonomous_active) return true
const path = String(p || '').trim()
if (!path) return true
if (!autonomousPathAllowed(path)) return false
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 === '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 (!cfg.autonomous_mode_enabled) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_mode_disabled' })
}
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 maxRuntimeMs = Math.min(Math.max(Math.floor(Number(maxRuntimeMsRaw) || 0), 60000), 7_200_000)
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 merged = bareAgentMergeConfigPatch(cfg, {
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: maxRuntimeMs,
autonomous_completion_required_checks: requiredChecks
})
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run start goal=' + goal.slice(0, 160))
return bareAgentJsonResult({
ok: true,
active: true,
goal,
scope_path: scopePath || null,
max_runtime_ms: maxRuntimeMs,
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 merged = bareAgentMergeConfigPatch(cfg, {
autonomous_stop_requested: true,
autonomous_status: 'stopped',
autonomous_last_error: reason || String(cfg.autonomous_last_error || '')
})
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 args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
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'
return bareAgentJsonResult({ ok: true, path, content: t })
}
if (toolName === 'write_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
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)
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') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
if (!bareAgentPathAllowed(path) || !vfs?.readFile || !vfs?.writeFile) {
return bareAgentJsonResult({ ok: false, error: 'path_or_vfs' })
}
appendProgress('edit_file ' + path)
const buf = await vfs.readFile(path)
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 = 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 : ''
let next = prev
if (full.length > 0) next = full
else if (oldStr) {
if (!prev.includes(oldStr)) {
return bareAgentJsonResult({ ok: false, error: 'old_string not found' })
}
next = prev.replace(oldStr, newStr)
} 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) await vfs.mkdir(dir, { recursive: true })
await vfs.writeFile(path, body)
return bareAgentJsonResult({ ok: true, bytes: body.length })
}
if (toolName === 'create_directory') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
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 : ''
if ((configRef.current || {}).autonomous_active) {
const cmd = command.trim().toLowerCase()
if (
cmd.includes('rm ') ||
cmd.includes(' git reset') ||
cmd.includes(' git clean') ||
cmd.startsWith('git ') ||
cmd.includes(' git ')
) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_command_denied' })
}
}
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' })
}
appendProgress('run_command ' + command.slice(0, 160))
const r = await captureExec(command, timeoutMs, { captureExit })
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, 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 (!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 === '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 = {}
const list = BARE_AGENT_PROC_READ_ALLOWLIST
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 })
}
}
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 })
}
}
/**
* @param {Record<string, unknown>} base
* @param {Record<string, unknown>} patch
*/
function bareAgentMergeConfigPatch(base, patch) {
const out = { ...base }
const keys = [
'backend',
'rest_base_url',
'rest_api_key',
'model',
'qvac_model',
'qvac_profile',
'qvac_ctx_size',
'qvac_device',
'qvac_main_gpu',
'qvac_gpu_layers',
'max_tokens',
'temperature',
'provider',
'max_iterations',
'stream',
'tool_parallelism',
'request_timeout_ms',
'allow_delete',
'require_confirm_token',
'owner_name',
'agent_label',
'show_reasoning',
'reasoning_mode',
'reasoning_max_chars',
'reasoning_include_tools',
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error',
'context_compaction',
'compaction_keep_recent',
'compaction_tool_chars'
]
const numKeys = new Set([
'max_tokens',
'temperature',
'max_iterations',
'tool_parallelism',
'request_timeout_ms',
'reasoning_max_chars',
'compaction_keep_recent',
'compaction_tool_chars',
'qvac_ctx_size',
'qvac_gpu_layers',
'autonomous_max_runtime_ms',
'autonomous_started_at_ms'
])
for (const k of keys) {
if (Object.prototype.hasOwnProperty.call(patch, k)) {
/** @type {unknown} */
const v = patch[k]
if (numKeys.has(k)) {
const n = Number(v)
if (Number.isFinite(n)) out[k] = n
} else if (
k === 'autonomous_completion_required_checks' ||
k === 'autonomous_allow_paths' ||
k === 'autonomous_deny_ops'
) {
out[k] = Array.isArray(v) ? v.map((x) => String(x ?? '')).filter(Boolean) : out[k]
} else if (
k === 'stream' ||
k === 'allow_delete' ||
k === 'show_reasoning' ||
k === 'reasoning_include_tools' ||
k === 'allow_bridge_mutations' ||
k === 'allow_host_notifications' ||
k === 'allow_host_actions' ||
k === 'emergency_stop_mutations' ||
k === 'autonomous_mode_enabled' ||
k === 'autonomous_active' ||
k === 'autonomous_stop_requested'
) {
out[k] = Boolean(v)
} else if (k === 'reasoning_mode') {
const mode = String(v ?? '').trim().toLowerCase()
out[k] = mode === 'summary' || mode === 'trace' ? mode : 'off'
} else if (k === 'context_compaction') {
const mode = String(v ?? '').trim().toLowerCase()
out[k] = mode === 'off' || mode === 'aggressive' ? mode : 'auto'
} else if (k === 'require_confirm_token') {
out[k] = String(v ?? '')
} else {
out[k] = String(v ?? '')
}
}
}
if (
patch.extra_headers &&
typeof patch.extra_headers === 'object' &&
!Array.isArray(patch.extra_headers)
) {
out.extra_headers = { .../** @type {Record<string, string>} */ (patch.extra_headers) }
}
return out
}
/**
* @param {Record<string, unknown>} ctx
* @param {{ config: string }} paths
* @param {Record<string, unknown>} config
*/
async function bareAgentSaveConfigFromTools(ctx, paths, config) {
const vfs = ctx.vfs
if (!vfs?.writeFile) return
const json = JSON.stringify(config, null, 2) + '\n'
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(json)
: new TextEncoder().encode(json)
await vfs.writeFile(paths.config, body)
}