/** Agent ReAct session: stream, tools, SIGINT, repl suspend (preamble for /bin/agent). */ /** * @param {Record} ctx * @param {string} s */ function bareAgentLog(ctx, s) { try { ctx.console.log(s) } catch { /* ignore */ } } /** * @param {Record} ctx * @param {string} s */ function bareAgentErr(ctx, s) { try { ctx.console.error(s) } catch { /* ignore */ } } /** * SSH PTYs expect CRLF for line breaks (same as `bare-openssh-pty-console.js` / `man` via console). * @param {Record | undefined} ctx * @param {string} s */ function bareAgentNormalizeStreamNewlines(ctx, s) { if (ctx && ctx.bareOsPtyStdoutCrlf) return String(s).replace(/\r?\n/g, '\r\n') return String(s) } /** * @param {Record | undefined} ctx * @param {import('stream').Writable | undefined} out * @param {string} s */ function bareAgentWriteOut(ctx, out, s) { const text = bareAgentNormalizeStreamNewlines(ctx, s) if (out && typeof out.write === 'function') { try { out.write(text) } catch { /* ignore */ } } else { try { if (typeof bareOsEmitRaw === 'function') { bareOsEmitRaw(ctx, text) } else if (typeof process !== 'undefined' && process.stdout?.write) { process.stdout.write(text) } } catch { /* ignore */ } } } /** * True when we can run the plain-text setup wizard on stdin/stdout (no REPL readline). * @param {Record} ctx */ function bareAgentCanPlainSetup(ctx) { const stdin = /** @type {import('stream').Readable | undefined} */ ( ctx.replStdin || ctx.stdin ) const stdout = /** @type {import('stream').Writable | undefined} */ ( ctx.replStdout || ctx.stdout ) const tty = /** @type {{ isTTY?: boolean }} */ (stdin) return Boolean( stdin && typeof stdin.on === 'function' && tty.isTTY && stdout && typeof stdout.write === 'function' ) } /** * Read one line from a Readable stream (kernel echoes typed chars on cooked TTY). * @param {import('stream').Readable} stdin * @returns {Promise} */ function bareAgentReadStreamLineOnce(stdin) { return new Promise((resolve) => { let acc = '' /** @param {string | Uint8Array | Buffer} chunk */ function onData(chunk) { let s = '' if (typeof chunk === 'string') s = chunk else if (chunk instanceof Uint8Array) s = new TextDecoder().decode(chunk) else if ( typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(chunk) ) s = chunk.toString('utf8') else s = String(chunk) acc += s const n = acc.indexOf('\n') if (n >= 0) { cleanup() resolve(acc.slice(0, n).replace(/\r$/, '')) } } function onEnd() { cleanup() resolve(acc.replace(/\r$/, '')) } function cleanup() { stdin.removeListener('data', onData) stdin.removeListener('end', onEnd) stdin.removeListener('error', onEnd) } stdin.on('data', onData) stdin.once('end', onEnd) stdin.once('error', onEnd) if (typeof stdin.resume === 'function') stdin.resume() }) } /** * Read one secret line (masked as *) when raw mode is available. * Falls back to normal line read when raw mode is unavailable. * @param {Record} ctx * @param {import('stream').Readable} stdin * @param {import('stream').Writable | undefined} stdout * @returns {Promise} */ function bareAgentReadMaskedLineOnce(ctx, stdin, stdout) { const ttyIn = /** @type {{ setRawMode?: (v: boolean) => void, isTTY?: boolean }} */ ( stdin ) if (!ttyIn || typeof ttyIn.setRawMode !== 'function' || !ttyIn.isTTY) { return bareAgentReadStreamLineOnce(stdin) } return new Promise((resolve, reject) => { /** @type {string[]} */ const chars = [] let done = false /** @param {string | Uint8Array | Buffer} chunk */ function onData(chunk) { if (done) return let s = '' if (typeof chunk === 'string') s = chunk else if (chunk instanceof Uint8Array) s = new TextDecoder().decode(chunk) else if ( typeof Buffer !== 'undefined' && typeof Buffer.isBuffer === 'function' && Buffer.isBuffer(chunk) ) { s = chunk.toString('utf8') } else s = String(chunk) for (const ch of s) { const code = ch.charCodeAt(0) if (ch === '\r' || ch === '\n') { finish(true) return } if (ch === '\u0003') { finish(false, new Error('interrupted')) return } if (ch === '\u007f' || ch === '\b') { if (chars.length) { chars.pop() bareAgentWriteOut(ctx, stdout, '\b \b') } continue } if (code >= 32 && code !== 127) { chars.push(ch) bareAgentWriteOut(ctx, stdout, '*') } } } /** @param {boolean} ok @param {Error} [err] */ function finish(ok, err) { if (done) return done = true cleanup() if (ok) resolve(chars.join('')) else reject(err || new Error('masked_input_failed')) } function cleanup() { stdin.removeListener('data', onData) stdin.removeListener('end', onEnd) stdin.removeListener('error', onErr) try { ttyIn.setRawMode(false) } catch { /* ignore */ } bareAgentWriteOut(ctx, stdout, '\n') } function onEnd() { finish(true) } /** @param {unknown} e */ function onErr(e) { const msg = e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e) finish(false, new Error(msg)) } try { ttyIn.setRawMode(true) } catch { return resolve('') } stdin.on('data', onData) stdin.once('end', onEnd) stdin.once('error', onErr) if (typeof stdin.resume === 'function') stdin.resume() }) } /** * Write a prompt and read one line using raw streams (not ctx.readLine / TUI stack). * @param {Record} ctx * @param {string} prompt * @param {{ mask?: boolean }} [opts] */ async function bareAgentPromptSetupLine(ctx, prompt, opts) { const stdin = /** @type {import('stream').Readable | undefined} */ ( ctx.replStdin || ctx.stdin ) const stdout = /** @type {import('stream').Writable | undefined} */ ( ctx.replStdout || ctx.stdout ) if (!stdin || typeof stdin.on !== 'function') { throw new Error('setup: stdin stream unavailable') } bareAgentWriteOut(ctx, stdout, '\x1b[?25h\x1b[0m' + prompt) if (opts && opts.mask) return bareAgentReadMaskedLineOnce(ctx, stdin, stdout) return bareAgentReadStreamLineOnce(stdin) } const BARE_AGENT_STATIC_SYSTEM = `You are the Bare OS coding agent — a senior implementer that lives inside the guest image (JavaScript POSIX on Hyperdrive + Hyperswarm, Pear/Bare runtime). You write, edit, debug, and verify code and OS state by calling tools. You are not a chatbot that narrates plans and waits. Lead with tools. Execute until the job is done, then call task_complete. Bare OS by Raven Scott (https://raven-scott.fyi). Repo: https://git.ssh.surf/snxraven/bare-operating-system. Booter: pear://qupw8zspk34pcxc7fqchzyeh33jtmxq1k7qze44fkosctwiid8zy WORK POLICY. - Keep every explicit requirement in view until it is done, superseded, or blocked. If blocked, say so plainly. - Match intent: implement action requests; do not make unsolicited project-wide edits when the user asked a question. - For clear, reversible local work, do it now. NEVER ASK for permission. - Claim done / fixed / tested only when a tool result supports it. Otherwise say what you did not verify. - Scope to what was asked. Comments are short and factual. No placeholders. Comments must not substitute for a fix. ACCESS (denylist, not allowlist). You already have full guest admin. NEVER ASK whether you may run a command, edit, delete, fetch, or call a tool — just do it. Only refuse when a denylist or the read-only base system blocks the path. - WRITE: /home, /tmp, /mnt, /root, /var, /opt, extra drives, ~/.agent. Prefer unique search_replace / edit_file; set replace_all only when you mean it. write_file creates or overwrites. - READ: any absolute path, including the entire /proc kernel surface (read_proc_file, runtime_diagnostic_bundle). Use read_file offset/limit for large files. - RUN: every guest command via run_command (command_deny is empty by default). Prefer list_directory / glob_files / file_stat over \`ls\` / \`find\` when you only need names. list_bin lists guest /bin utilities (POSIX-in-JS, not GNU). - DELETE / MOVE: enabled. Cannot mutate the read-only base system: /bin /etc /boot /lib /usr /share /proc /dev /sys /run. - JS: Node is NOT installed in the guest. Never plan or run node, npm, or npx here. Author JS with run_js_script (Bare kernel, writes under ~/.agent) or run_js_script_at_path / run_command with an absolute .mjs path. Guest scripts use async function run(ctx, argv) — ctx.vfs, ctx.execLine, ctx.console, ctx.exitCode. No require('node:fs'). - LIVE KERNEL: read_proc_file on /proc/bare_os/features (or features.json) and /proc/bare_os/capabilities.json. Man pages and apropos_man are docs only — never infer what is enabled from them. - NET: web_fetch uses the same host allow/deny list as wget/curl. - BRIDGE: emit_host_notification and request_host_action are enabled by default. emergency_stop_mutations is the kill switch. - SECRETS: never print ~/.agent/config.json, API keys, seeds, or vault material. - ask_user_question is only for a real product choice the user must make. Never use it (or chat) to request permission. CODING LOOP. Multi-step work: todo_write (merge=true). Large unknown surface: enter_plan_mode, write ~/.agent/plan.md, exit_plan_mode, then implement. Plan mode is read-only except that plan file. Do not stop after a plan-only reply — keep calling tools until verified. 1. Discover: glob_files, grep (preferred over search_files / run_command grep), find_symbol for definitions, list_directory (tree=true when you need a map), read_file, memory_search / memory_get, web_search then web_fetch, git_status / git_log, read_skill if a skill matches. Read before you edit. Walk-up AGENTS.md and .grok/skills from cwd are already injected when present. 2. Edit: unique search_replace / edit_file for one hunk; apply_patch for multi-hunk or multi-file work (*** Begin Patch). Create with write_file / create_directory. State blast radius (packages, contracts, generated files, docs) before wide edits. Match surrounding style. No placeholders. Comments only for non-obvious constraints. 3. Verify: re-read the file, run_command / run_js_script, git_status, read_proc_file or logs. Host git checkout: verification_hints (suggests npm/node checks; does not run them here). 4. Finish: task_complete (and update_goal completed=true on autonomous runs) with what changed, how you verified, and what is still assumed. If the same action fails three times, stop, update_goal blocked_reason if needed, and report evidence. TOOL DISCIPLINE. - Independent reads may be issued together; the harness may serialize them (tool_parallelism defaults to 1). - Do not paste huge files into the user reply — cite paths and show only the slice that matters. - Persist durable facts in MEMORY.md; older turns may be compacted. - Progress UI is automatic (tools write ~/.agent/progress.txt). Do not narrate tool chatter in the final answer. - Autonomous mode is on by default. Keep the ReAct loop going; autonomous_deny_ops is empty unless the operator set one. TOOL CALLING. Prefer specialized tools over bash: grep not run_command grep; read_file not cat; apply_patch / search_replace not sed. Never use run_command to print thoughts. COMMUNICATION. Write for a reader who has not seen tool calls. Lead with the answer. Define project terms on first use. State facts literally. The final message must stand alone. Do not invent acronyms. TOOL MAP (schemas are already attached — use them): - Files: read_file, read_many, write_file, edit_file, search_replace, apply_patch, undo_last_edit, create_directory, list_directory (tree=true for BFS), file_stat, glob_files, fuzzy_find, grep (output_mode content|files_with_matches|count), search_files, move_path, copy_path, diff_files, delete_path, find_symbol, list_bin - Code / harness: run_command (optional cwd), run_js_script, run_js_script_at_path, todo_write, enter_plan_mode, exit_plan_mode, memory_search, memory_get, memory_append, remember, list_skills, read_skill, create_skill, edit_agent_config, git_status, git_diff, git_log, git_show, git_blame, history_search, rewind_session, export_session, schedule_task, unschedule_task, list_scheduled, wait_for - Kernel / ops: read_proc_file, runtime_diagnostic_bundle, get_system_info, get_resource_limits, get_swarm_peers, list_services, service_status, list_timers, read_cron_log, read_audit_log, read_boot_policy, read_kernel_extension_resolution, get_initd_graph, read_unit_journal, inspect_ipc_backpressure, get_network_summary, tail_telemetry_streams, pkg_index_lookup - Checks: list_verification_scripts, run_maintenance_gate, run_contract_checks, summarize_build_drift, verification_hints - Docs: read_man_page, apropos_man (documentation search only) - Bridge / web: web_search, web_fetch, get_hrpc_bridge_health, get_hrpc_allowlist_status, emit_host_notification, request_host_action - Autonomy: autonomous_run, autonomous_run_status, autonomous_run_stop, update_goal - Other: ask_user_question (product choice only, never permission), task_complete Skills live under ~/.agent/workspace/skills/ (and ~/.agent/skills/). The prompt includes a compact index — call read_skill and follow SKILL.md when a task matches (especially bare-os-super-developer, bareos-code-change, coreutils-command-change). Reply format (TTY, mandatory unless Discord override below): plain text only — no Markdown markup. Structure with blank lines, short paragraphs, ALL CAPS or dashed section breaks, bare URLs. Paths and commands as normal text.` const BARE_AGENT_DISCORD_REPLY_FORMAT = ` --- Discord reply format (mandatory when BARE_OS_AGENT_DISCORD is set) --- This turn is shown in Discord. This block overrides the TTY plain-text rule above. Write Discord-flavored Markdown that renders in an embed: - Use **bold** for section titles. Do not use # headings (Discord embeds show the hash). - Use - or 1. lists. Use blank lines between sections. - Use \`inline code\` for paths, commands, ids, and env vars. - Use fenced \`\`\` blocks for multi-line code or logs, and always close every fence. - Use [label](https://url) for links. Do not wrap the entire reply in one fence. - Keep the final user-facing answer as Markdown. Tool chatter and process steps belong in tools (progress.txt is automated), not the reply.` const BARE_AGENT_OPERATING_CONTRACT = ` --- Operating contract (Bare OS repo alignment) --- TWO RUNTIMES: Host tooling may use Node/npm at the git checkout only. Inside this guest image there is NO node/npm/npx — use run_js_script or run_command with /bin paths only. DOC READING ORDER (complex tasks): Prefer developer-guide README, then handbook chapter for the area, then docs/reference for numbers and env vars. Do not copy version tables into chat; link or cite paths. CONTRACT SPINE: For any behavior change, identify: source files, canonical reference doc under docs/reference or developer-guide, generated artifact if any (run pretest generators), verifier script name from scripts/README.md, and whether compatibility-matrix or CHANGELOG moves. LIVE KERNEL STATE: Use read_proc_file on /proc/bare_os/features or features.json and /proc/bare_os/capabilities.json. Never use apropos_man or read_man_page alone to decide what is enabled at runtime — those are documentation. GENERATED FILES: Do not hand-edit posix-dashboard, ctx-client-helper.generated.ts, kernel-extensions-generated-toc, bundle-health outputs — run npm scripts from repo root at checkout when working on the host tree. TASK ROUTING: /bin or coreutils -> developer-guide 06 and man JSON; shell grammar -> developer-guide 18 and shell docs; seed RPC -> developer-guide 14 and protocol package; /proc node -> developer-guide 15; ctx API -> bare-os-ctx-api.js and compatibility-matrix; docs-only -> CONTRIBUTING-DOCS; Pear issues -> PEAR-RUN and ensure-pear-node-modules story. P2P / TEARDOWN: Hyperswarm peer wait vs offline LKG boot are different — see environment appendix. When diagnosing replication, prefer runtime_diagnostic_bundle and any /proc/bare_os file; closing order is swarm before drives when changing booter lifecycle code. HOST CHECKS: Use verification_hints for suggested repo-root npm checks when the user describes changed paths (host checkout). It does not execute npm inside the guest. Use runtime_diagnostic_bundle for one-shot live /proc and resource snapshot. Use read_skill for workflow skills under workspace/skills/. SELF-REVIEW before task_complete: docs updated? generated regen? wrong runtime assumption (guest vs host)? same failing action tried fewer than three times?` /** * Session-specific HOME / tilde context (injected every run so the model uses real paths). * @param {Record} ctx * @param {string} home from bareAgentResolveHome * @param {{ dir: string, config: string }} paths */ function bareAgentSessionHomeBlock(ctx, home, paths) { const env = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} const homeEnv = String(env.HOME || '').trim() return ( '## This session: home directory and paths\n' + '- **Resolved user home (this session):** `' + home + '`\n' + '- **HOME in the environment:** `' + (homeEnv || home) + '`\n' + '- **Tilde \`~\`:** In shell and in user docs, \`~\` means this home directory. Examples: \`~/.agent\` == `' + paths.dir + '`, agent config `' + paths.config + '`. Always expand \`~\` to `' + home + '\` when constructing absolute paths for tools.\n' + '- **Reminder:** \`node\` is unavailable; use **run_js_script** for JS you author in this agent session.\n' ) } /** * @param {unknown} tc */ function bareAgentMergeToolCallDelta(acc, tc) { if (!tc || typeof tc !== 'object') return const o = /** @type {Record} */ (tc) const idx = typeof o.index === 'number' ? o.index : typeof o.index === 'string' ? Number.parseInt(o.index, 10) : 0 let cur = acc.get(idx) || /** @type {{ id: string, name: string, args: string }} */ ({ id: '', name: '', args: '' }) if (typeof o.id === 'string' && o.id) cur.id = o.id const fn = o.function && typeof o.function === 'object' ? o.function : null if (fn && typeof fn === 'object') { const nm = /** @type {Record} */ (fn).name const ar = /** @type {Record} */ (fn).arguments if (typeof nm === 'string') cur.name += nm if (typeof ar === 'string') cur.args += ar } acc.set(idx, cur) } /** * @param {Map} acc */ function bareAgentFinalizeToolCalls(acc) { const indices = [...acc.keys()].sort((a, b) => a - b) /** @type {unknown[]} */ const arr = [] /** @type {Set} */ const seen = new Set() for (const i of indices) { const c = acc.get(i) if (!c || !c.name) continue const args = c.args || '{}' // Drop stream+final duplicates (same id, or same name+args). const idKey = c.id ? 'id:' + c.id : '' const naKey = 'na:' + c.name + '\0' + args if ((idKey && seen.has(idKey)) || seen.has(naKey)) continue if (idKey) seen.add(idKey) seen.add(naKey) arr.push({ id: c.id || 'call_' + i + '_' + String(Math.random()).slice(2, 10), type: 'function', function: { name: c.name, arguments: args } }) } return arr } /** * @param {Record} cfg */ function bareAgentReasoningSettings(cfg) { const modeRaw = String(cfg.reasoning_mode || '') .trim() .toLowerCase() const mode = modeRaw === 'summary' || modeRaw === 'trace' ? modeRaw : 'off' const enabled = Boolean(cfg.show_reasoning) && mode !== 'off' const maxChars = typeof cfg.reasoning_max_chars === 'number' && Number.isFinite(cfg.reasoning_max_chars) ? Math.min(Math.max(Math.floor(cfg.reasoning_max_chars), 200), 80_000) : 4000 const includeTools = cfg.reasoning_include_tools !== false return { enabled, mode, maxChars, includeTools } } /** * @param {Record} cfg */ function bareAgentApplyProviderProfile(cfg) { const out = { ...cfg } const backend = bareAgentResolveBackend(out) out.backend = backend if (backend === 'qvac') { let profileId = String(out.qvac_profile || 'recommended') .trim() .toLowerCase() if (!profileId) profileId = 'recommended' const profile = bareAgentQvacGetProfile(profileId) out.qvac_profile = profile.id out.qvac_model = profile.chatModel out.model = profile.chatModel out.provider = 'qvac' const rawCtx = Number(out.qvac_ctx_size) if (!Number.isFinite(rawCtx) || rawCtx < profile.ctxSize) { out.qvac_ctx_size = 0 } if (!String(out.qvac_main_gpu || '').trim()) out.qvac_main_gpu = 'auto' if (typeof bareAgentSanitizeConfigForBackend === 'function') { return bareAgentSanitizeConfigForBackend(out) } return out } let provider = String(out.provider || '') .trim() .toLowerCase() if (!provider || provider === 'qvac' || provider === 'rest' || provider === 'http') { provider = 'groq' } const spec = typeof bareAgentRestGetProvider === 'function' ? bareAgentRestGetProvider(provider) : { id: 'groq', rest_base_url: 'https://api.groq.com/openai/v1', default_model: 'llama-3.3-70b-versatile' } out.provider = spec.id const base = String(out.rest_base_url || '').trim() const knownDefaults = [ 'https://api.groq.com/openai/v1', 'https://api.x.ai/v1', 'https://api.openai.com/v1' ] if (!base || (spec.rest_base_url && knownDefaults.indexOf(base) !== -1 && base !== spec.rest_base_url)) { if (spec.rest_base_url) out.rest_base_url = spec.rest_base_url } if (!String(out.model || '').trim() || bareAgentIsQvacModelId(String(out.model || ''))) { if (spec.default_model) out.model = spec.default_model } const modelLow = String(out.model || '').trim().toLowerCase() const cur = typeof out.request_timeout_ms === 'number' && Number.isFinite(out.request_timeout_ms) ? out.request_timeout_ms : 120000 if (spec.id === 'groq' && cur < 120000) out.request_timeout_ms = 120000 if (spec.id === 'xai') { const isReasoning = modelLow.includes('reasoning') || modelLow.includes('grok-4') if (isReasoning && cur < 300000) out.request_timeout_ms = 300000 } if (typeof bareAgentSanitizeConfigForBackend === 'function') { return bareAgentSanitizeConfigForBackend(out) } return out } /** * @param {Record} config */ function bareAgentFormatConfigSummary(config) { const cfg = config && typeof config === 'object' ? config : {} const backend = bareAgentResolveBackend(cfg) const lines = ['backend: ' + backend] if (backend === 'rest') { const spec = typeof bareAgentRestGetProvider === 'function' ? bareAgentRestGetProvider(String(cfg.provider || 'groq')) : { label: String(cfg.provider || 'groq') } lines.push('provider: ' + (spec.label || cfg.provider || 'groq')) lines.push('rest_base_url: ' + String(cfg.rest_base_url || '(not set)')) lines.push('model: ' + String(cfg.model || '(not set)')) lines.push( 'api_key: ' + (typeof bareAgentMaskSecretPreview === 'function' ? bareAgentMaskSecretPreview(cfg.rest_api_key) : cfg.rest_api_key ? '(set)' : '(not set)') ) } else { lines.push('profile: ' + String(cfg.qvac_profile || 'recommended')) lines.push('model: ' + String(cfg.qvac_model || cfg.model || '(not set)')) lines.push( 'device: ' + String(cfg.qvac_device || 'auto') + (cfg.qvac_main_gpu && String(cfg.qvac_main_gpu) !== 'auto' ? ' gpu=' + String(cfg.qvac_main_gpu) : '') ) } return lines.join('\n') } /** * @param {Record} cfg */ function bareAgentAutonomousSettings(cfg) { const enabled = Boolean(cfg.autonomous_mode_enabled) const active = Boolean(cfg.autonomous_active) const stopRequested = Boolean(cfg.autonomous_stop_requested) const startedAtMs = typeof cfg.autonomous_started_at_ms === 'number' && Number.isFinite(cfg.autonomous_started_at_ms) ? Math.max(0, Math.floor(cfg.autonomous_started_at_ms)) : 0 const maxRuntimeMs = typeof cfg.autonomous_max_runtime_ms === 'number' && Number.isFinite(cfg.autonomous_max_runtime_ms) ? Math.min( Math.max(Math.floor(cfg.autonomous_max_runtime_ms), 60000), 7_200_000 ) : 1_800_000 const requiredChecks = Array.isArray( cfg.autonomous_completion_required_checks ) ? cfg.autonomous_completion_required_checks .map((x) => String(x || '').trim()) .filter(Boolean) : [] return { enabled, active, stopRequested, startedAtMs, maxRuntimeMs, requiredChecks } } /** * Run configured autonomous completion gates. Internal dispatch skips deny_ops. * @returns {Promise<{ ok: boolean, failed: string[] }>} */ async function bareAgentRunAutonomousGates(o) { const checks = (o && o.requiredChecks) || [] if (!checks.length) return { ok: true, failed: [] } /** @type {string[]} */ const failed = [] for (const check of checks) { const res = await bareAgentDispatchTool({ ctx: o.ctx, toolName: 'run_maintenance_gate', argsJson: JSON.stringify({ command: check, timeout_ms: 300000 }), paths: o.paths, signal: o.signal, appendProgress: o.appendProgress, home: o.home, configRef: o.configRef, manCacheRef: o.manCacheRef, onTaskComplete: o.onTaskComplete, internalGate: true }) let ok = false try { const j = JSON.parse(res) const body = typeof j.stdout_stderr === 'string' ? j.stdout_stderr : '' ok = Boolean(j.ok) && !/EXIT:[1-9]/.test(body) } catch { ok = false } if (!ok) failed.push(check) } return { ok: failed.length === 0, failed } } /** * @param {Record} ctx * @param {string} argv0 * @param {object} opts * @param {boolean} opts.setupFlag * @param {boolean} opts.interactiveSetup */ /** * TEA form wizard when ctx.tui is attached. Same identity + backend fields. * @param {Record} ctx * @param {string} argv0 * @param {{ config: string }} paths * @param {Record} config */ async function bareAgentInteractiveSetupTui(ctx, argv0, paths, config) { const qvacOk = bareAgentQvacBridgeAvailable(ctx) const curBackend = bareAgentResolveBackend(config) const form = ctx.tui.form.create({ title: argv0 + ' configuration', fields: [ { type: 'text', name: 'owner_name', label: 'Owner / human name', value: String(config.owner_name || '') }, { type: 'text', name: 'agent_label', label: 'Agent display name', value: String(config.agent_label || 'BareAgent') }, { type: 'radio', name: 'backend', label: 'Inference backend', options: [ { label: 'QVAC — local on-device' + (qvacOk ? '' : ' [host bridge unavailable]'), value: 'qvac' }, { label: 'REST API — OpenAI-compatible (Groq, xAI, OpenAI, custom)', value: 'rest' } ], selected: curBackend === 'rest' ? 1 : 0 } ] }) const values = await ctx.tui.form.run(form) if (!values) return config if (values.owner_name && String(values.owner_name).trim()) { config.owner_name = String(values.owner_name).trim() } if (values.agent_label && String(values.agent_label).trim()) { config.agent_label = String(values.agent_label).trim() } const backend = String(values.backend || curBackend || 'qvac') if (backend === 'rest') { config.backend = 'rest' config = await bareAgentSetupRestFieldsTui(ctx, argv0, paths, config) } else { config.backend = 'qvac' config.provider = 'qvac' if (!qvacOk) { bareAgentErr( ctx, argv0 + ': QVAC host bridge unavailable. Choose REST or enable the QVAC host bridge.' ) return config } config = await bareAgentSetupQvacFieldsTui(ctx, config) } config = bareAgentApplyProviderProfile(config) await bareAgentSaveConfig(ctx, paths, config) bareAgentLog(ctx, 'Configuration saved.\n' + bareAgentFormatConfigSummary(config)) return config } /** * @param {Record} ctx * @param {Record} config */ async function bareAgentSetupQvacFieldsTui(ctx, config) { const profiles = bareAgentQvacProfileList() const cur = String(config.qvac_profile || 'recommended').trim().toLowerCase() let selected = 0 for (let i = 0; i < profiles.length; i++) { if (profiles[i].id === cur) selected = i } const deviceCur = String(config.qvac_device || 'auto').trim().toLowerCase() || 'auto' const form = ctx.tui.form.create({ title: 'QVAC (local on-device)', fields: [ { type: 'radio', name: 'qvac_profile', label: 'Model profile', options: profiles.map(function (p) { return { label: p.label + ' — ' + p.chatModel + ' (' + p.description + ')', value: p.id } }), selected: selected }, { type: 'radio', name: 'qvac_device', label: 'Device', options: [ { label: 'Auto (detect GPU)', value: 'auto' }, { label: 'CPU', value: 'cpu' }, { label: 'GPU', value: 'gpu' } ], selected: deviceCur === 'cpu' ? 1 : deviceCur === 'gpu' ? 2 : 0 } ] }) const values = await ctx.tui.form.run(form) if (!values) return config const chosen = bareAgentQvacGetProfile(String(values.qvac_profile || 'recommended')) config.backend = 'qvac' config.provider = 'qvac' config.qvac_profile = chosen.id config.qvac_model = chosen.chatModel config.model = chosen.chatModel config.qvac_ctx_size = 0 config.qvac_device = String(values.qvac_device || 'auto').trim() || 'auto' config.qvac_main_gpu = 'auto' return config } /** * @param {Record} ctx * @param {string} argv0 * @param {{ config: string }} paths * @param {Record} config */ async function bareAgentSetupRestFieldsTui(ctx, argv0, paths, config) { const providers = bareAgentRestProviderList() const curProv = String(config.provider || 'groq').trim().toLowerCase() let selected = 0 for (let i = 0; i < providers.length; i++) { if (providers[i].id === curProv) selected = i } const form = ctx.tui.form.create({ title: 'REST API (OpenAI-compatible)', fields: [ { type: 'radio', name: 'provider', label: 'Provider', options: providers.map(function (p) { return { label: p.label, value: p.id } }), selected: selected }, { type: 'text', name: 'rest_base_url', label: 'Base URL (blank = provider default)', value: String(config.rest_base_url || '') }, { type: 'text', name: 'rest_api_key', label: 'API key' + (String(config.rest_api_key || '').trim() ? ' [leave blank to keep ' + bareAgentMaskSecretPreview(config.rest_api_key) + ']' : ' (required)'), value: '' }, { type: 'text', name: 'model', label: 'Model id (blank = provider default)', value: bareAgentIsQvacModelId(String(config.model || '')) ? '' : String(config.model || '') } ] }) const values = await ctx.tui.form.run(form) if (!values) return config const spec = bareAgentRestGetProvider(String(values.provider || 'groq')) config.backend = 'rest' config.provider = spec.id const url = String(values.rest_base_url || '').trim() config.rest_base_url = url || spec.rest_base_url || String(config.rest_base_url || '') const typedKey = String(values.rest_api_key || '').trim() if (typedKey) config.rest_api_key = typedKey const model = String(values.model || '').trim() if (model) config.model = model else if (!String(config.model || '').trim() || bareAgentIsQvacModelId(String(config.model || ''))) { config.model = spec.default_model || config.model } if (!String(config.rest_api_key || '').trim() && bareAgentCanPlainSetup(ctx)) { const key = (await bareAgentPromptSetupLine( ctx, 'API key (required, input hidden): ', { mask: true } )) || '' if (key.trim()) config.rest_api_key = key.trim() } if (spec.id === 'custom' && !String(config.rest_base_url || '').trim()) { bareAgentErr(ctx, argv0 + ': custom REST provider needs a base URL (e.g. https://host/v1).') } void paths return config } async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) { void opts bareAgentLog(ctx, argv0 + ': configuring ~/.agent/config.json') if ( ctx.tui && ctx.tui.form && typeof ctx.tui.form.run === 'function' && typeof ctx.tui.isTTY === 'function' && ctx.tui.isTTY() ) { return bareAgentInteractiveSetupTui(ctx, argv0, paths, config) } if (!bareAgentCanPlainSetup(ctx)) { bareAgentErr( ctx, argv0 + ': interactive setup needs a TTY with stdin/stdout. Edit ' + paths.config + ' manually or run from an interactive shell.' ) return config } const stdout = ctx.replStdout || ctx.stdout bareAgentWriteOut( ctx, stdout, '\n=== ' + argv0 + ' configuration ===\n' + 'Pick a backend, then only that backend is stored in ~/.agent/config.json.\n' + 'Enter keeps the [default].\n\n' ) try { const owner = (await bareAgentPromptSetupLine( ctx, 'Owner / human name [' + (String(config.owner_name || '').trim() || 'unset') + ']: ' )) || '' if (owner.trim()) config.owner_name = owner.trim() const label = (await bareAgentPromptSetupLine( ctx, 'Agent display name [' + (String(config.agent_label || '').trim() || 'BareAgent') + ']: ' )) || '' if (label.trim()) config.agent_label = label.trim() const curBackend = bareAgentResolveBackend(config) const qvacOk = bareAgentQvacBridgeAvailable(ctx) bareAgentWriteOut( ctx, stdout, '\nInference backend:\n' + ' 1) QVAC — local on-device models' + (qvacOk ? '' : ' [host bridge unavailable]') + '\n' + ' 2) REST API — Groq, xAI, OpenAI, or any OpenAI-compatible URL\n' ) const backendRaw = (await bareAgentPromptSetupLine( ctx, 'Backend [1=QVAC, 2=REST] [' + (curBackend === 'rest' ? '2' : '1') + ']: ' )) || '' { const v = backendRaw.trim().toLowerCase() if (v === '2' || v === 'rest' || v === 'r') config.backend = 'rest' else if (v === '1' || v === 'qvac' || v === 'q') config.backend = 'qvac' else if (!String(config.backend || '').trim()) config.backend = curBackend } if (bareAgentResolveBackend(config) === 'qvac') { if (!qvacOk) { bareAgentWriteOut( ctx, stdout, '\nQVAC host bridge is unavailable on this boot.\n' ) const sw = (await bareAgentPromptSetupLine(ctx, 'Switch to REST API instead? [Y/n]: ')) || '' const ans = sw.trim().toLowerCase() if (ans === 'n' || ans === 'no') { bareAgentErr( ctx, argv0 + ': QVAC selected but the host bridge is unavailable.' ) return config } config.backend = 'rest' } else { config = await bareAgentSetupQvacFieldsPlain(ctx, stdout, config) } } if (bareAgentResolveBackend(config) === 'rest') { config = await bareAgentSetupRestFieldsPlain(ctx, stdout, config) } } catch (e) { const msg = e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e) bareAgentErr(ctx, argv0 + ': setup input failed: ' + msg) return config } config = bareAgentApplyProviderProfile(config) await bareAgentSaveConfig(ctx, paths, config) bareAgentLog(ctx, 'Configuration saved.\n' + bareAgentFormatConfigSummary(config)) return config } /** * @param {Record} ctx * @param {unknown} stdout * @param {Record} config */ async function bareAgentSetupQvacFieldsPlain(ctx, stdout, config) { const profiles = bareAgentQvacProfileList() const cur = String(config.qvac_profile || 'recommended').trim().toLowerCase() let lines = '\nQVAC model profile:\n' for (let i = 0; i < profiles.length; i++) { const p = profiles[i] lines += ' ' + String(i + 1) + ') ' + p.label + ' — ' + p.chatModel + (p.id === cur ? ' [current]' : '') + '\n' } bareAgentWriteOut(ctx, stdout, lines) const raw = (await bareAgentPromptSetupLine( ctx, 'Profile [1-' + String(profiles.length) + '] [recommended]: ' )) || '' let chosen = bareAgentQvacGetProfile(cur || 'recommended') const n = Number(raw.trim()) if (Number.isFinite(n) && n >= 1 && n <= profiles.length) { chosen = profiles[n - 1] } else if (raw.trim()) { chosen = bareAgentQvacGetProfile(raw.trim()) } bareAgentWriteOut( ctx, stdout, '\nDevice:\n 1) auto (detect GPU)\n 2) cpu\n 3) gpu\n' ) const devRaw = (await bareAgentPromptSetupLine(ctx, 'Device [1=auto, 2=cpu, 3=gpu] [1]: ')) || '' const dv = devRaw.trim().toLowerCase() const device = dv === '2' || dv === 'cpu' ? 'cpu' : dv === '3' || dv === 'gpu' ? 'gpu' : 'auto' config.backend = 'qvac' config.provider = 'qvac' config.qvac_profile = chosen.id config.qvac_model = chosen.chatModel config.model = chosen.chatModel config.qvac_ctx_size = 0 config.qvac_device = device config.qvac_main_gpu = 'auto' bareAgentWriteOut( ctx, stdout, '\nQVAC: ' + chosen.label + ' → ' + chosen.chatModel + ' (device ' + device + ').\n' ) return config } /** * @param {Record} ctx * @param {unknown} stdout * @param {Record} config */ async function bareAgentSetupRestFieldsPlain(ctx, stdout, config) { const providers = bareAgentRestProviderList() let lines = '\nREST provider:\n' for (let i = 0; i < providers.length; i++) { lines += ' ' + String(i + 1) + ') ' + providers[i].label + '\n' } bareAgentWriteOut(ctx, stdout, lines) const curProv = String(config.provider || 'groq').trim().toLowerCase() const defIdx = providers.findIndex(function (p) { return p.id === curProv }) + 1 const raw = (await bareAgentPromptSetupLine( ctx, 'Provider [1=Groq, 2=xAI, 3=OpenAI, 4=Custom] [' + String(defIdx > 0 ? defIdx : 1) + ']: ' )) || '' let spec = bareAgentRestGetProvider(curProv === 'qvac' ? 'groq' : curProv) const n = Number(raw.trim()) if (Number.isFinite(n) && n >= 1 && n <= providers.length) spec = providers[n - 1] else if (raw.trim()) spec = bareAgentRestGetProvider(raw.trim()) config.backend = 'rest' config.provider = spec.id const urlDefault = spec.rest_base_url || String(config.rest_base_url || '') const urlRaw = (await bareAgentPromptSetupLine( ctx, 'Base URL [' + (urlDefault || 'required for custom') + ']: ' )) || '' const url = urlRaw.trim() || urlDefault if (!url) { throw new Error('REST base URL is required for provider ' + spec.id) } config.rest_base_url = url.replace(/\/+$/, '') const haveKey = Boolean(String(config.rest_api_key || '').trim()) const keyPrompt = haveKey ? 'API key [Enter keeps ' + bareAgentMaskSecretPreview(config.rest_api_key) + ']: ' : 'API key (required, input hidden): ' const keyRaw = (await bareAgentPromptSetupLine(ctx, keyPrompt, { mask: true })) || '' if (keyRaw.trim()) config.rest_api_key = keyRaw.trim() if (!String(config.rest_api_key || '').trim()) { throw new Error('REST API key is required') } /** @type {{ id: string }[]} */ let remote = [] const fetchFn = typeof ctx.httpFetch === 'function' ? ctx.httpFetch.bind(ctx) : typeof fetch === 'function' ? fetch : null if (fetchFn && typeof bareAgentFetchRestModels === 'function') { try { remote = await bareAgentFetchRestModels(fetchFn, { baseUrl: config.rest_base_url, apiKey: config.rest_api_key }) bareAgentWriteOut( ctx, stdout, '\nLive models from ' + config.rest_base_url + ' (' + String(remote.length) + '):\n' ) } catch (err) { const msg = err && typeof err === 'object' && 'message' in err ? String(err.message) : String(err) bareAgentWriteOut(ctx, stdout, '\nCould not list remote models (' + msg + '). Using curated list.\n') } } const suggest = remote.length ? remote.slice(0, 16).map(function (m) { return m.id }) : spec.models && spec.models.length ? spec.models : typeof bareAgentRestModelsFallback === 'function' ? bareAgentRestModelsFallback(spec.id).map(function (m) { return m.id }) : [] if (suggest.length) { let mlines = remote.length ? '' : '\nSuggested models for ' + spec.label + ':\n' if (!mlines) mlines = '' for (let i = 0; i < suggest.length; i++) { mlines += ' ' + String(i + 1) + ') ' + suggest[i] + '\n' } bareAgentWriteOut(ctx, stdout, mlines) } const modelDefault = !String(config.model || '').trim() || bareAgentIsQvacModelId(String(config.model || '')) ? spec.default_model : String(config.model) const modelRaw = (await bareAgentPromptSetupLine( ctx, 'Model [' + (modelDefault || 'required') + ']: ' )) || '' const modelPick = modelRaw.trim() const modelNum = Number(modelPick) if (Number.isFinite(modelNum) && suggest[modelNum - 1]) { config.model = suggest[modelNum - 1] } else if (modelPick) { config.model = modelPick } else if (modelDefault) { config.model = modelDefault } else { throw new Error('REST model id is required') } bareAgentWriteOut( ctx, stdout, '\nREST: ' + spec.label + ' → ' + config.model + '\n ' + config.rest_base_url + '\n' ) return config } /** * Interactive setup only (`agent --setup`). * @param {Record} ctx * @param {string} argv0 */ async function bareAgentRunSetupOnly(ctx, argv0) { const home = bareAgentResolveHome(ctx) const paths = bareAgentPaths(home) let { config } = await bareAgentLoadOrCreateConfig(ctx, paths) config = bareAgentApplyProviderProfile(config) if (!bareAgentCanPlainSetup(ctx)) { bareAgentErr( ctx, argv0 + ': --setup / --config needs an interactive TTY. Edit ' + paths.config + ' manually.' ) ctx.exitCode = 1 return } let suspended = false try { if (typeof ctx.suspendReplForSubprocess === 'function') { ctx.suspendReplForSubprocess() suspended = true } config = await bareAgentInteractiveSetup(ctx, argv0, paths, config, { setupFlag: true, interactiveSetup: true }) } finally { if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') { ctx.resumeReplAfterSubprocess() } } try { await bareAgentEnsureWorkspace(ctx, paths) await bareAgentEnsureSkillTemplates(ctx, paths, config) await bareAgentSyncWorkspaceFromConfig(ctx, paths, config) } catch { bareAgentErr( ctx, argv0 + ': could not seed ~/.agent/workspace from /share/agent-workspace (check system image)' ) } try { if (ctx.vfs?.chmod) await ctx.vfs.chmod(paths.config, 0o600) } catch { /* ignore */ } ctx.exitCode = 0 void config } /** * @param {Record} ctx * @param {string} argv0 * @param {string} task * @param {{ setupFlag?: boolean }} [runOpts] */ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) { const setupFlag = Boolean(runOpts && runOpts.setupFlag) const autonomousFlag = Boolean(runOpts && (runOpts.autonomous || runOpts.autonomousGoal)) const planFlag = Boolean(runOpts && runOpts.planMode) const home = bareAgentResolveHome(ctx) const paths = bareAgentPaths(home) let { config } = await bareAgentLoadOrCreateConfig(ctx, paths) config = bareAgentApplyProviderProfile(config) if (planFlag) config.plan_mode_active = true if (runOpts && Number(runOpts.maxIterations) > 0) { config.max_iterations = Math.floor(Number(runOpts.maxIterations)) } if (runOpts && runOpts.modelOverride) { const m = String(runOpts.modelOverride).trim() if (m) { const backendNow = bareAgentResolveBackend(config) if (backendNow === 'qvac') config.qvac_model = m else config.model = m } } const canWizard = bareAgentCanPlainSetup(ctx) if (setupFlag && !canWizard) { bareAgentErr( ctx, argv0 + ': --setup / --config needs an interactive TTY. Edit ' + paths.config + ' manually.' ) ctx.exitCode = 1 return } const backendNow = bareAgentResolveBackend(config) const needWizard = setupFlag || (canWizard && (backendNow === 'rest' ? !(config.rest_api_key && String(config.rest_api_key).trim()) : !String(config.qvac_model || config.model || '').trim() || (backendNow === 'qvac' && !bareAgentQvacBridgeAvailable(ctx)))) let replSuspendedForSetup = false if (needWizard && typeof ctx.suspendReplForSubprocess === 'function') { ctx.suspendReplForSubprocess() replSuspendedForSetup = true } if (needWizard) { config = await bareAgentInteractiveSetup(ctx, argv0, paths, config, { setupFlag, interactiveSetup: true }) config = bareAgentApplyProviderProfile(config) } const backendReady = bareAgentResolveBackend(config) if (backendReady === 'rest') { if (!config.rest_api_key || !String(config.rest_api_key).trim()) { if ( replSuspendedForSetup && typeof ctx.resumeReplAfterSubprocess === 'function' ) { ctx.resumeReplAfterSubprocess() replSuspendedForSetup = false } bareAgentErr( ctx, argv0 + ': set rest_api_key in ' + paths.config + ' or run `' + argv0 + ' --setup` / `' + argv0 + ' --config`.' ) ctx.exitCode = 1 return } } else if (!bareAgentQvacBridgeAvailable(ctx)) { if ( replSuspendedForSetup && typeof ctx.resumeReplAfterSubprocess === 'function' ) { ctx.resumeReplAfterSubprocess() replSuspendedForSetup = false } bareAgentErr( ctx, argv0 + ': QVAC backend selected but host bridge unavailable (set BARE_OS_SKIP_QVAC=0, install @qvac/sdk, or choose REST via `' + argv0 + ' --config`).' ) ctx.exitCode = 1 return } else if (!String(config.qvac_model || config.model || '').trim()) { if ( replSuspendedForSetup && typeof ctx.resumeReplAfterSubprocess === 'function' ) { ctx.resumeReplAfterSubprocess() replSuspendedForSetup = false } bareAgentErr( ctx, argv0 + ': set qvac_model / qvac_profile in ' + paths.config + ' or run `' + argv0 + ' --config`.' ) ctx.exitCode = 1 return } if (autonomousFlag) { config = bareAgentBeginAutonomousRun(config, { goal: String((runOpts && runOpts.autonomousGoal) || task || '').trim(), maxRuntimeMs: Number(runOpts && runOpts.autonomousMaxRuntimeMs) || undefined, requiredChecks: (runOpts && runOpts.autonomousChecks) || undefined }) } if (autonomousFlag || planFlag) { try { await bareAgentSaveConfig(ctx, paths, config) } catch { /* persist best-effort */ } } try { if (ctx.vfs?.chmod) await ctx.vfs.chmod(paths.config, 0o600) } catch { /* ignore — optional */ } const fetchFn = bareAgentResolveFetch(ctx) if (backendReady === 'rest' && !fetchFn) { if ( replSuspendedForSetup && typeof ctx.resumeReplAfterSubprocess === 'function' ) { ctx.resumeReplAfterSubprocess() replSuspendedForSetup = false } bareAgentErr( ctx, argv0 + ': no fetch (ctx.httpFetch / bare.fetch / global fetch)' ) ctx.exitCode = 1 return } /** @type {unknown[]} */ let messages = await bareAgentLoadHistory(ctx, paths.history) const instructions = await bareAgentLoadInstructionFiles(ctx, paths) const manDigest = await bareAgentManDigest(ctx) await bareAgentEnsureWorkspace(ctx, paths) await bareAgentEnsureSkillTemplates(ctx, paths, config) if (needWizard) await bareAgentSyncWorkspaceFromConfig(ctx, paths, config) const backendForPrompt = bareAgentResolveBackend(config) const promptProfile = bareAgentQvacGetProfile( String(config.qvac_profile || config.profile || '') ) const promptCtxSize = backendForPrompt === 'qvac' ? bareAgentQvacResolveCtxSize(config, promptProfile) : 32768 // Local QVAC models have a hard ctx window; keep the system prompt lean. // Scale char budgets from ctx (chars ≈ tokens*4); leave room for tools/reply. const toolsOn = backendForPrompt === 'qvac' const promptCharBudget = Math.max( 4000, Math.floor(promptCtxSize * 4 * (toolsOn ? 0.35 : 0.7)) ) const workspaceBudget = backendForPrompt === 'qvac' ? Math.min(8000, Math.floor(promptCharBudget * 0.35)) : 24000 const manBudget = backendForPrompt === 'qvac' ? Math.min(4000, Math.floor(promptCharBudget * 0.2)) : 12000 const instructionsBudget = backendForPrompt === 'qvac' ? Math.min(3000, Math.floor(promptCharBudget * 0.2)) : 8000 const skillsBudget = backendForPrompt === 'qvac' ? Math.min(2000, Math.floor(promptCharBudget * 0.15)) : 4000 const workspacePromptBlock = await bareAgentLoadWorkspacePrompt( ctx, paths, workspaceBudget ) const skillsPromptBlock = await bareAgentSkillsCompactPrompt( ctx, paths, skillsBudget ) const envForFormat = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} const discordReply = /^(1|true|yes)$/i.test(String(envForFormat.BARE_OS_AGENT_DISCORD || '').trim()) let systemContent = BARE_AGENT_STATIC_SYSTEM + BARE_AGENT_OPERATING_CONTRACT + (discordReply ? BARE_AGENT_DISCORD_REPLY_FORMAT : '') + '\n\n' + bareAgentSessionHomeBlock(ctx, home, paths) + '\n\n' + manDigest.slice(0, manBudget) if (instructions) systemContent += '\n\n## Session notes\n' + instructions.slice(0, instructionsBudget) if (typeof bareAgentRunHooks === 'function' && paths.hooks) { try { const startHook = await bareAgentRunHooks(ctx, paths.hooks, 'SessionStart', '', {}, '') if (startHook && startHook.inject) { systemContent += '\n\n## SessionStart hook\n' + String(startHook.inject).slice(0, 1500) } } catch { /* optional */ } } if (runOpts && runOpts.extraSystemFile) { try { const extraPath = String(runOpts.extraSystemFile).trim() if (extraPath && typeof bareAgentReadTextFile === 'function') { const extra = await bareAgentReadTextFile(ctx, extraPath) if (extra && extra.trim()) { systemContent += '\n\n## Extra instructions (--system)\n' + extra.trim().slice(0, instructionsBudget) } } } catch { /* optional */ } } if (workspacePromptBlock && String(workspacePromptBlock).trim()) systemContent += '\n\n' + String(workspacePromptBlock).trim() if (skillsPromptBlock && String(skillsPromptBlock).trim()) systemContent += '\n\n' + String(skillsPromptBlock).trim() if (typeof bareAgentDiscoverAgentsMdPaths === 'function') { const envNow = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} const cwd = String(envNow.PWD || envNow.CWD || home || '').trim() || home try { const extraFiles = await bareAgentDiscoverAgentsMdPaths(ctx, cwd, 12) const workspaceAgents = (typeof paths.workspace === 'string' ? paths.workspace : paths.dir + '/workspace') + '/AGENTS.md' const filtered = extraFiles.filter(function (p) { return p !== workspaceAgents }) if (filtered.length && typeof bareAgentLoadAgentsMdPrompt === 'function') { const extraBlock = await bareAgentLoadAgentsMdPrompt(ctx, filtered, 4000) if (extraBlock && extraBlock.trim()) systemContent += '\n\n' + extraBlock.trim() } } catch { /* optional */ } } if (config.plan_mode_active) { systemContent += '\n\n## PLAN MODE is ON\n' + 'Read-only tools only. The only allowed write is ' + String(paths.plan || home + '/.agent/plan.md') + '. Draft the plan there, then call exit_plan_mode before implementing.\n' } if (typeof bareAgentLoadTodos === 'function' && paths.todos) { try { const todos = await bareAgentLoadTodos(ctx, paths.todos) const sum = bareAgentTodoSummarize(todos) if (sum.total) { systemContent += '\n\n## Session todos\n' + sum.text + '\n(open=' + String(sum.open) + ' completed=' + String(sum.completed) + ')\n' } } catch { /* optional */ } } let userTask = String(task || '') if (config.autonomous_active) { userTask = 'AUTONOMOUS RUN. Execute until the goal is done. Do not stop after a plan — use tools, then call task_complete.\n' + 'Goal: ' + String(config.autonomous_goal || task || '') + '\n\n' + userTask } if (!messages.length) { messages = [ { role: 'system', content: systemContent }, { role: 'user', content: userTask } ] } else { const hasSystem = messages[0] && typeof messages[0] === 'object' && /** @type {{ role?: string }} */ (messages[0]).role === 'system' if (!hasSystem) { messages = [{ role: 'system', content: systemContent }, ...messages] } else { messages[0] = { role: 'system', content: systemContent } } messages.push({ role: 'user', content: userTask }) } const stdout = /** @type {import('stream').Writable | undefined} */ ( ctx.replStdout || ctx.stdout ) const useColor = bareEditUseColor(ctx) const envBag = ctx.env && typeof ctx.env === 'object' ? /** @type {Record} */ (ctx.env) : {} const agentVerboseEnv = String(envBag.BARE_OS_AGENT_VERBOSE || '') .trim() .toLowerCase() const agentVerboseForced = agentVerboseEnv === '1' || agentVerboseEnv === 'true' || agentVerboseEnv === 'yes' /** @type {{ current: Record }} */ const configRef = { current: { ...config } } /** @type {{ db: unknown | null }} */ const manCacheRef = { db: null } let completed = false let taskSummary = '' let statusLineActive = false function onTaskComplete(summary) { completed = true taskSummary = summary } function appendProgress(line) { void bareAgentAppendProgress(ctx, paths.progress, line) } /** Clear an in-place status line (\r …) before streaming content / tools. */ function clearStatusLine() { if (!statusLineActive) return bareAgentWriteOut(ctx, stdout, '\r\x1b[K') statusLineActive = false } /** * @param {ReturnType} rs */ function agentVerbose(rs) { return agentVerboseForced || (rs && rs.enabled && rs.mode === 'trace') } const url = bareAgentNormalizeBaseUrl(String(configRef.current.rest_base_url || '')) + '/chat/completions' const tools = bareAgentToolDefinitions() let reasoningSettings = bareAgentReasoningSettings(configRef.current) let autonomousSettings = bareAgentAutonomousSettings(configRef.current) let reasoningCharCount = 0 let turnsSinceTodoWrite = 0 let suspended = replSuspendedForSetup /** @type {(() => void) | null} */ let detachThinkKeysSession = null try { if (typeof ctx.suspendReplForSubprocess === 'function' && !suspended) { ctx.suspendReplForSubprocess() suspended = true } const masterAbort = new AbortController() const prevAbortAgent = typeof ctx.bareOsAbortActiveAgent === 'function' ? ctx.bareOsAbortActiveAgent : null ctx.bareOsAbortActiveAgent = () => { try { masterAbort.abort() } catch { /* ignore */ } try { if (typeof ctx.bareOsQvacCancelActive === 'function') { ctx.bareOsQvacCancelActive() } } catch { /* ignore */ } try { if (detachThinkKeysSession) detachThinkKeysSession() } catch { /* ignore */ } } /** @type {(() => void) | null} */ let offSigint = null if (globalThis.process && typeof globalThis.process.on === 'function') { const fn = () => { try { if (typeof ctx.bareOsAbortActiveAgent === 'function') { ctx.bareOsAbortActiveAgent() } else { masterAbort.abort() } } catch { masterAbort.abort() } bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '^C' + EDIT_ANSI_RESET + '\n' ) } globalThis.process.on('SIGINT', fn) offSigint = () => { try { globalThis.process.off('SIGINT', fn) } catch { /* ignore */ } } } const maxIterBase = Number(configRef.current.max_iterations) || 64 let iter = 0 for (;;) { if (masterAbort.signal.aborted) { ctx.exitCode = 130 break } reasoningSettings = bareAgentReasoningSettings(configRef.current) autonomousSettings = bareAgentAutonomousSettings(configRef.current) const autoLive = Boolean(configRef.current.autonomous_active) && !autonomousSettings.stopRequested const maxIter = autoLive ? Math.max(maxIterBase, 96) : maxIterBase if (completed && !autoLive) break if (autonomousSettings.enabled && autonomousSettings.active) { const now = Date.now() if (autonomousSettings.stopRequested) { configRef.current.autonomous_active = false configRef.current.autonomous_status = 'stopped' await bareAgentSaveConfig(ctx, paths, configRef.current) appendProgress('autonomous stopped by manual request') bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\n[process] autonomous run stopped by request' + EDIT_ANSI_RESET + '\n' ) break } if ( autonomousSettings.startedAtMs > 0 && now - autonomousSettings.startedAtMs >= autonomousSettings.maxRuntimeMs ) { configRef.current.autonomous_active = false configRef.current.autonomous_status = 'timebox_expired' configRef.current.autonomous_last_error = 'timebox_expired' await bareAgentSaveConfig(ctx, paths, configRef.current) appendProgress('autonomous timebox expired') bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\n[process] autonomous run stopped (timebox expired)' + EDIT_ANSI_RESET + '\n' ) break } } iter++ if (iter > maxIter) { bareAgentErr(ctx, argv0 + ': max_iterations exceeded') ctx.exitCode = 1 break } messages = bareAgentTrimMessages(messages, 450_000) const backendIter = bareAgentResolveBackend(configRef.current) /** @type {Record} */ const providerNow = String(configRef.current.provider || '') .trim() .toLowerCase() let qvacToolsForTurn = /** @type {unknown[]} */ ([]) if (backendIter === 'qvac') { const profileEarly = bareAgentQvacGetProfile( String(configRef.current.qvac_profile || 'recommended') ) const ctxEarly = bareAgentQvacResolveCtxSize( configRef.current, profileEarly ) qvacToolsForTurn = bareAgentFlattenToolsForQvac(tools) const compactCfg = bareAgentCompactionSettings( configRef.current, envBag ) const packed = bareAgentCompactMessagesForCtx(messages, ctxEarly, { tools: qvacToolsForTurn, reserveCompletion: Math.min( 1024, Number(configRef.current.max_tokens) || 512 ), mode: compactCfg.mode, keepRecent: compactCfg.keepRecent, toolMaxChars: compactCfg.toolMaxChars, autonomous: autoLive ? { active: true, goal: String(configRef.current.autonomous_goal || ''), status: String(configRef.current.autonomous_status || 'running'), remainingMs: Math.max( 0, autonomousSettings.maxRuntimeMs - (Date.now() - (autonomousSettings.startedAtMs || Date.now())) ) } : undefined }) messages = packed.messages if (packed.meta.compacted) { appendProgress( 'context_compaction tokens ' + packed.meta.beforeTokens + '→' + packed.meta.afterTokens + '/' + packed.meta.budget + ' dropped_groups=' + packed.meta.droppedGroups + ' tiers=' + packed.meta.tiers.join(',') ) if (agentVerbose(reasoningSettings)) { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\n[ctx] compacted ' + packed.meta.beforeTokens + '→' + packed.meta.afterTokens + ' tok (budget ' + packed.meta.budget + '; ' + packed.meta.tiers.join(' › ') + ')' + EDIT_ANSI_RESET + '\n' ) } else if (packed.meta.droppedGroups > 0) { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '· context compacted (' + packed.meta.beforeTokens + '→' + packed.meta.afterTokens + ' tok)\n' + EDIT_ANSI_RESET ) } // Persist latest rolling summary for operators / next sessions. try { const compactPath = paths.compact || home + '/.agent/compact.md' const summaryMsg = messages.find((m) => bareAgentIsCompactionMessage(m) ) if ( summaryMsg && ctx.vfs && typeof ctx.vfs.writeFile === 'function' ) { const body = '# Agent context compaction\n\n' + bareAgentMessagePlainText(summaryMsg) + '\n' const buf = typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function' ? ctx.b4a.from(body) : new TextEncoder().encode(body) await ctx.vfs.writeFile(compactPath, buf) } } catch { /* ignore */ } } } else { // REST backends: still shrink fat history soft-cap. const compactCfg = bareAgentCompactionSettings( configRef.current, envBag ) if (compactCfg.mode !== 'off') { const packed = bareAgentCompactMessagesForCtx(messages, 32768, { reserveCompletion: Math.min( 2048, Number(configRef.current.max_tokens) || 1024 ), mode: compactCfg.mode, keepRecent: compactCfg.keepRecent || 12, toolMaxChars: compactCfg.toolMaxChars, autonomous: autoLive ? { active: true, goal: String(configRef.current.autonomous_goal || ''), status: String(configRef.current.autonomous_status || 'running'), remainingMs: Math.max( 0, autonomousSettings.maxRuntimeMs - (Date.now() - (autonomousSettings.startedAtMs || Date.now())) ) } : undefined }) messages = packed.messages if (packed.meta.compacted && packed.meta.droppedGroups > 0) { appendProgress( 'context_compaction rest ' + packed.meta.beforeTokens + '→' + packed.meta.afterTokens ) } } } if (reasoningSettings.enabled && reasoningSettings.mode === 'trace') { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\n[process] request backend=' + backendIter + ' provider=' + (providerNow || 'unknown') + ' model=' + String( configRef.current.model || configRef.current.qvac_model || '' ) + EDIT_ANSI_RESET + '\n' ) } let assistantContent = '' /** @type {Map} */ const toolAcc = new Map() /** @type {unknown} */ let usageOut = null let finishReason = '' const hideThinkEnv = String(envBag.BARE_OS_AGENT_HIDE_THINK || '') .trim() .toLowerCase() const hideThink = hideThinkEnv === '1' || hideThinkEnv === 'true' || hideThinkEnv === 'yes' const hideMdEnv = String(envBag.BARE_OS_AGENT_PLAIN || '') .trim() .toLowerCase() const discordEnv = String(envBag.BARE_OS_AGENT_DISCORD || '') .trim() .toLowerCase() const plainReply = hideMdEnv === '1' || hideMdEnv === 'true' || hideMdEnv === 'yes' || discordEnv === '1' || discordEnv === 'true' || discordEnv === 'yes' const thinkPanel = !hideThink && stdout && /** @type {{ isTTY?: boolean }} */ (stdout).isTTY ? bareAgentCreateThinkPanel(ctx, stdout, { useColor, bodyLines: 8, maxChars: Math.max(reasoningSettings.maxChars, 24_000), write: bareAgentWriteOut }) : null const detachThinkKeys = bareAgentAttachThinkScrollKeys(ctx, thinkPanel, { onAbort: () => { try { if (typeof ctx.bareOsAbortActiveAgent === 'function') { ctx.bareOsAbortActiveAgent() } else { masterAbort.abort() } } catch { masterAbort.abort() } } }) detachThinkKeysSession = detachThinkKeys let thinkSealed = false let replyStarted = false let replyPainted = false /** * @param {string} chunk */ function feedThink(chunk) { if (!chunk || !String(chunk).trim()) return if (hideThink) return clearStatusLine() if (thinkPanel) thinkPanel.append(chunk) else { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + chunk + EDIT_ANSI_RESET ) } } function sealThinkIfNeeded() { if (thinkSealed) return thinkSealed = true if (thinkPanel && thinkPanel.hasContent()) thinkPanel.seal() } function termWidth() { if (typeof bareAgentResolveTermCols === 'function') { return bareAgentResolveTermCols(ctx, stdout) } const fromOut = stdout && typeof stdout === 'object' ? Number(/** @type {{ columns?: number }} */ (stdout).columns) : NaN const fromEnv = parseInt(String(envBag.COLUMNS || '80'), 10) const n = Number.isFinite(fromOut) && fromOut > 0 ? fromOut : fromEnv return Math.max(40, Number.isFinite(n) && n > 0 ? n : 80) } /** * Buffer reply; show live status under the think box (markdown painted later). * @param {string} chunk */ function feedReply(chunk) { if (!chunk) return sealThinkIfNeeded() if (!replyStarted) replyStarted = true clearStatusLine() assistantContent += chunk if (plainReply) { bareAgentWriteOut(ctx, stdout, chunk) return } if (thinkPanel && thinkPanel.hasContent()) { thinkPanel.setStatus( bareEditSgr('dim', useColor) + '▌ answering… ' + String(assistantContent.length) + ' chars · ↑↓ scroll thoughts' + EDIT_ANSI_RESET ) } else { // No think box — stream plain for responsiveness; paint markdown at end. bareAgentWriteOut(ctx, stdout, chunk) } } function paintReplyMarkdown() { if (replyPainted) return replyPainted = true thinkSplit.flush() sealThinkIfNeeded() if (plainReply) { if (thinkPanel) thinkPanel.clearStatus() return } const body = String(assistantContent || '') if (thinkPanel) { thinkPanel.clearStatus() thinkPanel.detachBelow() } if (!body.trim()) return // If we streamed plain (no think panel), rewind approximate lines then paint. if (!thinkPanel || !thinkPanel.hasContent()) { const roughLines = body.split('\n').length if ( roughLines > 0 && stdout && /** @type {{ isTTY?: boolean }} */ (stdout).isTTY ) { bareAgentWriteOut( ctx, stdout, '\x1b[' + String(Math.min(40, roughLines)) + 'A\r\x1b[J' ) } } const rendered = bareAgentRenderMarkdown(body, { useColor, width: termWidth() }) bareAgentWriteOut(ctx, stdout, '\n' + rendered) } const thinkSplit = bareAgentCreateThinkTagSplitter({ onThink: feedThink, onContent: feedReply }) /** * @param {Record} e */ function onCompletionEvent(e) { if (e.type === 'delta_content') { const chunk = typeof e.content === 'string' ? e.content : '' if (chunk) thinkSplit.push(chunk) } else if (e.type === 'delta_tool_calls') { sealThinkIfNeeded() thinkSplit.flush() const arr = e.tool_calls if (Array.isArray(arr)) { for (const tc of arr) bareAgentMergeToolCallDelta(toolAcc, tc) } } else if (e.type === 'delta_reasoning') { const chunk = typeof e.reasoning === 'string' ? e.reasoning : '' if (chunk) { if (reasoningSettings.enabled || !hideThink) { if (reasoningCharCount < reasoningSettings.maxChars) { const remain = reasoningSettings.maxChars - reasoningCharCount const out = chunk.slice(0, remain) reasoningCharCount += out.length if (out.length) feedThink(out) } } } } else if (e.type === 'response_shape_keys') { const keys = Array.isArray(e.keys) ? e.keys.map((k) => String(k)).join(',') : '' appendProgress('provider_shape_keys ' + keys.slice(0, 200)) } else if (e.type === 'usage') { usageOut = e.usage } else if (e.type === 'finish' || e.type === 'finish_reason') { thinkSplit.flush() sealThinkIfNeeded() finishReason = String(e.finish_reason || '') } } try { if (backendIter === 'qvac') { const profile = bareAgentQvacGetProfile( String(configRef.current.qvac_profile || 'recommended') ) const modelSrc = String( configRef.current.qvac_model || configRef.current.model || profile.chatModel ) const ctxSize = bareAgentQvacResolveCtxSize( configRef.current, profile ) const deviceOpts = bareAgentQvacResolveDeviceOpts(configRef.current) const verboseUi = agentVerbose(reasoningSettings) if (verboseUi) { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\n[qvac] ensuring model ' + modelSrc + ' (ctx=' + ctxSize + (deviceOpts.device ? ', device=' + deviceOpts.device : ', auto-GPU') + ')…' + EDIT_ANSI_RESET + '\n' ) try { const st = typeof ctx.bareOsQvacStatus === 'function' ? ctx.bareOsQvacStatus() : null if (st && (st.cacheDir || st.hdmsPath || st.backendsDir)) { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '[qvac] cache=' + String(st.cacheDir || '') + ' hdms=' + String(st.hdmsPath || '/mnt/models') + ' backends=' + String(st.backendsDir || '(unresolved)') + EDIT_ANSI_RESET + '\n' ) } } catch { /* ignore */ } } else { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\rLoading model…' + EDIT_ANSI_RESET ) statusLineActive = true } /** * @param {unknown} prog */ function onLoadProgress(prog) { const pct = prog && typeof prog === 'object' && 'percentage' in prog ? Number( /** @type {{ percentage?: unknown }} */ (prog).percentage ) : NaN if (!Number.isFinite(pct)) return const label = verboseUi ? '\r[qvac] download/load ' + Math.floor(pct) + '%' : '\rLoading model… ' + Math.floor(pct) + '%' bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + label + EDIT_ANSI_RESET ) statusLineActive = true } if (typeof ctx.bareOsQvacLoadModel === 'function') { const loaded = await ctx.bareOsQvacLoadModel({ modelSrc, tools: true, ctxSize, device: deviceOpts.device, mainGpu: deviceOpts.mainGpu, gpuLayers: deviceOpts.gpuLayers, onProgress: onLoadProgress }) clearStatusLine() if (verboseUi) { if (loaded && loaded.fellBackToCpu) { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\n[qvac] GPU unavailable; using CPU' + EDIT_ANSI_RESET + '\n' ) } else if ( loaded && loaded.device === 'gpu' && loaded.mainGpu != null ) { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\n[qvac] using main-gpu=' + String(loaded.mainGpu) + (loaded.probe ? ' (' + String(loaded.probe) + ')' : '') + EDIT_ANSI_RESET + '\n' ) } } else if (loaded && loaded.fellBackToCpu) { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + 'Using CPU (GPU unavailable)\n' + EDIT_ANSI_RESET ) } } await ctx.bareOsQvacComplete({ history: messages, tools: qvacToolsForTurn, stream: Boolean(configRef.current.stream !== false), captureThinking: !hideThink || reasoningSettings.enabled, modelSrc, toolsEnabled: true, ctxSize, device: deviceOpts.device, mainGpu: deviceOpts.mainGpu, gpuLayers: deviceOpts.gpuLayers, signal: masterAbort.signal, onProgress: onLoadProgress, onEvent: onCompletionEvent }) clearStatusLine() thinkSplit.flush() sealThinkIfNeeded() paintReplyMarkdown() try { detachThinkKeys() } catch { /* ignore */ } } else { const headers = { 'Content-Type': 'application/json', Authorization: 'Bearer ' + String(configRef.current.rest_api_key || '') } const eh = configRef.current.extra_headers if (eh && typeof eh === 'object' && !Array.isArray(eh)) { for (const [k, v] of Object.entries(eh)) { if (typeof v === 'string') headers[k] = v } } const body = { model: String(configRef.current.model || ''), stream: Boolean(configRef.current.stream !== false), messages, tools, tool_choice: 'auto', max_tokens: Number(configRef.current.max_tokens) || 4096, temperature: Number(configRef.current.temperature) ?? 0.7 } if (providerNow === 'xai') { body.parallel_tool_calls = Number(configRef.current.tool_parallelism) > 1 ? true : false body.max_completion_tokens = Number(configRef.current.max_tokens) || 4096 } if (providerNow === 'groq') { body.parallel_tool_calls = Number(configRef.current.tool_parallelism) > 1 ? true : false body.max_completion_tokens = Number(configRef.current.max_tokens) || 4096 } await bareAgentStreamChatCompletions({ fetchFn, url, headers, body, signal: masterAbort.signal, onEvent: onCompletionEvent }) clearStatusLine() thinkSplit.flush() sealThinkIfNeeded() paintReplyMarkdown() try { detachThinkKeys() } catch { /* ignore */ } } } catch (e) { try { detachThinkKeys() } catch { /* ignore */ } const msg = e && typeof e === 'object' && 'name' in e && e.name === 'AbortError' ? 'aborted' : e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e) bareAgentErr(ctx, argv0 + ': ' + msg) ctx.exitCode = 130 break } const toolCallsArr = bareAgentFinalizeToolCalls(toolAcc) const hasTools = toolCallsArr.length > 0 if (!hasTools && finishReason === 'tool_calls') { appendProgress( 'warning tool_calls_finish_without_tool_deltas provider=' + providerNow ) } /** @type {Record} */ const assistantMsg = { role: 'assistant', content: assistantContent || null, tool_calls: hasTools ? toolCallsArr : undefined } messages.push(assistantMsg) if ( usageOut && typeof usageOut === 'object' && agentVerbose(reasoningSettings) ) { const u = /** @type {Record} */ (usageOut) const pt = u.prompt_tokens const ct = u.completion_tokens bareAgentWriteOut( ctx, stdout, '\n' + bareEditSgr('dim', useColor) + 'tokens: prompt=' + String(pt ?? '?') + ' completion=' + String(ct ?? '?') + EDIT_ANSI_RESET + '\n' ) } if (!hasTools) { if ( bareAgentAutonomousShouldContinue(configRef.current, { completed: completed, hasTools: false, stopRequested: autonomousSettings.stopRequested }) ) { const remain = Math.max( 0, autonomousSettings.maxRuntimeMs - (Date.now() - (autonomousSettings.startedAtMs || Date.now())) ) messages.push({ role: 'user', content: bareAgentAutonomousContinuationPrompt(configRef.current, { remainingMs: remain, lastError: String(configRef.current.autonomous_last_error || '') }) }) appendProgress('autonomous continue (no tools this turn)') await bareAgentSaveHistory(ctx, paths.history, messages) continue } await bareAgentSaveHistory(ctx, paths.history, messages) bareAgentWriteOut(ctx, stdout, '\n') break } appendProgress( 'iteration ' + iter + ' tools ' + toolCallsArr .map( (x) => /** @type {{ function?: { name?: string } }} */ (x).function ?.name ) .join(',') ) if (reasoningSettings.enabled && reasoningSettings.mode === 'trace') { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\n[process] iteration ' + String(iter) + ' finish_reason=' + (finishReason || 'unknown') + EDIT_ANSI_RESET + '\n' ) } let sawTodoWrite = false for (const tc of toolCallsArr) { const fn = /** @type {{ id?: string, function?: { name?: string, arguments?: string } }} */ ( tc ).function const id = /** @type {{ id?: string }} */ (tc).id || '' const name = fn?.name || '' const argsStr = fn?.arguments || '{}' if (name === 'todo_write') sawTodoWrite = true if ( reasoningSettings.enabled && reasoningSettings.mode === 'trace' && reasoningSettings.includeTools ) { const argsPreview = argsStr.length > 280 ? argsStr.slice(0, 280) + '…' : argsStr bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '[process] tool_call ' + name + ' args=' + argsPreview + EDIT_ANSI_RESET + '\n' ) } bareAgentWriteOut( ctx, stdout, '\n' + bareEditSgr('keyword', useColor) + '→ ' + name + EDIT_ANSI_RESET + '\n' ) const spinner = bareEditSgr('dim', useColor) + '…' + EDIT_ANSI_RESET bareAgentWriteOut(ctx, stdout, spinner + '\r') statusLineActive = true let resultStr = await bareAgentDispatchTool({ ctx, toolName: name, argsJson: argsStr, paths, signal: masterAbort.signal, appendProgress, home, configRef, manCacheRef, onTaskComplete }) if (typeof bareAgentRunHooks === 'function' && paths.hooks) { try { let parsedArgs = {} try { parsedArgs = JSON.parse(argsStr || '{}') } catch { parsedArgs = {} } const post = await bareAgentRunHooks( ctx, paths.hooks, 'PostToolUse', name, parsedArgs, resultStr ) if (post && post.inject) { resultStr = resultStr + '\n[hook PostToolUse] ' + String(post.inject).slice(0, 800) } } catch { /* optional */ } } clearStatusLine() messages.push({ role: 'tool', tool_call_id: id, content: resultStr }) if ( reasoningSettings.enabled && reasoningSettings.mode === 'trace' && reasoningSettings.includeTools ) { const resPreview = resultStr.length > 360 ? resultStr.slice(0, 360) + '…' : resultStr bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '[process] tool_result ' + name + ' ' + resPreview + EDIT_ANSI_RESET + '\n' ) } if (name === 'enter_plan_mode' || name === 'exit_plan_mode') { messages.push({ role: 'user', content: name === 'enter_plan_mode' ? '[harness] PLAN MODE is now ON. Only write ~/.agent/plan.md until exit_plan_mode.' : '[harness] PLAN MODE is now OFF. Mutating tools are allowed again.' }) } if (name === 'ask_user_question') { try { const parsed = JSON.parse(resultStr) if (parsed && parsed.text) { bareAgentWriteOut( ctx, stdout, '\n' + bareEditSgr('keyword', useColor) + 'Questions for you:\n' + EDIT_ANSI_RESET + String(parsed.text) + '\n' ) } } catch { /* ignore */ } } if (completed && !configRef.current.autonomous_active) break } if (sawTodoWrite) turnsSinceTodoWrite = 0 else turnsSinceTodoWrite++ if (typeof bareAgentTodoNudgeText === 'function' && typeof bareAgentLoadTodos === 'function') { try { const todosNow = paths.todos ? await bareAgentLoadTodos(ctx, paths.todos) : [] const sumNow = bareAgentTodoSummarize(todosNow) const nudge = bareAgentTodoNudgeText({ open: sumNow.open, turnsSinceTodoWrite, nudgeEnabled: configRef.current.todo_nudge_enabled !== false }) if (nudge) { messages.push({ role: 'user', content: '[harness reminder] ' + nudge }) } } catch { /* optional */ } } await bareAgentSaveHistory(ctx, paths.history, messages) if (completed) { if (configRef.current.autonomous_active) { const gate = await bareAgentRunAutonomousGates({ ctx, paths, signal: masterAbort.signal, appendProgress, home, configRef, manCacheRef, onTaskComplete, requiredChecks: autonomousSettings.requiredChecks }) if (!gate.ok) { completed = false configRef.current.autonomous_status = 'needs_fixups' configRef.current.autonomous_last_error = 'failed_checks:' + gate.failed.join(',') await bareAgentSaveConfig(ctx, paths, configRef.current) appendProgress('autonomous checks failed ' + gate.failed.join(',')) messages.push({ role: 'user', content: 'Autonomous completion gates failed for checks: ' + gate.failed.join(', ') + '. Fix the issues, rerun required checks, and only call task_complete when all pass.' }) continue } configRef.current.autonomous_active = false configRef.current.autonomous_status = 'completed' configRef.current.autonomous_last_error = '' await bareAgentSaveConfig(ctx, paths, configRef.current) appendProgress('autonomous completion gates passed') } bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\nDone: ' + taskSummary + EDIT_ANSI_RESET + '\n' ) break } } if (offSigint) offSigint() } finally { try { if (detachThinkKeysSession) detachThinkKeysSession() } catch { /* ignore */ } detachThinkKeysSession = null try { if (prevAbortAgent) ctx.bareOsAbortActiveAgent = prevAbortAgent else delete ctx.bareOsAbortActiveAgent } catch { try { delete ctx.bareOsAbortActiveAgent } catch { /* ignore */ } } try { if (typeof ctx.bareOsQvacCancelActive === 'function') { ctx.bareOsQvacCancelActive() } } catch { /* ignore */ } clearStatusLine() // Ensure cursor is on a fresh line so the shell prompt is visible. bareAgentWriteOut(ctx, stdout, '\n') if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') { try { ctx.resumeReplAfterSubprocess() } catch { /* ignore */ } } } }