/** 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: 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: 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 1–8 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/.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} 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, * 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 }, * 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} */ let args = {} try { args = /** @type {Record} */ (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} */ ( 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 ') .replace(/\bsk-[a-zA-Z0-9]{16,}\b/g, '') .replace( /\b([A-Z0-9_]*(KEY|TOKEN|SECRET|PASS|PASSWORD)[A-Z0-9_]*)\s*=\s*([^\s]+)/gi, '$1=' ) } /** * @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} */ 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 }[]} */ 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 | 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} */ (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} */ (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} */ (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} */ 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 ') .replace(/\bsk-[a-zA-Z0-9]{16,}\b/g, '') .replace(/\b([A-Z0-9_]*(KEY|TOKEN|SECRET|PASS)[A-Z0-9_]*)\s*=\s*([^\s]+)/gi, '$1=') } 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} */ 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} */ 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} */ 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} */ 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} */ (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} */ (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} */ 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} */ 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} */ 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} */ (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} base * @param {Record} 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' ] const numKeys = new Set([ 'max_tokens', 'temperature', 'max_iterations', 'tool_parallelism', 'request_timeout_ms', 'reasoning_max_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 === '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} */ (patch.extra_headers) } } return out } /** * @param {Record} ctx * @param {{ config: string }} paths * @param {Record} 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) }