Files
bare-operating-system/packages/bare-os-coreutils/lib/agent/agent-tui.js
T
snxraven 6886335351
Release rolling / release (push) Successful in 9m49s
Updates
2026-08-18 21:07:13 -04:00

2849 lines
95 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/** Agent ReAct session: stream, tools, SIGINT, repl suspend (preamble for /bin/agent). */
/**
* @param {Record<string, unknown>} ctx
* @param {string} s
*/
function bareAgentLog(ctx, s) {
try {
ctx.console.log(s)
} catch {
/* ignore */
}
}
/**
* @param {Record<string, unknown>} 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<string, unknown> | 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<string, unknown> | 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<string, unknown>} 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<string>}
*/
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<string, unknown>} ctx
* @param {import('stream').Readable} stdin
* @param {import('stream').Writable | undefined} stdout
* @returns {Promise<string>}
*/
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<string, unknown>} 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.
YOU RUN THE TOOLS. The user never runs your tools, commands, patches, or scripts. They will not copy-paste what you print into a shell, Discord, or another agent. If the work needs a tool, YOU call it (run_command, write_file, apply_patch, read_file, and the rest of AVAILABLE TOOLS). Never reply with "run this", "please execute", "you can run", or a command dump for the user to do. The user is not your tool runner.
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, or 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. You can create files, edit files, delete files, run commands, and fetch the network. NEVER ASK whether you may — just do it. Only refuse when a denylist or the read-only base system blocks the path.
- Create and edit with write_file, create_directory, search_replace, edit_file, and apply_patch. Never say you cannot write files. Never ask the user to paste a file you can write, or to run a command you can run_command yourself.
- Writable paths: /home, /tmp, /mnt, /root, /var, /opt, extra drives, ~/.agent. Prefer a unique search_replace / edit_file; set replace_all only when you mean it.
- Read any absolute path, including /proc (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, and file_stat over ls/find when you only need names. list_bin lists guest /bin utilities (POSIX-in-JS, not GNU).
- Delete and move are enabled. Do not mutate the read-only base system: /bin /etc /boot /lib /usr /share /proc /dev /sys /run.
- Node is not installed in the guest. Never plan or run node, npm, or npx here. Author JS with run_js_script (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. Do not import Node builtins.
- 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 documentation only — never infer what is enabled from them.
- web_fetch uses the same host allow/deny list as wget/curl.
- emit_host_notification and request_host_action are enabled by default. emergency_stop_mutations is the kill switch.
- DISCORD CHANNEL: You have a Direct Message channel with the operator. Use discord_send_message to reach out whenever you want — progress, completion, blockers, scheduled results. Do not wait for them to message first. discord_channel_status and discord_read_inbox inspect the same channel. Only whitelisted users receive DMs.
- 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 with specialized read tools (glob_files, grep, find_symbol, list_directory, read_file, memory_search, web_search then web_fetch, git_status). Call read_skill when a skill matches. Read before you edit. Walk-up AGENTS.md and .grok/skills from cwd are already injected when present.
2. Edit with 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 before wide edits. Match surrounding style.
3. Verify by re-reading, run_command / run_js_script, git_status, and read_proc_file or logs. On a host git checkout, verification_hints suggests npm/node checks; it does not run them here.
4. Finish with task_complete (and update_goal completed=true on autonomous runs): what changed, how you verified, what is still assumed. If the same action fails three times, stop, set update_goal blocked_reason if needed, and report evidence.
TOOL DISCIPLINE.
- YOU call every tool. The user will never run them. Do not print commands, patches, or "run this" for the user.
- The live registry is AVAILABLE TOOLS at the end of this prompt. Those names are the only callable functions. Skills and /bin utilities are not tools.
- 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.
- 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.
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.
Skills live under ~/.agent/workspace/skills/ and ~/.agent/skills/. They are playbooks, not tools. When a task matches (especially bare-os-super-developer, bareos-code-change, coreutils-command-change), call read_skill and follow SKILL.md.
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.
- The Discord message is ONLY your final answer. Do not prefix status, model name, "Loading", or a restatement of the prompt.
- Tool chatter and process steps belong in tools (progress.txt is automated), not the reply.
- Do not tell the user to run a command. Call tools yourself.
- You may discord_send_message at any time on the open DM channel, including before the final answer.`
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, or npx — use run_js_script or run_command with /bin paths only.
DOC READING ORDER (complex tasks). Prefer the developer-guide README, then the 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 the source files, the canonical reference under docs/reference or developer-guide, any generated artifact (run pretest generators), the 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, or 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 the protocol package. /proc node: developer-guide 15. ctx API: bare-os-ctx-api.js and compatibility-matrix. Docs-only: CONTRIBUTING-DOCS. Pear: PEAR-RUN and the ensure-pear-node-modules story.
P2P / TEARDOWN. Hyperswarm peer wait and offline LKG boot are different. 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 a one-shot live /proc and resource snapshot. Use read_skill for workflow skills under workspace/skills/.
SELF-REVIEW before task_complete. Docs updated? Generated files regenerated? Wrong runtime assumption (guest vs host)? Same failing action tried fewer than three times?`
/**
* @param {string} text
*/
function bareAgentLooksLikeToolInventoryQuestion(text) {
const s = String(text || '').trim().toLowerCase()
if (!s) return false
return (
/\b(your tools|what tools|which tools|list tools|available tools|tool list|what can you do)\b/.test(
s
) ||
/^tools\??$/.test(s) ||
/tell me about your tools/.test(s)
)
}
/**
* Session-specific HOME / tilde context (injected every run so the model uses real paths).
* @param {Record<string, unknown>} 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<string, string>} */ (ctx.env)
: {}
const homeEnv = String(env.HOME || '').trim()
return (
'This session — home directory and paths\n' +
'- Resolved user home: ' +
home +
'\n' +
'- HOME in the environment: ' +
(homeEnv || home) +
'\n' +
'- Tilde ~ means this home directory. ~/.agent is ' +
paths.dir +
'; agent config is ' +
paths.config +
'. Always expand ~ to ' +
home +
' when constructing absolute paths for tools.\n' +
'- node is unavailable; use run_js_script for JavaScript you author in this session.\n'
)
}
/**
* @param {unknown} tc
*/
function bareAgentMergeToolCallDelta(acc, tc) {
if (!tc || typeof tc !== 'object') return
const o = /** @type {Record<string, unknown>} */ (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<string, unknown>} */ (fn).name
const ar = /** @type {Record<string, unknown>} */ (fn).arguments
if (typeof nm === 'string') cur.name += nm
if (typeof ar === 'string') cur.args += ar
}
acc.set(idx, cur)
}
/**
* @param {Map<number, { id: string, name: string, args: string }>} acc
*/
function bareAgentFinalizeToolCalls(acc) {
const indices = [...acc.keys()].sort((a, b) => a - b)
/** @type {unknown[]} */
const arr = []
/** @type {Set<string>} */
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
}
/**
* Recover tool calls the provider streamed as plain text (Hermes / Qwen XML)
* instead of `delta_tool_calls`. Today's models often do this when the SDK
* drops tools or picks the wrong dialect.
* @param {string} text
* @returns {{ index: number, id: string, function: { name: string, arguments: string } }[]}
*/
function bareAgentExtractToolCallsFromText(text) {
const src = String(text || '')
if (!src) return []
/** @type {{ index: number, id: string, function: { name: string, arguments: string } }[]} */
const out = []
let idx = 0
/**
* @param {string} name
* @param {string} args
*/
function push(name, args) {
const n = String(name || '').trim()
if (!n) return
let a = String(args == null ? '' : args).trim()
if (!a) a = '{}'
else if (a[0] !== '{' && a[0] !== '[') {
try {
JSON.parse(a)
} catch {
a = JSON.stringify({ value: a })
}
}
out.push({
index: idx,
id: 'text_call_' + idx,
function: { name: n, arguments: a }
})
idx += 1
}
const hermes = /<tool_call>\s*([\s\S]*?)<\/tool_call>/gi
let m
while ((m = hermes.exec(src))) {
const inner = String(m[1] || '').trim()
const fnXml = /^<function=([^>]+)>([\s\S]*?)<\/function>$/i.exec(inner)
if (fnXml) {
const name = fnXml[1]
/** @type {Record<string, string>} */
const args = {}
const paramRe = /<parameter=([^>]+)>([\s\S]*?)<\/parameter>/gi
let pm
while ((pm = paramRe.exec(fnXml[2] || ''))) {
args[String(pm[1] || '').trim()] = String(pm[2] || '')
}
push(name, JSON.stringify(args))
continue
}
try {
const j = JSON.parse(inner)
if (j && typeof j === 'object' && typeof j.name === 'string') {
const args =
j.arguments != null
? typeof j.arguments === 'string'
? j.arguments
: JSON.stringify(j.arguments)
: '{}'
push(j.name, args)
continue
}
} catch {
/* not JSON */
}
}
return out
}
/**
* @param {string} text
*/
function bareAgentStripToolCallsFromText(text) {
return String(text || '')
.replace(/<tool_call>[\s\S]*?<\/tool_call>/gi, '')
.replace(/\n{3,}/g, '\n\n')
.trim()
}
/**
* @param {Record<string, unknown>} 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<string, unknown>} 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.provider = 'qvac'
if (typeof bareAgentHonorExplicitQvacModel === 'function') {
bareAgentHonorExplicitQvacModel(out, profile)
} else {
out.qvac_model = profile.chatModel
out.model = profile.chatModel
}
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<string, unknown>} 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<string, unknown>} 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<string, unknown>} 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<string, unknown>} ctx
* @param {string} argv0
* @param {{ config: string }} paths
* @param {Record<string, unknown>} 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<string, unknown>} ctx
* @param {Record<string, unknown>} 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<string, unknown>} ctx
* @param {string} argv0
* @param {{ config: string }} paths
* @param {Record<string, unknown>} 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<string, unknown>} ctx
* @param {unknown} stdout
* @param {Record<string, unknown>} 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<string, unknown>} ctx
* @param {unknown} stdout
* @param {Record<string, unknown>} 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<string, unknown>} 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<string, unknown>} 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) {
config =
typeof bareAgentApplyLiveModel === 'function'
? bareAgentApplyLiveModel(config, m)
: Object.assign(config, {
qvac_model:
bareAgentResolveBackend(config) === 'qvac' ? m : config.qvac_model,
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)
}
if (runOpts && runOpts.modelOverride) {
const m = String(runOpts.modelOverride).trim()
if (m) {
config =
typeof bareAgentApplyLiveModel === 'function'
? bareAgentApplyLiveModel(config, m)
: Object.assign(config, {
qvac_model:
bareAgentResolveBackend(config) === 'qvac' ? m : config.qvac_model,
model: m
})
try {
await bareAgentSaveConfig(ctx, paths, config)
} catch {
/* persist best-effort so the next ask uses the same model */
}
}
}
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<string, string>} */ (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\nSession 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\nSessionStart 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\nExtra 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<string, string>} */ (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\nPLAN 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\nSession todos\n' +
sum.text +
'\n(open=' +
String(sum.open) +
' completed=' +
String(sum.completed) +
')\n'
}
} catch {
/* optional */
}
}
// Catalog last so small local models see callable tools after skills/notes.
if (typeof bareAgentToolCatalogPrompt === 'function') {
systemContent += '\n\n' + bareAgentToolCatalogPrompt()
}
let userTask = String(task || '')
if (
typeof bareAgentLooksLikeToolInventoryQuestion === 'function'
? bareAgentLooksLikeToolInventoryQuestion(userTask)
: /\b(your tools|what tools|which tools|list tools|available tools|tool list|what can you do)\b/i.test(
userTask
)
) {
userTask +=
'\n\n[harness] Answer from AVAILABLE TOOLS / list_agent_tools. List every callable tool name. Do not list skills or /bin names as tools.'
}
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<string, string>} */ (ctx.env)
: {}
const agentVerboseEnv = String(envBag.BARE_OS_AGENT_VERBOSE || '')
.trim()
.toLowerCase()
const agentVerboseForced =
agentVerboseEnv === '1' ||
agentVerboseEnv === 'true' ||
agentVerboseEnv === 'yes'
/** @type {{ current: Record<string, unknown> }} */
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<typeof bareAgentReasoningSettings>} 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()
appendProgress(
'tools_attached n=' +
String(tools.length) +
(typeof bareAgentToolCatalog === 'function'
? ' catalog=' + String(bareAgentToolCatalog(tools).length)
: '')
)
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<string, unknown>} */
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.qvac_model || configRef.current.model || ''
) +
EDIT_ANSI_RESET +
'\n'
)
}
let assistantContent = ''
/** @type {Map<number, { id: string, name: string, args: string }>} */
const toolAcc = new Map()
/** @type {unknown} */
let usageOut = null
let finishReason = ''
let recoveredFromText = false
function recoverToolsFromAssistantText() {
if (recoveredFromText) return
recoveredFromText = true
if (typeof bareAgentExtractToolCallsFromText !== 'function') return
const extracted = bareAgentExtractToolCallsFromText(assistantContent)
if (!toolAcc.size && extracted.length) {
for (const tc of extracted) bareAgentMergeToolCallDelta(toolAcc, tc)
appendProgress(
'recovered_tool_calls_from_text n=' + String(extracted.length)
)
}
if (extracted.length) {
assistantContent = bareAgentStripToolCallsFromText(assistantContent)
}
}
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<string, unknown>} 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
)
}
}
const qvacHistory =
typeof bareAgentSanitizeHistoryForQvac === 'function'
? bareAgentSanitizeHistoryForQvac(messages)
: messages
await ctx.bareOsQvacComplete({
history: qvacHistory,
tools: qvacToolsForTurn,
stream: Boolean(configRef.current.stream !== false),
captureThinking: !hideThink || reasoningSettings.enabled,
modelSrc,
toolsEnabled: true,
toolDialect:
typeof bareAgentQvacDetectToolDialect === 'function'
? bareAgentQvacDetectToolDialect(modelSrc)
: undefined,
ctxSize,
device: deviceOpts.device,
mainGpu: deviceOpts.mainGpu,
gpuLayers: deviceOpts.gpuLayers,
signal: masterAbort.signal,
onProgress: onLoadProgress,
onEvent: onCompletionEvent
})
clearStatusLine()
thinkSplit.flush()
sealThinkIfNeeded()
recoverToolsFromAssistantText()
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()
recoverToolsFromAssistantText()
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
}
let toolCallsArr = bareAgentFinalizeToolCalls(toolAcc)
if (!toolCallsArr.length && typeof bareAgentExtractToolCallsFromText === 'function') {
const extracted = bareAgentExtractToolCallsFromText(assistantContent)
if (extracted.length) {
for (const tc of extracted) bareAgentMergeToolCallDelta(toolAcc, tc)
toolCallsArr = bareAgentFinalizeToolCalls(toolAcc)
assistantContent = bareAgentStripToolCallsFromText(assistantContent)
appendProgress(
'recovered_tool_calls_from_text n=' + String(toolCallsArr.length)
)
}
}
const hasTools = toolCallsArr.length > 0
if (!hasTools && finishReason === 'tool_calls') {
appendProgress(
'warning tool_calls_finish_without_tool_deltas provider=' +
providerNow
)
}
/** @type {Record<string, unknown>} */
const assistantMsg = {
role: 'assistant',
content: assistantContent || '',
tool_calls: hasTools ? toolCallsArr : undefined
}
messages.push(assistantMsg)
if (
usageOut &&
typeof usageOut === 'object' &&
agentVerbose(reasoningSettings)
) {
const u = /** @type {Record<string, unknown>} */ (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 */
}
}
}
}