/** 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 autonomous agent inside a JavaScript POSIX-like environment on Hyperdrive + Hyperswarm (Pear/Bare runtime). JavaScript execution on this OS: **Node.js is not installed.** The \`node\`, \`npm\`, and \`npx\` commands **do not exist** and must never appear in plans or in run_command. To run JS as part of your agent work, **you must call the run_js_script tool** (writes under ~/.agent and executes via the Bare kernel). Optional: once a script exists on disk, run_command may invoke it by **absolute path** (e.g. \`/home/.../script.mjs\`)—same mechanism as \`/bin\` scripts—not via \`node\`. Bare OS Created by: Raven Scott (https://raven-scott.fyi) Your Repo: https://git.ssh.surf/snxraven/bare-operating-system Your booter address: pear://qupw8zspk34pcxc7fqchzyeh33jtmxq1k7qze44fkosctwiid8zy Capabilities: ctx.execLine for shell lines; ctx.vfs readFile/writeFile/mkdir/readdir/chmod. Paths under /home (personal Hyperdrive), /mnt, /tmp are writable where policy allows; /bin, /etc are system drive. Safety: never exfiltrate ~/.agent/config.json or API keys. Prefer least-privilege commands. Call task_complete(summary) when fully done. Discovery: man , /share/man/man.json; Tier-1 utilities in /bin. Tools: list_directory, file_stat, read_man_page, apropos_man, read_proc_file, get_swarm_peers, get_resource_limits, web_fetch (live http(s) pages and APIs; same host allowlist as wget); use list_directory instead of \`ls\` in run_command when only listing. Skills: modular instructions live under ~/.agent/workspace/skills/ (and optionally ~/.agent/skills/). The system message includes a compact skill index; use the read_skill tool to load full SKILL.md when a task matches a listed skill. Kernel features / capabilities that are **actually enabled or disabled** in this runtime come from **read_proc_file** on \`/proc/bare_os/features\` (same payload as \`/proc/bare_os/features.json\`) and \`/proc/bare_os/capabilities.json\`. **Do not** infer current kernel state from \`apropos_man\` or man pages—that only searches documentation keywords. Prefer tools over guessing for filesystem and shell facts. Reply format (mandatory): Every message you stream to the user must be plain text only — readable in a terminal without a Markdown renderer. Do not use Markdown or similar markup: no emphasis/backtick/code-fence/link syntax, no # headings, no list markers used as markup. Structure with blank lines, short paragraphs, indentation, ALL CAPS or dashed lines for section breaks if needed, and bare URLs when linking. Paths and commands appear as normal text.` 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. ASK FIRST: Destructive deletes, bridge mutations (emit_host_notification, request_host_action), vault/identity exfil patterns, or widening HTTP allowlists — confirm with the user unless config already allows. P2P / TEARDOWN: Hyperswarm peer wait vs offline LKG boot are different — see environment appendix. When diagnosing replication, prefer runtime_diagnostic_bundle and read_proc allowlisted swarm files; closing order is swarm before drives when changing booter lifecycle code. WORKFLOW: Plan briefly, execute tools, verify with read_proc or logs, then task_complete. Before large edits state blast radius (packages, contracts, docs). Before task_complete, self-review: docs updated? generated regen? wrong runtime assumption? STOP CONDITIONS: If the same failing action repeats three times, stop and summarize evidence; do not loop blindly. TOOLS: Use verification_hints for suggested repo-root npm checks when the user describes changed paths (host checkout). Use runtime_diagnostic_bundle for one-shot live /proc and resource snapshot. Use read_skill for workflow skills under workspace/skills/.` /** * 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 = [] for (const i of indices) { const c = acc.get(i) if (!c || !c.name) continue arr.push({ id: c.id || 'call_' + i + '_' + String(Math.random()).slice(2, 10), type: 'function', function: { name: c.name, arguments: c.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 provider = String(out.provider || '').trim().toLowerCase() const model = String(out.model || '').trim().toLowerCase() const defaultGroq = 'https://api.groq.com/openai/v1' if (provider === 'groq') { const base = String(out.rest_base_url || '').trim() if (!base || base === 'https://api.x.ai/v1') out.rest_base_url = defaultGroq const cur = typeof out.request_timeout_ms === 'number' && Number.isFinite(out.request_timeout_ms) ? out.request_timeout_ms : 120000 if (cur < 120000) out.request_timeout_ms = 120000 } if (provider === 'xai') { const base = String(out.rest_base_url || '').trim() if (!base || base === defaultGroq) out.rest_base_url = 'https://api.x.ai/v1' const isReasoningModel = model.includes('reasoning') || model.includes('grok-4.20') if (isReasoningModel) { const cur = typeof out.request_timeout_ms === 'number' && Number.isFinite(out.request_timeout_ms) ? out.request_timeout_ms : 120000 if (cur < 300000) out.request_timeout_ms = 300000 } } return out } /** * @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 } } /** * @param {Record} ctx * @param {string} argv0 * @param {object} opts * @param {boolean} opts.setupFlag * @param {boolean} opts.interactiveSetup */ async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) { void opts bareAgentLog(ctx, argv0 + ': configuring ~/.agent/config.json') 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' + 'Answer each question (Enter keeps the [default]). Typed input is echoed by the terminal.\n\n' ) try { const owner = (await bareAgentPromptSetupLine( ctx, 'Owner / human name (who operates this agent) [' + (String(config.owner_name || '').trim() || 'unset') + ']: ' )) || '' if (owner.trim()) config.owner_name = owner.trim() const label = (await bareAgentPromptSetupLine( ctx, 'Agent display name / label (shown in notes and logs) [' + (String(config.agent_label || '').trim() || 'BareAgent') + ']: ' )) || '' if (label.trim()) config.agent_label = label.trim() const url = (await bareAgentPromptSetupLine( ctx, 'REST base URL [' + String(config.rest_base_url || '') + ']: ' )) || '' if (url.trim()) config.rest_base_url = url.trim() const keyRaw = (await bareAgentPromptSetupLine( ctx, 'REST API key [leave empty to skip]: ', { mask: true } )) || '' if (keyRaw.trim()) config.rest_api_key = keyRaw.trim() const modelRaw = (await bareAgentPromptSetupLine( ctx, 'Model [' + String(config.model || '') + ']: ' )) || '' if (modelRaw.trim()) config.model = modelRaw.trim() const provRaw = (await bareAgentPromptSetupLine( ctx, 'Provider label [' + String(config.provider || '') + ']: ' )) || '' if (provRaw.trim()) config.provider = provRaw.trim() const autonomousModeRaw = (await bareAgentPromptSetupLine( ctx, 'Enable autonomous coding mode? (y/N) [' + (config.autonomous_mode_enabled ? 'y' : 'n') + ']: ' )) || '' { const v = autonomousModeRaw.trim().toLowerCase() if (v === 'y' || v === 'yes' || v === '1' || v === 'true') config.autonomous_mode_enabled = true else if (v === 'n' || v === 'no' || v === '0' || v === 'false') config.autonomous_mode_enabled = false } const showReasoningRaw = (await bareAgentPromptSetupLine( ctx, 'Show thinking/process output? (y/N) [' + (config.show_reasoning ? 'y' : 'n') + ']: ' )) || '' { const v = showReasoningRaw.trim().toLowerCase() if (v === 'y' || v === 'yes' || v === '1' || v === 'true') config.show_reasoning = true else if (v === 'n' || v === 'no' || v === '0' || v === 'false') config.show_reasoning = false } const modeRaw = (await bareAgentPromptSetupLine( ctx, 'Reasoning mode off|summary|trace [' + String(config.reasoning_mode || 'off') + ']: ' )) || '' if (modeRaw.trim()) { const m = modeRaw.trim().toLowerCase() if (m === 'off' || m === 'summary' || m === 'trace') config.reasoning_mode = m } const maxCharsRaw = (await bareAgentPromptSetupLine( ctx, 'Reasoning max chars [default ' + String(config.reasoning_max_chars || 4000) + ']: ' )) || '' if (maxCharsRaw.trim()) { const n = Number(maxCharsRaw) if (Number.isFinite(n) && n >= 200) config.reasoning_max_chars = Math.floor(n) } const includeToolsRaw = (await bareAgentPromptSetupLine( ctx, 'Include tool traces in process output? (Y/n) [' + (config.reasoning_include_tools === false ? 'n' : 'y') + ']: ' )) || '' { const v = includeToolsRaw.trim().toLowerCase() if (v === 'y' || v === 'yes' || v === '1' || v === 'true') config.reasoning_include_tools = true else if (v === 'n' || v === 'no' || v === '0' || v === 'false') config.reasoning_include_tools = false } } 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 } await bareAgentSaveConfig(ctx, paths, config) bareAgentLog(ctx, 'Configuration saved.') 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 home = bareAgentResolveHome(ctx) const paths = bareAgentPaths(home) let { config } = await bareAgentLoadOrCreateConfig(ctx, paths) config = bareAgentApplyProviderProfile(config) 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 needWizard = setupFlag || (!(config.rest_api_key && String(config.rest_api_key).trim()) && canWizard) 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 }) } 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 } try { if (ctx.vfs?.chmod) await ctx.vfs.chmod(paths.config, 0o600) } catch { /* ignore — optional */ } const fetchFn = bareAgentResolveFetch(ctx) if (!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 workspacePromptBlock = await bareAgentLoadWorkspacePrompt( ctx, paths, 24000 ) const skillsPromptBlock = await bareAgentSkillsCompactPrompt(ctx, paths, 4000) let systemContent = BARE_AGENT_STATIC_SYSTEM + BARE_AGENT_OPERATING_CONTRACT + '\n\n' + bareAgentSessionHomeBlock(ctx, home, paths) + '\n\n' + manDigest.slice(0, 12000) if (instructions) systemContent += '\n\n## Session notes\n' + instructions.slice(0, 8000) if (workspacePromptBlock && String(workspacePromptBlock).trim()) systemContent += '\n\n' + String(workspacePromptBlock).trim() if (skillsPromptBlock && String(skillsPromptBlock).trim()) systemContent += '\n\n' + String(skillsPromptBlock).trim() if (!messages.length) { messages = [ { role: 'system', content: systemContent }, { role: 'user', content: task } ] } 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: task }) } const stdout = /** @type {import('stream').Writable | undefined} */ ( ctx.replStdout || ctx.stdout ) const useColor = bareEditUseColor(ctx) /** @type {{ current: Record }} */ const configRef = { current: { ...config } } /** @type {{ db: unknown | null }} */ const manCacheRef = { db: null } let completed = false let taskSummary = '' function onTaskComplete(summary) { completed = true taskSummary = summary } function appendProgress(line) { void bareAgentAppendProgress(ctx, paths.progress, line) } 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 suspended = replSuspendedForSetup try { if (typeof ctx.suspendReplForSubprocess === 'function' && !suspended) { ctx.suspendReplForSubprocess() suspended = true } const masterAbort = new AbortController() /** @type {(() => void) | null} */ let offSigint = null if (globalThis.process && typeof globalThis.process.on === 'function') { const fn = () => { 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 maxIter = Number(configRef.current.max_iterations) || 64 let iter = 0 for (;;) { reasoningSettings = bareAgentReasoningSettings(configRef.current) autonomousSettings = bareAgentAutonomousSettings(configRef.current) if (completed) 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 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 } } /** @type {Record} */ const providerNow = String(configRef.current.provider || '').trim().toLowerCase() 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 } if (reasoningSettings.enabled && reasoningSettings.mode === 'trace') { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\n[process] request provider=' + (providerNow || 'unknown') + ' model=' + String(configRef.current.model || '') + EDIT_ANSI_RESET + '\n' ) } let assistantContent = '' /** @type {Map} */ const toolAcc = new Map() /** @type {unknown} */ let usageOut = null let finishReason = '' try { await bareAgentStreamChatCompletions({ fetchFn, url, headers, body, signal: masterAbort.signal, onEvent: (ev) => { const e = /** @type {Record} */ (ev) if (e.type === 'delta_content') { const chunk = typeof e.content === 'string' ? e.content : '' assistantContent += chunk bareAgentWriteOut(ctx, stdout, chunk) } else if (e.type === 'delta_tool_calls') { const arr = e.tool_calls if (Array.isArray(arr)) { for (const tc of arr) bareAgentMergeToolCallDelta(toolAcc, tc) } } else if (e.type === 'delta_reasoning' && reasoningSettings.enabled) { const chunk = typeof e.reasoning === 'string' ? e.reasoning : '' if (chunk && reasoningCharCount < reasoningSettings.maxChars) { const remain = reasoningSettings.maxChars - reasoningCharCount const out = chunk.slice(0, remain) reasoningCharCount += out.length if (out.length) { bareAgentWriteOut( ctx, stdout, '\n' + bareEditSgr('dim', useColor) + '[thinking] ' + out + EDIT_ANSI_RESET + '\n' ) } } } 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_reason') { finishReason = String(e.finish_reason || '') } } }) } catch (e) { 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') { 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 (autonomousSettings.enabled && autonomousSettings.active && autonomousSettings.requiredChecks.length) { /** @type {string[]} */ const failedChecks = [] for (const check of autonomousSettings.requiredChecks) { const res = await bareAgentDispatchTool({ ctx, toolName: 'run_maintenance_gate', argsJson: JSON.stringify({ command: check, timeout_ms: 300000 }), paths, signal: masterAbort.signal, appendProgress, home, configRef, manCacheRef, onTaskComplete }) 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) failedChecks.push(check) } if (failedChecks.length) { configRef.current.autonomous_status = 'needs_fixups' configRef.current.autonomous_last_error = 'failed_checks:' + failedChecks.join(',') await bareAgentSaveConfig(ctx, paths, configRef.current) appendProgress('autonomous checks failed ' + failedChecks.join(',')) messages.push({ role: 'user', content: 'Autonomous completion gates failed for checks: ' + failedChecks.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') } 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' ) } 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 (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) + '[tool] ' + name + EDIT_ANSI_RESET + '\n' ) const spinner = bareEditSgr('dim', useColor) + '… running ' + name + EDIT_ANSI_RESET bareAgentWriteOut(ctx, stdout, spinner + '\r') const resultStr = await bareAgentDispatchTool({ ctx, toolName: name, argsJson: argsStr, paths, signal: masterAbort.signal, appendProgress, home, configRef, manCacheRef, onTaskComplete }) bareAgentWriteOut(ctx, stdout, '\x1b[K') 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 (completed) break } await bareAgentSaveHistory(ctx, paths.history, messages) if (completed) { bareAgentWriteOut( ctx, stdout, bareEditSgr('dim', useColor) + '\nDone: ' + taskSummary + EDIT_ANSI_RESET + '\n' ) break } } if (offSigint) offSigint() } finally { if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') { ctx.resumeReplAfterSubprocess() } } }