Files
bare-operating-system/packages/bare-os-seeder/kernel/bin/agent
T
2026-08-18 15:51:13 -04:00

15587 lines
490 KiB
Plaintext
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.
/* BARE_OS_BIN_API 1.0.0 — bump when staged /bin script semantics change (see developer guide). */
/** Shared helpers for drive-resident /bin scripts (prepended before each command). */
function bareStdin(ctx) {
return typeof ctx.shellStdin === 'string' ? ctx.shellStdin : ''
}
/** @param {number} mode @param {'file' | 'directory' | 'symlink'} type */
function bareFormatModeString(mode, type) {
const typeChar = type === 'directory' ? 'd' : type === 'symlink' ? 'l' : '-'
const perm = mode & 0o777
const r = (bit) => (perm & bit ? 'r' : '-')
const w = (bit) => (perm & bit ? 'w' : '-')
const x = (bit) => (perm & bit ? 'x' : '-')
return (
typeChar +
r(0o400) +
w(0o200) +
x(0o100) +
r(0o040) +
w(0o020) +
x(0o010) +
r(0o004) +
w(0o002) +
x(0o001)
)
}
/** @param {number} mtimeMs @param {number} [nowMs] */
function bareFormatLsMtime(mtimeMs, nowMs) {
const now = nowMs != null ? nowMs : Date.now()
const d = new Date(mtimeMs)
const months = [
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec'
]
const mon = months[d.getMonth()]
const day = String(d.getDate()).padStart(2, ' ')
const sixMo = 180 * 24 * 3600 * 1000
if (Math.abs(now - mtimeMs) > sixMo) {
const yr = String(d.getFullYear()).padStart(4, ' ')
return mon + ' ' + day + ' ' + yr
}
const hh = String(d.getHours()).padStart(2, '0')
const mm = String(d.getMinutes()).padStart(2, '0')
return mon + ' ' + day + ' ' + hh + ':' + mm
}
/** @param {number} size */
function barePosixBlocks(size) {
return Math.ceil(Number(size) / 512) || 0
}
/**
* Raw stdout for NUL/binary when **`process.stdout.write`** is missing.
* If **`ctx.bareOsBinWrite(Uint8Array|string)`** is set (tests / host), use it.
* @param {Record<string, unknown>} ctx
* @param {string | Uint8Array} chunk
* @returns {boolean}
*/
function bareOsEmitRaw(ctx, chunk) {
if (typeof ctx.bareOsBinWrite === 'function') {
const b4 = ctx.b4a
const u8 =
typeof chunk === 'string'
? b4 && typeof b4.from === 'function'
? b4.from(chunk)
: new TextEncoder().encode(chunk)
: chunk
ctx.bareOsBinWrite(u8 instanceof Uint8Array ? u8 : new Uint8Array(u8))
return true
}
const w = globalThis.process?.stdout?.write
if (typeof w === 'function') {
w.call(globalThis.process.stdout, chunk)
return true
}
return false
}
/** Session env map (`vfs.env`, then `ctx.env`). Never throws. */
function bareOsEnv(ctx) {
const v = ctx && ctx.vfs && ctx.vfs.env
if (v && typeof v === 'object') return v
const e = ctx && ctx.env
if (e && typeof e === 'object') return e
return {}
}
/**
* Strict POSIX-ish decimal integer (no octal, no exponent, no empty).
* @param {unknown} s
* @returns {number}
*/
function bareOsParseDecInt(s) {
const t = String(s == null ? '' : s).trim()
if (!/^[+-]?(?:0|[1-9][0-9]*)$/.test(t)) return NaN
const n = Number.parseInt(t, 10)
return Number.isSafeInteger(n) ? n : NaN
}
/** @param {unknown} s */
function bareOsParseNonNegInt(s) {
const n = bareOsParseDecInt(s)
return n >= 0 ? n : NaN
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} name
* @param {number} fallback
* @param {number} [min]
* @param {number} [max]
*/
function bareOsEnvInt(ctx, name, fallback, min, max) {
const raw = bareOsEnv(ctx)[name]
if (raw == null || raw === '') return fallback
const n = Number.parseInt(String(raw), 10)
if (!Number.isFinite(n)) return fallback
let v = n
if (min != null && v < min) v = min
if (max != null && v > max) v = max
return v
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} msg
* @param {number} [code]
*/
function bareOsFail(ctx, msg, code) {
if (msg) ctx.console.error(msg)
ctx.exitCode = code == null ? 1 : code
}
/** @param {unknown} e */
function bareOsIsNotFoundErr(e) {
const code = e && typeof e === 'object' ? e.code : ''
if (code === 'ENOENT') return true
const msg = String((e && e.message) || e || '')
return /ENOENT|No such file|not found/i.test(msg)
}
/**
* @param {Record<string, unknown>} ctx
* @param {unknown} buf
* @returns {Uint8Array}
*/
function bareOsToU8(ctx, buf) {
if (!buf) return new Uint8Array(0)
if (buf instanceof Uint8Array) return buf
if (ctx && ctx.b4a && typeof ctx.b4a.from === 'function') return ctx.b4a.from(buf)
return new Uint8Array(buf)
}
/** @param {string} dir @param {string} name */
function bareOsJoinPath(dir, name) {
const d = String(dir || '').replace(/\/+$/, '')
const n = String(name || '').replace(/^\/+/, '')
if (!d || d === '/') return '/' + n
return d + '/' + n
}
/** @param {string} p */
function bareOsBaseName(p) {
const t = String(p || '').replace(/\/+$/, '')
if (!t || t === '/') return t === '/' ? '/' : ''
const i = t.lastIndexOf('/')
return i < 0 ? t : t.slice(i + 1) || t
}
/** @param {string} p */
function bareOsParentDir(p) {
const t = String(p || '').replace(/\/+$/, '') || '/'
if (t === '/') return '/'
const i = t.lastIndexOf('/')
return i <= 0 ? '/' : t.slice(0, i) || '/'
}
/** @param {string} p */
function bareOsNormPath(p) {
return String(p || '').replace(/\/+$/, '') || '/'
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} p
*/
function bareOsResolvePath(ctx, p) {
if (ctx && ctx.vfs && typeof ctx.vfs.resolveLogical === 'function') {
try {
return String(ctx.vfs.resolveLogical(p) || p)
} catch {
/* fall through */
}
}
return String(p || '')
}
/**
* True when dest is src or lives under src (self-copy / self-move).
* @param {Record<string, unknown>} ctx
* @param {string} src
* @param {string} dest
*/
function bareOsDestInsideSrc(ctx, src, dest) {
const s = bareOsNormPath(bareOsResolvePath(ctx, src))
const d = bareOsNormPath(bareOsResolvePath(ctx, dest))
if (s === d) return true
if (s === '/') return d !== '/'
return d === s || d.startsWith(s + '/')
}
const BARE_OS_B64_ALPH =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
/** @param {Uint8Array} u8 */
function bareOsB64Encode(u8) {
let out = ''
let i = 0
for (; i + 2 < u8.length; i += 3) {
const n = (u8[i] << 16) | (u8[i + 1] << 8) | u8[i + 2]
out +=
BARE_OS_B64_ALPH[(n >> 18) & 63] +
BARE_OS_B64_ALPH[(n >> 12) & 63] +
BARE_OS_B64_ALPH[(n >> 6) & 63] +
BARE_OS_B64_ALPH[n & 63]
}
const rest = u8.length - i
if (rest === 1) {
const n = u8[i] << 16
out += BARE_OS_B64_ALPH[(n >> 18) & 63] + BARE_OS_B64_ALPH[(n >> 12) & 63] + '=='
} else if (rest === 2) {
const n = (u8[i] << 16) | (u8[i + 1] << 8)
out +=
BARE_OS_B64_ALPH[(n >> 18) & 63] +
BARE_OS_B64_ALPH[(n >> 12) & 63] +
BARE_OS_B64_ALPH[(n >> 6) & 63] +
'='
}
return out
}
/**
* RFC 4648 Base64 decode (also accepts URL-safe alphabet). Rejects junk.
* @param {string} s
* @returns {Uint8Array}
*/
function bareOsB64Decode(s) {
const t = String(s).replace(/\s+/g, '')
if (!t) return new Uint8Array(0)
if (t.length % 4 === 1) throw new Error('invalid base64 length')
let pad = 0
if (t.endsWith('==')) pad = 2
else if (t.endsWith('=')) pad = 1
const body = pad ? t.slice(0, t.length - pad) : t
const bytes = []
let buf = 0
let bits = 0
for (let i = 0; i < body.length; i++) {
const c = body[i]
let v = BARE_OS_B64_ALPH.indexOf(c)
if (v < 0) {
if (c === '-') v = 62
else if (c === '_') v = 63
else throw new Error('invalid base64 character')
}
buf = (buf << 6) | v
bits += 6
if (bits >= 8) {
bits -= 8
bytes.push((buf >> bits) & 255)
}
}
if (pad) {
const want = Math.floor((body.length * 6) / 8)
if (bytes.length > want) bytes.length = want
}
return new Uint8Array(bytes)
}
/**
* @param {string} s
* @returns {Uint8Array}
*/
function bareOsHexDecode(s) {
const t = String(s).replace(/\s+/g, '')
if (t.length % 2 !== 0) throw new Error('odd hex length')
const out = new Uint8Array(t.length / 2)
for (let i = 0; i < out.length; i++) {
const pair = t.slice(i * 2, i * 2 + 2)
if (!/^[0-9a-fA-F]{2}$/.test(pair)) throw new Error('invalid hex')
out[i] = Number.parseInt(pair, 16)
}
return out
}
/** @param {Uint8Array} u8 */
function bareOsHexEncode(u8) {
let s = ''
for (let i = 0; i < u8.length; i++) s += u8[i].toString(16).padStart(2, '0')
return s
}
/** ANSI helpers for /bin/edit (preamble; no import in src). */
const EDIT_ANSI_RESET = '\x1b[0m'
/**
* @param {Record<string, unknown>} [ctx]
* @returns {import('stream').Writable | undefined}
*/
function bareEditResolveStdout(ctx) {
if (!ctx || typeof ctx !== 'object') return globalThis.process?.stdout
const c = /** @type {{ replStdout?: unknown, stdout?: unknown }} */ (ctx)
const out = c.replStdout || c.stdout || globalThis.process?.stdout
return /** @type {import('stream').Writable | undefined} */ (out)
}
/**
* @param {Record<string, unknown>} [ctx]
*/
function bareEditUseColor(ctx) {
const env =
ctx && typeof ctx === 'object' && ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
if (env.NO_COLOR != null && String(env.NO_COLOR) !== '') return false
const out = bareEditResolveStdout(ctx)
return Boolean(out && /** @type {{ isTTY?: boolean }} */ (out).isTTY)
}
/**
* @param {'keyword'|'string'|'comment'|'number'|'status'|'inverse'|'dim'} cls
* @param {boolean} on
*/
function bareEditSgr(cls, on) {
if (!on) return ''
switch (cls) {
case 'keyword':
return '\x1b[36m'
case 'string':
return '\x1b[32m'
case 'comment':
return '\x1b[90m'
case 'number':
return '\x1b[33m'
case 'status':
return '\x1b[44m\x1b[97m'
case 'inverse':
return '\x1b[7m'
case 'dim':
return '\x1b[2m'
default:
return ''
}
}
/**
* @param {string} fullLine
* @param {Array<{ start: number, end: number, cls: string }>} spans offsets into fullLine
* @param {number} visStart first column (0-based)
* @param {number} maxLen max code units to show
* @param {boolean} useColor
*/
function bareEditPaintLineWindow(fullLine, spans, visStart, maxLen, useColor) {
const slice = fullLine.slice(visStart, visStart + maxLen)
if (!useColor) return slice
const n = slice.length
const relSpans = spans
.map((s) => ({
start: Math.max(0, s.start - visStart),
end: Math.min(n, s.end - visStart),
cls: s.cls
}))
.filter((s) => s.end > 0 && s.start < n)
.sort((a, b) => a.start - b.start)
let out = ''
let pos = 0
for (const sp of relSpans) {
if (sp.start > pos) out += slice.slice(pos, sp.start)
out +=
bareEditSgr(/** @type {'keyword'} */ (sp.cls), true) +
slice.slice(sp.start, sp.end) +
EDIT_ANSI_RESET
pos = sp.end
}
if (pos < n) out += slice.slice(pos)
return out
}
/**
* Move cursor (1-based row/col, DEC origin). Clamp to sane bounds for escape parsing.
* @param {number} row1
* @param {number} col1
*/
function bareEditCup(row1, col1) {
const r = Math.max(1, Math.min(Math.floor(row1), 9999))
const c = Math.max(1, Math.min(Math.floor(col1), 9999))
return '\x1b[' + r + ';' + c + 'H'
}
/**
* @param {Record<string, unknown>} ctx
* @param {import('stream').Writable} stdout
* @param {string} s
*/
function bareEditWrite(ctx, stdout, s) {
if (!stdout || typeof stdout.write !== 'function') return
try {
stdout.write(s)
} catch {
try {
ctx.console?.error?.('edit: stdout write failed')
} catch {
/* ignore */
}
}
}
/** TTY key parsing for /bin/edit: consume one logical key from a mutable byte queue. */
/**
* @param {number} b1
*/
function bareEditUtf8TrailCount(b1) {
if (b1 >= 0xc0 && b1 < 0xe0) return 1
if (b1 >= 0xe0 && b1 < 0xf0) return 2
if (b1 >= 0xf0) return 3
return 0
}
/**
* @param {number[]} bytes
*/
function bareEditUtf8DecodeKey(bytes) {
try {
const u = new Uint8Array(bytes)
if (typeof TextDecoder !== 'undefined') {
return new TextDecoder('utf-8', { fatal: false }).decode(u)
}
} catch {
/* fall through */
}
let s = ''
for (const b of bytes) s += String.fromCharCode(b)
return s
}
/**
* @param {string} seq CSI payload after ESC [, including final byte (e.g. "A", "1;5A", "3~")
*/
function bareEditCsiToEvent(seq) {
if (seq === '3~') return { type: 'ctrl', code: 'delete' }
if (seq === '5~') return { type: 'nav', key: 'pageup' }
if (seq === '6~') return { type: 'nav', key: 'pagedown' }
const last = seq.charAt(seq.length - 1)
if (last === '~') {
if (seq === '1~' || seq === '7~') return { type: 'nav', key: 'home' }
if (seq === '4~' || seq === '8~') return { type: 'nav', key: 'end' }
const tildeNum = /^(\d+)~$/.exec(seq)
if (tildeNum) {
const n = parseInt(tildeNum[1], 10)
const fnMap = {
11: 1,
12: 2,
13: 3,
14: 4,
15: 5,
17: 6,
18: 7,
19: 8,
20: 9,
21: 10,
23: 11,
24: 12
}
if (fnMap[n] != null) return { type: 'fn', n: fnMap[n] }
}
return { type: 'unknown' }
}
if (last === 'Z') return { type: 'nav', key: 'stab' }
if (last === 'A' || last === 'B' || last === 'C' || last === 'D') {
const map = { A: 'up', B: 'down', C: 'right', D: 'left' }
return { type: 'nav', key: map[last] }
}
if (last === 'H') return { type: 'nav', key: 'home' }
if (last === 'F') return { type: 'nav', key: 'end' }
return { type: 'unknown' }
}
/**
* @param {number} b3 byte after ESC O
*/
function bareEditSs3ToEvent(b3) {
if (b3 === 72) return { type: 'nav', key: 'home' }
if (b3 === 70) return { type: 'nav', key: 'end' }
if (b3 === 65) return { type: 'nav', key: 'up' }
if (b3 === 66) return { type: 'nav', key: 'down' }
if (b3 === 67) return { type: 'nav', key: 'right' }
if (b3 === 68) return { type: 'nav', key: 'left' }
if (b3 === 80) return { type: 'fn', n: 1 }
if (b3 === 81) return { type: 'fn', n: 2 }
if (b3 === 82) return { type: 'fn', n: 3 }
if (b3 === 83) return { type: 'fn', n: 4 }
return { type: 'unknown' }
}
/** ESC [ 200 ~ */
const BARE_EDIT_BRACKET_PASTE_START = [27, 91, 50, 48, 48, 126]
/** ESC [ 201 ~ */
const BARE_EDIT_BRACKET_PASTE_END = [27, 91, 50, 48, 49, 126]
/**
* @param {number[]} q
* @param {number[]} prefix
*/
function bareEditStartsWithBytes(q, prefix) {
if (q.length < prefix.length) return false
for (let i = 0; i < prefix.length; i++) {
if (q[i] !== prefix[i]) return false
}
return true
}
/**
* True if q could still become BARE_EDIT_BRACKET_PASTE_START with more bytes.
* @param {number[]} q
*/
function bareEditCouldBeBracketPastePrefix(q) {
const pre = BARE_EDIT_BRACKET_PASTE_START
const n = Math.min(q.length, pre.length)
for (let i = 0; i < n; i++) {
if (q[i] !== pre[i]) return false
}
return true
}
/**
* @param {number[]} bytes
*/
function bareEditDecodePasteInner(bytes) {
try {
if (typeof TextDecoder !== 'undefined') {
return new TextDecoder('utf-8', { fatal: false }).decode(
new Uint8Array(bytes)
)
}
} catch {
/* fall through */
}
let s = ''
for (const b of bytes) s += String.fromCharCode(b)
return s
}
/**
* If queue begins with ESC [ 200 ~ but no closing ESC [ 201 ~ (stdin closed), return inner bytes as text.
* @param {number[]} q
* @returns {string|undefined}
*/
function bareEditFinalizeBracketedPasteOnEof(q) {
const SL = BARE_EDIT_BRACKET_PASTE_START.length
if (q.length >= SL && bareEditStartsWithBytes(q, BARE_EDIT_BRACKET_PASTE_START)) {
const inner = q.slice(SL)
q.length = 0
return bareEditDecodePasteInner(inner)
}
return undefined
}
/**
* xterm bracketed paste: ESC [ 200 ~ … ESC [ 201 ~
* @param {number[]} q mutable queue
* @returns {string|null|undefined} string if consumed; null if incomplete; undefined if not bracketed paste at front
*/
function bareEditTryConsumeBracketedPaste(q) {
const SL = BARE_EDIT_BRACKET_PASTE_START.length
const EL = BARE_EDIT_BRACKET_PASTE_END.length
if (!q.length) return undefined
if (q.length < SL) {
return bareEditCouldBeBracketPastePrefix(q) ? null : undefined
}
if (!bareEditStartsWithBytes(q, BARE_EDIT_BRACKET_PASTE_START)) {
return undefined
}
for (let i = SL; i <= q.length - EL; i++) {
let ok = true
for (let j = 0; j < EL; j++) {
if (q[i + j] !== BARE_EDIT_BRACKET_PASTE_END[j]) {
ok = false
break
}
}
if (ok) {
const inner = q.slice(SL, i)
q.splice(0, i + EL)
return bareEditDecodePasteInner(inner)
}
}
return null
}
/**
* @param {number[]} q mutable queue (front = index 0)
* @returns {Record<string, unknown> | null} null if more bytes needed
*/
function bareEditTryConsumeKey(q) {
if (!q.length) return null
const b1 = q[0]
if (b1 === 3) {
q.shift()
return { type: 'ctrl', code: 'interrupt' }
}
if (b1 === 8 || b1 === 127) {
q.shift()
return { type: 'ctrl', code: 'backspace' }
}
if (b1 === 13 || b1 === 10) {
q.shift()
return { type: 'key', ch: '\n' }
}
if (b1 === 9) {
q.shift()
return { type: 'key', ch: '\t' }
}
if (b1 === 27) {
if (q.length < 2) return null
const b2 = q[1]
if (b2 === 91) {
let i = 2
while (i < q.length) {
const b = q[i]
if (b >= 0x40 && b <= 0x7e) {
const seq = String.fromCharCode.apply(null, q.slice(2, i + 1))
q.splice(0, i + 1)
return bareEditCsiToEvent(seq)
}
i++
}
return null
}
if (b2 === 79) {
if (q.length < 3) return null
const b3 = q[2]
q.splice(0, 3)
return bareEditSs3ToEvent(b3)
}
q.splice(0, 2)
return { type: 'key', ch: String.fromCharCode(b2) }
}
if (b1 < 0x20) {
q.shift()
return { type: 'ctrl', code: b1 }
}
const need = bareEditUtf8TrailCount(b1)
if (q.length < 1 + need) return null
const chunk = q.splice(0, 1 + need)
return { type: 'key', ch: bareEditUtf8DecodeKey(chunk) }
}
/** Stdin chunk queue + async key reader for edit/chat TUIs (requires edit-key-parse.js first). */
/**
* @param {unknown} chunk
* @returns {number[]}
*/
function bareEditChunkBytes(chunk) {
if (chunk == null) return []
if (typeof chunk === 'string') {
const out = []
for (let i = 0; i < chunk.length; i++) out.push(chunk.charCodeAt(i) & 0xff)
return out
}
const len = /** @type {{ length: number, [k: number]: number }} */ (chunk).length
const out = []
for (let i = 0; i < len; i++) out.push(Number(chunk[i]) & 0xff)
return out
}
/**
* @param {{ nextByte: () => Promise<number|undefined>, dispose?: () => void, _keyq?: number[] }} reader
*/
async function bareEditReadKey(reader) {
reader._keyq = reader._keyq || []
const q = reader._keyq
for (;;) {
const pasteText = bareEditTryConsumeBracketedPaste(q)
if (pasteText !== undefined && pasteText !== null) {
return { type: 'paste', text: pasteText }
}
if (pasteText === null) {
const b = await reader.nextByte()
if (b === undefined) {
const eofPaste = bareEditFinalizeBracketedPasteOnEof(q)
if (eofPaste !== undefined) {
return { type: 'paste', text: eofPaste }
}
if (!q.length) return { type: 'eof' }
if (q.length === 1 && q[0] === 27) {
q.length = 0
return { type: 'key', ch: '\x1b' }
}
const lone = q.shift()
if (lone !== undefined && lone < 0x20) {
return { type: 'ctrl', code: lone }
}
if (lone !== undefined) {
return { type: 'key', ch: String.fromCharCode(lone) }
}
return { type: 'eof' }
}
q.push(b)
continue
}
const ev = bareEditTryConsumeKey(q)
if (ev) return ev
const b = await reader.nextByte()
if (b === undefined) {
const eofPaste = bareEditFinalizeBracketedPasteOnEof(q)
if (eofPaste !== undefined) {
return { type: 'paste', text: eofPaste }
}
if (!q.length) return { type: 'eof' }
if (q.length === 1 && q[0] === 27) {
q.length = 0
return { type: 'key', ch: '\x1b' }
}
const lone = q.shift()
if (lone !== undefined && lone < 0x20) {
return { type: 'ctrl', code: lone }
}
if (lone !== undefined) {
return { type: 'key', ch: String.fromCharCode(lone) }
}
return { type: 'eof' }
}
q.push(b)
}
}
/**
* @param {import('stream').Readable} stdin
*/
function bareEditCreateStdinReader(stdin) {
/** @type {number[]} */
const bytes = []
/** @type {(() => void)[]} */
const waiters = []
function drain() {
while (waiters.length && bytes.length) {
const w = waiters.shift()
if (w) w()
}
}
/** @param {unknown} chunk */
function onData(chunk) {
bytes.push(...bareEditChunkBytes(chunk))
drain()
}
stdin.on('data', onData)
return {
nextByte() {
if (bytes.length) return Promise.resolve(bytes.shift())
return new Promise((resolve) => {
waiters.push(() => resolve(bytes.shift()))
})
},
dispose() {
stdin.removeListener('data', onData)
}
}
}
/** Minimal AbortController for Bare/Pear when globalThis lacks it (drive-resident /bin preamble). */
function bareAgentEnsureAbortPolyfill() {
const g =
typeof globalThis !== 'undefined'
? globalThis
: typeof global !== 'undefined'
? global
: typeof self !== 'undefined'
? self
: /** @type {Record<string, unknown>} */ ({})
if (typeof g.AbortController === 'function') return
function BareAbortSignal() {
/** @type {boolean} */
this.aborted = false
/** @type {unknown} */
this.reason = undefined
/** @type {{ fn: () => void, once: boolean }[]} */
this._listeners = []
}
BareAbortSignal.prototype.addEventListener = function (type, fn, opts) {
if (type !== 'abort' || typeof fn !== 'function') return
const once = !!(opts && opts.once)
if (this.aborted) {
if (once) {
try {
fn.call(this)
} catch {
/* ignore */
}
}
return
}
this._listeners.push({ fn: /** @type {() => void} */ (fn), once })
}
BareAbortSignal.prototype.removeEventListener = function (type, fn) {
if (type !== 'abort' || typeof fn !== 'function') return
this._listeners = this._listeners.filter((x) => x.fn !== fn)
}
BareAbortSignal.prototype.throwIfAborted = function () {
if (!this.aborted) return
const DOMException = g.DOMException
if (typeof DOMException === 'function') {
throw new DOMException('Aborted', 'AbortError')
}
const e = new Error('Aborted')
e.name = 'AbortError'
throw e
}
function BareAbortController() {
this.signal = new BareAbortSignal()
}
BareAbortController.prototype.abort = function (reason) {
const s = this.signal
if (s.aborted) return
s.aborted = true
s.reason = reason
const list = s._listeners.slice()
s._listeners.length = 0
for (const x of list) {
try {
x.fn.call(s)
} catch {
/* ignore */
}
}
}
if (typeof globalThis !== 'undefined') {
globalThis.AbortController = BareAbortController
globalThis.AbortSignal = BareAbortSignal
} else {
g.AbortController = BareAbortController
g.AbortSignal = BareAbortSignal
}
}
bareAgentEnsureAbortPolyfill()
/** TextEncoder/TextDecoder when missing on globalThis (Bare/Pear guest eval). */
/**
* @param {Uint8Array} bytes
*/
function bareAgentUtf8Decode(bytes) {
let out = ''
let i = 0
const len = bytes.length
while (i < len) {
const b0 = bytes[i++]
if (b0 < 0x80) {
out += String.fromCharCode(b0)
continue
}
if ((b0 & 0xe0) === 0xc0) {
if (i >= len || (bytes[i] & 0xc0) !== 0x80) {
out += '\ufffd'
continue
}
const b1 = bytes[i++]
const cp = ((b0 & 0x1f) << 6) | (b1 & 0x3f)
if (cp < 0x80) out += '\ufffd'
else out += String.fromCharCode(cp)
continue
}
if ((b0 & 0xf0) === 0xe0) {
if (i + 1 >= len || (bytes[i] & 0xc0) !== 0x80 || (bytes[i + 1] & 0xc0) !== 0x80) {
out += '\ufffd'
continue
}
const b1 = bytes[i++]
const b2 = bytes[i++]
let cp = ((b0 & 0x0f) << 12) | ((b1 & 0x3f) << 6) | (b2 & 0x3f)
if (cp < 0x800 || (cp >= 0xd800 && cp <= 0xdfff)) out += '\ufffd'
else out += String.fromCharCode(cp)
continue
}
if ((b0 & 0xf8) === 0xf0) {
if (
i + 2 >= len ||
(bytes[i] & 0xc0) !== 0x80 ||
(bytes[i + 1] & 0xc0) !== 0x80 ||
(bytes[i + 2] & 0xc0) !== 0x80
) {
out += '\ufffd'
continue
}
const b1 = bytes[i++]
const b2 = bytes[i++]
const b3 = bytes[i++]
let cp =
((b0 & 0x07) << 18) |
((b1 & 0x3f) << 12) |
((b2 & 0x3f) << 6) |
(b3 & 0x3f)
if (cp < 0x10000 || cp > 0x10ffff) out += '\ufffd'
else {
cp -= 0x10000
out += String.fromCharCode(0xd800 + (cp >> 10), 0xdc00 + (cp & 0x3ff))
}
continue
}
out += '\ufffd'
}
return out
}
/**
* @param {string} str
*/
function bareAgentUtf8Encode(str) {
const out = []
for (let i = 0; i < str.length; i++) {
let c = str.charCodeAt(i)
if (c < 0x80) {
out.push(c)
} else if (c < 0x800) {
out.push(0xc0 | (c >> 6), 0x80 | (c & 0x3f))
} else if (c >= 0xd800 && c <= 0xdbff && i + 1 < str.length) {
const c2 = str.charCodeAt(i + 1)
if ((c2 & 0xfc00) === 0xdc00) {
i++
const cp = 0x10000 + ((c & 0x3ff) << 10) + (c2 & 0x3ff)
out.push(
0xf0 | (cp >> 18),
0x80 | ((cp >> 12) & 0x3f),
0x80 | ((cp >> 6) & 0x3f),
0x80 | (cp & 0x3f)
)
} else {
out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f))
}
} else if (c < 0xd800 || c >= 0xe000) {
out.push(0xe0 | (c >> 12), 0x80 | ((c >> 6) & 0x3f), 0x80 | (c & 0x3f))
}
}
return new Uint8Array(out)
}
function bareAgentEnsureTextCodecPolyfill() {
const g =
typeof globalThis !== 'undefined'
? globalThis
: typeof global !== 'undefined'
? global
: typeof self !== 'undefined'
? self
: /** @type {Record<string, unknown>} */ ({})
if (typeof g.TextDecoder !== 'function') {
function BareTextDecoder() {
/** @type {'utf-8'} */
this.encoding = 'utf-8'
}
BareTextDecoder.prototype.decode = function (input, options) {
let u8
if (input instanceof Uint8Array) u8 = input
else if (typeof ArrayBuffer !== 'undefined' && input instanceof ArrayBuffer)
u8 = new Uint8Array(input)
else if (input != null && typeof input === 'object' && 'length' in input) {
u8 = new Uint8Array(/** @type {ArrayLike<number>} */ (input))
} else if (input == null || input === undefined) {
return ''
} else {
return ''
}
const stream = !!(options && options.stream)
void stream
return bareAgentUtf8Decode(u8)
}
if (typeof globalThis !== 'undefined') globalThis.TextDecoder = BareTextDecoder
else g.TextDecoder = BareTextDecoder
}
if (typeof g.TextEncoder !== 'function') {
function BareTextEncoder() {
this.encoding = 'utf-8'
}
BareTextEncoder.prototype.encode = function (input) {
return bareAgentUtf8Encode(String(input == null ? '' : input))
}
if (typeof globalThis !== 'undefined') globalThis.TextEncoder = BareTextEncoder
else g.TextEncoder = BareTextEncoder
}
}
bareAgentEnsureTextCodecPolyfill()
/** Shared helpers for /bin/agent tools (preamble for agent bundle). */
/**
* Read-only base system (kernel / system Hyperdrive + virtual fs).
* Everything else is writable by default (denylist, not allowlist).
* @type {readonly string[]}
*/
var BARE_AGENT_MUTATE_DENY_PREFIXES = Object.freeze([
'/bin',
'/etc',
'/boot',
'/lib',
'/usr',
'/share',
'/proc',
'/dev',
'/sys',
'/run'
])
/**
* Seed list for diagnostic snapshots. Live reads allow any /proc path.
* @type {readonly string[]}
*/
var BARE_AGENT_PROC_READ_ALLOWLIST = Object.freeze([
'/proc/bare_os/metrics_live.json',
'/proc/bare_os/features',
'/proc/bare_os/features.json',
'/proc/bare_os/swarm.json',
'/proc/bare_os/capabilities.json',
'/proc/bare_os/swarm_replication_status.json',
'/proc/bare_os/swarm_relay_status.json',
'/proc/bare_os/swarm_datagrams_status.json',
'/proc/bare_os/swarm_connection_manager_status.json',
'/proc/bare_os/swarm_key_broker_status.json',
'/proc/bare_os/swarm_holepunch_status.json',
'/proc/bare_os/swarm_datagram_replication_status.json',
'/proc/bare_os/swarm_status.json'
])
/**
* @param {string} absPath
* @returns {string}
*/
function bareAgentNormalizeAbsPath(absPath) {
const p = String(absPath || '').replace(/\\/g, '/')
if (!p.startsWith('/') || p.includes('..')) return ''
if (p.length > 1) return p.replace(/\/+$/, '')
return p
}
/**
* @param {string} absPath
* @param {unknown} prefixes
* @returns {boolean}
*/
function bareAgentPrefixDenied(absPath, prefixes) {
const p = bareAgentNormalizeAbsPath(absPath)
if (!p) return true
const list =
Array.isArray(prefixes) && prefixes.length
? prefixes
: BARE_AGENT_MUTATE_DENY_PREFIXES
for (let i = 0; i < list.length; i++) {
const pref = String(list[i] || '').replace(/\/+$/, '')
if (!pref) continue
if (p === pref || p.startsWith(pref + '/')) return true
}
return false
}
/**
* Any absolute guest path is readable (denylist-empty).
* @param {string} absPath
*/
function bareAgentPathAllowedRead(absPath) {
return Boolean(bareAgentNormalizeAbsPath(absPath))
}
/**
* Writes/renames/deletes: whole VFS except the read-only base system.
* @param {string} absPath
* @param {unknown} [prefixes]
*/
function bareAgentPathAllowedMutate(absPath, prefixes) {
const p = bareAgentNormalizeAbsPath(absPath)
if (!p || p === '/') return false
return !bareAgentPrefixDenied(p, prefixes)
}
/**
* @param {unknown} statObj
* @param {string} path
*/
function bareAgentSerializeStat(statObj, path) {
if (!statObj || typeof statObj !== 'object')
return { path, error: 'no_stat' }
const s = /** @type {Record<string, unknown>} */ (statObj)
/** @type {'file' | 'dir' | 'symlink' | 'other'} */
let kind = 'other'
try {
if (typeof s.isDirectory === 'function' && s.isDirectory()) kind = 'dir'
else if (typeof s.isSymbolicLink === 'function' && s.isSymbolicLink()) kind = 'symlink'
else if (typeof s.isFile === 'function' && s.isFile()) kind = 'file'
else if (typeof s.mode === 'number') {
const M = Number(s.mode)
if ((M & 0o170000) === 0o040000) kind = 'dir'
else if ((M & 0o170000) === 0o120000) kind = 'symlink'
else if ((M & 0o170000) === 0o100000) kind = 'file'
}
} catch {
/* ignore */
}
/** @type {Record<string, unknown>} */
const out = {
path,
kind,
size: typeof s.size === 'number' ? s.size : undefined,
mode: typeof s.mode === 'number' ? s.mode : undefined,
mtimeMs: typeof s.mtimeMs === 'number' ? s.mtimeMs : undefined,
uid: typeof s.uid === 'number' ? s.uid : undefined,
gid: typeof s.gid === 'number' ? s.gid : undefined
}
if (typeof s.target === 'string') out.target = s.target
return out
}
/**
* @param {string} text
* @param {number} maxChars
*/
function bareAgentTruncateChars(text, maxChars) {
const t = String(text || '')
const n = Math.floor(maxChars)
if (!Number.isFinite(n) || n <= 0) return ''
if (t.length <= n) return t
return t.slice(0, n) + '\n… truncated'
}
/**
* @param {Record<string, unknown>} page
* @param {number} maxChars
*/
function bareAgentManExtractPageSlice(page, maxChars) {
if (!page || typeof page !== 'object')
return { error: 'bad_page' }
const m = Math.min(Math.max(Math.floor(maxChars) || 8000, 500), 64_000)
const name = typeof page.name === 'string' ? page.name : ''
const section = typeof page.section === 'number' ? page.section : 0
const title = typeof page.title === 'string' ? page.title : ''
const synopsis = Array.isArray(page.synopsis)
? page.synopsis.map((x) => String(x)).join('\n')
: ''
const description = bareAgentTruncateChars(
typeof page.description === 'string' ? page.description : '',
Math.floor(m * 0.55)
)
let opts = ''
if (Array.isArray(page.options)) {
const lines = []
for (const o of page.options) {
if (!o || typeof o !== 'object') continue
const fl = typeof o.flag === 'string' ? o.flag : ''
const me = typeof o.meaning === 'string' ? o.meaning : ''
if (fl || me) lines.push(fl + (fl && me ? ' — ' : '') + me)
}
opts = bareAgentTruncateChars(lines.join('\n'), Math.floor(m * 0.35))
}
const blob =
name +
'(' +
section +
') — ' +
title +
'\n\nSYNOPSIS\n' +
synopsis +
'\n\nDESCRIPTION\n' +
description +
(opts ? '\n\nOPTIONS\n' + opts : '')
return {
name,
section,
title,
synopsis,
description,
options_text: opts || undefined,
text: bareAgentTruncateChars(blob, m)
}
}
/**
* Same semantics as `man -k`: substring match on indexed keywords (merged DB).
* @param {unknown} db
* @param {string} needle
* @param {number} maxHits
*/
function bareAgentManAproposHits(db, needle, maxHits) {
const n = String(needle || '').toLowerCase()
const max = Math.min(Math.max(Math.floor(maxHits) || 40, 1), 200)
if (!n || !db || typeof db !== 'object')
return /** @type {{ lines: string[], truncated: boolean }} */ ({
lines: [],
truncated: false
})
const d = /** @type {Record<string, unknown>} */ (db)
const pages = Array.isArray(d.pages) ? d.pages : []
const apropos = Array.isArray(d.apropos) ? d.apropos : []
const seen = new Set()
/** @type {string[]} */
const lines = []
let truncated = false
for (const row of apropos) {
if (!row || typeof row !== 'object') continue
const kw = typeof row.kw === 'string' ? row.kw : ''
if (!kw.includes(n)) continue
const idx = row.pageRef
if (typeof idx !== 'number' || !pages[idx]) continue
if (seen.has(idx)) continue
seen.add(idx)
const p = /** @type {Record<string, unknown>} */ (pages[idx])
const name = typeof p.name === 'string' ? p.name : ''
const sec = typeof p.section === 'number' ? p.section : 0
const title = typeof p.title === 'string' ? p.title : ''
lines.push(name + '(' + sec + ') - ' + title)
if (lines.length >= max) {
truncated = true
break
}
}
lines.sort()
return { lines, truncated }
}
/**
* @param {Record<string, unknown>} ctx
* @param {unknown} vfs
* @param {{ db: unknown | null }} cacheRef
* @returns {Promise<unknown | null>}
*/
async function bareAgentManEnsureDbLoaded(ctx, vfs, cacheRef) {
if (cacheRef.db) return cacheRef.db
if (!vfs || typeof vfs.readFile !== 'function') return null
try {
const buf = await vfs.readFile('/share/man/man.json')
if (!buf || !buf.length) return null
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
const parsed = JSON.parse(t)
cacheRef.db = parsed
return parsed
} catch {
return null
}
}
/**
* Resolve one manual page from merged DB (like `man [[section] name]`).
* @param {unknown} db
* @param {string} topic
* @param {number | null} sectionExplicit
*/
function bareAgentManResolvePage(db, topic, sectionExplicit) {
const name = String(topic || '').toLowerCase()
if (!name || !db || typeof db !== 'object')
return { error: 'not_found' }
const d = /** @type {Record<string, unknown>} */ (db)
const index = d.index
if (!index || typeof index !== 'object') return { error: 'not_found' }
const idx = /** @type {Record<string, unknown>} */ (index)[name]
if (typeof idx !== 'number') return { error: 'not_found' }
const pages = Array.isArray(d.pages) ? d.pages : []
const page = pages[idx]
if (!page || typeof page !== 'object') return { error: 'not_found' }
const sec = typeof page.section === 'number' ? page.section : 0
if (sectionExplicit !== null && sectionExplicit !== sec) {
return { error: 'wrong_section', foundSection: sec }
}
return { page: /** @type {Record<string, unknown>} */ (page) }
}
/**
* @param {string} absPath
* @returns {boolean}
*/
function bareAgentProcReadPathAllowed(absPath) {
const p = bareAgentNormalizeAbsPath(absPath)
return Boolean(p && (p === '/proc' || p.startsWith('/proc/')))
}
/** ~/.agent paths, config, history trim, man digest (preamble for /bin/agent). */
/**
* @param {Record<string, unknown>} ctx
* @returns {string}
*/
function bareAgentResolveHome(ctx) {
const env =
ctx && typeof ctx === 'object' && ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
const vfsHome =
ctx &&
typeof ctx === 'object' &&
ctx.vfs &&
typeof ctx.vfs === 'object' &&
typeof /** @type {{ home?: string }} */ (ctx.vfs).home === 'string'
? String(/** @type {{ home?: string }} */ (ctx.vfs).home)
: ''
const h = env.HOME || vfsHome || '/home/guest'
return h.replace(/\/+$/, '') || '/home/guest'
}
/**
* @param {string} home
*/
function bareAgentPaths(home) {
const base = home + '/.agent'
return {
dir: base,
config: base + '/config.json',
history: base + '/history.json',
progress: base + '/progress.txt',
instructions: base + '/instructions.md',
context: base + '/context.md',
compact: base + '/compact.md',
cmdOut: base + '/last_command_out.txt',
workspace: base + '/workspace',
workspaceMemory: base + '/workspace/memory',
workspaceSkills: base + '/workspace/skills',
skillsGlobal: base + '/skills',
todos: base + '/todos.json',
plan: base + '/plan.md',
hooks: base + '/hooks',
ask: base + '/ask.json',
edits: base + '/edits.json',
lastCwd: base + '/last_cwd'
}
}
function bareAgentDefaultConfig() {
return {
backend: 'qvac',
rest_base_url: 'https://api.groq.com/openai/v1',
rest_api_key: '',
model: 'QWEN3_1_7B_INST_Q4',
qvac_model: 'QWEN3_1_7B_INST_Q4',
qvac_profile: 'recommended',
qvac_ctx_size: 0,
qvac_device: '',
qvac_main_gpu: 'auto',
qvac_gpu_layers: -1,
max_tokens: 4096,
temperature: 0.7,
provider: 'qvac',
max_iterations: 64,
stream: true,
tool_parallelism: 1,
request_timeout_ms: 120000,
extra_headers: /** @type {Record<string, string>} */ ({}),
access_policy: 'full',
allow_delete: true,
require_confirm_token: '',
owner_name: '',
agent_label: '',
show_reasoning: false,
reasoning_mode: 'off',
reasoning_max_chars: 4000,
reasoning_include_tools: true,
allow_bridge_mutations: true,
allow_host_notifications: true,
allow_host_actions: true,
emergency_stop_mutations: false,
autonomous_mode_enabled: true,
autonomous_max_runtime_ms: 1800000,
autonomous_completion_required_checks: [],
autonomous_allow_paths: ['*'],
autonomous_deny_ops: [],
command_deny: [],
mutate_deny_prefixes: [
'/bin',
'/etc',
'/boot',
'/lib',
'/usr',
'/share',
'/proc',
'/dev',
'/sys',
'/run'
],
autonomous_active: false,
autonomous_started_at_ms: 0,
autonomous_stop_requested: false,
autonomous_goal: '',
autonomous_status: 'idle',
autonomous_last_error: '',
context_compaction: 'auto',
compaction_keep_recent: 8,
compaction_tool_chars: 1600,
plan_mode_active: false,
todo_nudge_enabled: true
}
}
/**
* @param {unknown} v
* @returns {v is Record<string, unknown>}
*/
function bareAgentIsPlainObject(v) {
return v != null && typeof v === 'object' && !Array.isArray(v)
}
/**
* Shallow merge known keys from src into defaults.
* @param {Record<string, unknown>} defaults
* @param {Record<string, unknown>} src
*/
function bareAgentMergeConfig(defaults, src) {
const out = { ...defaults }
const known = new Set([
'backend',
'rest_base_url',
'rest_api_key',
'model',
'qvac_model',
'qvac_profile',
'qvac_ctx_size',
'qvac_device',
'qvac_main_gpu',
'qvac_gpu_layers',
'max_tokens',
'temperature',
'provider',
'max_iterations',
'stream',
'tool_parallelism',
'request_timeout_ms',
'extra_headers',
'access_policy',
'allow_delete',
'require_confirm_token',
'command_deny',
'mutate_deny_prefixes',
'owner_name',
'agent_label',
'show_reasoning',
'reasoning_mode',
'reasoning_max_chars',
'reasoning_include_tools',
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error',
'context_compaction',
'compaction_keep_recent',
'compaction_tool_chars',
'plan_mode_active',
'todo_nudge_enabled'
])
for (const k of Object.keys(src)) {
if (k.startsWith('x-')) continue
if (!known.has(k)) continue
const val = src[k]
if (k === 'extra_headers' && bareAgentIsPlainObject(val)) {
out.extra_headers = /** @type {Record<string, string>} */ ({ ...val })
continue
}
if (k === 'backend') {
const b = String(val ?? '').trim().toLowerCase()
out.backend = b === 'rest' || b === 'openai' || b === 'http' ? 'rest' : 'qvac'
continue
}
if (
k === 'rest_base_url' ||
k === 'rest_api_key' ||
k === 'model' ||
k === 'qvac_model' ||
k === 'qvac_profile' ||
k === 'provider' ||
k === 'owner_name' ||
k === 'agent_label' ||
k === 'autonomous_goal' ||
k === 'autonomous_status' ||
k === 'autonomous_last_error' ||
k === 'qvac_device' ||
k === 'qvac_main_gpu' ||
k === 'require_confirm_token'
) {
out[k] = String(val ?? '')
continue
}
if (k === 'context_compaction') {
const mode = String(val ?? '').trim().toLowerCase()
out.context_compaction =
mode === 'off' || mode === 'aggressive' ? mode : 'auto'
continue
}
if (k === 'reasoning_mode') {
const mode = String(val ?? '').trim().toLowerCase()
out.reasoning_mode =
mode === 'summary' || mode === 'trace' ? mode : 'off'
continue
}
if (
k === 'max_tokens' ||
k === 'temperature' ||
k === 'max_iterations' ||
k === 'tool_parallelism' ||
k === 'request_timeout_ms' ||
k === 'reasoning_max_chars' ||
k === 'qvac_ctx_size' ||
k === 'qvac_gpu_layers' ||
k === 'autonomous_max_runtime_ms' ||
k === 'autonomous_started_at_ms' ||
k === 'compaction_keep_recent' ||
k === 'compaction_tool_chars'
) {
const n = Number(val)
out[k] = Number.isFinite(n) ? n : defaults[k]
continue
}
if (
k === 'autonomous_completion_required_checks' ||
k === 'autonomous_allow_paths' ||
k === 'autonomous_deny_ops' ||
k === 'command_deny' ||
k === 'mutate_deny_prefixes'
) {
out[k] = Array.isArray(val) ? val.map((x) => String(x ?? '')).filter(Boolean) : defaults[k]
continue
}
if (
k === 'stream' ||
k === 'allow_delete' ||
k === 'show_reasoning' ||
k === 'reasoning_include_tools' ||
k === 'allow_bridge_mutations' ||
k === 'allow_host_notifications' ||
k === 'allow_host_actions' ||
k === 'emergency_stop_mutations' ||
k === 'autonomous_mode_enabled' ||
k === 'autonomous_active' ||
k === 'autonomous_stop_requested' ||
k === 'plan_mode_active' ||
k === 'todo_nudge_enabled'
) {
out[k] = Boolean(val)
continue
}
if (k === 'require_confirm_token') {
out.require_confirm_token = String(val ?? '')
continue
}
if (k === 'access_policy') {
const pol = String(val ?? '').trim().toLowerCase()
out.access_policy = pol === 'restricted' ? 'restricted' : 'full'
continue
}
}
return out
}
/**
* Old configs persisted restrictive defaults. Missing access_policy means
* upgrade onto full guest admin (denylist-only) so existing homes match.
* @param {Record<string, unknown>} raw
* @param {Record<string, unknown>} merged
*/
function bareAgentApplyAccessPolicyUpgrade(raw, merged) {
const src = bareAgentIsPlainObject(raw) ? raw : {}
if (Object.prototype.hasOwnProperty.call(src, 'access_policy')) {
return { config: merged, upgraded: false }
}
const next = { ...merged }
next.access_policy = 'full'
next.allow_delete = true
next.require_confirm_token = ''
next.allow_bridge_mutations = true
next.allow_host_notifications = true
next.allow_host_actions = true
next.emergency_stop_mutations = false
next.autonomous_deny_ops = []
next.autonomous_allow_paths = ['*']
next.command_deny = Array.isArray(next.command_deny) ? next.command_deny : []
next.autonomous_mode_enabled = true
if (!Array.isArray(next.mutate_deny_prefixes) || !next.mutate_deny_prefixes.length) {
next.mutate_deny_prefixes = bareAgentDefaultConfig().mutate_deny_prefixes
}
return { config: next, upgraded: true }
}
/**
* @param {Record<string, unknown>} raw
*/
function bareAgentValidateConfigShape(raw) {
if (!bareAgentIsPlainObject(raw)) throw new Error('config must be a JSON object')
for (const k of Object.keys(raw)) {
if (k.startsWith('x-')) continue
const known = [
'backend',
'rest_base_url',
'rest_api_key',
'model',
'qvac_model',
'qvac_profile',
'qvac_ctx_size',
'qvac_device',
'qvac_main_gpu',
'qvac_gpu_layers',
'max_tokens',
'temperature',
'provider',
'max_iterations',
'stream',
'tool_parallelism',
'request_timeout_ms',
'extra_headers',
'access_policy',
'allow_delete',
'require_confirm_token',
'command_deny',
'mutate_deny_prefixes',
'owner_name',
'agent_label',
'show_reasoning',
'reasoning_mode',
'reasoning_max_chars',
'reasoning_include_tools',
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error',
'context_compaction',
'compaction_keep_recent',
'compaction_tool_chars',
'plan_mode_active',
'todo_nudge_enabled'
]
if (!known.includes(k)) {
throw new Error('unknown config key: ' + k)
}
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {{ dir: string, config: string }} paths
* @returns {Promise<{ config: ReturnType<typeof bareAgentDefaultConfig>, created: boolean }>}
*/
async function bareAgentLoadOrCreateConfig(ctx, paths) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.mkdir !== 'function')
throw new Error('agent: vfs.mkdir unavailable')
await vfs.mkdir(paths.dir, { recursive: true })
let missingOrEmpty = false
/** @type {Record<string, unknown>} */
let raw = {}
try {
if (typeof vfs.readFile === 'function') {
const buf = await vfs.readFile(paths.config)
if (!buf || !buf.length) missingOrEmpty = true
else {
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
raw = JSON.parse(t)
}
} else missingOrEmpty = true
} catch {
missingOrEmpty = true
raw = {}
}
if (!bareAgentIsPlainObject(raw)) raw = {}
if (missingOrEmpty || Object.keys(raw).length === 0) {
raw = bareAgentDefaultConfig()
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
raw = bareAgentSanitizeConfigForBackend(raw)
}
await bareAgentSaveConfig(ctx, paths, /** @type {any} */ (raw))
return { config: /** @type {any} */ (raw), created: true }
}
bareAgentValidateConfigShape(raw)
const merged = bareAgentMergeConfig(bareAgentDefaultConfig(), raw)
const applied = bareAgentApplyAccessPolicyUpgrade(raw, merged)
let next = applied.config
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
next = bareAgentSanitizeConfigForBackend(next)
}
const dirty =
applied.upgraded ||
JSON.stringify(next) !== JSON.stringify(merged)
if (dirty) {
try {
await bareAgentSaveConfig(ctx, paths, /** @type {any} */ (next))
} catch {
/* keep upgraded in-memory even if persist fails */
}
}
return { config: /** @type {any} */ (next), created: false }
}
/**
* @param {Record<string, unknown>} ctx
* @param {{ config: string }} paths
* @param {Record<string, unknown>} config
*/
async function bareAgentSaveConfig(ctx, paths, config) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.writeFile !== 'function')
throw new Error('agent: vfs.writeFile unavailable')
let next = config
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
next = bareAgentSanitizeConfigForBackend(config)
if (config && typeof config === 'object' && config !== next) {
for (const k of Object.keys(config)) {
if (!Object.prototype.hasOwnProperty.call(next, k)) delete config[k]
}
Object.assign(config, next)
}
}
bareAgentValidateConfigShape(next)
const json = JSON.stringify(next, null, 2) + '\n'
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(json)
: new TextEncoder().encode(json)
await vfs.writeFile(paths.config, body)
}
/**
* Drop middle messages until JSON size fits (keep first system + recent tail).
* @param {unknown[]} msgs
* @param {number} maxBytes
*/
function bareAgentTrimMessages(msgs, maxBytes) {
if (!Array.isArray(msgs) || maxBytes <= 0) return []
/** @type {unknown[]} */
let out = msgs.slice()
while (JSON.stringify(out).length > maxBytes && out.length > 3) {
out.splice(2, 1)
}
return out
}
/**
* Rough token estimate (chars/4). Good enough for local ctx budgeting.
* @param {unknown} value
*/
function bareAgentEstimateTokens(value) {
try {
const n = JSON.stringify(value == null ? '' : value).length
return Math.max(0, Math.ceil(n / 4))
} catch {
return 0
}
}
/**
* Trim history + shrink system content so prompt fits a QVAC ctx window.
* Reserves room for completion and optional tools JSON.
* @param {unknown[]} msgs
* @param {number} ctxSize
* @param {{ tools?: unknown[], reserveCompletion?: number }} [opts]
*/
function bareAgentTrimMessagesForCtx(msgs, ctxSize, opts) {
const ctx = Math.max(2048, Math.floor(Number(ctxSize) || 4096))
const reserve =
opts && Number.isFinite(Number(opts.reserveCompletion))
? Math.max(256, Math.floor(Number(opts.reserveCompletion)))
: Math.min(1024, Math.max(256, Math.floor(ctx * 0.15)))
const toolsTok = bareAgentEstimateTokens(
opts && Array.isArray(opts.tools) && opts.tools.length ? opts.tools : []
)
const budget = Math.max(512, ctx - reserve - toolsTok)
/** @type {unknown[]} */
let out = Array.isArray(msgs) ? msgs.slice() : []
// Drop middle turns first (keep system + recent).
while (bareAgentEstimateTokens(out) > budget && out.length > 3) {
out.splice(2, 1)
}
// Shrink system blob if still over (workspace/man/skills dominate).
if (bareAgentEstimateTokens(out) > budget && out[0] && typeof out[0] === 'object') {
const sys = /** @type {Record<string, unknown>} */ (out[0])
if (sys.role === 'system' && typeof sys.content === 'string') {
let content = sys.content
let guard = 0
while (
bareAgentEstimateTokens(out) > budget &&
content.length > 800 &&
guard < 24
) {
content =
content.slice(0, Math.floor(content.length * 0.82)) + '\n… truncated'
out[0] = { ...sys, content }
guard++
}
}
}
return out
}
/**
* Best-effort load instruction files.
* @param {Record<string, unknown>} ctx
* @param {{ instructions: string, context: string }} paths
*/
async function bareAgentLoadInstructionFiles(ctx, paths) {
const vfs = ctx.vfs
const parts = []
if (!vfs || typeof vfs.readFile !== 'function') return ''
try {
const b = await vfs.readFile(paths.instructions)
if (b && b.length) {
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
if (t.trim()) parts.push('## User instructions (~/.agent/instructions.md)\n' + t.trim())
}
} catch {
/* ignore */
}
try {
const b = await vfs.readFile(paths.context)
if (b && b.length) {
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
if (t.trim()) parts.push('## Agent context (~/.agent/context.md)\n' + t.trim())
}
} catch {
/* ignore */
}
return parts.join('\n\n')
}
/**
* One-time compact index from man.json for system prompt.
* @param {Record<string, unknown>} ctx
*/
async function bareAgentManDigest(ctx) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return ''
try {
const buf = await vfs.readFile('/share/man/man.json')
if (!buf || !buf.length) return ''
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
const j = JSON.parse(t)
const pages = Array.isArray(j.pages) ? j.pages : []
/** @type {string[]} */
const lines = []
const max = Math.min(pages.length, 400)
for (let i = 0; i < max; i++) {
const p = pages[i]
if (!p || typeof p !== 'object') continue
const name = typeof p.name === 'string' ? p.name : ''
const title = typeof p.title === 'string' ? p.title : ''
if (name) lines.push('- ' + name + (title ? ': ' + title : ''))
}
let s = lines.join('\n')
if (s.length > 24000) s = s.slice(0, 24000) + '\n…'
return (
'Manual page index (see `man <name>` for full text). Sample entries:\n' + s
)
} catch {
return '(man digest unavailable)'
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
*/
async function bareAgentAppendProgress(ctx, path, line) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function' || typeof vfs.writeFile !== 'function')
return
let prev = ''
try {
const b = await vfs.readFile(path)
if (b && b.length) {
prev =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
}
} catch {
/* ignore */
}
const chunk =
new Date().toISOString() +
' ' +
line.replace(/\r?\n/g, ' ') +
'\n'
const maxKeep = 120_000
let next = prev + chunk
if (next.length > maxKeep) next = next.slice(-maxKeep)
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(next)
: new TextEncoder().encode(next)
await vfs.writeFile(path, body)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {unknown[]} messages
*/
async function bareAgentSaveHistory(ctx, path, messages) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.writeFile !== 'function') return
const compacted =
typeof bareAgentCompactMessagesForDisk === 'function'
? bareAgentCompactMessagesForDisk(messages, 500_000, { keepRecent: 16 })
: bareAgentTrimMessages(messages, 500_000)
const trimmed =
typeof bareAgentTrimMessages === 'function'
? bareAgentTrimMessages(compacted, 500_000)
: compacted
const json = JSON.stringify(trimmed, null, 2) + '\n'
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(json)
: new TextEncoder().encode(json)
await vfs.writeFile(path, body)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @returns {Promise<unknown[]>}
*/
async function bareAgentLoadHistory(ctx, path) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return []
try {
const buf = await vfs.readFile(path)
if (!buf || !buf.length) return []
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
const j = JSON.parse(t)
return Array.isArray(j) ? j : []
} catch {
return []
}
}
/**
* Start an autonomous run on a config object (does not persist).
* @param {Record<string, unknown>} cfg
* @param {{ goal: string, maxRuntimeMs?: number, requiredChecks?: string[], scopePath?: string }} opts
*/
function bareAgentBeginAutonomousRun(cfg, opts) {
const goal = String((opts && opts.goal) || '').trim()
const maxRuntimeMs = Math.min(
Math.max(Math.floor(Number((opts && opts.maxRuntimeMs) || cfg.autonomous_max_runtime_ms) || 0), 60000),
7_200_000
)
const requiredChecks = Array.isArray(opts && opts.requiredChecks)
? opts.requiredChecks.map((x) => String(x || '').trim()).filter(Boolean)
: Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: []
return {
...cfg,
autonomous_mode_enabled: true,
autonomous_active: true,
autonomous_stop_requested: false,
autonomous_started_at_ms: Date.now(),
autonomous_goal: goal,
autonomous_status: 'running',
autonomous_last_error: '',
autonomous_max_runtime_ms: maxRuntimeMs,
autonomous_completion_required_checks: requiredChecks
}
}
/**
* @param {Record<string, unknown>} cfg
* @param {string} [reason]
*/
function bareAgentStopAutonomousRun(cfg, reason) {
return {
...cfg,
autonomous_stop_requested: true,
autonomous_status: 'stopping',
autonomous_last_error: reason || String(cfg.autonomous_last_error || '')
}
}
/**
* @param {Record<string, unknown>} cfg
* @param {{ remainingMs?: number, lastError?: string }} [extra]
*/
function bareAgentAutonomousContinuationPrompt(cfg, extra) {
const goal = String(cfg.autonomous_goal || '').trim() || '(goal unset)'
const remain =
extra && typeof extra.remainingMs === 'number'
? Math.max(0, Math.round(extra.remainingMs / 1000))
: 0
return (
'AUTONOMOUS RUN still active. Do not stop with a plan — take the next concrete tool action.\n' +
'Goal: ' +
goal +
'\n' +
(remain > 0 ? 'Time remaining: ' + String(remain) + 's.\n' : '') +
(extra && extra.lastError ? 'Last gate error: ' + extra.lastError + '\n' : '') +
'Call task_complete(summary) only when the goal is actually finished and verified.'
)
}
/**
* @param {Record<string, unknown>} cfg
* @param {{ completed?: boolean, hasTools?: boolean, stopRequested?: boolean }} state
*/
function bareAgentAutonomousShouldContinue(cfg, state) {
if (!cfg || !cfg.autonomous_active) return false
if (state && state.stopRequested) return false
if (state && state.completed) return false
if (state && state.hasTools) return false
return true
}
async function bareAgentResetChatSession(ctx, paths, argv0) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.mkdir !== 'function' || typeof vfs.writeFile !== 'function') {
throw new Error('agent reset: vfs unavailable')
}
await vfs.mkdir(paths.dir, { recursive: true })
const emptyHist =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from('[]\n')
: new TextEncoder().encode('[]\n')
await vfs.writeFile(paths.history, emptyHist)
const stamp = new Date().toISOString() + ' chat session reset\n'
const progBody =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(stamp)
: new TextEncoder().encode(stamp)
await vfs.writeFile(paths.progress, progBody)
try {
const z =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from('')
: new TextEncoder().encode('')
await vfs.writeFile(paths.cmdOut, z)
} catch {
/* ignore */
}
try {
ctx.console.log(
argv0 + ': chat session cleared (' + paths.history + ', ' + paths.progress + ')'
)
} catch {
/* ignore */
}
}
/**
* QVAC helpers for /bin/agent (no SDK import — host bridge via ctx.bareOsQvac*).
*/
/** @typedef {'lite'|'recommended'|'strong'|'tool-tiny'} BareAgentQvacProfileId */
/**
* Native context windows from upstream model cards (not conservative laptop defaults).
* Qwen3 dense (0.6B/1.7B/4B): https://huggingface.co/Qwen/Qwen3-0.6B — 32,768
* Llama 3.2 1B (tool-calling finetune base): Meta Llama 3.2 — 128,000
* Absolute ceiling for overrides / host clamp.
*/
const BARE_AGENT_QVAC_CTX_QWEN3 = 32768
const BARE_AGENT_QVAC_CTX_LLAMA32_1B = 131072
const BARE_AGENT_QVAC_CTX_ABSOLUTE_MAX = 131072
/**
* @typedef {{
* id: BareAgentQvacProfileId,
* label: string,
* description: string,
* chatModel: string,
* tools: boolean,
* minRamGb: number,
* minDiskGb: number,
* approxDownloadGb: number,
* ctxSize: number
* }} BareAgentQvacProfile
*/
/** @type {Record<BareAgentQvacProfileId, BareAgentQvacProfile>} */
const BARE_AGENT_QVAC_PROFILES = {
lite: {
id: 'lite',
label: 'Lite',
description:
'Smallest download (Qwen3-0.6B). Model-card context 32k; tools enabled.',
chatModel: 'QWEN3_600M_INST_Q4',
tools: true,
minRamGb: 4,
minDiskGb: 2,
approxDownloadGb: 0.5,
ctxSize: BARE_AGENT_QVAC_CTX_QWEN3
},
recommended: {
id: 'recommended',
label: 'Recommended',
description:
'Best balance for Bare OS agent tool calling (Qwen3-1.7B, model-card context 32k).',
chatModel: 'QWEN3_1_7B_INST_Q4',
tools: true,
minRamGb: 8,
minDiskGb: 5,
approxDownloadGb: 2.5,
ctxSize: BARE_AGENT_QVAC_CTX_QWEN3
},
strong: {
id: 'strong',
label: 'Strong',
description:
'Better reasoning (Qwen3-4B). Model-card context 32k (131k with YaRN not enabled).',
chatModel: 'QWEN3_4B_INST_Q4_K_M',
tools: true,
minRamGb: 16,
minDiskGb: 8,
approxDownloadGb: 3.5,
ctxSize: BARE_AGENT_QVAC_CTX_QWEN3
},
'tool-tiny': {
id: 'tool-tiny',
label: 'Tool-tiny',
description:
'Llama 3.2 1B tool-calling fallback (model-card context 128k).',
chatModel: 'LLAMA_TOOL_CALLING_1B_INST_Q4_K',
tools: true,
minRamGb: 6,
minDiskGb: 3,
approxDownloadGb: 1,
ctxSize: BARE_AGENT_QVAC_CTX_LLAMA32_1B
}
}
/**
* Model-card / train context for a QVAC registry id (fallback 32k).
* @param {string} [modelId]
* @returns {number}
*/
function bareAgentQvacModelCardCtxSize(modelId) {
const id = String(modelId || '')
.trim()
.toUpperCase()
if (!id) return BARE_AGENT_QVAC_CTX_QWEN3
if (id.includes('LLAMA') || id.includes('LLAMA_TOOL')) {
return BARE_AGENT_QVAC_CTX_LLAMA32_1B
}
if (id.includes('QWEN3')) return BARE_AGENT_QVAC_CTX_QWEN3
return BARE_AGENT_QVAC_CTX_QWEN3
}
/** @returns {BareAgentQvacProfile[]} */
function bareAgentQvacProfileList() {
return Object.values(BARE_AGENT_QVAC_PROFILES)
}
/**
* @param {string} [id]
* @returns {BareAgentQvacProfile}
*/
function bareAgentQvacGetProfile(id) {
const key = String(id || '').trim().toLowerCase()
return BARE_AGENT_QVAC_PROFILES[key] || BARE_AGENT_QVAC_PROFILES.recommended
}
/**
* Flatten OpenAI nested tool defs → QVAC flat shape.
* @param {unknown[]} tools
* @returns {unknown[]}
*/
function bareAgentFlattenToolsForQvac(tools) {
if (!Array.isArray(tools)) return []
/** @type {unknown[]} */
const out = []
for (const t of tools) {
if (!t || typeof t !== 'object') continue
const o = /** @type {Record<string, unknown>} */ (t)
if (o.function && typeof o.function === 'object') {
const fn = /** @type {Record<string, unknown>} */ (o.function)
out.push({
type: 'function',
name: typeof fn.name === 'string' ? fn.name : '',
description: typeof fn.description === 'string' ? fn.description : '',
parameters:
fn.parameters && typeof fn.parameters === 'object'
? fn.parameters
: { type: 'object', properties: {} }
})
continue
}
if (typeof o.name === 'string') {
out.push({
type: 'function',
name: o.name,
description: typeof o.description === 'string' ? o.description : '',
parameters:
o.parameters && typeof o.parameters === 'object'
? o.parameters
: { type: 'object', properties: {} }
})
}
}
return out.filter((x) => x && typeof x === 'object' && /** @type {any} */ (x).name)
}
/**
* @param {Record<string, unknown>} ctx
* @returns {boolean}
*/
function bareAgentQvacBridgeAvailable(ctx) {
if (!ctx || typeof ctx !== 'object') return false
if (typeof ctx.bareOsQvacAvailable === 'function') {
try {
return Boolean(ctx.bareOsQvacAvailable())
} catch {
return false
}
}
return typeof ctx.bareOsQvacComplete === 'function'
}
/**
* Normalize backend from config (qvac | rest).
* @param {Record<string, unknown>} config
* @returns {'qvac'|'rest'}
*/
function bareAgentResolveBackend(config) {
const b = String(config?.backend || '').trim().toLowerCase()
if (b === 'rest' || b === 'openai' || b === 'http') return 'rest'
if (b === 'qvac') return 'qvac'
const p = String(config?.provider || '').trim().toLowerCase()
if (p === 'qvac') return 'qvac'
if (p === 'groq' || p === 'xai' || p === 'openai' || p === 'custom') return 'rest'
// Default: qvac when key unset; rest when key present (legacy configs)
if (config?.rest_api_key && String(config.rest_api_key).trim()) return 'rest'
return 'qvac'
}
/** @type {readonly string[]} */
var BARE_AGENT_QVAC_ONLY_KEYS = Object.freeze([
'qvac_model',
'qvac_profile',
'qvac_ctx_size',
'qvac_device',
'qvac_main_gpu',
'qvac_gpu_layers'
])
/** @type {readonly string[]} */
var BARE_AGENT_REST_ONLY_KEYS = Object.freeze(['rest_base_url', 'rest_api_key'])
/**
* @typedef {{
* id: string,
* label: string,
* rest_base_url: string,
* default_model: string,
* models: string[]
* }} BareAgentRestProvider
*/
/** @type {Record<string, BareAgentRestProvider>} */
const BARE_AGENT_REST_PROVIDERS = {
groq: {
id: 'groq',
label: 'Groq',
rest_base_url: 'https://api.groq.com/openai/v1',
default_model: 'llama-3.3-70b-versatile',
models: [
'llama-3.3-70b-versatile',
'llama-3.1-8b-instant',
'openai/gpt-oss-120b',
'openai/gpt-oss-20b',
'qwen/qwen3-32b',
'moonshotai/kimi-k2-instruct'
]
},
xai: {
id: 'xai',
label: 'xAI (Grok)',
rest_base_url: 'https://api.x.ai/v1',
default_model: 'grok-4',
models: ['grok-4', 'grok-3', 'grok-3-mini', 'grok-3-fast', 'grok-2-1212']
},
openai: {
id: 'openai',
label: 'OpenAI',
rest_base_url: 'https://api.openai.com/v1',
default_model: 'gpt-4.1',
models: ['gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-4o', 'o4-mini']
},
custom: {
id: 'custom',
label: 'Custom OpenAI-compatible',
rest_base_url: '',
default_model: '',
models: []
}
}
/** @returns {BareAgentRestProvider[]} */
function bareAgentRestProviderList() {
return [BARE_AGENT_REST_PROVIDERS.groq, BARE_AGENT_REST_PROVIDERS.xai, BARE_AGENT_REST_PROVIDERS.openai, BARE_AGENT_REST_PROVIDERS.custom]
}
/**
* @param {string} [id]
* @returns {BareAgentRestProvider}
*/
function bareAgentRestGetProvider(id) {
const key = String(id || '').trim().toLowerCase()
if (key === 'http' || key === 'rest') return BARE_AGENT_REST_PROVIDERS.custom
return BARE_AGENT_REST_PROVIDERS[key] || BARE_AGENT_REST_PROVIDERS.groq
}
/**
* @param {string} [model]
*/
function bareAgentIsQvacModelId(model) {
const m = String(model || '').trim()
if (!m) return false
return (
/^QWEN/i.test(m) ||
/^LLAMA_TOOL/i.test(m) ||
/QWEN3/i.test(m) ||
/LLAMA_TOOL_CALLING/i.test(m)
)
}
/**
* Drop keys that belong to the other backend so config.json matches the choice.
* @param {Record<string, unknown>} config
* @returns {Record<string, unknown>}
*/
function bareAgentSanitizeConfigForBackend(config) {
const out = { ...(config && typeof config === 'object' ? config : {}) }
const backend = bareAgentResolveBackend(out)
out.backend = backend
if (backend === 'rest') {
for (let i = 0; i < BARE_AGENT_QVAC_ONLY_KEYS.length; i++) {
delete out[BARE_AGENT_QVAC_ONLY_KEYS[i]]
}
const prov = String(out.provider || '')
.trim()
.toLowerCase()
if (!prov || prov === 'qvac') out.provider = 'groq'
if (bareAgentIsQvacModelId(String(out.model || ''))) {
const spec = bareAgentRestGetProvider(String(out.provider || 'groq'))
if (spec.default_model) out.model = spec.default_model
}
} else {
for (let i = 0; i < BARE_AGENT_REST_ONLY_KEYS.length; i++) {
delete out[BARE_AGENT_REST_ONLY_KEYS[i]]
}
out.provider = 'qvac'
}
return out
}
/**
* @param {string} [secret]
*/
function bareAgentMaskSecretPreview(secret) {
const t = String(secret || '')
if (!t.trim()) return '(not set)'
if (t.length <= 8) return '********'
return t.slice(0, 3) + '…' + t.slice(-4)
}
/**
* Resolve context window for QVAC load from the model card / profile.
* Legacy undersized `qvac_ctx_size` values (e.g. 4096/8192) are ignored so
* setup stays automatic; only overrides ≥ the profile default apply.
* Host may still cap via `BARE_OS_QVAC_MAX_CTX`.
* @param {Record<string, unknown>} config
* @param {BareAgentQvacProfile} profile
*/
function bareAgentQvacResolveCtxSize(config, profile) {
const modelId = String(
(config && (config.qvac_model || config.model)) ||
(profile && profile.chatModel) ||
''
)
const cardCtx = bareAgentQvacModelCardCtxSize(modelId)
const profileCtx = Math.max(
2048,
Number(profile && profile.ctxSize) || cardCtx
)
let ctx = Math.min(
BARE_AGENT_QVAC_CTX_ABSOLUTE_MAX,
Math.max(profileCtx, cardCtx)
)
const raw = Number(config && config.qvac_ctx_size)
if (Number.isFinite(raw) && raw >= profileCtx) {
ctx = Math.min(BARE_AGENT_QVAC_CTX_ABSOLUTE_MAX, Math.floor(raw))
}
// Full Bare OS tool list ≈ 4.5k tokens; leave room for system + reply.
// Tools are always on for agent sessions — keep enough ctx for schemas.
if (ctx < 8192) ctx = 8192
return ctx
}
/**
* Device / main-gpu / layers for QVAC load (config + profile defaults).
* @param {Record<string, unknown>} config
* @returns {{ device?: string, mainGpu?: string | number, gpuLayers?: number }}
*/
function bareAgentQvacResolveDeviceOpts(config) {
/** @type {{ device?: string, mainGpu?: string | number, gpuLayers?: number }} */
const out = {}
const device = String(config && config.qvac_device ? config.qvac_device : '')
.trim()
.toLowerCase()
if (device === 'cpu' || device === 'gpu') out.device = device
const mainRaw = config && config.qvac_main_gpu
if (mainRaw !== undefined && mainRaw !== null && String(mainRaw).trim() !== '') {
const s = String(mainRaw).trim().toLowerCase()
if (s === 'auto' || s === 'dedicated' || s === 'integrated') out.mainGpu = s
else if (/^\d+$/.test(s)) out.mainGpu = Number.parseInt(s, 10)
} else {
out.mainGpu = 'auto'
}
const layers = Number(config && config.qvac_gpu_layers)
if (Number.isFinite(layers) && layers >= 0) out.gpuLayers = Math.floor(layers)
return out
}
/**
* Shared QVAC chat catalog + REST /models helpers (preamble for agent and discord-bot).
*/
/** @type {{ id: string, family: string, label: string, tools: boolean, ramGb: number, profile?: string }[]} */
var BARE_AGENT_QVAC_CHAT_MODELS = [
{ id: 'QWEN3_600M_INST_Q4', family: 'qwen3', label: 'Qwen3 0.6B Instruct Q4', tools: true, ramGb: 4, profile: 'lite' },
{ id: 'QWEN3_1_7B_INST_Q4', family: 'qwen3', label: 'Qwen3 1.7B Instruct Q4', tools: true, ramGb: 8, profile: 'recommended' },
{ id: 'QWEN3_4B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Instruct Q4_K_M', tools: true, ramGb: 16, profile: 'strong' },
{ id: 'QWEN3_4B_Q4_K_M', family: 'qwen3', label: 'Qwen3 4B Q4_K_M', tools: true, ramGb: 16 },
{ id: 'QWEN3_8B_INST_Q4_K_M', family: 'qwen3', label: 'Qwen3 8B Instruct Q4_K_M', tools: true, ramGb: 24 },
{ id: 'LLAMA_TOOL_CALLING_1B_INST_Q4_K', family: 'llama', label: 'Llama 3.2 1B tool-calling', tools: true, ramGb: 6, profile: 'tool-tiny' },
{ id: 'LLAMA_3_2_1B_INST_Q4_0', family: 'llama', label: 'Llama 3.2 1B Instruct Q4_0', tools: true, ramGb: 6 },
{ id: 'SMOLLM2_360M_INST_Q8', family: 'smol', label: 'SmolLM2 360M Instruct Q8', tools: false, ramGb: 3 },
{ id: 'GPT_OSS_20B_INST_Q4_K_M', family: 'gpt-oss', label: 'GPT-OSS 20B Instruct Q4_K_M', tools: true, ramGb: 24 },
{ id: 'GEMMA4_2B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 2B multimodal Q4', tools: true, ramGb: 8 },
{ id: 'GEMMA4_2B_MULTIMODAL_Q6_K', family: 'gemma', label: 'Gemma 4 2B multimodal Q6', tools: true, ramGb: 10 },
{ id: 'GEMMA4_4B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 4B multimodal Q4', tools: true, ramGb: 16 },
{ id: 'GEMMA4_31B_MULTIMODAL_Q4_K_M', family: 'gemma', label: 'Gemma 4 31B multimodal Q4', tools: true, ramGb: 48 },
{ id: 'QWEN3VL_2B_MULTIMODAL_Q4_K', family: 'qwen3', label: 'Qwen3-VL 2B multimodal Q4', tools: true, ramGb: 10 },
{ id: 'QWEN3_5_2B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 2B multimodal Q4', tools: true, ramGb: 10 },
{ id: 'QWEN3_5_4B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 4B multimodal Q4', tools: true, ramGb: 16 },
{ id: 'QWEN3_5_0_8B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 8B multimodal Q4', tools: true, ramGb: 24 },
{ id: 'QWEN3_5_9B_MULTIMODAL_Q4_K_M', family: 'qwen3.5', label: 'Qwen3.5 9B multimodal Q4', tools: true, ramGb: 28 },
{ id: 'QWEN3_6_27B_MULTIMODAL_Q4_K_XL', family: 'large', label: 'Qwen3.6 27B multimodal Q4', tools: true, ramGb: 48 }
]
/** @type {Record<string, string[]>} */
var BARE_AGENT_REST_MODEL_FALLBACKS = {
groq: [
'llama-3.3-70b-versatile',
'llama-3.1-8b-instant',
'openai/gpt-oss-120b',
'openai/gpt-oss-20b',
'qwen/qwen3-32b',
'moonshotai/kimi-k2-instruct',
'meta-llama/llama-4-scout-17b-16e-instruct',
'meta-llama/llama-4-maverick-17b-128e-instruct',
'groq/compound'
],
xai: ['grok-4', 'grok-3', 'grok-3-mini', 'grok-3-fast', 'grok-2-1212', 'grok-2-vision-1212'],
openai: ['gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano', 'gpt-4o', 'gpt-4o-mini', 'o4-mini', 'o3'],
custom: []
}
/**
* @param {string} [profileId]
*/
function bareAgentQvacModelForProfile(profileId) {
const id = String(profileId || '').trim().toLowerCase()
for (let i = 0; i < BARE_AGENT_QVAC_CHAT_MODELS.length; i++) {
if (BARE_AGENT_QVAC_CHAT_MODELS[i].profile === id) return BARE_AGENT_QVAC_CHAT_MODELS[i]
}
return BARE_AGENT_QVAC_CHAT_MODELS[1] || BARE_AGENT_QVAC_CHAT_MODELS[0]
}
/**
* @param {string} [modelId]
*/
function bareAgentQvacFindChatModel(modelId) {
const id = String(modelId || '').trim()
for (let i = 0; i < BARE_AGENT_QVAC_CHAT_MODELS.length; i++) {
if (BARE_AGENT_QVAC_CHAT_MODELS[i].id === id) return BARE_AGENT_QVAC_CHAT_MODELS[i]
}
return null
}
/**
* @param {{ id: string, label?: string, family?: string }[]} models
* @param {{ family?: string, query?: string }} [opts]
*/
function bareAgentFilterModelList(models, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const fam = String(o.family || 'all').trim().toLowerCase()
const q = String(o.query || '').trim().toLowerCase()
const rows = Array.isArray(models) ? models : []
/** @type {{ id: string, label?: string, family?: string }[]} */
const out = []
for (let i = 0; i < rows.length; i++) {
const m = rows[i]
if (!m || !m.id) continue
if (fam && fam !== 'all' && String(m.family || '').toLowerCase() !== fam) continue
if (q) {
const blob = (String(m.id) + ' ' + String(m.label || '')).toLowerCase()
if (blob.indexOf(q) === -1) continue
}
out.push(m)
}
return out
}
/**
* @param {unknown} json
* @returns {{ id: string, label: string, family: string, owned_by: string }[]}
*/
function bareAgentParseOpenAiModels(json) {
const raw =
json && typeof json === 'object' && Array.isArray(/** @type {{ data?: unknown }} */ (json).data)
? /** @type {{ data: unknown[] }} */ (json).data
: Array.isArray(json)
? json
: []
const skip = /embed|whisper|tts|dall-e|davinci|babbage|audio|moderation|realtime|image|sora/i
/** @type {{ id: string, label: string, family: string, owned_by: string }[]} */
const out = []
const seen = Object.create(null)
for (let i = 0; i < raw.length; i++) {
const row = raw[i] && typeof raw[i] === 'object' ? /** @type {Record<string, unknown>} */ (raw[i]) : null
if (!row) continue
const id = String(row.id || row.name || '').trim()
if (!id || seen[id] || skip.test(id)) continue
seen[id] = 1
const owned = String(row.owned_by || row.ownedBy || '')
out.push({
id: id,
label: id,
family: owned || 'api',
owned_by: owned
})
}
out.sort(function (a, b) {
return a.id.localeCompare(b.id)
})
return out
}
/**
* @param {string} [provider]
*/
function bareAgentRestModelsFallback(provider) {
const key = String(provider || 'groq').trim().toLowerCase()
const ids = BARE_AGENT_REST_MODEL_FALLBACKS[key] || BARE_AGENT_REST_MODEL_FALLBACKS.groq
return (ids || []).map(function (id) {
return { id: id, label: id, family: key, owned_by: key }
})
}
/**
* @param {(url: string, init?: object) => Promise<{ ok?: boolean, status?: number, json?: () => Promise<unknown>, text?: () => Promise<string> }>} fetchFn
* @param {{ baseUrl?: string, apiKey?: string }} opts
*/
async function bareAgentFetchRestModels(fetchFn, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const base = String(o.baseUrl || '').trim().replace(/\/+$/, '')
if (!base) throw new Error('rest_base_url required')
if (typeof fetchFn !== 'function') throw new Error('fetch unavailable')
const url = base + '/models'
/** @type {Record<string, string>} */
const headers = { Accept: 'application/json' }
const key = String(o.apiKey || '').trim()
if (key) headers.Authorization = 'Bearer ' + key
const res = await fetchFn(url, { method: 'GET', headers: headers })
if (!res || res.ok === false) {
const st = res && res.status != null ? String(res.status) : 'fetch_failed'
throw new Error('models_http_' + st)
}
let json
if (typeof res.json === 'function') json = await res.json()
else if (typeof res.text === 'function') json = JSON.parse(await res.text())
else throw new Error('models_unreadable')
const list = bareAgentParseOpenAiModels(json)
if (!list.length) throw new Error('models_empty')
return list
}
/**
* Agent Markdown workspace at ~/.agent/workspace — soul files + optional daily memory.
* Defaults ship on the system image at /share/agent-workspace/ and are seeded
* into the personal drive when SOUL.md is missing (portable across peers via Hyperdrive).
*/
/** @type {readonly string[]} Canonical Markdown load order */
var BARE_AGENT_WORKSPACE_FILES = Object.freeze([
'SOUL.md',
'AGENTS.md',
'IDENTITY.md',
'USER.md',
'TOOLS.md',
'MEMORY.md',
'BOOTSTRAP.md',
'HEARTBEAT.md',
'PROMPT.md'
])
/** System-drive templates (kernel share) */
var BARE_AGENT_WORKSPACE_SHARE = '/share/agent-workspace'
/** Relative paths under workspace/ and share root for skill templates */
var BARE_AGENT_SKILL_SEED_REL = Object.freeze([
'skills/.gitkeep',
'skills/p2p-os-status/SKILL.md',
'skills/bare-os-kernel-proc/SKILL.md',
'skills/bare-os-super-developer/SKILL.md',
'skills/agent-ops/SKILL.md',
'skills/xai-compat/SKILL.md',
'skills/holesail/SKILL.md',
'skills/hdms/SKILL.md',
'skills/bareos-code-change/SKILL.md',
'skills/hyperdrive-replication/SKILL.md',
'skills/protomux-channel/SKILL.md',
'skills/ctx-api-change/SKILL.md',
'skills/proc-node-change/SKILL.md',
'skills/seed-rpc-change/SKILL.md',
'skills/coreutils-command-change/SKILL.md',
'skills/shell-grammar-change/SKILL.md',
'skills/docs-contract-update/SKILL.md',
'skills/kernel-program-extension/SKILL.md',
'skills/appstore/SKILL.md',
'skills/pear-dev/SKILL.md',
'skills/pear-runtime-debug/SKILL.md',
'skills/holepunch-local-mirror/SKILL.md'
])
/**
* @param {Record<string, unknown>} ctx
* @param {Uint8Array} buf
*/
function bareAgentWorkspaceDecode(ctx, buf) {
if (!buf || !buf.length) return ''
if (
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
)
return ctx.b4a.toString(buf)
return String(new TextDecoder().decode(buf))
}
/**
* @returns {string} UTC YYYY-MM-DD
*/
function bareAgentWorkspaceUtcYmd() {
const d = new Date()
const y = d.getUTCFullYear()
const m = d.getUTCMonth() + 1
const day = d.getUTCDate()
const pad = (n) => (n < 10 ? '0' : '') + n
return y + '-' + pad(m) + '-' + pad(day)
}
/**
* Seed ~/.agent/workspace from /share/agent-workspace when SOUL.md is absent.
* @param {Record<string, unknown>} ctx
* @param {{ dir: string, workspace: string, workspaceMemory: string, workspaceSkills: string }} paths
*/
async function bareAgentEnsureWorkspace(ctx, paths) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.mkdir !== 'function' || typeof vfs.readFile !== 'function')
return
if (typeof vfs.writeFile !== 'function') return
try {
const b = await vfs.readFile(paths.workspace + '/SOUL.md')
if (b && b.length) return
} catch {
/* missing — seed */
}
try {
await vfs.mkdir(paths.workspace, { recursive: true })
await vfs.mkdir(paths.workspaceMemory, { recursive: true })
} catch {
return
}
const share = BARE_AGENT_WORKSPACE_SHARE
for (const name of BARE_AGENT_WORKSPACE_FILES) {
try {
const buf = await vfs.readFile(share + '/' + name)
await vfs.writeFile(paths.workspace + '/' + name, buf)
} catch {
/* template missing on image — skip */
}
}
try {
const gk = await vfs.readFile(share + '/memory/.gitkeep')
await vfs.writeFile(paths.workspaceMemory + '/.gitkeep', gk)
} catch {
/* optional */
}
/** @type {[string, string][]} */
const stubs = [
['loader.stub.js', paths.dir + '/loader.js'],
['index.stub.js', paths.dir + '/index.js'],
['skill-loader.stub.js', paths.dir + '/skill-loader.js'],
['README-agent.md', paths.dir + '/README-agent.md']
]
for (const [srcName, dest] of stubs) {
try {
const buf = await vfs.readFile(share + '/' + srcName)
await vfs.writeFile(dest, buf)
} catch {
/* optional */
}
}
}
/**
* Ensure workspace/skills templates and ~/.agent/skill-loader.js exist (idempotent; for upgrades).
* @param {Record<string, unknown>} ctx
* @param {{ workspace: string, workspaceSkills: string, dir: string }} paths
* @param {Record<string, unknown>} [config]
*/
async function bareAgentEnsureSkillTemplates(ctx, paths, config) {
const vfs = ctx.vfs
if (
!vfs ||
typeof vfs.readFile !== 'function' ||
typeof vfs.writeFile !== 'function' ||
typeof vfs.mkdir !== 'function'
)
return
const provider =
config && typeof config === 'object' ? String(config.provider || '').trim().toLowerCase() : ''
try {
await vfs.mkdir(paths.workspaceSkills, { recursive: true })
} catch {
return
}
const share = BARE_AGENT_WORKSPACE_SHARE
for (const rel of BARE_AGENT_SKILL_SEED_REL) {
if (rel === 'skills/xai-compat/SKILL.md' && provider !== 'xai') {
if (typeof vfs.unlink === 'function') {
try {
await vfs.unlink(paths.workspace + '/' + rel)
} catch {
/* ignore */
}
}
continue
}
const dest = paths.workspace + '/' + rel
try {
const b = await vfs.readFile(dest)
if (b && b.length) continue
} catch {
/* missing — copy */
}
try {
const buf = await vfs.readFile(share + '/' + rel)
const parent = dest.replace(/\/[^/]+$/, '')
await vfs.mkdir(parent, { recursive: true })
await vfs.writeFile(dest, buf)
} catch {
/* template missing on image */
}
}
try {
await vfs.readFile(paths.dir + '/skill-loader.js')
} catch {
try {
const buf = await vfs.readFile(share + '/skill-loader.stub.js')
await vfs.writeFile(paths.dir + '/skill-loader.js', buf)
} catch {
/* optional */
}
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} text
*/
function bareAgentWorkspaceEncode(ctx, text) {
const s = String(text)
if (
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.from === 'function'
)
return ctx.b4a.from(s)
return new TextEncoder().encode(s)
}
/**
* @param {Record<string, unknown>} config
*/
function bareAgentIdentityMarkdownForConfig(config) {
const label = String(config.agent_label || '').trim() || 'BareAgent'
const owner = String(config.owner_name || '').trim()
const role = owner
? 'Decentralized OS intelligence for **' +
owner +
'** · upstream [bare-operating-system](https://git.ssh.surf/snxraven/bare-operating-system).'
: 'Decentralized OS Intelligence for snxraven\'s bare-operating-system'
return (
'# IDENTITY.md\n\n' +
'**Name:** ' +
label +
'\n' +
'**Role:** ' +
role +
'\n' +
'**Emoji:** 🦾\n' +
'**Version:** 0.1\n'
)
}
/**
* @param {Record<string, unknown>} config
*/
function bareAgentUserMarkdownForConfig(config) {
const owner = String(config.owner_name || '').trim()
const opLine = owner
? '- **Operator (this Hyperdrive):** ' + owner + '\n'
: '- **Operator:** (set `owner_name` via `agent --config` or `edit_agent_config`)\n'
return (
'# USER.md - About the Owner\n\n' +
opLine +
'- **Upstream maintainer (repo):** snxraven\n' +
'- **Location:** Atlanta, Georgia, US\n' +
'- **Expertise:** P2P systems, Bare runtime, Hyperdrive, decentralized identity, POSIX-in-JS\n' +
'- **Preferences:** Concise technical answers, bullet points, no corporate speak, direct honesty\n' +
'- **Permissions:** Full access to system drive and personal Hyperdrive within tool policy\n'
)
}
/**
* Rewrite IDENTITY.md / USER.md from ~/.agent/config.json (owner_name, agent_label).
* Call after seeding workspace or when those keys change.
* @param {Record<string, unknown>} ctx
* @param {{ workspace: string }} paths
* @param {Record<string, unknown>} config
*/
async function bareAgentSyncWorkspaceFromConfig(ctx, paths, config) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.writeFile !== 'function' || typeof vfs.mkdir !== 'function')
return
const label = String(config.agent_label || '').trim()
const owner = String(config.owner_name || '').trim()
if (!label && !owner) return
try {
await vfs.mkdir(paths.workspace, { recursive: true })
} catch {
return
}
try {
const idMd = bareAgentIdentityMarkdownForConfig(config)
await vfs.writeFile(
paths.workspace + '/IDENTITY.md',
bareAgentWorkspaceEncode(ctx, idMd)
)
const userMd = bareAgentUserMarkdownForConfig(config)
await vfs.writeFile(paths.workspace + '/USER.md', bareAgentWorkspaceEncode(ctx, userMd))
} catch {
/* ignore — best-effort */
}
}
/**
* Build concatenated system prompt block (Markdown) from workspace files.
* @param {Record<string, unknown>} ctx
* @param {{ workspace: string, workspaceMemory: string }} paths
* @param {number} [maxChars]
*/
async function bareAgentLoadWorkspacePrompt(ctx, paths, maxChars) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return ''
const cap = Math.min(Math.max(Number(maxChars) || 24000, 4000), 64000)
let out = '# Agent workspace (~/.agent/workspace)\n\n'
for (const name of BARE_AGENT_WORKSPACE_FILES) {
try {
const buf = await vfs.readFile(paths.workspace + '/' + name)
const t = bareAgentWorkspaceDecode(ctx, buf).trim()
out += '=== ' + name + ' ===\n' + (t || '(empty)') + '\n\n'
} catch {
out += '=== ' + name + ' ===\n(File not found)\n\n'
}
}
const day = bareAgentWorkspaceUtcYmd()
try {
const buf = await vfs.readFile(paths.workspaceMemory + '/' + day + '.md')
const t = bareAgentWorkspaceDecode(ctx, buf).trim()
if (t) out += '=== memory/' + day + '.md ===\n' + t + '\n\n'
} catch {
/* no daily log */
}
if (out.length > cap) out = out.slice(0, cap) + '\n… truncated\n'
return out
}
/**
* Agent skills: discover SKILL.md under workspace/skills/ (highest precedence) then ~/.agent/skills/.
* Depends on bareAgentWorkspaceDecode from agent-workspace.js (same preamble order).
*/
/**
* @param {string} text
* @returns {{ front: Record<string, string>, body: string }}
*/
function bareAgentParseSkillFrontmatter(text) {
const t = String(text || '')
if (!t.startsWith('---')) return { front: {}, body: t.trim() }
const nl = t.indexOf('\n')
const afterFirst = nl === -1 ? '' : t.slice(nl + 1)
const end = afterFirst.search(/\n---\s*(?:\n|$)/)
if (end === -1) return { front: {}, body: t.trim() }
const yamlBlock = afterFirst.slice(0, end)
const body = afterFirst.slice(end + 1).replace(/^---\s*/, '').replace(/^\r?\n/, '')
/** @type {Record<string, string>} */
const front = {}
for (const line of yamlBlock.split(/\r?\n/)) {
const m = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/)
if (m) front[m[1]] = m[2].trim()
}
return { front, body: body.trim() }
}
/**
* @param {string} full SKILL.md text
* @param {string} folderName directory basename
*/
function bareAgentSkillMetaFromMarkdown(full, folderName) {
const { front } = bareAgentParseSkillFrontmatter(full)
const name = (front.name || folderName || 'unnamed').trim() || folderName
const descFromFront =
front.description && String(front.description).trim()
? String(front.description).trim()
: ''
const descFromBody =
full
.split(/\r?\n/)
.find((l) => {
const x = l.trim()
return x && !x.startsWith('---') && !x.startsWith('#')
})
?.trim() || ''
const description = (descFromFront || descFromBody || 'Skill').slice(0, 400)
return { name, description }
}
/**
* @param {Record<string, unknown>} ctx
* @param {{ workspaceSkills: string, skillsGlobal: string }} paths
* @returns {Promise<{ id: string, name: string, description: string, path: string, source: string }[]>}
*/
async function bareAgentDiscoverSkills(ctx, paths) {
const vfs = ctx.vfs
if (!vfs || typeof vfs.readdir !== 'function' || typeof vfs.readFile !== 'function')
return []
/** @type {{ id: string, name: string, description: string, path: string, source: string }[]} */
const out = []
const seen = new Set()
/**
* @param {string} root
* @param {string} source
*/
async function scanRoot(root, source) {
let names = []
try {
names = await vfs.readdir(root)
} catch {
return
}
if (!Array.isArray(names)) return
for (const raw of names) {
const entry = String(raw)
if (!entry || entry.startsWith('.')) continue
const skillMd = root.replace(/\/+$/, '') + '/' + entry + '/SKILL.md'
try {
const buf = await vfs.readFile(skillMd)
if (!buf || !buf.length) continue
const full = bareAgentWorkspaceDecode(ctx, buf)
const meta = bareAgentSkillMetaFromMarkdown(full, entry)
const keys = [entry.toLowerCase(), meta.name.toLowerCase()]
let dup = false
for (const k of keys) {
if (seen.has(k)) dup = true
}
if (dup) continue
for (const k of keys) seen.add(k)
out.push({
id: entry,
name: meta.name,
description: meta.description,
path: skillMd,
source
})
} catch {
/* not a skill dir */
}
}
}
await scanRoot(paths.workspaceSkills, 'workspace')
await scanRoot(paths.skillsGlobal, 'global')
let extras = Array.isArray(paths.extraSkillRoots) ? paths.extraSkillRoots : []
if (!extras.length && typeof bareAgentDiscoverProjectSkillRoots === 'function') {
const env =
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: {}
const start = String(env.PWD || env.CWD || env.HOME || '').trim()
if (start) extras = await bareAgentDiscoverProjectSkillRoots(ctx, start)
}
for (let i = 0; i < extras.length; i++) {
const item = extras[i]
const root = typeof item === 'string' ? item : String((item && item.path) || '')
const source =
typeof item === 'object' && item && item.source ? String(item.source) : 'project'
if (root) await scanRoot(root, source)
}
return out
}
/**
* Compact Markdown block for system prompt (names + short descriptions only).
* @param {Record<string, unknown>} ctx
* @param {{ workspaceSkills: string, skillsGlobal: string }} paths
* @param {number} [maxChars]
*/
async function bareAgentSkillsCompactPrompt(ctx, paths, maxChars) {
const cap = Math.min(Math.max(Number(maxChars) || 4000, 500), 12000)
const skills = await bareAgentDiscoverSkills(ctx, paths)
let block =
'## Available skills (compact index)\n' +
'Each skill is a directory with **SKILL.md** (optional YAML frontmatter: `name`, `description`, …).\n' +
'**Workspace skills** (`~/.agent/workspace/skills/`) override **global** (`~/.agent/skills/`) and walk-up `.grok/skills` / `.agents/skills` when names match.\n' +
'To run one: call the **read_skill** tool with the skill id or frontmatter `name` before following its instructions.\n\n'
if (!skills.length) {
block += '(No skills discovered yet — add folders under `workspace/skills/<id>/SKILL.md`.)\n'
return block.length > cap ? block.slice(0, cap) + '\n…\n' : block
}
block += '| id | name | source | description |\n| --- | --- | --- | --- |\n'
for (const s of skills) {
const desc = s.description.replace(/\|/g, '/').replace(/\r?\n/g, ' ').slice(0, 160)
block +=
'| `' +
s.id.replace(/`/g, "'") +
'` | ' +
s.name.replace(/\|/g, '/').replace(/\r?\n/g, ' ') +
' | ' +
s.source +
' | ' +
desc +
' |\n'
}
if (block.length > cap) block = block.slice(0, cap) + '\n… truncated\n'
return block
}
/**
* Load full SKILL.md for a skill matched by folder id or frontmatter name (case-insensitive).
* @param {Record<string, unknown>} ctx
* @param {{ workspaceSkills: string, skillsGlobal: string }} paths
* @param {string} skillQuery
*/
async function bareAgentLoadSkillMarkdown(ctx, paths, skillQuery) {
const q = String(skillQuery || '')
.trim()
.toLowerCase()
if (!q) return { ok: false, error: 'empty_skill', content: '', path: '' }
const vfs = ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function')
return { ok: false, error: 'vfs unavailable', content: '', path: '' }
const skills = await bareAgentDiscoverSkills(ctx, paths)
const hit =
skills.find((s) => s.id.toLowerCase() === q) ||
skills.find((s) => s.name.toLowerCase() === q)
if (!hit) return { ok: false, error: 'skill_not_found', content: '', path: '' }
try {
const buf = await vfs.readFile(hit.path)
if (!buf || !buf.length)
return { ok: false, error: 'empty_file', content: '', path: hit.path }
const t = bareAgentWorkspaceDecode(ctx, buf)
return { ok: true, skill: hit.name, id: hit.id, path: hit.path, source: hit.source, content: t }
} catch (e) {
const msg = e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return { ok: false, error: msg, content: '', path: hit.path }
}
}
/** SSE line split + data payload parse (shared by agent-openai + tests). */
/**
* Parse one SSE `data:` JSON line after the `data: ` prefix.
* @param {string} dataLine content after `data: ` prefix
*/
function bareAgentParseSseDataPayload(dataLine) {
const t = String(dataLine).trim()
if (t === '[DONE]') return { kind: 'done' }
try {
const j = JSON.parse(t)
return { kind: 'json', value: j }
} catch {
return { kind: 'raw', value: t }
}
}
/**
* Split SSE buffer into lines (keep incomplete tail).
* @param {string} buf
* @returns {{ lines: string[], rest: string }}
*/
function bareAgentSplitSseLines(buf) {
const lines = []
let start = 0
for (let i = 0; i < buf.length; i++) {
if (buf.charCodeAt(i) === 10) {
lines.push(buf.slice(start, i))
start = i + 1
}
}
return { lines, rest: buf.slice(start) }
}
/** OpenAI-compatible chat/completions HTTP + SSE (preamble for /bin/agent). */
/**
* @param {Record<string, unknown>} ctx
* @returns {typeof fetch | null}
*/
function bareAgentResolveFetch(ctx) {
if (typeof ctx.httpFetch === 'function')
return /** @type {typeof fetch} */ (ctx.httpFetch.bind(ctx))
const bare = ctx.bare && typeof ctx.bare === 'object' ? ctx.bare : null
let f =
bare && typeof bare.fetch === 'function'
? bare.fetch
: bare &&
bare.default &&
typeof bare.default === 'object' &&
typeof bare.default.fetch === 'function'
? bare.default.fetch
: null
if (typeof f === 'function') return /** @type {typeof fetch} */ (f.bind(bare))
if (typeof globalThis.fetch === 'function')
return globalThis.fetch.bind(globalThis)
return null
}
/**
* @param {string} base
*/
function bareAgentNormalizeBaseUrl(base) {
let s = String(base || '').trim()
while (s.endsWith('/')) s = s.slice(0, -1)
return s
}
/**
* @param {Record<string, unknown>} obj
* @param {string} key
*/
function bareAgentDeepGet(obj, key) {
const parts = key.split('.')
let cur = obj
for (const p of parts) {
if (cur == null || typeof cur !== 'object') return undefined
cur = /** @type {Record<string, unknown>} */ (cur)[p]
}
return cur
}
/**
* Stream chat/completions; invoke onEvent for each parsed chunk.
* @param {{
* fetchFn: typeof fetch,
* url: string,
* headers: Record<string, string>,
* body: Record<string, unknown>,
* signal?: AbortSignal | null,
* onEvent: (ev: Record<string, unknown>) => void
* }} opts
*/
async function bareAgentStreamChatCompletions(opts) {
const { fetchFn, url, headers, body, signal, onEvent } = opts
const res = await fetchFn(url, {
method: 'POST',
headers,
body: JSON.stringify(body),
signal: signal || undefined
})
if (!res.ok) {
let errText = ''
try {
errText = await res.text()
} catch {
/* ignore */
}
throw new Error('HTTP ' + res.status + ' ' + errText.slice(0, 800))
}
const stream = res.body
if (!stream || typeof stream.getReader !== 'function') {
throw new Error('agent: response body is not a readable stream')
}
const reader = stream.getReader()
const dec = new TextDecoder()
let buf = ''
let emittedShape = false
try {
for (;;) {
const { done, value } = await reader.read()
if (done) break
buf += dec.decode(value, { stream: true })
const sp = bareAgentSplitSseLines(buf)
buf = sp.rest
for (const line of sp.lines) {
if (!line.trim()) continue
if (line.startsWith(':')) continue
if (!line.startsWith('data:')) continue
const payload = line.slice(5).replace(/^\s/, '')
const parsed = bareAgentParseSseDataPayload(payload)
if (parsed.kind === 'done') {
onEvent({ type: 'sse_done' })
continue
}
if (parsed.kind !== 'json' || !parsed.value || typeof parsed.value !== 'object')
continue
const j = /** @type {Record<string, unknown>} */ (parsed.value)
if (!emittedShape) {
emittedShape = true
onEvent({
type: 'response_shape_keys',
keys: Object.keys(j).slice(0, 24)
})
}
if (typeof j.type === 'string') {
if (j.type === 'response.reasoning_summary_text.delta' && typeof j.delta === 'string') {
onEvent({ type: 'delta_reasoning', reasoning: j.delta })
}
if (j.type === 'response.output_text.delta' && typeof j.delta === 'string') {
onEvent({ type: 'delta_content', content: j.delta })
}
if (
j.type === 'response.function_call_arguments.delta' &&
typeof j.delta === 'string'
) {
onEvent({
type: 'delta_tool_calls',
tool_calls: [{ index: 0, function: { arguments: j.delta } }]
})
}
}
const choices = bareAgentDeepGet(j, 'choices')
const ch0 =
Array.isArray(choices) && choices[0] && typeof choices[0] === 'object'
? /** @type {Record<string, unknown>} */ (choices[0])
: null
const delta =
ch0 && typeof ch0.delta === 'object'
? /** @type {Record<string, unknown>} */ (ch0.delta)
: null
const finishReason =
typeof ch0?.finish_reason === 'string' ? ch0.finish_reason : ''
const usage =
typeof j.usage === 'object' && j.usage ? j.usage : undefined
if (usage) {
onEvent({ type: 'usage', usage })
}
if (delta) {
const c = delta.content
if (typeof c === 'string' && c.length) {
onEvent({
type: 'delta_content',
content: c
})
}
const toolCalls = delta.tool_calls
if (toolCalls !== undefined)
onEvent({
type: 'delta_tool_calls',
tool_calls: toolCalls
})
const rc = delta.reasoning_content
if (typeof rc === 'string' && rc.length) {
onEvent({
type: 'delta_reasoning',
reasoning: rc
})
}
const r = delta.reasoning
if (typeof r === 'string' && r.length) {
onEvent({
type: 'delta_reasoning',
reasoning: r
})
} else if (Array.isArray(r)) {
for (const chunk of r) {
if (!chunk || typeof chunk !== 'object') continue
const ro = /** @type {Record<string, unknown>} */ (chunk)
const tx =
typeof ro.text === 'string'
? ro.text
: typeof ro.content === 'string'
? ro.content
: ''
if (tx) {
onEvent({
type: 'delta_reasoning',
reasoning: tx
})
}
}
}
}
if (finishReason)
onEvent({
type: 'finish_reason',
finish_reason: finishReason
})
}
}
} finally {
try {
reader.releaseLock()
} catch {
/* ignore */
}
}
}
/**
* Non-streaming completion (same endpoint, stream:false).
*/
async function bareAgentCompleteOnce(opts) {
const { fetchFn, url, headers, body, signal } = opts
const res = await fetchFn(url, {
method: 'POST',
headers,
body: JSON.stringify({ ...body, stream: false }),
signal: signal || undefined
})
if (!res.ok) {
let errText = ''
try {
errText = await res.text()
} catch {
/* ignore */
}
throw new Error('HTTP ' + res.status + ' ' + errText.slice(0, 800))
}
const j = /** @type {Record<string, unknown>} */ (await res.json())
return j
}
/** HTTP fetch + HTML extract helpers for agent `web_fetch` tool (preamble for /bin/agent). */
/**
* @param {unknown} e
* @returns {string}
*/
function bareWebFmtErr(e) {
if (e === undefined)
return 'promise_rejected_with_undefined (no rejection reason)'
if (e === null) return 'promise_rejected_with_null'
if (typeof e === 'string') return e
if (typeof e !== 'object') return String(e)
const o = /** @type {Record<string, unknown>} */ (e)
const msg = o.message
if (typeof msg === 'string' && msg.trim())
return bareWebFmtErrAugment(o, msg.trim())
if (typeof msg === 'number' || typeof msg === 'boolean')
return bareWebFmtErrAugment(o, String(msg))
const nm = o.name
const code = o.code
const errno = o.errno
/** @type {string[]} */
const bits = []
if (typeof nm === 'string' && nm.trim()) bits.push(nm)
if (code !== undefined && code !== null && String(code) !== '')
bits.push('code=' + String(code))
if (errno !== undefined && errno !== null && String(errno) !== '')
bits.push('errno=' + String(errno))
const cause = o.cause
if (cause !== undefined && cause !== null && cause !== e) {
const cs = bareWebFmtErr(cause)
if (cs && cs !== 'unknown_error')
bits.push('cause=(' + cs.slice(0, 280) + ')')
}
const errs = o.errors
if (Array.isArray(errs) && errs.length) {
errs.slice(0, 5).forEach((sub, i) => {
bits.push('agg' + i + '=' + bareWebFmtErr(sub).slice(0, 120))
})
}
if (bits.length) return bits.join(' ')
try {
const j = JSON.stringify(o)
if (j && j !== '{}' && j !== '[]') return j.slice(0, 400)
} catch {
/* ignore */
}
try {
if (
typeof /** @type {{ toString?: () => string }} */ (o).toString ===
'function'
) {
const t = /** @type {{ toString: () => string }} */ (o).toString()
if (t && t !== '[object Object]') return t.slice(0, 400)
}
} catch {
/* ignore */
}
return 'unknown_error'
}
/**
* Append errno/syscall from Node-ish errors when message alone is vague.
* @param {Record<string, unknown>} o
* @param {string} base
*/
function bareWebFmtErrAugment(o, base) {
const syscall = o.syscall
const code = o.code
const errno = o.errno
/** @type {string[]} */
const tail = []
if (typeof syscall === 'string' && syscall.trim())
tail.push('syscall=' + syscall)
if (code !== undefined && code !== null && String(code) !== '')
tail.push(String(code))
if (errno !== undefined && errno !== null && String(errno) !== '')
tail.push('errno=' + String(errno))
const cause = o.cause
if (cause !== undefined && cause !== null) {
const cs = bareWebFmtErr(cause)
if (cs && cs !== 'unknown_error')
tail.push('cause=(' + cs.slice(0, 240) + ')')
}
return tail.length ? base + ' [' + tail.join(', ') + ']' : base
}
/**
* Tool args often omit numeric fields; `Number(undefined)` is NaN and `NaN ?? d` is still NaN.
* @param {unknown} n
* @param {number} def
*/
function bareWebFiniteOr(n, def) {
const x = Number(n)
return Number.isFinite(x) ? x : def
}
/**
* @param {Record<string, unknown>} ctx
* @returns {typeof fetch | null}
*/
function bareWebResolveFetch(ctx) {
if (typeof ctx.httpFetch === 'function')
return /** @type {typeof fetch} */ (ctx.httpFetch.bind(ctx))
const bare = ctx.bare && typeof ctx.bare === 'object' ? ctx.bare : null
let f =
bare && typeof bare.fetch === 'function'
? bare.fetch
: bare &&
bare.default &&
typeof bare.default === 'object' &&
typeof bare.default.fetch === 'function'
? bare.default.fetch
: null
if (typeof f === 'function') return /** @type {typeof fetch} */ (f.bind(bare))
if (typeof globalThis.fetch === 'function')
return globalThis.fetch.bind(globalThis)
return null
}
/**
* bundled bare-fetch rejects the fetch promise with `signal.reason` on abort.
* `controller.abort()` with no argument sets `reason === undefined`, so callers
* see `promise_rejected_with_undefined`. Always pass an explicit reason.
* @param {number} timeoutMs
*/
function bareWebTimeoutAbortReason(timeoutMs) {
const msg = 'web_fetch: exceeded ' + timeoutMs + 'ms (timeout)'
try {
if (typeof DOMException === 'function')
return new DOMException(msg, 'TimeoutError')
} catch {
/* ignore */
}
const e = new Error(msg)
e.name = 'TimeoutError'
return e
}
/**
* @param {AbortSignal} sig
*/
function bareWebSignalAbortReason(sig) {
try {
const r = /** @type {{ reason?: unknown }} */ (sig).reason
if (r !== undefined && r !== null) return r
} catch {
/* ignore */
}
const e = new Error('web_fetch aborted (signal)')
e.name = 'AbortError'
return e
}
/**
* @param {AbortSignal | null | undefined} a
* @param {AbortSignal | null | undefined} b
*/
function bareWebUnionAbort(a, b) {
if (!a) return b || undefined
if (!b) return a
if (typeof AbortSignal.any === 'function') return AbortSignal.any([a, b])
const c = new AbortController()
/**
* @param {AbortSignal} sig
*/
const forward = (sig) => {
try {
c.abort(bareWebSignalAbortReason(sig))
} catch {
/* ignore — second source may fire after controller already aborted */
}
}
try {
const as = /** @type {AbortSignal} */ (a)
const bs = /** @type {AbortSignal} */ (b)
if (as.aborted) forward(as)
else as.addEventListener('abort', () => forward(as), { once: true })
if (bs.aborted) forward(bs)
else bs.addEventListener('abort', () => forward(bs), { once: true })
} catch {
/* ignore */
}
return c.signal
}
/**
* @param {Uint8Array[]} parts
*/
function bareWebConcatUint8(parts) {
let n = 0
for (const p of parts) n += p.length
const out = new Uint8Array(n)
let o = 0
for (const p of parts) {
out.set(p, o)
o += p.length
}
return out
}
/**
* @param {Response} res
* @param {number} maxBytes
* @param {AbortSignal | undefined} signal
*/
async function bareWebReadBodyLimited(res, maxBytes, signal) {
if (!res.body || typeof res.body.getReader !== 'function') {
try {
const ab = await res.arrayBuffer()
const u8 = new Uint8Array(ab)
return {
bytes: u8.byteLength > maxBytes ? u8.slice(0, maxBytes) : u8,
truncated: u8.byteLength > maxBytes
}
} catch {
return { bytes: new Uint8Array(0), truncated: false }
}
}
const reader = res.body.getReader()
/** @type {Uint8Array[]} */
const chunks = []
let total = 0
try {
for (;;) {
if (signal && signal.aborted) {
try {
await reader.cancel()
} catch {
/* ignore */
}
break
}
const { done, value } = await reader.read()
if (done) break
if (!value || !value.length) continue
total += value.length
if (total > maxBytes) {
const prev = total - value.length
const take = Math.max(0, maxBytes - prev)
if (take > 0) chunks.push(value.subarray(0, take))
try {
await reader.cancel()
} catch {
/* ignore */
}
return { bytes: bareWebConcatUint8(chunks), truncated: true }
}
chunks.push(value)
}
} finally {
try {
reader.releaseLock()
} catch {
/* ignore */
}
}
return { bytes: bareWebConcatUint8(chunks), truncated: false }
}
/**
* @param {string | null | undefined} ct
*/
function bareWebCharsetFromContentType(ct) {
const m = /charset\s*=\s*["']?([^"';\s]+)/i.exec(String(ct || ''))
return (m ? m[1] : 'utf-8').trim().toLowerCase()
}
/**
* @param {Uint8Array} bytes
* @param {string} label
*/
function bareWebDecodeBytes(bytes, label) {
try {
const dec = new TextDecoder(label || 'utf-8', {
fatal: false,
ignoreBOM: true
})
return dec.decode(bytes)
} catch {
return new TextDecoder('utf-8', { fatal: false }).decode(bytes)
}
}
/**
* @param {string} s
*/
function bareWebDecodeHtmlEntities(s) {
let t = String(s || '')
t = t.replace(/&nbsp;/gi, ' ')
t = t.replace(/&quot;/gi, '"')
t = t.replace(/&#39;/g, "'")
t = t.replace(/&apos;/gi, "'")
t = t.replace(/&amp;/gi, '&')
t = t.replace(/&lt;/gi, '<')
t = t.replace(/&gt;/gi, '>')
t = t.replace(/&#x([0-9a-f]+);/gi, (_, h) => {
const c = parseInt(h, 16)
return Number.isFinite(c) ? String.fromCodePoint(c) : _
})
t = t.replace(/&#(\d+);/g, (_, d) => {
const c = parseInt(d, 10)
return Number.isFinite(c) ? String.fromCodePoint(c) : _
})
return t
}
/**
* @param {string} html
*/
function bareWebExtractHtmlText(html) {
let s = String(html || '')
s = s.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
s = s.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
s = s.replace(/<noscript\b[^<]*(?:(?!<\/noscript>)<[^<]*)*<\/noscript>/gi, '')
s = s.replace(/<!--[\s\S]*?-->/g, '')
s = s.replace(/<[^>]+>/g, ' ')
s = bareWebDecodeHtmlEntities(s)
s = s.replace(/\s+/g, ' ').trim()
return s
}
/**
* @param {string} html
* @param {string} baseUrl
* @param {number} maxLinks
*/
function bareWebExtractLinks(html, baseUrl, maxLinks) {
const cap = Math.min(Math.max(Number(maxLinks) || 200, 1), 500)
let uBase = null
try {
uBase = baseUrl ? new URL(String(baseUrl)) : null
} catch {
uBase = null
}
const seen = new Set()
/** @type {string[]} */
const out = []
const re = /<a\b[^>]*\bhref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi
let m
const h = String(html || '')
while ((m = re.exec(h)) !== null) {
const href = (m[1] || m[2] || m[3] || '').trim()
if (!href || href.startsWith('javascript:') || href.startsWith('#'))
continue
try {
const abs = uBase ? new URL(href, uBase).href : new URL(href).href
const proto = new URL(abs).protocol
if (proto !== 'http:' && proto !== 'https:') continue
if (!seen.has(abs)) {
seen.add(abs)
out.push(abs)
}
} catch {
/* skip */
}
if (out.length >= cap) break
}
return { links: out, links_truncated: out.length >= cap }
}
/**
* @param {string} tag
*/
function bareWebMetaContent(tag) {
const q =
/content\s*=\s*"([^"]*)"/i.exec(tag) ||
/content\s*=\s*'([^']*)'/i.exec(tag) ||
/content\s*=\s*([^\s>]+)/i.exec(tag)
return q ? bareWebDecodeHtmlEntities(q[1]).trim() : ''
}
/**
* @param {string} html
*/
function bareWebExtractMeta(html) {
const h = String(html || '')
const titleM = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(h)
const title = titleM
? bareWebDecodeHtmlEntities(titleM[1].replace(/<[^>]+>/g, ' ')).trim()
: ''
let description = ''
const metaDescRe = /<meta[^>]*\bname\s*=\s*["']description["'][^>]*>/i.exec(h)
if (metaDescRe) description = bareWebMetaContent(metaDescRe[0])
let og_title = ''
const ogT = /<meta[^>]*\bproperty\s*=\s*["']og:title["'][^>]*>/i.exec(h)
if (ogT) og_title = bareWebMetaContent(ogT[0])
let og_description = ''
const ogD = /<meta[^>]*\bproperty\s*=\s*["']og:description["'][^>]*>/i.exec(h)
if (ogD) og_description = bareWebMetaContent(ogD[0])
return {
title,
description,
og_title,
og_description
}
}
/**
* @param {string} text
*/
function bareWebMaybeParseJson(text) {
try {
return { ok: true, value: JSON.parse(String(text)) }
} catch {
return { ok: false }
}
}
/**
* @param {string} ct
*/
function bareWebLooksLikeHtml(ct) {
return /\btext\/html\b/i.test(String(ct || ''))
}
/**
* @param {string} ct
*/
function bareWebLooksLikeJson(ct) {
const s = String(ct || '').toLowerCase()
return (
/\bapplication\/json\b/.test(s) ||
/\bapplication\/.*\+json\b/.test(s) ||
/\btext\/json\b/.test(s)
)
}
/**
* @param {{
* ctx: Record<string, unknown>,
* url: string,
* method?: string,
* headers?: Record<string, unknown>,
* body?: string,
* content_type?: string,
* max_response_bytes?: number,
* max_redirects?: number,
* timeout_ms?: number,
* format?: string,
* max_links?: number,
* signal?: AbortSignal | null
* }} o
*/
async function bareWebRunTool(o) {
const ctx = o.ctx
const fetchFn = bareWebResolveFetch(ctx)
if (!fetchFn) {
return {
ok: false,
error:
'web_fetch: no HTTP client (set ctx.httpFetch, bare.fetch, or global fetch)'
}
}
let startUrl = String(o.url || '').trim()
let method = String(o.method || 'GET').toUpperCase()
if (
!['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'].includes(
method
)
) {
return { ok: false, error: 'web_fetch: unsupported method' }
}
let body =
o.body != null && method !== 'GET' && method !== 'HEAD'
? String(o.body)
: undefined
let u0
try {
u0 = new URL(startUrl)
} catch {
return { ok: false, error: 'web_fetch: invalid URL' }
}
if (u0.protocol !== 'http:' && u0.protocol !== 'https:') {
return { ok: false, error: 'web_fetch: only http(s) URLs are allowed' }
}
const maxRedirects = Math.min(
Math.max(bareWebFiniteOr(o.max_redirects, 5), 0),
20
)
const maxBytes = Math.min(
Math.max(bareWebFiniteOr(o.max_response_bytes, 524288), 1024),
2 * 1024 * 1024
)
const timeoutMs = Math.min(
Math.max(bareWebFiniteOr(o.timeout_ms, 30000), 500),
120000
)
const fmtRaw = String(o.format || 'auto').toLowerCase()
const maxLinks = Number(o.max_links) || 200
/** @type {string[]} */
const redirectChain = [startUrl]
let currentUrl = startUrl
let redirectsUsed = 0
for (;;) {
const controller = new AbortController()
const timer = setTimeout(() => {
try {
controller.abort(bareWebTimeoutAbortReason(timeoutMs))
} catch {
/* ignore */
}
}, timeoutMs)
const signal = bareWebUnionAbort(o.signal || undefined, controller.signal)
/** @type {Record<string, string>} */
const hdrObj = {}
const hin = o.headers
if (hin && typeof hin === 'object' && !Array.isArray(hin)) {
for (const [k, v] of Object.entries(hin)) {
if (typeof v === 'string' && k) hdrObj[k] = v
}
}
if (
body != null &&
method !== 'GET' &&
method !== 'HEAD' &&
!Object.keys(hdrObj).some((k) => k.toLowerCase() === 'content-type')
) {
hdrObj['Content-Type'] =
typeof o.content_type === 'string' && o.content_type.trim()
? o.content_type.trim()
: 'application/octet-stream'
}
/** @type {RequestInit} */
const init = {
method,
headers: hdrObj,
signal: signal || undefined,
redirect: 'manual'
}
if (body != null && method !== 'GET' && method !== 'HEAD') {
init.body = body
}
let res
try {
res = await fetchFn(currentUrl, init)
} catch (e) {
clearTimeout(timer)
const msg = bareWebFmtErr(
e === undefined
? new Error(
'web_fetch: fetch rejected with undefined (bare-fetch uses signal.reason; upstream abort() had no reason)'
)
: e
)
return {
ok: false,
error: 'web_fetch: request failed: ' + msg.slice(0, 400),
url_final: currentUrl,
redirect_chain: redirectChain
}
}
clearTimeout(timer)
const st = res.status
if (st >= 300 && st < 400) {
if (redirectsUsed >= maxRedirects) {
return {
ok: false,
error: 'web_fetch: too many redirects',
status: st,
url_final: currentUrl,
redirect_chain: redirectChain
}
}
const loc = res.headers.get('Location')
if (!loc) {
return {
ok: false,
error: 'web_fetch: redirect without Location',
status: st,
url_final: currentUrl,
redirect_chain: redirectChain
}
}
let nextUrl
try {
nextUrl = new URL(loc, currentUrl).href
} catch {
return {
ok: false,
error: 'web_fetch: bad redirect URL',
status: st,
url_final: currentUrl,
redirect_chain: redirectChain
}
}
redirectChain.push(nextUrl)
currentUrl = nextUrl
redirectsUsed++
if (st === 301 || st === 302 || st === 303) {
method = 'GET'
body = undefined
}
continue
}
const ct = res.headers.get('content-type') || ''
const url_final = currentUrl
const responseType = typeof res.type === 'string' ? res.type : undefined
let setCookie
try {
const hdrs = res.headers
if (hdrs && typeof hdrs.getSetCookie === 'function') {
const sc = hdrs.getSetCookie()
if (Array.isArray(sc) && sc.length) setCookie = sc.slice(0, 32)
}
} catch {
setCookie = undefined
}
if (method === 'HEAD') {
return {
ok: true,
url_final,
status: st,
content_type: ct,
response_type: responseType,
set_cookie: setCookie,
redirect_chain: redirectChain.length > 1 ? redirectChain : undefined,
truncated: false,
extract: { note: 'HEAD — body omitted' }
}
}
let bodyRead
try {
bodyRead = await bareWebReadBodyLimited(
res,
maxBytes,
signal || undefined
)
} catch (e) {
const msg = bareWebFmtErr(e)
return {
ok: false,
error: 'web_fetch: read body failed: ' + msg.slice(0, 400),
url_final,
status: st,
redirect_chain: redirectChain.length > 1 ? redirectChain : undefined
}
}
const charset = bareWebCharsetFromContentType(ct)
const text = bareWebDecodeBytes(bodyRead.bytes, charset)
const fmt =
fmtRaw === 'auto'
? bareWebLooksLikeJson(ct)
? 'json'
: bareWebLooksLikeHtml(ct)
? 'markdownish'
: 'raw'
: fmtRaw
/** @type {unknown} */
let extract
if (fmt === 'json') {
const p = bareWebMaybeParseJson(text)
extract = p.ok
? { json: p.value }
: { parse_error: true, text_slice: text.slice(0, 8000) }
} else if (fmt === 'links') {
extract = bareWebExtractLinks(text, url_final, maxLinks)
} else if (fmt === 'meta') {
extract = bareWebExtractMeta(text)
} else if (fmt === 'markdownish' || fmt === 'text') {
const plain = bareWebExtractHtmlText(text)
extract = {
text: plain,
approx_chars: plain.length
}
} else if (fmt === 'raw') {
extract = {
raw_text: text.length > 12000 ? text.slice(0, 12000) + '\n…' : text,
char_count: text.length
}
} else {
extract = {
text: text.length > 12000 ? text.slice(0, 12000) + '\n…' : text,
char_count: text.length
}
}
const raw_preview = text.slice(0, 2000)
return {
ok: true,
url_final,
status: st,
content_type: ct,
response_type: responseType,
set_cookie: setCookie,
redirect_chain: redirectChain.length > 1 ? redirectChain : undefined,
truncated: bodyRead.truncated,
extract,
raw_preview: fmt === 'raw' || fmt === 'json' ? undefined : raw_preview
}
}
}
/**
* Guest-safe Grok Build harness ports: todos, plan mode, memory, glob,
* AGENTS.md walk-up, JSON hooks, and search_replace uniqueness.
*/
var BARE_AGENT_PLAN_READONLY_TOOLS = Object.freeze({
read_file: 1,
list_directory: 1,
list_dir: 1,
file_stat: 1,
search_files: 1,
grep: 1,
glob_files: 1,
glob: 1,
get_system_info: 1,
list_bin: 1,
read_man_page: 1,
apropos_man: 1,
read_proc_file: 1,
get_swarm_peers: 1,
get_resource_limits: 1,
web_fetch: 1,
web_search: 1,
git_status: 1,
read_skill: 1,
list_services: 1,
service_status: 1,
list_timers: 1,
read_cron_log: 1,
read_audit_log: 1,
read_boot_policy: 1,
read_kernel_extension_resolution: 1,
get_initd_graph: 1,
read_unit_journal: 1,
inspect_ipc_backpressure: 1,
get_network_summary: 1,
tail_telemetry_streams: 1,
pkg_index_lookup: 1,
list_verification_scripts: 1,
verification_hints: 1,
runtime_diagnostic_bundle: 1,
memory_search: 1,
memory_get: 1,
memory_append: 1,
list_skills: 1,
fuzzy_find: 1,
wait_for: 1,
read_many: 1,
git_diff: 1,
git_log: 1,
git_show: 1,
git_blame: 1,
history_search: 1,
list_scheduled: 1,
find_symbol: 1,
diff_files: 1,
export_session: 1,
remember: 1,
todo_write: 1,
enter_plan_mode: 1,
exit_plan_mode: 1,
ask_user_question: 1,
update_goal: 1,
autonomous_run_status: 1,
get_hrpc_bridge_health: 1,
get_hrpc_allowlist_status: 1
})
var BARE_AGENT_PLAN_WRITE_TOOLS = Object.freeze({
write_file: 1,
edit_file: 1,
search_replace: 1,
apply_patch: 1
})
/**
* @param {string} pattern
* @returns {RegExp | null}
*/
function bareAgentGlobToRegExp(pattern) {
const src = String(pattern || '').trim()
if (!src) return null
let out = '^'
for (let i = 0; i < src.length; i++) {
const c = src.charAt(i)
if (c === '*' && src.charAt(i + 1) === '*') {
const next = src.charAt(i + 2)
if (next === '/' || next === '') {
out += '.*'
i += next === '/' ? 2 : 1
continue
}
}
if (c === '*') {
out += '[^/]*'
continue
}
if (c === '?') {
out += '[^/]'
continue
}
if ('\\^$+()[]{}|.'.indexOf(c) !== -1) out += '\\' + c
else out += c
}
out += '$'
try {
return new RegExp(out)
} catch {
return null
}
}
/**
* @param {string} rel
* @param {string} pattern
*/
function bareAgentGlobMatch(rel, pattern) {
const re = bareAgentGlobToRegExp(pattern)
if (!re) return false
const n = String(rel || '').replace(/^\/+/, '')
if (re.test(n)) return true
const base = n.split('/').pop() || n
return re.test(base)
}
/**
* @param {unknown} updates
* @param {{ merge?: boolean }} [opts]
* @param {{ id: string, content: string, status: string }[]} [prev]
*/
function bareAgentTodoApply(updates, opts, prev) {
const merge = Boolean(opts && opts.merge)
/** @type {{ id: string, content: string, status: string }[]} */
const list = merge && Array.isArray(prev) ? prev.slice() : []
const byId = Object.create(null)
for (let i = 0; i < list.length; i++) byId[list[i].id] = i
const rows = Array.isArray(updates) ? updates : []
const seen = Object.create(null)
for (let i = 0; i < rows.length; i++) {
const row = rows[i] && typeof rows[i] === 'object' ? rows[i] : {}
const id = String(row.id || '').trim()
if (!id) throw new Error('todo_id_required')
if (seen[id]) throw new Error('duplicate_todo_id:' + id)
seen[id] = 1
const statusRaw = String(row.status || 'pending').trim().toLowerCase()
const status =
statusRaw === 'in_progress' || statusRaw === 'completed' || statusRaw === 'cancelled'
? statusRaw
: 'pending'
const content = String(row.content == null ? '' : row.content).trim()
if (byId[id] != null) {
const cur = list[byId[id]]
if (content) cur.content = content
cur.status = status
} else {
list.push({ id, content: content || id, status })
byId[id] = list.length - 1
}
}
return list
}
/**
* @param {{ id: string, content: string, status: string }[]} todos
*/
function bareAgentTodoSummarize(todos) {
const rows = Array.isArray(todos) ? todos : []
let pending = 0
let inProgress = 0
let completed = 0
const lines = []
for (let i = 0; i < rows.length; i++) {
const t = rows[i]
const st = String(t.status || 'pending')
if (st === 'completed' || st === 'cancelled') completed++
else if (st === 'in_progress') inProgress++
else pending++
lines.push('- [' + st + '] ' + t.id + ': ' + String(t.content || '').slice(0, 160))
}
return {
total: rows.length,
pending,
in_progress: inProgress,
completed,
open: pending + inProgress,
text: lines.join('\n')
}
}
/**
* @param {{ open?: number, turnsSinceTodoWrite?: number, nudgeEnabled?: boolean }} opts
*/
function bareAgentTodoNudgeText(opts) {
const o = opts && typeof opts === 'object' ? opts : {}
if (o.nudgeEnabled === false) return ''
const turns = Number(o.turnsSinceTodoWrite) || 0
const open = Number(o.open) || 0
if (open > 0 && turns >= 3) {
return (
'Open todos remain (' +
String(open) +
'). Use todo_write to mark progress or complete items before stopping.'
)
}
if (open === 0 && turns >= 5) {
return 'Multi-step work: call todo_write to track remaining steps (merge=true to update one id).'
}
return ''
}
/**
* @param {string} toolName
* @param {Record<string, unknown>} args
* @param {{ plan?: string }} paths
*/
function bareAgentPlanModeToolAllowed(toolName, args, paths) {
const name = String(toolName || '')
if (BARE_AGENT_PLAN_READONLY_TOOLS[name]) return true
if (!BARE_AGENT_PLAN_WRITE_TOOLS[name]) return false
const plan = String((paths && paths.plan) || '')
const p = String(args.path || args.file_path || '')
return Boolean(plan && p && p === plan)
}
/**
* @param {string} hay
* @param {string} needle
*/
function bareAgentCountOccurrences(hay, needle) {
if (!needle) return 0
let n = 0
let from = 0
while (from <= hay.length) {
const i = hay.indexOf(needle, from)
if (i === -1) break
n++
from = i + Math.max(needle.length, 1)
}
return n
}
/**
* @param {string} text
* @param {string} oldStr
* @param {string} newStr
* @param {boolean} replaceAll
*/
function bareAgentSearchReplaceApply(text, oldStr, newStr, replaceAll) {
const prev = String(text == null ? '' : text)
if (!oldStr) {
if (prev.trim()) {
return { ok: false, error: 'empty_old_string_cannot_overwrite' }
}
return { ok: true, next: String(newStr == null ? '' : newStr), replacements: 1 }
}
const count = bareAgentCountOccurrences(prev, oldStr)
if (count === 0) return { ok: false, error: 'old_string not found', count: 0 }
if (count > 1 && !replaceAll) {
return {
ok: false,
error: 'old_string not unique',
count,
hint: 'add surrounding lines to make the match unique, or set replace_all true'
}
}
const next = replaceAll ? prev.split(oldStr).join(newStr) : prev.replace(oldStr, newStr)
return { ok: true, next, replacements: replaceAll ? count : 1 }
}
/**
* @param {string} text
* @param {{ offset?: number, limit?: number, numbered?: boolean }} [opts]
*/
function bareAgentSliceFileLines(text, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const raw = String(text == null ? '' : text)
const lines = raw.split('\n')
if (lines.length && lines[lines.length - 1] === '') lines.pop()
const offset =
typeof o.offset === 'number' && o.offset >= 1 ? Math.floor(o.offset) : 1
const limit =
typeof o.limit === 'number' && o.limit >= 1
? Math.floor(o.limit)
: lines.length
const start = Math.min(lines.length, offset - 1)
const end = Math.min(lines.length, start + limit)
const slice = lines.slice(start, end)
const numbered = o.numbered !== false
const body = numbered
? slice
.map(function (line, i) {
return String(start + i + 1) + '→' + line
})
.join('\n')
: slice.join('\n')
return {
content: body,
start_line: start + 1,
end_line: end,
total_lines: lines.length,
truncated: start > 0 || end < lines.length
}
}
/**
* @param {string} query
*/
function bareAgentMemoryTokens(query) {
return String(query || '')
.toLowerCase()
.split(/[^a-z0-9_./-]+/)
.filter(function (t) {
return t.length > 1
})
}
/**
* @param {string} text
* @param {string[]} tokens
*/
function bareAgentMemoryScore(text, tokens) {
const low = String(text || '').toLowerCase()
if (!tokens.length || !low) return 0
let hits = 0
for (let i = 0; i < tokens.length; i++) {
if (low.indexOf(tokens[i]) !== -1) hits++
}
return hits / tokens.length
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} startDir
* @param {number} [maxHops]
* @returns {Promise<string[]>}
*/
async function bareAgentDiscoverAgentsMdPaths(ctx, startDir, maxHops) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return []
/** @type {string[]} */
const found = []
const seen = Object.create(null)
let dir = String(startDir || '').replace(/\/+$/, '') || '/'
const hops = Math.min(Math.max(Number(maxHops) || 12, 1), 24)
const names = ['AGENTS.md', 'Claude.md', 'CLAUDE.md']
for (let n = 0; n < hops; n++) {
for (let i = 0; i < names.length; i++) {
const p = (dir === '/' ? '' : dir) + '/' + names[i]
if (seen[p]) continue
seen[p] = 1
try {
const buf = await vfs.readFile(p)
if (buf && buf.length) found.push(p)
} catch {
/* missing */
}
}
const rulesDirs = [dir + '/.grok/rules', dir + '/.agent/rules']
for (let r = 0; r < rulesDirs.length; r++) {
if (typeof vfs.readdir !== 'function') continue
let entries = []
try {
entries = await vfs.readdir(rulesDirs[r])
} catch {
continue
}
if (!Array.isArray(entries)) continue
entries = entries.slice().sort()
for (let e = 0; e < entries.length; e++) {
const name = String(entries[e] || '')
if (!/\.md$/i.test(name)) continue
const p = rulesDirs[r] + '/' + name
if (seen[p]) continue
seen[p] = 1
try {
const buf = await vfs.readFile(p)
if (buf && buf.length) found.push(p)
} catch {
/* skip */
}
}
}
if (dir === '/' || dir === '') break
const slash = dir.lastIndexOf('/')
dir = slash <= 0 ? '/' : dir.slice(0, slash)
}
return found
}
/**
* @param {Record<string, unknown>} ctx
* @param {string[]} files
* @param {number} [maxChars]
*/
async function bareAgentLoadAgentsMdPrompt(ctx, files, maxChars) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return ''
const cap = Math.min(Math.max(Number(maxChars) || 6000, 500), 16000)
const parts = []
let used = 0
for (let i = 0; i < files.length; i++) {
if (used >= cap) break
try {
const buf = await vfs.readFile(files[i])
if (!buf || !buf.length) continue
const t =
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
const body = String(t || '').trim()
if (!body) continue
const block = '### ' + files[i] + '\n' + body
const room = cap - used
parts.push(block.slice(0, room))
used += Math.min(block.length, room)
} catch {
/* skip */
}
}
if (!parts.length) return ''
return (
'## Discovered project agent files (cwd walk-up, Grok-style)\n' +
parts.join('\n\n')
)
}
/**
* @param {Record<string, unknown>} hook
* @param {string} toolName
* @param {Record<string, unknown>} args
*/
function bareAgentHookDenies(hook, toolName, args) {
if (!hook || typeof hook !== 'object') return ''
const event = String(hook.event || hook.type || 'PreToolUse')
if (event !== 'PreToolUse' && event !== 'pre') return ''
const tools = Array.isArray(hook.tools) ? hook.tools.map(String) : []
if (tools.length && tools.indexOf(toolName) === -1) return ''
const reSrc = String(hook.deny_regex || hook.denyRegex || '').trim()
if (!reSrc) return ''
let re
try {
re = new RegExp(reSrc, 'i')
} catch {
return ''
}
const blob =
toolName +
' ' +
String(args.command || args.path || args.file_path || args.pattern || '')
if (!re.test(blob)) return ''
return String(hook.reason || hook.message || 'blocked by ~/.agent/hooks').slice(0, 240)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} hooksDir
* @param {string} toolName
* @param {Record<string, unknown>} args
*/
async function bareAgentRunPreToolHooks(ctx, hooksDir, toolName, args) {
if (typeof bareAgentRunHooks === 'function') {
const out = await bareAgentRunHooks(ctx, hooksDir, 'PreToolUse', toolName, args, '')
return out.deny || ''
}
return ''
}
/**
* @param {Record<string, unknown>} ctx
* @param {unknown} buf
*/
function bareAgentPortDecode(ctx, buf) {
if (!buf || !buf.length) return ''
if (
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
) {
return ctx.b4a.toString(buf)
}
return String(new TextDecoder().decode(buf))
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} text
*/
function bareAgentPortEncode(ctx, text) {
if (
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.from === 'function'
) {
return ctx.b4a.from(String(text == null ? '' : text))
}
return new TextEncoder().encode(String(text == null ? '' : text))
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
*/
async function bareAgentReadTextFile(ctx, path) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return ''
try {
const buf = await vfs.readFile(path)
return bareAgentPortDecode(ctx, buf)
} catch {
return ''
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {string} text
*/
async function bareAgentWriteTextFile(ctx, path, text) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.writeFile !== 'function') {
throw new Error('vfs.writeFile unavailable')
}
if (typeof vfs.mkdir === 'function') {
const dir = String(path || '').replace(/\/[^/]+$/, '')
if (dir && dir !== path) {
try {
await vfs.mkdir(dir, { recursive: true })
} catch {
/* parent may already exist */
}
}
}
await vfs.writeFile(path, bareAgentPortEncode(ctx, text))
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {unknown} fallback
*/
async function bareAgentReadJsonFile(ctx, path, fallback) {
const t = await bareAgentReadTextFile(ctx, path)
if (!t.trim()) return fallback
try {
return JSON.parse(t)
} catch {
return fallback
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {unknown} value
*/
async function bareAgentWriteJsonFile(ctx, path, value) {
await bareAgentWriteTextFile(ctx, path, JSON.stringify(value, null, 2) + '\n')
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
*/
async function bareAgentVfsIsDir(ctx, path) {
const vfs = ctx && ctx.vfs
if (!vfs) return false
if (typeof vfs.lstat === 'function' || typeof vfs.stat === 'function') {
try {
const st =
typeof vfs.lstat === 'function' ? await vfs.lstat(path) : await vfs.stat(path)
if (st && typeof st.isDirectory === 'function') return Boolean(st.isDirectory())
if (st && typeof st === 'object' && 'isDirectory' in st) {
return Boolean(/** @type {{ isDirectory?: unknown }} */ (st).isDirectory)
}
} catch {
return false
}
}
if (typeof vfs.readdir === 'function') {
try {
await vfs.readdir(path)
return true
} catch {
return false
}
}
return false
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} root
* @param {{ maxFiles?: number, maxDepth?: number }} [opts]
* @returns {Promise<string[]>}
*/
async function bareAgentVfsWalkFiles(ctx, root, opts) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readdir !== 'function') return []
const maxFiles = Math.min(Math.max(Number(opts && opts.maxFiles) || 400, 1), 4000)
const maxDepth = Math.min(Math.max(Number(opts && opts.maxDepth) || 8, 1), 16)
const skip = Object.create(null)
skip['.git'] = 1
skip['node_modules'] = 1
skip['.bare-os'] = 1
const rootN = String(root || '/').replace(/\/+$/, '') || '/'
const rules =
opts && Array.isArray(opts.ignore)
? opts.ignore
: typeof bareAgentLoadIgnoreRules === 'function'
? await bareAgentLoadIgnoreRules(ctx, rootN)
: []
/** @type {string[]} */
const out = []
/** @type {{ dir: string, depth: number }[]} */
const queue = [{ dir: rootN, depth: 0 }]
while (queue.length && out.length < maxFiles) {
const cur = queue.shift()
if (!cur) break
let names
try {
names = await vfs.readdir(cur.dir)
} catch {
continue
}
if (!Array.isArray(names)) continue
for (let i = 0; i < names.length && out.length < maxFiles; i++) {
const name = String(names[i] || '')
if (!name || name === '.' || name === '..' || skip[name]) continue
const full = (cur.dir === '/' ? '' : cur.dir) + '/' + name
let rel = full
if (rootN !== '/' && full.indexOf(rootN + '/') === 0) rel = full.slice(rootN.length + 1)
else if (full.charAt(0) === '/') rel = full.slice(1)
const isDir = await bareAgentVfsIsDir(ctx, full)
if (rules.length && bareAgentIgnoreMatch(rel, isDir, rules)) continue
if (isDir) {
if (cur.depth + 1 < maxDepth) queue.push({ dir: full, depth: cur.depth + 1 })
} else {
out.push(full)
}
}
}
return out
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} root
* @param {string} pattern
* @param {{ maxFiles?: number, maxDepth?: number }} [opts]
*/
async function bareAgentGlobFiles(ctx, root, pattern, opts) {
const files = await bareAgentVfsWalkFiles(ctx, root, opts)
const rootN = String(root || '/').replace(/\/+$/, '') || '/'
/** @type {string[]} */
const hits = []
for (let i = 0; i < files.length; i++) {
const abs = files[i]
let rel = abs
if (rootN !== '/' && abs.indexOf(rootN + '/') === 0) rel = abs.slice(rootN.length + 1)
else if (abs.charAt(0) === '/') rel = abs.slice(1)
if (bareAgentGlobMatch(rel, pattern) || bareAgentGlobMatch(abs, pattern)) {
hits.push(abs)
}
}
return hits
}
/**
* @param {Record<string, unknown>} ctx
* @param {string[]} files
* @param {string} query
* @param {{ maxHits?: number, snippet?: number }} [opts]
*/
async function bareAgentMemorySearchFiles(ctx, files, query, opts) {
const tokens = bareAgentMemoryTokens(query)
const maxHits = Math.min(Math.max(Number(opts && opts.maxHits) || 8, 1), 24)
const snippet = Math.min(Math.max(Number(opts && opts.snippet) || 280, 80), 1200)
/** @type {{ path: string, score: number, snippet: string }[]} */
const scored = []
for (let i = 0; i < files.length; i++) {
const path = files[i]
const text = await bareAgentReadTextFile(ctx, path)
if (!text) continue
const score = tokens.length ? bareAgentMemoryScore(text, tokens) : 0.15
if (score <= 0) continue
let snip = text.trim().replace(/\s+/g, ' ').slice(0, snippet)
if (tokens.length) {
const low = text.toLowerCase()
let at = -1
for (let t = 0; t < tokens.length; t++) {
const j = low.indexOf(tokens[t])
if (j !== -1 && (at === -1 || j < at)) at = j
}
if (at >= 0) {
const start = Math.max(0, at - 40)
snip =
(start > 0 ? '…' : '') +
text.slice(start, start + snippet).replace(/\s+/g, ' ')
}
}
scored.push({ path, score: Math.round(score * 1000) / 1000, snippet: snip })
}
scored.sort(function (a, b) {
return b.score - a.score
})
return scored.slice(0, maxHits)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
*/
async function bareAgentLoadTodos(ctx, path) {
const raw = await bareAgentReadJsonFile(ctx, path, [])
if (!Array.isArray(raw)) return []
/** @type {{ id: string, content: string, status: string }[]} */
const out = []
for (let i = 0; i < raw.length; i++) {
const row = raw[i] && typeof raw[i] === 'object' ? raw[i] : null
if (!row) continue
const id = String(row.id || '').trim()
if (!id) continue
const statusRaw = String(row.status || 'pending').trim().toLowerCase()
const status =
statusRaw === 'in_progress' || statusRaw === 'completed' || statusRaw === 'cancelled'
? statusRaw
: 'pending'
out.push({
id,
content: String(row.content == null ? id : row.content),
status
})
}
return out
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {{ id: string, content: string, status: string }[]} todos
*/
async function bareAgentSaveTodos(ctx, path, todos) {
await bareAgentWriteJsonFile(ctx, path, Array.isArray(todos) ? todos : [])
}
/**
* @param {Record<string, unknown>} args
*/
function bareAgentToolPathArg(args) {
if (!args || typeof args !== 'object') return ''
if (typeof args.path === 'string' && args.path.trim()) return args.path.trim()
if (typeof args.file_path === 'string' && args.file_path.trim()) {
return args.file_path.trim()
}
return ''
}
/**
* @param {string} text
*/
function bareAgentLooksBinaryText(text) {
const s = String(text || '')
if (!s) return false
if (s.indexOf('\0') !== -1) return true
let bad = 0
const n = Math.min(s.length, 800)
for (let i = 0; i < n; i++) {
const c = s.charCodeAt(i)
if (c === 9 || c === 10 || c === 13) continue
if (c < 32) bad++
}
return bad > 8
}
/**
* Guest-native grep (Grok-style). VFS walk + JS regex — no host rg/node.
* @param {Record<string, unknown>} ctx
* @param {{
* pattern: string,
* root?: string,
* glob?: string,
* ignore_case?: boolean,
* before?: number,
* after?: number,
* context?: number,
* max_matches?: number,
* files_with_matches?: boolean
* }} opts
*/
async function bareAgentGrepFiles(ctx, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const pattern = String(o.pattern || '')
if (!pattern) return { ok: false, error: 'pattern_required' }
let re
try {
re = new RegExp(pattern, o.ignore_case ? 'i' : '')
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return { ok: false, error: 'bad_regex', detail: msg }
}
const root = String(o.root || '/').replace(/\/+$/, '') || '/'
const before = Math.min(
8,
Math.max(0, Math.floor(Number(o.before != null ? o.before : o.context) || 0))
)
const after = Math.min(
8,
Math.max(0, Math.floor(Number(o.after != null ? o.after : o.context) || 0))
)
const maxMatches = Math.min(
200,
Math.max(1, Math.floor(Number(o.max_matches) || 50))
)
const mode = String(o.output_mode || '').toLowerCase()
const filesOnly = Boolean(o.files_with_matches) || mode === 'files_with_matches'
const countOnly = Boolean(o.count) || mode === 'count'
const glob = typeof o.glob === 'string' && o.glob.trim() ? o.glob.trim() : ''
const files = glob
? await bareAgentGlobFiles(ctx, root, glob, { maxFiles: 800, maxDepth: 12 })
: await bareAgentVfsWalkFiles(ctx, root, { maxFiles: 800, maxDepth: 12 })
/** @type {{ path: string, line?: number, text?: string }[]} */
const matches = []
/** @type {string[]} */
const filesHit = []
/** @type {{ path: string, count: number }[]} */
const counts = []
let truncated = false
for (let f = 0; f < files.length; f++) {
const path = files[f]
const text = await bareAgentReadTextFile(ctx, path)
if (!text || bareAgentLooksBinaryText(text)) continue
const lines = text.split('\n')
if (lines.length && lines[lines.length - 1] === '') lines.pop()
let fileHit = false
let fileCount = 0
for (let i = 0; i < lines.length; i++) {
re.lastIndex = 0
if (!re.test(lines[i])) continue
fileHit = true
fileCount++
if (filesOnly || countOnly) continue
const start = Math.max(0, i - before)
const end = Math.min(lines.length, i + 1 + after)
const slice = lines.slice(start, end)
const body =
before || after
? slice
.map(function (ln, j) {
const n = start + j + 1
const mark = n === i + 1 ? ':' : '-'
return String(n) + mark + ln
})
.join('\n')
: String(i + 1) + ':' + lines[i]
matches.push({ path, line: i + 1, text: body })
if (matches.length >= maxMatches) {
truncated = true
break
}
}
if (fileHit && filesOnly) {
filesHit.push(path)
if (filesHit.length >= maxMatches) {
truncated = true
break
}
}
if (fileHit && countOnly) {
counts.push({ path, count: fileCount })
if (counts.length >= maxMatches) {
truncated = true
break
}
}
if (truncated) break
}
if (filesOnly) {
return {
ok: true,
pattern,
root,
glob: glob || null,
output_mode: 'files_with_matches',
files_with_matches: filesHit,
count: filesHit.length,
truncated
}
}
if (countOnly) {
let total = 0
for (let i = 0; i < counts.length; i++) total += counts[i].count
return {
ok: true,
pattern,
root,
glob: glob || null,
output_mode: 'count',
files: counts,
count: total,
truncated
}
}
return {
ok: true,
pattern,
root,
glob: glob || null,
output_mode: 'content',
matches,
count: matches.length,
truncated
}
}
/**
* Codex / Grok apply_patch parser (Begin Patch … End Patch).
* @param {string} text
* @returns {{ ok: boolean, error?: string, ops?: object[] }}
*/
function bareAgentParseApplyPatch(text) {
const raw = String(text || '').replace(/\r\n/g, '\n')
if (!raw.trim()) return { ok: false, error: 'empty_patch' }
const all = raw.split('\n')
let start = 0
let end = all.length
for (let i = 0; i < all.length; i++) {
if (/^\s*\*\*\*\s*Begin Patch\s*$/i.test(all[i])) {
start = i + 1
break
}
}
for (let i = all.length - 1; i >= 0; i--) {
if (/^\s*\*\*\*\s*End Patch\s*$/i.test(all[i])) {
end = i
break
}
}
const body = all.slice(start, end)
/** @type {object[]} */
const ops = []
/** @type {Record<string, unknown> | null} */
let cur = null
/** @type {{ context: string, old_lines: string[], new_lines: string[], is_end_of_file?: boolean } | null} */
let chunk = null
function flushChunk() {
if (cur && cur.type === 'update' && chunk) {
const chunks = Array.isArray(cur.chunks) ? cur.chunks : []
chunks.push(chunk)
cur.chunks = chunks
chunk = null
}
}
function flushOp() {
flushChunk()
if (cur) ops.push(cur)
cur = null
}
for (let i = 0; i < body.length; i++) {
const line = body[i]
if (/^\s*\*\*\*\s*Add File:\s*/i.test(line)) {
flushOp()
cur = {
type: 'add',
path: line.replace(/^\s*\*\*\*\s*Add File:\s*/i, '').trim(),
content: ''
}
continue
}
if (/^\s*\*\*\*\s*Delete File:\s*/i.test(line)) {
flushOp()
cur = {
type: 'delete',
path: line.replace(/^\s*\*\*\*\s*Delete File:\s*/i, '').trim()
}
continue
}
if (/^\s*\*\*\*\s*Update File:\s*/i.test(line)) {
flushOp()
cur = {
type: 'update',
path: line.replace(/^\s*\*\*\*\s*Update File:\s*/i, '').trim(),
move_to: '',
chunks: []
}
continue
}
if (/^\s*\*\*\*\s*Move to:\s*/i.test(line) && cur && cur.type === 'update') {
cur.move_to = line.replace(/^\s*\*\*\*\s*Move to:\s*/i, '').trim()
continue
}
if (/^\s*\*\*\*\s*End of File\s*$/i.test(line)) {
if (chunk) chunk.is_end_of_file = true
flushChunk()
continue
}
if (line.indexOf('@@') === 0 && cur && cur.type === 'update') {
flushChunk()
chunk = {
context: line.replace(/^@@\s?/, '').trim(),
old_lines: [],
new_lines: []
}
continue
}
if (cur && cur.type === 'add') {
const bodyLine = line.charAt(0) === '+' ? line.slice(1) : line
cur.content = cur.content ? String(cur.content) + '\n' + bodyLine : bodyLine
continue
}
if (cur && cur.type === 'update') {
if (!chunk) {
chunk = { context: '', old_lines: [], new_lines: [] }
}
const tag = line.charAt(0)
const rest = line.length ? line.slice(1) : ''
if (tag === '-') chunk.old_lines.push(rest)
else if (tag === '+') chunk.new_lines.push(rest)
else {
const ctxLine = tag === ' ' ? rest : line
chunk.old_lines.push(ctxLine)
chunk.new_lines.push(ctxLine)
}
}
}
flushOp()
if (!ops.length) return { ok: false, error: 'no_patch_ops' }
return { ok: true, ops }
}
/**
* @param {string} text
* @param {{ context: string, old_lines: string[], new_lines: string[], is_end_of_file?: boolean }} chunk
*/
function bareAgentApplyPatchChunk(text, chunk) {
const src = String(text == null ? '' : text)
const oldBlock = (chunk.old_lines || []).join('\n')
const newBlock = (chunk.new_lines || []).join('\n')
if (!oldBlock && !newBlock) {
return { ok: false, error: 'empty_chunk' }
}
if (!oldBlock) {
const next = src
? src.replace(/\s*$/, '') + (src.endsWith('\n') ? '' : '\n') + newBlock + '\n'
: newBlock + (newBlock.endsWith('\n') ? '' : '\n')
return { ok: true, next }
}
let from = 0
if (chunk.context) {
const at = src.indexOf(chunk.context)
if (at === -1) {
return { ok: false, error: 'chunk_context_not_found', context: chunk.context }
}
from = at
}
const hay = src.slice(from)
const count = bareAgentCountOccurrences(hay, oldBlock)
if (count === 0) return { ok: false, error: 'chunk_old_not_found' }
if (count > 1 && !chunk.context) {
return {
ok: false,
error: 'chunk_old_not_unique',
count,
hint: 'add @@ context or more surrounding lines'
}
}
const next = src.slice(0, from) + hay.replace(oldBlock, newBlock)
return { ok: true, next }
}
/**
* @param {Record<string, unknown>} ctx
* @param {object[]} ops
* @param {{ home?: string, denyPrefixes?: unknown }} [opts]
*/
async function bareAgentApplyPatchOps(ctx, ops, opts) {
const home = String((opts && opts.home) || '')
const deny = opts && opts.denyPrefixes
/** @type {object[]} */
const results = []
const rows = Array.isArray(ops) ? ops : []
for (let i = 0; i < rows.length; i++) {
const op = rows[i] && typeof rows[i] === 'object' ? rows[i] : {}
let path = String(op.path || '').trim()
if (path && path.charAt(0) !== '/' && home) {
path = home.replace(/\/+$/, '') + '/' + path.replace(/^\.\//, '')
}
if (!path) {
results.push({ ok: false, error: 'path_required' })
continue
}
if (
typeof bareAgentPathAllowedMutate === 'function' &&
!bareAgentPathAllowedMutate(path, deny)
) {
results.push({ ok: false, path, error: 'path_not_allowed' })
continue
}
if (op.type === 'add') {
const content = String(op.content == null ? '' : op.content)
await bareAgentWriteTextFile(
ctx,
path,
content.endsWith('\n') ? content : content + '\n'
)
results.push({ ok: true, op: 'add', path })
continue
}
if (op.type === 'delete') {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.unlink !== 'function') {
results.push({ ok: false, path, error: 'vfs.unlink unavailable' })
continue
}
try {
await vfs.unlink(path)
results.push({ ok: true, op: 'delete', path })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
results.push({ ok: false, path, error: msg })
}
continue
}
if (op.type === 'update') {
let text = await bareAgentReadTextFile(ctx, path)
if (!text && text !== '') {
results.push({ ok: false, path, error: 'missing_file' })
continue
}
const chunks = Array.isArray(op.chunks) ? op.chunks : []
let failed = null
for (let c = 0; c < chunks.length; c++) {
const applied = bareAgentApplyPatchChunk(text, chunks[c])
if (!applied.ok) {
failed = applied
break
}
text = applied.next
}
if (failed) {
results.push({ ok: false, path, error: failed.error, hint: failed.hint || null })
continue
}
const dest = String(op.move_to || '').trim() || path
const destAbs =
dest.charAt(0) === '/'
? dest
: home
? home.replace(/\/+$/, '') + '/' + dest.replace(/^\.\//, '')
: dest
if (
destAbs !== path &&
typeof bareAgentPathAllowedMutate === 'function' &&
!bareAgentPathAllowedMutate(destAbs, deny)
) {
results.push({ ok: false, path, error: 'move_path_not_allowed', dest: destAbs })
continue
}
await bareAgentWriteTextFile(ctx, destAbs, text)
if (destAbs !== path && ctx.vfs && typeof ctx.vfs.unlink === 'function') {
try {
await ctx.vfs.unlink(path)
} catch {
/* keep original if unlink fails */
}
}
results.push({
ok: true,
op: destAbs !== path ? 'move' : 'update',
path,
dest: destAbs !== path ? destAbs : undefined,
chunks: chunks.length
})
continue
}
results.push({ ok: false, path, error: 'unknown_op' })
}
const failed = results.filter(function (r) {
return !r.ok
})
return {
ok: failed.length === 0,
applied: results.length - failed.length,
failed: failed.length,
results
}
}
/**
* Parse DuckDuckGo instant-answer JSON into a compact result list.
* @param {unknown} payload
* @param {number} [max]
*/
function bareAgentParseSearchResults(payload, max) {
const cap = Math.min(Math.max(Number(max) || 8, 1), 16)
/** @type {{ title: string, url: string, snippet: string }[]} */
const out = []
const seen = Object.create(null)
function add(title, url, snippet) {
const u = String(url || '').trim()
if (!u || seen[u] || out.length >= cap) return
if (!/^https?:\/\//i.test(u)) return
seen[u] = 1
out.push({
title: String(title || u).slice(0, 160),
url: u,
snippet: String(snippet || '').replace(/\s+/g, ' ').trim().slice(0, 280)
})
}
const obj = payload && typeof payload === 'object' ? payload : {}
const rec = /** @type {Record<string, unknown>} */ (obj)
if (rec.AbstractURL || rec.Abstract) {
add(
String(rec.Heading || rec.AbstractSource || 'Abstract'),
String(rec.AbstractURL || ''),
String(rec.AbstractText || rec.Abstract || '')
)
}
const results = Array.isArray(rec.Results) ? rec.Results : []
for (let i = 0; i < results.length; i++) {
const row = results[i] && typeof results[i] === 'object' ? results[i] : {}
add(row.Text || row.Name, row.FirstURL, row.Text)
}
const related = Array.isArray(rec.RelatedTopics) ? rec.RelatedTopics : []
function walk(list) {
for (let i = 0; i < list.length && out.length < cap; i++) {
const row = list[i] && typeof list[i] === 'object' ? list[i] : {}
if (Array.isArray(row.Topics)) walk(row.Topics)
else add(row.Text, row.FirstURL, row.Text)
}
}
walk(related)
return out
}
/**
* Parse Grok-style intervals (5m / 2h / 1d) or a five-field cron line.
* @param {string} raw
* @returns {{ kind: 'everyMs', everyMs: number } | { kind: 'calendar', onCalendar: string } | { error: string }}
*/
function bareAgentParseScheduleInterval(raw) {
const s = String(raw || '').trim()
if (!s) return { error: 'interval_required' }
const compact = s.replace(/\s+/g, '')
const m = /^(\d+)(ms|s|m|h|d)$/i.exec(compact)
if (m) {
const n = Number(m[1])
const unit = m[2].toLowerCase()
let ms = 0
if (unit === 'ms') ms = n
else if (unit === 's') ms = n * 1000
else if (unit === 'm') ms = n * 60 * 1000
else if (unit === 'h') ms = n * 60 * 60 * 1000
else ms = n * 24 * 60 * 60 * 1000
if (ms < 1000) return { error: 'interval_too_short', min_ms: 1000 }
if (ms > 86400000) return { error: 'interval_too_long', max_ms: 86400000 }
return { kind: 'everyMs', everyMs: ms }
}
const fields = s.split(/\s+/)
if (fields.length === 5) return { kind: 'calendar', onCalendar: s }
return { error: 'bad_interval', hint: 'use 5m, 2h, 1d, or five cron fields' }
}
/**
* @param {string} id
*/
function bareAgentParseIgnoreRules(text) {
const lines = String(text || '').split(/\r?\n/)
/** @type {{ pattern: string, negate: boolean, dirOnly: boolean }[]} */
const rules = []
for (let i = 0; i < lines.length; i++) {
let line = String(lines[i] || '').trim()
if (!line || line.charAt(0) === '#') continue
let negate = false
if (line.charAt(0) === '!') {
negate = true
line = line.slice(1)
}
let dirOnly = false
if (line.charAt(line.length - 1) === '/') {
dirOnly = true
line = line.slice(0, -1)
}
if (line.charAt(0) === '/') line = line.slice(1)
if (!line) continue
rules.push({ pattern: line, negate, dirOnly })
}
return rules
}
/**
* Last matching gitignore-style rule wins.
* @param {string} rel
* @param {boolean} isDir
* @param {{ pattern: string, negate: boolean, dirOnly: boolean }[]} rules
*/
function bareAgentIgnoreMatch(rel, isDir, rules) {
const n = String(rel || '').replace(/^\/+/, '')
if (!n || !Array.isArray(rules) || !rules.length) return false
let ignored = false
for (let i = 0; i < rules.length; i++) {
const rule = rules[i]
if (rule.dirOnly && !isDir) continue
let hit = false
if (rule.pattern.indexOf('/') === -1) {
const parts = n.split('/')
for (let p = 0; p < parts.length; p++) {
if (bareAgentGlobMatch(parts[p], rule.pattern)) {
hit = true
break
}
}
} else {
hit = bareAgentGlobMatch(n, rule.pattern) || bareAgentGlobMatch(n, '**/' + rule.pattern)
}
if (!hit) continue
ignored = !rule.negate
}
return ignored
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} root
*/
async function bareAgentLoadIgnoreRules(ctx, root) {
const names = ['.gitignore', '.agentignore', '.grokignore']
/** @type {string[]} */
const chunks = []
for (let i = 0; i < names.length; i++) {
const t = await bareAgentReadTextFile(ctx, String(root || '').replace(/\/+$/, '') + '/' + names[i])
if (t && t.trim()) chunks.push(t)
}
return bareAgentParseIgnoreRules(chunks.join('\n'))
}
/**
* Simple Grok-style fuzzy score (basename + path subsequence). Higher is better.
* @param {string} query
* @param {string} path
*/
function bareAgentFuzzyScore(query, path) {
const q = String(query || '').toLowerCase().trim()
const p = String(path || '')
if (!q || !p) return 0
const low = p.toLowerCase()
const base = (p.split('/').pop() || p).toLowerCase()
if (base === q) return 1000
if (base.indexOf(q) === 0) return 800 - Math.min(base.length, 80)
if (base.indexOf(q) !== -1) return 600 - base.indexOf(q)
if (low.indexOf(q) !== -1) return 400
let qi = 0
let score = 0
let streak = 0
for (let i = 0; i < low.length && qi < q.length; i++) {
if (low.charAt(i) === q.charAt(qi)) {
qi++
streak++
score += 8 + streak * 4
} else streak = 0
}
if (qi < q.length) return 0
if (base.length && q.length / base.length > 0.5) score += 40
return score
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} root
* @param {string} query
* @param {{ max?: number, maxFiles?: number }} [opts]
*/
async function bareAgentFuzzyFind(ctx, root, query, opts) {
const files = await bareAgentVfsWalkFiles(ctx, root, {
maxFiles: Math.min(Math.max(Number(opts && opts.maxFiles) || 600, 40), 2000),
maxDepth: 12
})
const cap = Math.min(Math.max(Number(opts && opts.max) || 20, 1), 80)
/** @type {{ path: string, score: number }[]} */
const scored = []
for (let i = 0; i < files.length; i++) {
const score = bareAgentFuzzyScore(query, files[i])
if (score <= 0) continue
scored.push({ path: files[i], score })
}
scored.sort(function (a, b) {
return b.score - a.score
})
return scored.slice(0, cap)
}
/**
* @param {Record<string, unknown>} hook
* @param {string} event
* @param {string} toolName
* @param {Record<string, unknown>} args
* @param {string} [result]
*/
function bareAgentHookMatch(hook, event, toolName, args, result) {
if (!hook || typeof hook !== 'object') return null
const ev = String(hook.event || hook.type || 'PreToolUse')
const want = String(event || '')
if (ev !== want && ev.toLowerCase() !== want.toLowerCase()) return null
const tools = Array.isArray(hook.tools) ? hook.tools.map(String) : []
if (tools.length && toolName && tools.indexOf(toolName) === -1) return null
const reSrc = String(hook.deny_regex || hook.match_regex || hook.denyRegex || '').trim()
if (reSrc) {
let re
try {
re = new RegExp(reSrc, 'i')
} catch {
return null
}
const blob =
toolName +
' ' +
String((args && (args.command || args.path || args.file_path || args.pattern)) || '') +
' ' +
String(result || '').slice(0, 400)
if (!re.test(blob)) return null
}
return hook
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} hooksDir
* @param {string} event
* @param {string} [toolName]
* @param {Record<string, unknown>} [args]
* @param {string} [result]
*/
async function bareAgentRunHooks(ctx, hooksDir, event, toolName, args, result) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readdir !== 'function' || typeof vfs.readFile !== 'function') {
return { deny: '', inject: '' }
}
let names = []
try {
names = await vfs.readdir(hooksDir)
} catch {
return { deny: '', inject: '' }
}
if (!Array.isArray(names)) return { deny: '', inject: '' }
names = names.filter(function (n) {
return /\.json$/i.test(String(n || ''))
})
names.sort()
/** @type {string[]} */
const injects = []
for (let i = 0; i < names.length; i++) {
try {
const t = await bareAgentReadTextFile(ctx, hooksDir + '/' + names[i])
if (!t.trim()) continue
const hook = JSON.parse(t)
const matched = bareAgentHookMatch(hook, event, toolName || '', args || {}, result || '')
if (!matched) continue
if (event === 'PreToolUse' || event === 'pre') {
const reason = String(matched.reason || matched.message || '').trim()
if (reason && (matched.deny === true || matched.deny_regex || matched.denyRegex)) {
return { deny: reason.slice(0, 240), inject: '' }
}
if (typeof bareAgentHookDenies === 'function') {
const d = bareAgentHookDenies(matched, toolName || '', args || {})
if (d) return { deny: d, inject: '' }
}
}
const inj = String(matched.inject || matched.append || '').trim()
if (inj) injects.push(inj.slice(0, 800))
} catch {
/* ignore */
}
}
return { deny: '', inject: injects.join('\n') }
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} editsPath
* @param {{ path: string, prev: string, tool?: string }} rec
*/
async function bareAgentPushEdit(ctx, editsPath, rec) {
const prev = await bareAgentReadJsonFile(ctx, editsPath, [])
const list = Array.isArray(prev) ? prev : []
list.push({
path: String(rec.path || ''),
prev: String(rec.prev == null ? '' : rec.prev),
tool: String(rec.tool || ''),
ts: new Date().toISOString()
})
while (list.length > 20) list.shift()
await bareAgentWriteJsonFile(ctx, editsPath, list)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} editsPath
*/
async function bareAgentPopEdit(ctx, editsPath) {
const prev = await bareAgentReadJsonFile(ctx, editsPath, [])
const list = Array.isArray(prev) ? prev : []
const last = list.pop()
await bareAgentWriteJsonFile(ctx, editsPath, list)
return last && typeof last === 'object' ? last : null
}
/**
* @param {unknown[]} messages
* @param {string} query
* @param {number} [max]
*/
function bareAgentHistorySearch(messages, query, max) {
const tokens = bareAgentMemoryTokens(query)
const cap = Math.min(Math.max(Number(max) || 8, 1), 24)
if (!Array.isArray(messages) || !tokens.length) return []
/** @type {{ role: string, score: number, snippet: string }[]} */
const hits = []
for (let i = 0; i < messages.length; i++) {
const m = messages[i] && typeof messages[i] === 'object' ? messages[i] : null
if (!m) continue
const role = String(m.role || '')
if (role !== 'user' && role !== 'assistant') continue
const text = String(m.content || '')
const score = bareAgentMemoryScore(text, tokens)
if (score <= 0) continue
hits.push({
role,
score: Math.round(score * 1000) / 1000,
snippet: text.replace(/\s+/g, ' ').trim().slice(0, 280)
})
}
hits.sort(function (a, b) {
return b.score - a.score
})
return hits.slice(0, cap)
}
function bareAgentScheduleId(id) {
let s = String(id || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9-]+/g, '-')
.replace(/^-+|-+$/g, '')
if (!s) s = 'task'
if (s.indexOf('agent-') !== 0) s = 'agent-' + s
return s.slice(0, 40)
}
/**
* User-turn rewind points (Grok /rewind). Index 0 is the first user message.
* @param {unknown[]} messages
*/
function bareAgentRewindPoints(messages) {
/** @type {{ userIndex: number, messageIndex: number, preview: string }[]} */
const points = []
if (!Array.isArray(messages)) return points
for (let i = 0; i < messages.length; i++) {
const m = messages[i] && typeof messages[i] === 'object' ? messages[i] : null
if (!m || String(m.role || '') !== 'user') continue
points.push({
userIndex: points.length,
messageIndex: i,
preview: String(m.content || '')
.replace(/\s+/g, ' ')
.trim()
.slice(0, 140)
})
}
return points
}
/**
* Drop the last N user turns (and everything after that user message).
* userIndex rewinds to that user turn (keeps messages before it).
* keep_user keeps the target user message so the prompt can be retried.
* @param {unknown[]} messages
* @param {{ steps?: number, userIndex?: number, keep_user?: boolean }} [opts]
*/
function bareAgentRewindHistory(messages, opts) {
const list = Array.isArray(messages) ? messages.slice() : []
const points = bareAgentRewindPoints(list)
if (!points.length) return { ok: false, error: 'nothing_to_rewind', messages: list, dropped: 0 }
const o = opts && typeof opts === 'object' ? opts : {}
let target
if (o.userIndex != null && Number.isFinite(Number(o.userIndex))) {
target = points[Math.floor(Number(o.userIndex))]
if (!target) return { ok: false, error: 'bad_user_index', messages: list, dropped: 0 }
} else {
const steps = Math.min(points.length, Math.max(1, Math.floor(Number(o.steps) || 1)))
target = points[points.length - steps]
}
const keepUser = Boolean(o.keep_user)
const cut = keepUser ? target.messageIndex : target.messageIndex - 1
const next = cut < 0 ? [] : list.slice(0, cut + 1)
return {
ok: true,
messages: next,
dropped: list.length - next.length,
target: target,
keep_user: keepUser
}
}
/**
* Markdown transcript (Grok /export).
* @param {unknown[]} messages
*/
function bareAgentExportTranscript(messages) {
const lines = [
'# Agent session export',
'',
'Exported: ' + new Date().toISOString(),
''
]
const list = Array.isArray(messages) ? messages : []
for (let i = 0; i < list.length; i++) {
const m = list[i] && typeof list[i] === 'object' ? list[i] : {}
const role = String(m.role || 'unknown')
let content = String(m.content || '')
if (role === 'tool' && content.length > 1200) content = content.slice(0, 1200) + '\n… truncated'
const calls = Array.isArray(m.tool_calls) ? m.tool_calls : []
const names = []
for (let c = 0; c < calls.length; c++) {
const fn = calls[c] && calls[c].function ? calls[c].function : {}
if (fn && fn.name) names.push(String(fn.name))
}
lines.push('## ' + String(i + 1) + '. ' + role)
lines.push('')
if (names.length) lines.push('tools: ' + names.join(', '))
lines.push(content || '(empty)')
lines.push('')
}
return lines.join('\n')
}
/**
* Line-based unified diff (Grok-style file compare, no git required).
* @param {string} oldText
* @param {string} newText
* @param {{ from?: string, to?: string, context?: number }} [opts]
*/
function bareAgentUnifiedDiff(oldText, newText, opts) {
const pathA = String((opts && opts.from) || 'a')
const pathB = String((opts && opts.to) || 'b')
const ctxN = Math.min(8, Math.max(0, Math.floor(Number(opts && opts.context) || 3)))
const a = String(oldText || '')
.replace(/\r\n/g, '\n')
.split('\n')
const b = String(newText || '')
.replace(/\r\n/g, '\n')
.split('\n')
if (a.length && a[a.length - 1] === '') a.pop()
if (b.length && b[b.length - 1] === '') b.pop()
if (a.join('\n') === b.join('\n')) {
return { ok: true, identical: true, text: '', added: 0, removed: 0 }
}
const maxLines = 800
if (a.length > maxLines || b.length > maxLines) {
return {
ok: true,
truncated: true,
identical: false,
text:
'--- ' +
pathA +
'\n+++ ' +
pathB +
'\n@@ files too large for inline LCS diff (' +
a.length +
'/' +
b.length +
' lines) @@\n',
added: 0,
removed: 0
}
}
const n = a.length
const m = b.length
/** @type {number[][]} */
const dp = new Array(n + 1)
for (let i = 0; i <= n; i++) {
dp[i] = new Array(m + 1)
for (let j = 0; j <= m; j++) dp[i][j] = 0
}
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= m; j++) {
dp[i][j] =
a[i - 1] === b[j - 1]
? dp[i - 1][j - 1] + 1
: dp[i - 1][j] >= dp[i][j - 1]
? dp[i - 1][j]
: dp[i][j - 1]
}
}
/** @type {{ op: string, line: string }[]} */
const ops = []
let i = n
let j = m
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
ops.push({ op: ' ', line: a[i - 1] })
i--
j--
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
ops.push({ op: '+', line: b[j - 1] })
j--
} else {
ops.push({ op: '-', line: a[i - 1] })
i--
}
}
ops.reverse()
let added = 0
let removed = 0
for (let k = 0; k < ops.length; k++) {
if (ops[k].op === '+') added++
else if (ops[k].op === '-') removed++
}
const lines = ['--- ' + pathA, '+++ ' + pathB]
let idx = 0
while (idx < ops.length) {
while (idx < ops.length && ops[idx].op === ' ') idx++
if (idx >= ops.length) break
let start = Math.max(0, idx - ctxN)
let end = idx
while (end < ops.length) {
if (ops[end].op !== ' ') {
end++
continue
}
let run = 0
let p = end
while (p < ops.length && ops[p].op === ' ') {
run++
p++
}
if (run > ctxN * 2) {
end += ctxN
break
}
end = p
}
end = Math.min(ops.length, end)
let oldLine = 1
let newLine = 1
for (let k = 0; k < start; k++) {
if (ops[k].op !== '+') oldLine++
if (ops[k].op !== '-') newLine++
}
let oldCount = 0
let newCount = 0
for (let k = start; k < end; k++) {
if (ops[k].op !== '+') oldCount++
if (ops[k].op !== '-') newCount++
}
lines.push(
'@@ -' +
String(oldLine) +
',' +
String(oldCount) +
' +' +
String(newLine) +
',' +
String(newCount) +
' @@'
)
for (let k = start; k < end; k++) {
lines.push(ops[k].op + ops[k].line)
}
idx = end
}
return {
ok: true,
identical: false,
truncated: false,
text: lines.join('\n') + '\n',
added,
removed
}
}
/**
* Definition-oriented regex for a symbol name (JS / Python / Rust / C-like).
* @param {string} name
*/
function bareAgentSymbolRegex(name) {
const esc = String(name || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
if (!esc) return ''
return (
'(?:(?:export\\s+)?(?:default\\s+)?(?:async\\s+)?function\\s+' +
esc +
'\\b|(?:export\\s+)?(?:default\\s+)?class\\s+' +
esc +
'\\b|(?:export\\s+)?(?:const|let|var)\\s+' +
esc +
'\\b|def\\s+' +
esc +
'\\s*\\(|fn\\s+' +
esc +
'\\b|' +
esc +
'\\s*=\\s*(?:async\\s+)?(?:function|\\(|class))'
)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} root
* @param {string} name
* @param {{ glob?: string, max?: number }} [opts]
*/
async function bareAgentFindSymbol(ctx, root, name, opts) {
const pattern = bareAgentSymbolRegex(name)
if (!pattern) return { ok: false, error: 'name_required' }
const out = await bareAgentGrepFiles(ctx, {
pattern,
root,
glob: opts && opts.glob,
max_matches: opts && opts.max ? opts.max : 40
})
if (!out || out.ok === false) return out
return {
ok: true,
name,
root,
matches: out.matches || [],
count: out.count || 0,
truncated: Boolean(out.truncated)
}
}
/**
* Grok list_dir-style BFS tree (bounded).
* @param {Record<string, unknown>} ctx
* @param {string} root
* @param {{ max?: number, maxDepth?: number }} [opts]
*/
async function bareAgentRenderTree(ctx, root, opts) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readdir !== 'function') {
return { ok: false, error: 'readdir unavailable' }
}
const base = String(root || '/').replace(/\/+$/, '') || '/'
const maxItems = Math.min(Math.max(Number(opts && opts.max) || 200, 20), 800)
const maxDepth = Math.min(Math.max(Number(opts && opts.maxDepth) || 6, 1), 12)
const skip = Object.create(null)
skip['.git'] = 1
skip['node_modules'] = 1
skip['.bare-os'] = 1
/** @type {string[]} */
const lines = [base + '/']
/** @type {{ dir: string, prefix: string, depth: number }[]} */
const queue = [{ dir: base, prefix: '', depth: 0 }]
let count = 1
let truncated = false
while (queue.length && count < maxItems) {
const cur = queue.shift()
if (!cur) break
let names = []
try {
names = await vfs.readdir(cur.dir)
} catch {
continue
}
if (!Array.isArray(names)) continue
names = names
.map(function (n) {
return String(n || '')
})
.filter(function (n) {
return n && n !== '.' && n !== '..' && !skip[n]
})
.sort()
for (let i = 0; i < names.length && count < maxItems; i++) {
const name = names[i]
const full = (cur.dir === '/' ? '' : cur.dir) + '/' + name
const isDir = await bareAgentVfsIsDir(ctx, full)
const last = i === names.length - 1
const branch = last ? '`-- ' : '|-- '
lines.push(cur.prefix + branch + name + (isDir ? '/' : ''))
count++
if (isDir && cur.depth + 1 < maxDepth) {
queue.push({
dir: full,
prefix: cur.prefix + (last ? ' ' : '| '),
depth: cur.depth + 1
})
}
}
if (count >= maxItems) {
truncated = true
break
}
}
return { ok: true, path: base, tree: lines.join('\n'), count, truncated }
}
/**
* VFS copy (file or directory). Dest parents are created.
* @param {Record<string, unknown>} ctx
* @param {string} from
* @param {string} to
*/
async function bareAgentCopyPath(ctx, from, to) {
const src = String(from || '').replace(/\/+$/, '')
let dest = String(to || '')
if (!src || !dest) return { ok: false, error: 'from_and_to_required' }
const srcIsDir = await bareAgentVfsIsDir(ctx, src)
const destIsDir = await bareAgentVfsIsDir(ctx, dest.replace(/\/+$/, ''))
if (destIsDir) {
const base = src.split('/').pop() || 'copy'
dest = dest.replace(/\/+$/, '') + '/' + base
}
if (!srcIsDir) {
const text = await bareAgentReadTextFile(ctx, src)
await bareAgentWriteTextFile(ctx, dest, text)
return { ok: true, from: src, to: dest, kind: 'file' }
}
const files = await bareAgentVfsWalkFiles(ctx, src, { maxFiles: 400, maxDepth: 12 })
for (let i = 0; i < files.length; i++) {
const rel = files[i].slice(src.length)
const text = await bareAgentReadTextFile(ctx, files[i])
await bareAgentWriteTextFile(ctx, dest + rel, text)
}
return { ok: true, from: src, to: dest, kind: 'directory', files: files.length }
}
/**
* SKILL.md body with YAML frontmatter (Grok skill format).
* @param {{ name: string, description: string, body?: string }} spec
*/
function bareAgentSkillMarkdown(spec) {
const name = String((spec && spec.name) || '').trim() || 'skill'
const description = String((spec && spec.description) || '').trim() || name
const body = String((spec && spec.body) || '').trim() || '# ' + name + '\n'
return (
'---\nname: ' +
name.replace(/\n/g, ' ') +
'\ndescription: ' +
description.replace(/\n/g, ' ') +
'\n---\n\n' +
body +
'\n'
)
}
/**
* Walk-up Grok/Claude/Cursor skill roots (project-local).
* @param {Record<string, unknown>} ctx
* @param {string} startDir
*/
async function bareAgentDiscoverProjectSkillRoots(ctx, startDir) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readdir !== 'function') return []
/** @type {{ path: string, source: string }[]} */
const roots = []
const seen = Object.create(null)
let dir = String(startDir || '').replace(/\/+$/, '') || '/'
const suffixes = ['.grok/skills', '.agents/skills', '.claude/skills', '.cursor/skills']
for (let hop = 0; hop < 12; hop++) {
for (let i = 0; i < suffixes.length; i++) {
const p = (dir === '/' ? '' : dir) + '/' + suffixes[i]
if (seen[p]) continue
seen[p] = 1
try {
const names = await vfs.readdir(p)
if (Array.isArray(names) && names.length) roots.push({ path: p, source: 'project' })
} catch {
/* missing */
}
}
if (dir === '/') break
const parent = dir.replace(/\/[^/]+$/, '') || '/'
if (parent === dir) break
dir = parent
}
return roots
}
/** OpenAI-style tool schemas + dispatch (preamble for /bin/agent). */
/**
* Host-checkout verification hints (keep loosely aligned with scripts/lib/agent-check-hints-data.mjs).
* @param {string} combined paths + topic
* @returns {string[]}
*/
function bareAgentVerificationHintsList(combined) {
const c = String(combined || '').toLowerCase()
/** @type {string[]} */
const hints = []
if (/(^|\/)kernel\/|kernel\\|\/boot\/init|lib\/init|lib\\init/.test(c)) {
hints.push('npm run bundle:kernel', 'node scripts/verify-kernel-seeder-parity.mjs')
}
if (/bare-os-coreutils|kernel\/bin|kernel\\bin/.test(c)) {
hints.push('npm run build -w bare-os-coreutils', 'node scripts/verify-man-coverage.mjs')
}
if (/bare-os-booter|bare-os-ctx-api/.test(c)) {
hints.push(
'npm run test -w bare-os-booter',
'node scripts/verify-ctx-api-feature-bits.mjs'
)
}
if (/bare-os-protocol|seed-rpc|channel\.js/.test(c)) {
hints.push('npm run test -w bare-os-protocol')
}
if (/bare-os-seeder/.test(c)) {
hints.push('node scripts/verify-kernel-seeder-parity.mjs')
}
if (/bare-os-bare-libs|kernel\/lib\/bare|kernel\\lib\\bare/.test(c)) {
hints.push('npm run build -w bare-os-bare-libs', 'node scripts/verify-bundle-health.mjs')
}
if (/shell|sh\.js|test\.js/.test(c) && /booter/.test(c)) {
hints.push('npm run test:shell-fast')
}
if (/docs\/|handbook\/|developer-guide\//.test(c)) {
hints.push('npm run pretest', 'node scripts/verify-doc-links.mjs')
}
if (!hints.length) hints.push('npm run pretest', 'npm test')
return [...new Set(hints)]
}
/**
* @param {string} s
*/
function bareAgentShellQuote(s) {
return "'" + String(s).replace(/'/g, "'\\''") + "'"
}
/**
* @param {unknown} v
* @returns {string}
*/
function bareAgentJsonResult(v) {
try {
return JSON.stringify(v)
} catch {
return '{"error":"json_stringify_failed"}'
}
}
/**
* @returns {unknown[]}
*/
function bareAgentToolDefinitions() {
return [
{
type: 'function',
function: {
name: 'read_file',
description:
'Read a UTF-8 text file from the VFS. Path must be absolute (e.g. /home/guest/...). Optional offset/limit return numbered line slices (Grok-style).',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Absolute file path' },
file_path: { type: 'string', description: 'Alias for path' },
max_bytes: {
type: 'integer',
description: 'Max bytes to read (default 256000)'
},
offset: {
type: 'integer',
description: '1-based start line for a numbered slice'
},
limit: {
type: 'integer',
description: 'Max lines to return from offset'
},
numbered: {
type: 'boolean',
description: 'Prefix N→ on each line when slicing (default true if offset/limit set)'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'write_file',
description:
'Create or overwrite a file. Parent directories are created as needed.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
content: { type: 'string', description: 'Full file contents' }
},
required: ['path', 'content']
}
}
},
{
type: 'function',
function: {
name: 'edit_file',
description:
'Edit a text file: full replacement via content, or search/replace. old_string must be unique unless replace_all is true.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
file_path: { type: 'string', description: 'Alias for path' },
content: {
type: 'string',
description: 'If set (non-empty), full file replacement'
},
old_string: {
type: 'string',
description: 'Search string (used with new_string)'
},
new_string: { type: 'string', description: 'Replacement text' },
replace_all: {
type: 'boolean',
description: 'Replace every occurrence of old_string (default false)'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'create_directory',
description: 'Create a directory (recursive).',
parameters: {
type: 'object',
properties: {
path: { type: 'string' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'search_files',
description:
'Run grep -R to list paths matching a pattern (bounded). Uses the shell.',
parameters: {
type: 'object',
properties: {
pattern: { type: 'string' },
root: {
type: 'string',
description: 'Directory to search (default /home)'
},
max_lines: { type: 'integer', description: 'Default 200' }
},
required: ['pattern']
}
}
},
{
type: 'function',
function: {
name: 'run_command',
description:
'Run a shell command line via ctx.execLine (same as interactive shell). Output is captured to a temp file. Set capture_exit true to append a final EXIT:<code> line.',
parameters: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Full command string (e.g. ls -la /bin)'
},
cwd: {
type: 'string',
description: 'Working directory (cd there first). Persists as ~/.agent/last_cwd.'
},
timeout_ms: { type: 'integer' },
capture_exit: {
type: 'boolean',
description: 'If true, append last line EXIT:<code> to capture (default false)'
}
},
required: ['command']
}
}
},
{
type: 'function',
function: {
name: 'run_js_script',
description:
'REQUIRED to run agent-authored JavaScript: Node is not installed. Writes code to ~/.agent/_tmp_agent_run.mjs and runs it by absolute path (Bare kernel — same as /bin scripts). Do not use run_command with node/npm/npx. Prefer async function run(ctx, argv). stdout/stderr captured.',
parameters: {
type: 'object',
properties: {
code: { type: 'string', description: 'Full ESM/CommonJS script body' }
},
required: ['code']
}
}
},
{
type: 'function',
function: {
name: 'get_system_info',
description:
'Lightweight context: API version, uname, optional resource hook. Questions about **which kernel features are on/off in this session** → read_proc_file on /proc/bare_os/features (or features.json) and /proc/bare_os/capabilities.json; not apropos_man. For other /proc JSON use read_proc_file; swarm → get_swarm_peers; resource table → get_resource_limits. want=capabilities|swarm still returns those blobs when needed.',
parameters: {
type: 'object',
properties: {
want: {
type: 'string',
enum: ['summary', 'capabilities', 'swarm'],
description: 'Optional focus (default summary)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'edit_agent_config',
description:
'Merge keys into ~/.agent/config.json (shallow merge for known keys only).',
parameters: {
type: 'object',
properties: {
patch: {
type: 'object',
description:
'Partial config object (rest_base_url, model, temperature, owner_name, agent_label, …)'
}
},
required: ['patch']
}
}
},
{
type: 'function',
function: {
name: 'list_bin',
description: 'List Tier-1 utilities in /bin via VFS.',
parameters: {
type: 'object',
properties: {
limit: { type: 'integer', description: 'Max names (default 400)' }
}
}
}
},
{
type: 'function',
function: {
name: 'list_directory',
description:
'List directory entries via ctx.vfs.readdir. Optional one-line stat per entry (bounded).',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Absolute directory path' },
max_entries: { type: 'integer', description: 'Max names (default 500, cap 2000)' },
include_stat: {
type: 'boolean',
description: 'If true, call stat on each entry (slower; default false)'
},
tree: {
type: 'boolean',
description: 'Grok-style bounded BFS tree instead of a flat listing'
},
max_depth: { type: 'integer', description: 'Tree depth (default 6)' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'file_stat',
description:
'Stat a path: size, mtime, type, mode. Uses lstat when follow_symlinks is false (default).',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
follow_symlinks: {
type: 'boolean',
description: 'If true, use stat (follow); if false, lstat (default false)'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'move_path',
description:
'Rename or move a file or directory via shell mv. Any writable VFS path is allowed; the read-only base system (/bin, /etc, /boot, /lib, /usr, /share, /proc, /dev) is denied.',
parameters: {
type: 'object',
properties: {
from_path: { type: 'string' },
to_path: { type: 'string' }
},
required: ['from_path', 'to_path']
}
}
},
{
type: 'function',
function: {
name: 'delete_path',
description:
'Delete a file or directory (recursive optional). Allowed by default. Optional confirm_token only if require_confirm_token is set. Cannot delete the read-only base system.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
recursive: {
type: 'boolean',
description: 'Remove directories recursively (default false)'
},
confirm_token: {
type: 'string',
description: 'Must match config require_confirm_token when that key is non-empty'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'read_man_page',
description:
'Read one manual page from /share/man/man.json (bounded text). Prefer over parsing man output.',
parameters: {
type: 'object',
properties: {
topic: { type: 'string', description: 'Page name (e.g. grep, agent)' },
section: {
type: 'integer',
description: 'Manual section 18 if disambiguating (optional)'
},
max_chars: { type: 'integer', description: 'Cap rendered slice (default 12000)' }
},
required: ['topic']
}
}
},
{
type: 'function',
function: {
name: 'apropos_man',
description:
'Keyword search over the merged man DB (same idea as man -k). Returns matching name(section) lines. This is documentation search only—never use it to answer what kernel features are currently enabled or disabled (use read_proc_file on /proc/bare_os/features).',
parameters: {
type: 'object',
properties: {
keyword: { type: 'string' },
max_results: { type: 'integer', description: 'Default 40, max 200' }
},
required: ['keyword']
}
}
},
{
type: 'function',
function: {
name: 'read_proc_file',
description:
'Read any /proc path (bounded), including the full /proc/bare_os kernel surface. Canonical live feature state: /proc/bare_os/features or features.json. Prefer this over shelling cat.',
parameters: {
type: 'object',
properties: {
path: {
type: 'string',
description:
'Absolute /proc path (any kernel node). Examples: /proc/bare_os/features.json, /proc/bare_os/capabilities.json, /proc/bare_os/swarm.json.'
},
max_bytes: { type: 'integer', description: 'Default 256000' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'get_swarm_peers',
description: 'Return parsed /proc/bare_os/swarm.json when readable (P2P / Hyperswarm snapshot).',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_resource_limits',
description:
'Return ctx.bareOsGetResourceStatus() when available (pipeline / resource snapshot).',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'run_js_script_at_path',
description:
'Execute an existing .mjs script by absolute path (Bare kernel runner). Same as running that path with run_command but dedicated for clarity.',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Absolute path to .mjs file' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'web_fetch',
description:
'Fetch live HTTP(S) URLs and return structured content for the assistant. Uses ctx.httpFetch (same policy as wget/curl: BARE_OS_HTTP_ALLOWLIST / DENYLIST). For official docs index use read_man_page / apropos_man — they are not web pages. Supports GET/HEAD/POST and extract modes: auto (JSON vs HTML vs text), markdownish plain text from HTML, links (anchor hrefs), meta (title/og:), raw UTF-8 slice, or json parse.',
parameters: {
type: 'object',
properties: {
url: { type: 'string', description: 'Absolute http(s) URL' },
method: {
type: 'string',
description: 'HTTP method (default GET)',
enum: ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
},
headers: {
type: 'object',
description: 'Optional header map (string values only)'
},
body: {
type: 'string',
description: 'Request body for non-GET (e.g. JSON string for APIs)'
},
content_type: {
type: 'string',
description: 'Content-Type when body is set (default application/octet-stream)'
},
format: {
type: 'string',
enum: ['auto', 'json', 'markdownish', 'text', 'links', 'meta', 'raw'],
description:
'auto: sniff Content-Type; json: parse JSON; markdownish/text: strip HTML to readable text; links: absolute http(s) links; meta: title/description/og tags; raw: bounded UTF-8 text'
},
max_response_bytes: {
type: 'integer',
description: 'Cap downloaded bytes (default 524288, max 2MiB)'
},
max_redirects: {
type: 'integer',
description: 'Max redirects to follow (default 5)'
},
timeout_ms: {
type: 'integer',
description: 'Per-request timeout ms (default 30000, max 120000)'
},
max_links: {
type: 'integer',
description: 'Max links when format=links (default 200)'
}
},
required: ['url']
}
}
},
{
type: 'function',
function: {
name: 'read_skill',
description:
'Load the full SKILL.md for a modular agent skill (folder id or frontmatter name, case-insensitive). Workspace ~/.agent/workspace/skills/ overrides ~/.agent/skills/. Use after checking the compact skills index in the system prompt.',
parameters: {
type: 'object',
properties: {
skill: {
type: 'string',
description: 'Skill folder name (e.g. p2p-os-status) or YAML frontmatter name'
},
max_bytes: {
type: 'integer',
description: 'Max bytes of SKILL.md (default 256000)'
}
},
required: ['skill']
}
}
},
{
type: 'function',
function: {
name: 'list_services',
description:
'List initd service definitions and current runtime phases from /proc/bare_os/initd_readiness.json when available.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'service_status',
description:
'Read one initd unit status by name from /proc/bare_os/initd_readiness.json and include journal hint paths when present.',
parameters: {
type: 'object',
properties: {
name: { type: 'string', description: 'Unit name, e.g. bare-cron' }
},
required: ['name']
}
}
},
{
type: 'function',
function: {
name: 'list_timers',
description:
'List user timer drop-ins from ~/.config/bare-os/timers and optionally include short file previews.',
parameters: {
type: 'object',
properties: {
include_preview: {
type: 'boolean',
description: 'Include bounded timer file text previews (default false)'
},
max_entries: {
type: 'integer',
description: 'Maximum timer files to return (default 128, max 512)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_cron_log',
description:
'Read /var/log/bare-os/cron.log with bounded output and optional tail mode.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 12000)'
},
tail_only: {
type: 'boolean',
description: 'When true, return only the trailing max_chars slice'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_audit_log',
description:
'Read /var/log/bare-os/audit.log with bounded output and best-effort secret redaction.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 12000)'
},
tail_only: {
type: 'boolean',
description: 'When true, return only the trailing max_chars slice'
},
redact: {
type: 'boolean',
description: 'Apply lightweight token redaction (default true)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_boot_policy',
description:
'Read /etc/bare-os/boot.policy.json and return text plus parsed JSON when available.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 20000)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_kernel_extension_resolution',
description:
'Read /run/bare-os/kernel-ext-resolution.json for extension ordering, conflicts, and pin outcomes.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 20000)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'get_initd_graph',
description:
'Read initd dependency DAG/readiness graph from /proc/bare_os/initd_dag.json and /proc/bare_os/initd_readiness.json.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'read_unit_journal',
description:
'Read a bounded/redacted tail of /run/bare-os/unit-journal/<unit>.ndjson.',
parameters: {
type: 'object',
properties: {
unit: { type: 'string', description: 'Initd unit name, e.g. bare-cron' },
max_chars: { type: 'integer', description: 'Maximum output chars (default 12000)' },
tail_only: { type: 'boolean', description: 'Return trailing max_chars only' }
},
required: ['unit']
}
}
},
{
type: 'function',
function: {
name: 'inspect_ipc_backpressure',
description:
'Inspect IPC/backpressure operator snapshots from /proc/bare_os JSON surfaces.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_network_summary',
description:
'Read a typed network/swarm summary from /proc/bare_os surfaces with best-effort fallback paths.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'tail_telemetry_streams',
description:
'Read bounded/redacted tails from telemetry logs such as /var/log/bare-os/audit.log, logger.jsonl, and initd logs.',
parameters: {
type: 'object',
properties: {
max_chars: { type: 'integer', description: 'Maximum chars per stream (default 8000)' }
}
}
}
},
{
type: 'function',
function: {
name: 'pkg_index_lookup',
description:
'Run pkg-swarm-index lookup and return parsed output for one package key.',
parameters: {
type: 'object',
properties: {
key: { type: 'string', description: 'Package key to lookup' }
},
required: ['key']
}
}
},
{
type: 'function',
function: {
name: 'list_verification_scripts',
description:
'List known verification scripts from /scripts and summarize likely check families.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'run_maintenance_gate',
description:
'Run one named maintenance check with bounded capture (id maps to a known verifier). Use run_command for arbitrary guest commands.',
parameters: {
type: 'object',
properties: {
command: { type: 'string', description: 'Allowlisted command id' },
cwd: { type: 'string', description: 'Optional working directory' },
timeout_ms: { type: 'integer', description: 'Timeout in milliseconds' }
},
required: ['command']
}
}
},
{
type: 'function',
function: {
name: 'run_contract_checks',
description:
'Run a grouped set of contract checks by profile id (allowlisted) with bounded output.',
parameters: {
type: 'object',
properties: {
profile: { type: 'string', description: 'Check profile id, e.g. core, docs, parity' }
},
required: ['profile']
}
}
},
{
type: 'function',
function: {
name: 'summarize_build_drift',
description:
'Summarize build/generated drift by comparing git status and key generated artifacts.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_hrpc_bridge_health',
description:
'Read HRPC bridge/operator health from /proc/bare_os surfaces and include host capability hints when available.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_hrpc_allowlist_status',
description:
'Inspect effective HRPC allowlist/probe status using hrpc probe and operator snapshots.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'emit_host_notification',
description:
'Request an audited host notification via HRPC route. Enabled by default; emergency_stop_mutations or a denylist can still block.',
parameters: {
type: 'object',
properties: {
title: { type: 'string' },
message: { type: 'string' },
level: { type: 'string', enum: ['info', 'warn', 'error'] }
},
required: ['title', 'message']
}
}
},
{
type: 'function',
function: {
name: 'request_host_action',
description:
'Request a schema-validated host action through HRPC. Enabled by default; emergency_stop_mutations or a denylist can still block.',
parameters: {
type: 'object',
properties: {
action: { type: 'string', description: 'Host action id' },
payload: { type: 'object', description: 'Action payload object' }
},
required: ['action']
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run',
description:
'Start an autonomous coding run with a goal. Enables autonomous mode, keeps the ReAct loop going until task_complete, stop, or timebox. Optional scope path, runtime cap, and quality-gate ids.',
parameters: {
type: 'object',
properties: {
goal: { type: 'string', description: 'Task goal the agent should complete autonomously' },
scope_path: { type: 'string', description: 'Preferred working scope path (optional)' },
max_runtime_ms: { type: 'integer', description: 'Optional runtime cap override' },
required_checks: {
type: 'array',
items: { type: 'string' },
description: 'Optional quality gates (allowlisted check ids)'
}
},
required: ['goal']
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_status',
description:
'Return current autonomous run state, elapsed/runtime budget, configured checks, and latest status.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_stop',
description:
'Request manual stop for an autonomous run; loop will stop safely on next control checkpoint.',
parameters: {
type: 'object',
properties: {
reason: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
name: 'verification_hints',
description:
'Suggest npm/node verification commands for a developer working at the Bare OS git checkout on the host (paths or topic keywords). Does not run commands.',
parameters: {
type: 'object',
properties: {
topic: {
type: 'string',
description: 'Free-text task or area (e.g. kernel init, seed RPC, shell)'
},
paths_touched: {
type: 'string',
description:
'Optional comma-separated path-like strings from the repo (forward slashes ok)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'runtime_diagnostic_bundle',
description:
'Non-secret snapshot: ctx API version, optional resource status, and every readable /proc/bare_os file. Prefer over many separate read_proc_file calls.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'search_replace',
description:
'Replace old_string with new_string in a file. Match must be unique unless replace_all is true. Same uniqueness rules as Grok Build edit_file.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
file_path: { type: 'string', description: 'Alias for path' },
old_string: { type: 'string' },
new_string: { type: 'string' },
replace_all: { type: 'boolean' }
},
required: ['old_string', 'new_string']
}
}
},
{
type: 'function',
function: {
name: 'glob_files',
description:
'Walk the VFS from root and return paths matching a glob (supports ** and *). Prefer this over run_command find/ls.',
parameters: {
type: 'object',
properties: {
pattern: {
type: 'string',
description: 'Glob such as **/*.js or src/**/foo.md'
},
root: {
type: 'string',
description: 'Directory to walk (default session home)'
},
max_results: { type: 'integer', description: 'Default 100, cap 400' }
},
required: ['pattern']
}
}
},
{
type: 'function',
function: {
name: 'todo_write',
description:
'Create or update the session todo list (Grok-style). merge=true updates by id; merge=false replaces the list.',
parameters: {
type: 'object',
properties: {
todos: {
type: 'array',
description: 'Items with id, optional content, status pending|in_progress|completed|cancelled',
items: {
type: 'object',
properties: {
id: { type: 'string' },
content: { type: 'string' },
status: { type: 'string' }
},
required: ['id']
}
},
merge: {
type: 'boolean',
description: 'Default true: update matching ids, keep others'
}
},
required: ['todos']
}
}
},
{
type: 'function',
function: {
name: 'memory_search',
description:
'Search ~/.agent/workspace/memory, MEMORY.md, and compact.md by keyword (guest-safe, no embeddings).',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
max_hits: { type: 'integer', description: 'Default 8' }
},
required: ['query']
}
}
},
{
type: 'function',
function: {
name: 'memory_get',
description:
'Read one memory file. Path must be under ~/.agent/workspace/memory or a named file there.',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Absolute path or basename under workspace/memory' },
max_bytes: { type: 'integer' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'enter_plan_mode',
description:
'Switch to read-only plan mode. Writes are denied except ~/.agent/plan.md until exit_plan_mode.',
parameters: {
type: 'object',
properties: {
note: { type: 'string', description: 'Optional starter text appended to plan.md' }
}
}
}
},
{
type: 'function',
function: {
name: 'exit_plan_mode',
description: 'Leave plan mode and allow mutating tools again.',
parameters: {
type: 'object',
properties: {
summary: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
name: 'ask_user_question',
description:
'Pose one or more multiple-choice questions to the user (Grok-style). Persists to ~/.agent/ask.json.',
parameters: {
type: 'object',
properties: {
questions: {
type: 'array',
items: {
type: 'object',
properties: {
question: { type: 'string' },
options: {
type: 'array',
items: {
type: 'object',
properties: {
label: { type: 'string' },
description: { type: 'string' }
}
}
},
multi_select: { type: 'boolean' }
},
required: ['question']
}
}
},
required: ['questions']
}
}
},
{
type: 'function',
function: {
name: 'grep',
description:
'Search file contents with a JS regular expression (Grok-style, VFS-native). Prefer this over run_command grep. Supports glob, ignore_case, context lines, and files_with_matches.',
parameters: {
type: 'object',
properties: {
pattern: {
type: 'string',
description: 'JavaScript regular expression (no surrounding slashes)'
},
path: {
type: 'string',
description: 'Directory or file to search (default session home)'
},
root: { type: 'string', description: 'Alias for path' },
glob: {
type: 'string',
description: 'Optional file glob such as **/*.js'
},
ignore_case: { type: 'boolean' },
before: { type: 'integer', description: 'Context lines before each match' },
after: { type: 'integer', description: 'Context lines after each match' },
context: { type: 'integer', description: 'Context lines before and after' },
max_matches: { type: 'integer', description: 'Default 50, cap 200' },
files_with_matches: {
type: 'boolean',
description: 'Return matching paths only'
},
output_mode: {
type: 'string',
description: 'content (default) | files_with_matches | count'
}
},
required: ['pattern']
}
}
},
{
type: 'function',
function: {
name: 'apply_patch',
description:
'Apply a Codex/Grok multi-file patch (*** Begin Patch … *** End Patch). Supports *** Add File, *** Delete File, *** Update File, optional *** Move to, and @@ hunks with + / - / context lines. Prefer this for multi-hunk or multi-file edits.',
parameters: {
type: 'object',
properties: {
patch: { type: 'string', description: 'Full apply_patch document' },
input: { type: 'string', description: 'Alias for patch' }
},
required: ['patch']
}
}
},
{
type: 'function',
function: {
name: 'update_goal',
description:
'Report progress on the current autonomous/session goal. completed=true ends the goal. blocked_reason is a failure signal after 3+ failed attempts — never use it for success.',
parameters: {
type: 'object',
properties: {
completed: { type: 'boolean' },
message: { type: 'string', description: 'Short progress or completion note' },
blocked_reason: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
name: 'web_search',
description:
'Search the public web (DuckDuckGo instant answers via the same HTTP policy as web_fetch / wget). Use for current facts, docs, and error lookup. Then web_fetch promising URLs.',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
max_results: { type: 'integer', description: 'Default 8, cap 16' }
},
required: ['query']
}
}
},
{
type: 'function',
function: {
name: 'git_status',
description:
'Guest git status --short plus diff --stat (isomorphic-git /bin/git). Prefer this over parsing git via run_command when you only need a snapshot.',
parameters: {
type: 'object',
properties: {
cwd: {
type: 'string',
description: 'Repo directory (default PWD or home)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'memory_append',
description:
'Append a durable FACT / CHECK / RISK / HANDOFF line to MEMORY.md or today\'s daily log. Prefer this over write_file for memory.',
parameters: {
type: 'object',
properties: {
text: { type: 'string', description: 'What to remember' },
kind: {
type: 'string',
enum: ['FACT', 'CHECK', 'RISK', 'HANDOFF', 'NOTE'],
description: 'Ledger tag (default NOTE)'
},
daily: {
type: 'boolean',
description: 'Append to memory/YYYY-MM-DD.md instead of MEMORY.md'
}
},
required: ['text']
}
}
},
{
type: 'function',
function: {
name: 'list_skills',
description:
'List discovered SKILL.md ids from workspace/skills and ~/.agent/skills. Use read_skill to load one.',
parameters: {
type: 'object',
properties: {
max: { type: 'integer', description: 'Max entries (default 40)' }
}
}
}
},
{
type: 'function',
function: {
name: 'schedule_task',
description:
'Create or replace a guest timer that re-runs the agent (Grok scheduler, mapped to ~/.config/bare-os/timers). Interval like 5m, 2h, 1d, or five cron fields. Max 8 timers on the guest.',
parameters: {
type: 'object',
properties: {
id: { type: 'string', description: 'Timer id (stored as agent-<id>.timer)' },
interval: { type: 'string', description: '5m, 2h, 1d, or cron (min hour day month dow)' },
prompt: { type: 'string', description: 'Task the agent should run on each fire' },
auto: {
type: 'boolean',
description: 'Pass --auto (default true)'
}
},
required: ['interval', 'prompt']
}
}
},
{
type: 'function',
function: {
name: 'unschedule_task',
description: 'Delete a guest agent timer by id (agent-<id>.timer).',
parameters: {
type: 'object',
properties: {
id: { type: 'string' }
},
required: ['id']
}
}
},
{
type: 'function',
function: {
name: 'list_scheduled',
description:
'List guest agent timers (agent-*.timer) under ~/.config/bare-os/timers, with previews.',
parameters: { type: 'object', properties: {} }
}
},
{
type: 'function',
function: {
name: 'fuzzy_find',
description:
'Fuzzy-search file names under a root (Grok-style). Prefer this when you remember part of a filename.',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
root: { type: 'string', description: 'Directory to walk (default home)' },
max: { type: 'integer', description: 'Default 20, cap 80' }
},
required: ['query']
}
}
},
{
type: 'function',
function: {
name: 'read_many',
description:
'Read several UTF-8 files in one call (bounded). Prefer over many read_file calls for a small set.',
parameters: {
type: 'object',
properties: {
paths: { type: 'array', items: { type: 'string' } },
max_bytes_each: { type: 'integer', description: 'Default 32000' }
},
required: ['paths']
}
}
},
{
type: 'function',
function: {
name: 'wait_for',
description:
'Poll a file or guest command until a regex matches, or timeout (Grok monitor lite; sync).',
parameters: {
type: 'object',
properties: {
pattern: { type: 'string', description: 'JS regex' },
path: { type: 'string', description: 'File to poll' },
command: { type: 'string', description: 'Guest command whose output is polled' },
timeout_ms: { type: 'integer', description: 'Default 15000, cap 120000' },
interval_ms: { type: 'integer', description: 'Default 400, min 100' },
ignore_case: { type: 'boolean' }
},
required: ['pattern']
}
}
},
{
type: 'function',
function: {
name: 'undo_last_edit',
description:
'Restore the last file snapshot from ~/.agent/edits.json (write_file / edit / apply_patch / delete).',
parameters: { type: 'object', properties: {} }
}
},
{
type: 'function',
function: {
name: 'git_diff',
description: 'git diff (optional path / --stat) via /bin/git. Prefer over raw run_command.',
parameters: {
type: 'object',
properties: {
cwd: { type: 'string' },
path: { type: 'string' },
stat: { type: 'boolean', description: 'Only --stat (default false)' }
}
}
}
},
{
type: 'function',
function: {
name: 'history_search',
description: 'Search this session\'s ~/.agent/history.json by keyword.',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
max: { type: 'integer' }
},
required: ['query']
}
}
},
{
type: 'function',
function: {
name: 'git_log',
description:
'git log --oneline (optional -N / path) via /bin/git. Prefer over raw run_command.',
parameters: {
type: 'object',
properties: {
cwd: { type: 'string' },
max: { type: 'integer', description: 'Default 20, cap 80' },
path: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
name: 'git_show',
description: 'git show <rev> (optional path / --stat) via /bin/git.',
parameters: {
type: 'object',
properties: {
cwd: { type: 'string' },
rev: { type: 'string', description: 'Commit-ish (default HEAD)' },
path: { type: 'string' },
stat: { type: 'boolean' }
}
}
}
},
{
type: 'function',
function: {
name: 'git_blame',
description: 'git blame a file via /bin/git.',
parameters: {
type: 'object',
properties: {
cwd: { type: 'string' },
path: { type: 'string' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'copy_path',
description:
'Copy a file or directory on the VFS (parents created). Prefer this over run_command cp.',
parameters: {
type: 'object',
properties: {
from_path: { type: 'string' },
to_path: { type: 'string' }
},
required: ['from_path', 'to_path']
}
}
},
{
type: 'function',
function: {
name: 'diff_files',
description:
'Unified diff of two UTF-8 files (no git). Prefer this when comparing two paths that are not a git hunk.',
parameters: {
type: 'object',
properties: {
from_path: { type: 'string' },
to_path: { type: 'string' },
context: { type: 'integer', description: 'Context lines (default 3)' }
},
required: ['from_path', 'to_path']
}
}
},
{
type: 'function',
function: {
name: 'find_symbol',
description:
'Find likely definitions of a symbol (function/class/const/def/fn) under a root. Prefer over ad-hoc grep for go-to-definition.',
parameters: {
type: 'object',
properties: {
name: { type: 'string' },
root: { type: 'string' },
glob: { type: 'string' },
max: { type: 'integer' }
},
required: ['name']
}
}
},
{
type: 'function',
function: {
name: 'create_skill',
description:
'Write a Grok-style SKILL.md (YAML frontmatter + body) under ~/.agent/workspace/skills/<id>/.',
parameters: {
type: 'object',
properties: {
id: { type: 'string', description: 'Folder name (kebab-case)' },
name: { type: 'string' },
description: { type: 'string' },
body: { type: 'string', description: 'Markdown instructions after frontmatter' }
},
required: ['id', 'description']
}
}
},
{
type: 'function',
function: {
name: 'remember',
description:
'Save a durable FACT to MEMORY.md now (Grok /remember). Same store as memory_append.',
parameters: {
type: 'object',
properties: {
text: { type: 'string' },
daily: { type: 'boolean' }
},
required: ['text']
}
}
},
{
type: 'function',
function: {
name: 'rewind_session',
description:
'Grok /rewind: drop the last N user turns from ~/.agent/history.json (and everything after). Default 1 turn.',
parameters: {
type: 'object',
properties: {
steps: { type: 'integer', description: 'How many user turns to drop (default 1)' },
user_index: { type: 'integer', description: 'Rewind to this 0-based user turn instead' },
keep_user: { type: 'boolean', description: 'Keep the target user prompt' }
}
}
}
},
{
type: 'function',
function: {
name: 'export_session',
description:
'Grok /export: write the current chat history as Markdown. Default ~/.agent/export.md.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
name: 'task_complete',
description:
'Call when the user task is fully done. Provide a concise summary.',
parameters: {
type: 'object',
properties: {
summary: { type: 'string' }
},
required: ['summary']
}
}
}
]
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} absPath
*/
function bareAgentPathAllowed(absPath) {
if (typeof bareAgentPathAllowedRead === 'function') {
return bareAgentPathAllowedRead(absPath)
}
const p = String(absPath || '').replace(/\\/g, '/')
return Boolean(p.startsWith('/') && !p.includes('..'))
}
/**
* @param {string} command
* @param {unknown} denyList
*/
function bareAgentCommandDeniedByList(command, denyList) {
const cmd = String(command || '').toLowerCase()
const list = Array.isArray(denyList) ? denyList : []
for (let i = 0; i < list.length; i++) {
const needle = String(list[i] || '').trim().toLowerCase()
if (needle && cmd.includes(needle)) return needle
}
return ''
}
/**
* @param {{
* ctx: Record<string, unknown>,
* toolName: string,
* argsJson: string,
* paths: { dir: string, config: string, cmdOut: string, workspace?: string, workspaceSkills?: string, skillsGlobal?: string },
* signal?: AbortSignal,
* appendProgress: (line: string) => void,
* home: string,
* configRef: { current: Record<string, unknown> },
* manCacheRef?: { db: unknown | null },
* onTaskComplete: (summary: string) => void
* }} o
*/
async function bareAgentDispatchTool(o) {
const {
ctx,
toolName,
argsJson,
paths,
signal,
appendProgress,
home,
configRef,
manCacheRef,
onTaskComplete
} = o
const internalGate = Boolean(o.internalGate)
const manDbCache = manCacheRef || { db: null }
/** @type {Record<string, unknown>} */
let args = {}
try {
args = /** @type {Record<string, unknown>} */ (JSON.parse(argsJson || '{}'))
} catch {
return bareAgentJsonResult({ ok: false, error: 'invalid_tool_arguments_json' })
}
const cfgNow = configRef.current || {}
if (
!internalGate &&
cfgNow.autonomous_active &&
Array.isArray(cfgNow.autonomous_deny_ops) &&
cfgNow.autonomous_deny_ops.map((x) => String(x)).includes(toolName)
) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_op_denied', tool: toolName })
}
if (
!internalGate &&
cfgNow.plan_mode_active &&
typeof bareAgentPlanModeToolAllowed === 'function' &&
!bareAgentPlanModeToolAllowed(toolName, args, paths)
) {
return bareAgentJsonResult({
ok: false,
error: 'plan_mode_readonly',
tool: toolName,
hint: 'exit_plan_mode before mutating, or write only ' + String(paths.plan || '~/.agent/plan.md')
})
}
if (!internalGate && typeof bareAgentRunPreToolHooks === 'function' && paths.hooks) {
const hookReason = await bareAgentRunPreToolHooks(ctx, paths.hooks, toolName, args)
if (hookReason) {
return bareAgentJsonResult({
ok: false,
error: 'hook_denied',
tool: toolName,
reason: hookReason
})
}
}
if (toolName === 'list_dir') {
return bareAgentDispatchTool({ ...o, toolName: 'list_directory' })
}
if (toolName === 'glob') {
return bareAgentDispatchTool({ ...o, toolName: 'glob_files' })
}
if (toolName === 'ripgrep') {
return bareAgentDispatchTool({ ...o, toolName: 'grep' })
}
if (toolName === 'remember') {
return bareAgentDispatchTool({
...o,
toolName: 'memory_append',
argsJson: JSON.stringify({ ...args, kind: 'FACT' })
})
}
const vfs = ctx.vfs
const mutateDenyPrefixes = (function () {
const cfg = configRef.current || {}
if (Array.isArray(cfg.mutate_deny_prefixes) && cfg.mutate_deny_prefixes.length) {
return cfg.mutate_deny_prefixes.map((x) => String(x || '')).filter(Boolean)
}
return typeof BARE_AGENT_MUTATE_DENY_PREFIXES !== 'undefined'
? BARE_AGENT_MUTATE_DENY_PREFIXES
: ['/bin', '/etc', '/boot', '/lib', '/usr', '/share', '/proc', '/dev', '/sys', '/run']
})()
const AUTONOMOUS_CHECK_ALLOW = {
'coreutils-test': 'npm test -w bare-os-coreutils',
'verify-kernel-seeder-parity': 'node scripts/verify-kernel-seeder-parity.mjs',
'verify-man-coverage': 'node scripts/verify-man-coverage.mjs',
'verify-ctx-api-feature-bits': 'node scripts/verify-ctx-api-feature-bits.mjs'
}
const execLine =
typeof ctx.execLine === 'function'
? /** @type {(s: string, opts?: unknown) => Promise<unknown>} */ (
ctx.execLine.bind(ctx)
)
: null
/**
* @param {string} line
* @param {number | undefined} timeoutMs
* @param {{ captureExit?: boolean }} [captureOpts]
*/
async function captureExec(line, timeoutMs, captureOpts) {
const outPath = paths.cmdOut
const captureExit = Boolean(captureOpts && captureOpts.captureExit)
const trimmed = String(line || '').trim()
const compoundShell =
/\n/.test(trimmed) ||
/(^|[;\s])(for|if|while|until|case|function)\b/.test(trimmed) ||
/\b(do|done|then|else|fi|esac)\b/.test(trimmed) ||
/&&|\|\||\(\(|\{|\}/.test(trimmed)
/**
* Do not wrap with `{ cmd ; }` — Bare OS `splitTokensBySemicolon` splits on every `;`
* at depth 0 and does not treat `{ … }` as a compound, so `{` became argv[0]
* (`unknown command: {`). Redirect only; read exit from env after `execLine`.
*/
const wrapped =
(compoundShell
? 'sh -c ' + bareAgentShellQuote(trimmed)
: line) +
' > ' +
bareAgentShellQuote(outPath) +
' 2>&1'
const opts =
signal || timeoutMs
? {
signal,
timeoutMs: timeoutMs || undefined
}
: undefined
try {
if (opts) await execLine(wrapped, opts)
else await execLine(wrapped)
} catch (e) {
const msg = e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return { ok: false, exitNote: msg }
}
let captured = ''
try {
if (vfs && typeof vfs.readFile === 'function') {
const buf = await vfs.readFile(outPath)
if (buf && buf.length) {
captured =
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
}
}
} catch {
/* ignore */
}
if (captureExit) {
const env = ctx.vfs?.env && typeof ctx.vfs.env === 'object' ? ctx.vfs.env : null
const rawEc =
env && env.BARE_OS_EXIT_STATUS != null && env.BARE_OS_EXIT_STATUS !== ''
? env.BARE_OS_EXIT_STATUS
: ctx.exitCode
const n = Number(rawEc)
const codeStr = String(Number.isFinite(n) ? n : 0)
const exitLine = '\nEXIT:' + codeStr + '\n'
captured += exitLine
try {
if (vfs?.readFile && vfs?.writeFile && ctx.b4a && typeof ctx.b4a.concat === 'function') {
let prev = await vfs.readFile(outPath)
const prevBytes =
prev && prev.length
? prev instanceof Uint8Array
? prev
: ctx.b4a.from(prev)
: ctx.b4a.from('')
await vfs.writeFile(
outPath,
ctx.b4a.concat([prevBytes, ctx.b4a.from(exitLine)])
)
}
} catch {
/* ignore */
}
}
const max = 120_000
if (captured.length > max) captured = captured.slice(0, max) + '\n… truncated'
return { ok: true, stdout_stderr: captured }
}
/**
* @param {string} text
*/
function redactSensitiveText(text) {
return String(text || '')
.replace(/\bBearer\s+\S+/gi, 'Bearer <redacted>')
.replace(/\bsk-[a-zA-Z0-9]{16,}\b/g, '<redacted_sk>')
.replace(
/\b([A-Z0-9_]*(KEY|TOKEN|SECRET|PASS|PASSWORD)[A-Z0-9_]*)\s*=\s*([^\s]+)/gi,
'$1=<redacted>'
)
}
/**
* @param {string} path
* @param {number} maxChars
* @param {boolean} tailOnly
* @param {boolean} redact
*/
async function readBoundedText(path, maxChars, tailOnly, redact) {
if (!vfs || typeof vfs.readFile !== 'function') {
return { ok: false, error: 'vfs unavailable' }
}
try {
const b = await vfs.readFile(path)
if (!b || !b.length) return { ok: false, error: 'empty_or_missing' }
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
if (redact) t = redactSensitiveText(t)
const out = tailOnly ? t.slice(-maxChars) : bareAgentTruncateChars(t, maxChars)
return { ok: true, path, text: out, truncated: t.length > out.length }
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return { ok: false, error: msg }
}
}
/**
* @param {string} p
*/
function autonomousPathAllowed(p) {
const s = String(p || '').trim()
if (!s) return true
return bareAgentPathAllowed(s)
}
/**
* @param {string} p
* @param {'read' | 'mutate'} [kind]
*/
function enforceAutonomousPath(p, kind) {
const cfg = configRef.current || {}
const path = String(p || '').trim()
if (!path) return true
if (kind === 'mutate') {
if (
typeof bareAgentPathAllowedMutate === 'function' &&
!bareAgentPathAllowedMutate(path, mutateDenyPrefixes)
) {
return false
}
} else if (!bareAgentPathAllowed(path)) {
return false
}
if (!cfg.autonomous_active) return true
if (kind !== 'mutate') return true
const allowList = Array.isArray(cfg.autonomous_allow_paths)
? cfg.autonomous_allow_paths.map((x) => String(x || '').trim()).filter(Boolean)
: []
if (!allowList.length || allowList.includes('*')) return true
for (const pref of allowList) {
if (path === pref || path.startsWith(pref.endsWith('/') ? pref : pref + '/')) return true
}
return false
}
try {
if (toolName === 'read_skill') {
const skill = typeof args.skill === 'string' ? args.skill.trim() : ''
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 512_000)
: 256_000
if (!skill) {
return bareAgentJsonResult({ ok: false, error: 'skill_required' })
}
const skillPaths = {
workspaceSkills:
typeof paths.workspaceSkills === 'string'
? paths.workspaceSkills
: paths.dir + '/workspace/skills',
skillsGlobal:
typeof paths.skillsGlobal === 'string' ? paths.skillsGlobal : paths.dir + '/skills'
}
appendProgress('read_skill ' + skill)
const loaded = await bareAgentLoadSkillMarkdown(ctx, skillPaths, skill)
if (!loaded.ok) {
return bareAgentJsonResult({
ok: false,
error: loaded.error || 'load_failed',
skill
})
}
let content = loaded.content
if (content.length > maxB) content = content.slice(0, maxB) + '\n… truncated'
return bareAgentJsonResult({
ok: true,
id: loaded.id,
name: loaded.skill,
path: loaded.path,
source: loaded.source,
content
})
}
if (toolName === 'glob_files') {
const pattern = typeof args.pattern === 'string' ? args.pattern.trim() : ''
if (!pattern) return bareAgentJsonResult({ ok: false, error: 'pattern_required' })
const root =
typeof args.root === 'string' && args.root.trim()
? args.root.trim()
: home || '/home'
if (!bareAgentPathAllowed(root)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const maxResults =
typeof args.max_results === 'number' && Number.isFinite(args.max_results)
? Math.min(Math.max(Math.floor(args.max_results), 1), 400)
: 100
if (typeof bareAgentGlobFiles !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'glob_unavailable' })
}
appendProgress('glob_files ' + pattern + ' @ ' + root)
const files = await bareAgentGlobFiles(ctx, root, pattern, {
maxFiles: Math.max(maxResults * 4, 200),
maxDepth: 10
})
return bareAgentJsonResult({
ok: true,
root,
pattern,
count: Math.min(files.length, maxResults),
truncated: files.length > maxResults,
files: files.slice(0, maxResults)
})
}
if (toolName === 'todo_write') {
const todosPath = paths.todos || paths.dir + '/todos.json'
const merge = args.merge !== false
const prev =
typeof bareAgentLoadTodos === 'function'
? await bareAgentLoadTodos(ctx, todosPath)
: []
let next
try {
next = bareAgentTodoApply(args.todos, { merge }, prev)
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
if (typeof bareAgentSaveTodos === 'function') {
await bareAgentSaveTodos(ctx, todosPath, next)
}
const summary = bareAgentTodoSummarize(next)
appendProgress('todo_write open=' + String(summary.open) + '/' + String(summary.total))
return bareAgentJsonResult({
ok: true,
merge,
todos: next,
summary
})
}
if (toolName === 'memory_search') {
const query = typeof args.query === 'string' ? args.query.trim() : ''
if (!query) return bareAgentJsonResult({ ok: false, error: 'query_required' })
const maxHits =
typeof args.max_hits === 'number' && Number.isFinite(args.max_hits)
? Math.min(Math.max(Math.floor(args.max_hits), 1), 24)
: 8
const memDir =
typeof paths.workspaceMemory === 'string'
? paths.workspaceMemory
: paths.dir + '/workspace/memory'
const workspace =
typeof paths.workspace === 'string' ? paths.workspace : paths.dir + '/workspace'
const seeds = []
if (typeof bareAgentVfsWalkFiles === 'function') {
const walked = await bareAgentVfsWalkFiles(ctx, memDir, {
maxFiles: 80,
maxDepth: 4
})
for (let i = 0; i < walked.length; i++) seeds.push(walked[i])
}
seeds.push(workspace + '/MEMORY.md')
if (paths.compact) seeds.push(paths.compact)
else seeds.push(paths.dir + '/compact.md')
const uniq = []
const seenMem = Object.create(null)
for (let i = 0; i < seeds.length; i++) {
if (seenMem[seeds[i]]) continue
seenMem[seeds[i]] = 1
uniq.push(seeds[i])
}
appendProgress('memory_search ' + query.slice(0, 80))
const hits =
typeof bareAgentMemorySearchFiles === 'function'
? await bareAgentMemorySearchFiles(ctx, uniq, query, { maxHits })
: []
return bareAgentJsonResult({ ok: true, query, hits })
}
if (toolName === 'memory_get') {
const rawPath = typeof args.path === 'string' ? args.path.trim() : ''
if (!rawPath) return bareAgentJsonResult({ ok: false, error: 'path_required' })
const memDir =
typeof paths.workspaceMemory === 'string'
? paths.workspaceMemory
: paths.dir + '/workspace/memory'
const workspace =
typeof paths.workspace === 'string' ? paths.workspace : paths.dir + '/workspace'
let path = rawPath
if (!path.startsWith('/')) path = memDir + '/' + path.replace(/^\/+/, '')
const allowed =
path === workspace + '/MEMORY.md' ||
path === (paths.compact || paths.dir + '/compact.md') ||
path === memDir ||
path.indexOf(memDir + '/') === 0
if (!allowed) {
return bareAgentJsonResult({
ok: false,
error: 'memory_path_denied',
hint: 'path must be under ' + memDir
})
}
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 256_000)
: 64_000
appendProgress('memory_get ' + path)
let text = await bareAgentReadTextFile(ctx, path)
if (!text) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing', path })
const truncated = text.length > maxB
if (truncated) text = text.slice(0, maxB) + '\n… truncated'
return bareAgentJsonResult({ ok: true, path, content: text, truncated })
}
if (toolName === 'enter_plan_mode') {
const planPath = paths.plan || paths.dir + '/plan.md'
configRef.current = { ...(configRef.current || {}), plan_mode_active: true }
if (typeof bareAgentSaveConfigFromTools === 'function') {
await bareAgentSaveConfigFromTools(ctx, paths, configRef.current)
}
const note = typeof args.note === 'string' ? args.note.trim() : ''
let prev = ''
try {
prev = await bareAgentReadTextFile(ctx, planPath)
} catch {
prev = ''
}
if (!prev.trim()) {
const starter =
'# Plan\n\n' +
(note ? note + '\n' : '- Investigate with read-only tools.\n- Write the plan here.\n- Call exit_plan_mode when ready to implement.\n')
await bareAgentWriteTextFile(ctx, planPath, starter)
} else if (note) {
await bareAgentWriteTextFile(ctx, planPath, prev.replace(/\s*$/, '') + '\n\n' + note + '\n')
}
appendProgress('enter_plan_mode ' + planPath)
return bareAgentJsonResult({
ok: true,
plan_mode_active: true,
plan: planPath
})
}
if (toolName === 'exit_plan_mode') {
const summary = typeof args.summary === 'string' ? args.summary.trim() : ''
configRef.current = { ...(configRef.current || {}), plan_mode_active: false }
if (typeof bareAgentSaveConfigFromTools === 'function') {
await bareAgentSaveConfigFromTools(ctx, paths, configRef.current)
}
if (summary && paths.plan) {
const prev = await bareAgentReadTextFile(ctx, paths.plan)
await bareAgentWriteTextFile(
ctx,
paths.plan,
(prev ? prev.replace(/\s*$/, '') + '\n\n' : '') + '## Exit\n' + summary + '\n'
)
}
appendProgress('exit_plan_mode')
return bareAgentJsonResult({
ok: true,
plan_mode_active: false,
summary: summary || null
})
}
if (toolName === 'ask_user_question') {
const rows = Array.isArray(args.questions) ? args.questions : []
if (!rows.length) {
return bareAgentJsonResult({ ok: false, error: 'questions_required' })
}
/** @type {Record<string, unknown>[]} */
const questions = []
for (let i = 0; i < rows.length; i++) {
const row = rows[i] && typeof rows[i] === 'object' ? rows[i] : {}
const question = String(row.question || '').trim()
if (!question) continue
const opts = Array.isArray(row.options) ? row.options : []
questions.push({
question,
multi_select: Boolean(row.multi_select),
options: opts.map(function (opt) {
if (opt && typeof opt === 'object') {
return {
label: String(opt.label || ''),
description: String(opt.description || '')
}
}
return { label: String(opt || ''), description: '' }
})
})
}
if (!questions.length) {
return bareAgentJsonResult({ ok: false, error: 'questions_required' })
}
const askPath = paths.ask || paths.dir + '/ask.json'
const payload = {
asked_at: new Date().toISOString(),
questions
}
if (typeof bareAgentWriteJsonFile === 'function') {
await bareAgentWriteJsonFile(ctx, askPath, payload)
}
const text = questions
.map(function (q, i) {
const opts = Array.isArray(q.options)
? q.options
.map(function (o, j) {
return (
' ' +
String(j + 1) +
') ' +
String(o.label || '') +
(o.description ? ' — ' + String(o.description) : '')
)
})
.join('\n')
: ''
return (
String(i + 1) +
'. ' +
q.question +
(q.multi_select ? ' (multi-select)' : '') +
(opts ? '\n' + opts : '')
)
})
.join('\n')
appendProgress('ask_user_question n=' + String(questions.length))
return bareAgentJsonResult({
ok: true,
ask: askPath,
questions,
text,
hint: 'Wait for the user to answer these questions on the next turn.'
})
}
if (toolName === 'task_complete') {
const summary = typeof args.summary === 'string' ? args.summary : ''
appendProgress('task_complete: ' + summary.slice(0, 200))
onTaskComplete(summary || '(done)')
return bareAgentJsonResult({
ok: true,
completed: true,
summary
})
}
if (toolName === 'autonomous_run') {
const goal = typeof args.goal === 'string' ? args.goal.trim() : ''
const scopePath = typeof args.scope_path === 'string' ? args.scope_path.trim() : ''
const cfg = configRef.current || {}
if (!goal) return bareAgentJsonResult({ ok: false, error: 'goal_required' })
if (scopePath && !autonomousPathAllowed(scopePath)) {
return bareAgentJsonResult({ ok: false, error: 'scope_path_denied' })
}
const maxRuntimeMsRaw =
typeof args.max_runtime_ms === 'number' && Number.isFinite(args.max_runtime_ms)
? args.max_runtime_ms
: cfg.autonomous_max_runtime_ms
const requiredChecks = Array.isArray(args.required_checks)
? args.required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: []
const unknown = requiredChecks.filter((x) => !Object.prototype.hasOwnProperty.call(AUTONOMOUS_CHECK_ALLOW, x))
if (unknown.length) {
return bareAgentJsonResult({ ok: false, error: 'unknown_required_checks', unknown, allowlist: Object.keys(AUTONOMOUS_CHECK_ALLOW) })
}
const started =
typeof bareAgentBeginAutonomousRun === 'function'
? bareAgentBeginAutonomousRun(cfg, {
goal,
maxRuntimeMs: maxRuntimeMsRaw,
requiredChecks
})
: {
...cfg,
autonomous_mode_enabled: true,
autonomous_active: true,
autonomous_stop_requested: false,
autonomous_started_at_ms: Date.now(),
autonomous_goal: goal,
autonomous_status: 'running',
autonomous_last_error: '',
autonomous_max_runtime_ms: Math.min(
Math.max(Math.floor(Number(maxRuntimeMsRaw) || 0), 60000),
7_200_000
),
autonomous_completion_required_checks: requiredChecks
}
const merged = bareAgentMergeConfigPatch(cfg, started)
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run start goal=' + goal.slice(0, 160))
return bareAgentJsonResult({
ok: true,
active: true,
enabled: true,
goal,
scope_path: scopePath || null,
max_runtime_ms: merged.autonomous_max_runtime_ms,
required_checks: requiredChecks,
status: 'running'
})
}
if (toolName === 'autonomous_run_status') {
const cfg = configRef.current || {}
const started = Number(cfg.autonomous_started_at_ms) || 0
const elapsed = started > 0 ? Math.max(0, Date.now() - started) : 0
const maxRuntime = Number(cfg.autonomous_max_runtime_ms) || 0
return bareAgentJsonResult({
ok: true,
autonomous_mode_enabled: Boolean(cfg.autonomous_mode_enabled),
active: Boolean(cfg.autonomous_active),
stop_requested: Boolean(cfg.autonomous_stop_requested),
goal: String(cfg.autonomous_goal || ''),
status: String(cfg.autonomous_status || 'idle'),
last_error: String(cfg.autonomous_last_error || ''),
started_at_ms: started,
elapsed_ms: elapsed,
max_runtime_ms: maxRuntime,
remaining_ms: maxRuntime > 0 ? Math.max(0, maxRuntime - elapsed) : 0,
required_checks: Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks
: []
})
}
if (toolName === 'autonomous_run_stop') {
const cfg = configRef.current || {}
const reason = typeof args.reason === 'string' ? args.reason.trim() : ''
const stopped =
typeof bareAgentStopAutonomousRun === 'function'
? bareAgentStopAutonomousRun(cfg, reason)
: {
...cfg,
autonomous_stop_requested: true,
autonomous_status: 'stopping',
autonomous_last_error: reason || String(cfg.autonomous_last_error || '')
}
const merged = bareAgentMergeConfigPatch(cfg, stopped)
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run_stop ' + (reason || 'requested'))
return bareAgentJsonResult({ ok: true, stop_requested: true, reason: reason || null })
}
if (toolName === 'read_file') {
const path =
typeof bareAgentToolPathArg === 'function'
? bareAgentToolPathArg(args)
: typeof args.path === 'string'
? args.path
: ''
if (!enforceAutonomousPath(path, 'read')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 1_000_000)
: 256_000
if (!bareAgentPathAllowed(path)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('read_file ' + path)
const buf = await vfs.readFile(path)
if (!buf) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
if (t.length > maxB) t = t.slice(0, maxB) + '\n… truncated'
const hasSlice =
(typeof args.offset === 'number' && Number.isFinite(args.offset)) ||
(typeof args.limit === 'number' && Number.isFinite(args.limit))
if (hasSlice && typeof bareAgentSliceFileLines === 'function') {
const sliced = bareAgentSliceFileLines(t, {
offset:
typeof args.offset === 'number' && Number.isFinite(args.offset)
? args.offset
: 1,
limit:
typeof args.limit === 'number' && Number.isFinite(args.limit)
? args.limit
: undefined,
numbered: args.numbered !== false
})
return bareAgentJsonResult({ ok: true, path, ...sliced })
}
return bareAgentJsonResult({ ok: true, path, content: t })
}
if (toolName === 'write_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path, 'mutate')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const content = typeof args.content === 'string' ? args.content : ''
if (!path.startsWith('/') || path.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
}
if (!vfs || typeof vfs.writeFile !== 'function' || typeof vfs.mkdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('write_file ' + path)
if (typeof bareAgentPushEdit === 'function' && paths.edits) {
try {
const prev = await bareAgentReadTextFile(ctx, path)
await bareAgentPushEdit(ctx, paths.edits, { path, prev, tool: 'write_file' })
} catch {
/* new file */
}
}
let dir = path.replace(/\/[^/]+$/, '')
if (dir && dir !== path) await vfs.mkdir(dir, { recursive: true })
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(content)
: new TextEncoder().encode(content)
await vfs.writeFile(path, body)
return bareAgentJsonResult({ ok: true, bytes: body.length })
}
if (toolName === 'edit_file' || toolName === 'search_replace') {
const path =
typeof bareAgentToolPathArg === 'function'
? bareAgentToolPathArg(args)
: typeof args.path === 'string'
? args.path
: ''
if (!enforceAutonomousPath(path, 'mutate')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs?.readFile || !vfs?.writeFile) {
return bareAgentJsonResult({ ok: false, error: 'path_or_vfs' })
}
appendProgress(toolName + ' ' + path)
const buf = await vfs.readFile(path)
if (typeof bareAgentPushEdit === 'function' && paths.edits) {
try {
const snap =
buf && buf.length
? typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
: ''
await bareAgentPushEdit(ctx, paths.edits, { path, prev: snap, tool: toolName })
} catch {
/* ignore */
}
}
let prev =
buf && buf.length
? typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
: ''
const full =
toolName === 'search_replace'
? ''
: typeof args.content === 'string'
? args.content
: ''
const oldStr = typeof args.old_string === 'string' ? args.old_string : ''
const newStr = typeof args.new_string === 'string' ? args.new_string : ''
const replaceAll = Boolean(args.replace_all)
let next = prev
let replacements = 0
if (full.length > 0) {
next = full
replacements = 1
} else if (typeof bareAgentSearchReplaceApply === 'function') {
const applied = bareAgentSearchReplaceApply(prev, oldStr, newStr, replaceAll)
if (!applied.ok) {
return bareAgentJsonResult({
ok: false,
error: applied.error,
count: applied.count,
hint: applied.hint
})
}
next = applied.next
replacements = applied.replacements || 0
} else if (oldStr) {
if (!prev.includes(oldStr)) {
return bareAgentJsonResult({ ok: false, error: 'old_string not found' })
}
next = replaceAll ? prev.split(oldStr).join(newStr) : prev.replace(oldStr, newStr)
replacements = 1
} else {
return bareAgentJsonResult({
ok: false,
error: 'need content or old_string+new_string'
})
}
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(next)
: new TextEncoder().encode(next)
let dir = path.replace(/\/[^/]+$/, '')
if (dir && dir !== path && typeof vfs.mkdir === 'function') {
await vfs.mkdir(dir, { recursive: true })
}
await vfs.writeFile(path, body)
return bareAgentJsonResult({
ok: true,
bytes: body.length,
replacements,
replace_all: replaceAll
})
}
if (toolName === 'create_directory') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path, 'mutate')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!path.startsWith('/')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
}
if (!vfs || typeof vfs.mkdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('mkdir ' + path)
await vfs.mkdir(path, { recursive: true })
return bareAgentJsonResult({ ok: true })
}
if (toolName === 'search_files') {
const pattern = typeof args.pattern === 'string' ? args.pattern : ''
const root = typeof args.root === 'string' ? args.root : '/home'
const maxLines =
typeof args.max_lines === 'number' && Number.isFinite(args.max_lines)
? Math.min(Math.floor(args.max_lines), 500)
: 200
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('search_files ' + pattern + ' @ ' + root)
const cmd =
'grep -Rnl -- ' +
bareAgentShellQuote(pattern) +
' ' +
bareAgentShellQuote(root) +
' 2>/dev/null | head -n ' +
maxLines
const r = await captureExec(cmd, 60000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'run_command') {
const command = typeof args.command === 'string' ? args.command : ''
const deniedBy = bareAgentCommandDeniedByList(
command,
(configRef.current || {}).command_deny
)
if (deniedBy) {
return bareAgentJsonResult({
ok: false,
error: 'command_denied',
matched: deniedBy
})
}
const timeoutMs =
typeof args.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
? Math.min(Math.floor(args.timeout_ms), 600000)
: 120000
const captureExit = Boolean(args.capture_exit)
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
let cwd = typeof args.cwd === 'string' ? args.cwd.trim() : ''
if (!cwd && paths.lastCwd) {
try {
cwd = (await bareAgentReadTextFile(ctx, paths.lastCwd)).trim()
} catch {
cwd = ''
}
}
let line = command
if (cwd) {
if (!bareAgentPathAllowed(cwd)) {
return bareAgentJsonResult({ ok: false, error: 'cwd_not_allowed' })
}
line = 'cd ' + bareAgentShellQuote(cwd) + ' && ' + command
try {
if (paths.lastCwd) await bareAgentWriteTextFile(ctx, paths.lastCwd, cwd + '\n')
} catch {
/* optional */
}
}
appendProgress('run_command ' + line.slice(0, 160))
const r = await captureExec(line, timeoutMs, { captureExit })
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, cwd: cwd || null, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'run_js_script') {
const code = typeof args.code === 'string' ? args.code : ''
const scriptPath = paths.dir + '/_tmp_agent_run.mjs'
if (!vfs?.writeFile || !execLine) {
return bareAgentJsonResult({ ok: false, error: 'vfs or execLine' })
}
appendProgress('run_js_script (' + code.length + ' chars)')
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(code)
: new TextEncoder().encode(code)
await vfs.writeFile(scriptPath, body)
/** Absolute path → kernel-runner runs .mjs like `./script.mjs` (no host `node` binary). captureExec adds stdout redirect. */
const cmd = bareAgentShellQuote(scriptPath)
const r = await captureExec(cmd, 60000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'get_system_info') {
/** @type {Record<string, unknown>} */
const info = {}
try {
info.ctxApiVersion =
typeof ctx.ctxApiVersion === 'string'
? ctx.ctxApiVersion
: typeof ctx.ctxApiVersion === 'number'
? String(ctx.ctxApiVersion)
: undefined
} catch {
/* ignore */
}
const want =
typeof args.want === 'string' ? args.want : 'summary'
if (want === 'summary') {
info.discovery_hint =
'For live kernel feature flags use read_proc_file on /proc/bare_os/features (or features.json); apropos_man only searches man-page text, not runtime state. Otherwise prefer read_proc_file, get_swarm_peers, get_resource_limits, read_man_page / apropos_man instead of dumping large blobs here.'
}
if (want === 'capabilities' && vfs?.readFile) {
try {
const b = await vfs.readFile('/proc/bare_os/capabilities.json')
if (b && b.length) {
info.capabilities_json =
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
}
} catch {
/* ignore */
}
}
if (want === 'swarm' && vfs?.readFile) {
try {
const b = await vfs.readFile('/proc/bare_os/swarm.json')
if (b && b.length) {
info.swarm =
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
ctx.b4a.toString
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
}
} catch {
/* ignore */
}
}
try {
if (execLine) await execLine('uname -a > ' + bareAgentShellQuote(paths.cmdOut) + ' 2>&1')
if (vfs?.readFile) {
const buf = await vfs.readFile(paths.cmdOut)
if (buf && buf.length) {
info.uname =
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf).trim()
: String(new TextDecoder().decode(buf)).trim()
}
}
} catch {
/* ignore */
}
appendProgress('get_system_info ' + want)
return bareAgentJsonResult({ ok: true, want, info })
}
if (toolName === 'edit_agent_config') {
const patch = args.patch
if (!patch || typeof patch !== 'object' || Array.isArray(patch)) {
return bareAgentJsonResult({ ok: false, error: 'bad patch' })
}
const merged = bareAgentMergeConfigPatch(configRef.current, patch)
configRef.current = merged
await bareAgentSaveConfigFromTools(ctx, paths, merged)
if (
Object.prototype.hasOwnProperty.call(patch, 'owner_name') ||
Object.prototype.hasOwnProperty.call(patch, 'agent_label')
) {
const workspace =
typeof paths.workspace === 'string'
? paths.workspace
: paths.dir + '/workspace'
await bareAgentSyncWorkspaceFromConfig(ctx, { workspace }, merged)
}
appendProgress('edit_agent_config')
return bareAgentJsonResult({ ok: true, saved: true })
}
if (toolName === 'list_bin') {
const limit =
typeof args.limit === 'number' && Number.isFinite(args.limit)
? Math.min(Math.floor(args.limit), 800)
: 400
if (!vfs || typeof vfs.readdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'readdir unavailable' })
}
appendProgress('list_bin')
try {
const names = await vfs.readdir('/bin')
const arr = Array.isArray(names) ? [...names].slice(0, limit) : []
arr.sort()
return bareAgentJsonResult({
ok: true,
count: arr.length,
names: arr
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'list_directory') {
const dir = typeof args.path === 'string' ? args.path : ''
const maxEnt =
typeof args.max_entries === 'number' && Number.isFinite(args.max_entries)
? Math.min(Math.floor(args.max_entries), 2000)
: 500
const includeStat = Boolean(args.include_stat)
if (!bareAgentPathAllowed(dir)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (Boolean(args.tree) && typeof bareAgentRenderTree === 'function') {
appendProgress('list_directory tree ' + dir)
const tree = await bareAgentRenderTree(ctx, dir, {
max: maxEnt,
maxDepth: args.max_depth
})
return bareAgentJsonResult(tree)
}
if (!vfs || typeof vfs.readdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'readdir unavailable' })
}
appendProgress('list_directory ' + dir)
try {
const names = await vfs.readdir(dir)
const arr = Array.isArray(names) ? [...names] : []
arr.sort()
const slice = arr.slice(0, maxEnt)
const base = dir.replace(/\/+$/, '') || '/'
/** @type {{ name: string, stat?: Record<string, unknown> }[]} */
const entries = []
for (const n of slice) {
const entry = { name: n }
if (includeStat && (vfs.lstat || vfs.stat)) {
try {
const full = base + '/' + n
const st =
typeof vfs.lstat === 'function'
? await vfs.lstat(full)
: await vfs.stat(full)
entry.stat = bareAgentSerializeStat(st, full)
} catch {
/* ignore per-entry stat errors */
}
}
entries.push(entry)
}
return bareAgentJsonResult({
ok: true,
path: dir,
count: entries.length,
truncated: arr.length > maxEnt,
entries
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'file_stat') {
const path = typeof args.path === 'string' ? args.path : ''
const follow = Boolean(args.follow_symlinks)
if (!bareAgentPathAllowed(path)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs) {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('file_stat ' + path)
try {
/** @type {unknown} */
let st = null
if (follow && typeof vfs.stat === 'function') st = await vfs.stat(path)
else if (typeof vfs.lstat === 'function') st = await vfs.lstat(path)
else if (typeof vfs.stat === 'function') st = await vfs.stat(path)
if (!st) return bareAgentJsonResult({ ok: false, error: 'stat unavailable' })
const serialized = bareAgentSerializeStat(st, path)
if (
serialized.kind === 'symlink' &&
typeof vfs.readlink === 'function'
) {
try {
serialized.target = await vfs.readlink(path)
} catch {
/* ignore */
}
}
return bareAgentJsonResult({ ok: true, stat: serialized })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'move_path') {
const from = typeof args.from_path === 'string' ? args.from_path : ''
const to = typeof args.to_path === 'string' ? args.to_path : ''
if (
!bareAgentPathAllowedMutate(from, mutateDenyPrefixes) ||
!bareAgentPathAllowedMutate(to, mutateDenyPrefixes) ||
from.includes('..') ||
to.includes('..')
) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('move_path')
const cmd =
'mv -- ' + bareAgentShellQuote(from) + ' ' + bareAgentShellQuote(to)
const r = await captureExec(cmd, 120000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'delete_path') {
const path = typeof args.path === 'string' ? args.path : ''
const recursive = Boolean(args.recursive)
const token = typeof args.confirm_token === 'string' ? args.confirm_token : ''
const cfg = configRef.current
const allowDel = Boolean(cfg && cfg.allow_delete)
const reqTok =
cfg && typeof cfg.require_confirm_token === 'string'
? String(cfg.require_confirm_token)
: ''
if (!allowDel) {
return bareAgentJsonResult({
ok: false,
error: 'delete_disabled',
hint: 'allow_delete is on by default; this home set it false'
})
}
if (reqTok && token !== reqTok) {
return bareAgentJsonResult({ ok: false, error: 'confirm_token_required' })
}
if (!bareAgentPathAllowedMutate(path, mutateDenyPrefixes) || path.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs) {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('delete_path ' + path)
if (typeof bareAgentPushEdit === 'function' && paths.edits) {
try {
const prev = await bareAgentReadTextFile(ctx, path)
await bareAgentPushEdit(ctx, paths.edits, { path, prev, tool: 'delete_path' })
} catch {
/* missing */
}
}
try {
/** @type {unknown} */
let st = null
if (typeof vfs.lstat === 'function') st = await vfs.lstat(path)
else if (typeof vfs.stat === 'function') st = await vfs.stat(path)
const isDir =
st &&
typeof st === 'object' &&
typeof /** @type {{ isDirectory?: () => boolean }} */ (st).isDirectory ===
'function' &&
st.isDirectory()
if (isDir && recursive && typeof vfs.rm === 'function') {
await vfs.rm(path, { recursive: true })
return bareAgentJsonResult({ ok: true, removed: 'directory', recursive: true })
}
if (isDir && !recursive) {
return bareAgentJsonResult({
ok: false,
error: 'is_directory',
hint: 'pass recursive true to remove a directory tree'
})
}
if (typeof vfs.unlink !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'unlink unavailable' })
}
await vfs.unlink(path)
return bareAgentJsonResult({ ok: true, removed: isDir ? 'directory' : 'file' })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'read_man_page') {
const topic = typeof args.topic === 'string' ? args.topic : ''
const maxC =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.floor(args.max_chars), 64_000)
: 12_000
let secExplicit = null
if (
typeof args.section === 'number' &&
Number.isFinite(args.section) &&
args.section >= 1 &&
args.section <= 8
) {
secExplicit = Math.floor(args.section)
}
const db = await bareAgentManEnsureDbLoaded(ctx, vfs, manDbCache)
if (!db) {
return bareAgentJsonResult({ ok: false, error: 'man_db_unavailable' })
}
appendProgress('read_man_page ' + topic)
const resolved = bareAgentManResolvePage(db, topic, secExplicit)
if ('error' in resolved && resolved.error === 'wrong_section') {
return bareAgentJsonResult({
ok: false,
error: 'wrong_section',
foundSection: resolved.foundSection
})
}
if (!resolved.page) {
return bareAgentJsonResult({ ok: false, error: 'not_found' })
}
const slice = bareAgentManExtractPageSlice(resolved.page, maxC)
return bareAgentJsonResult({ ok: true, ...slice })
}
if (toolName === 'apropos_man') {
const kw = typeof args.keyword === 'string' ? args.keyword : ''
const maxRes =
typeof args.max_results === 'number' && Number.isFinite(args.max_results)
? Math.floor(args.max_results)
: 40
const db = await bareAgentManEnsureDbLoaded(ctx, vfs, manDbCache)
if (!db) {
return bareAgentJsonResult({ ok: false, error: 'man_db_unavailable' })
}
appendProgress('apropos_man ' + kw)
const { lines, truncated } = bareAgentManAproposHits(db, kw, maxRes)
return bareAgentJsonResult({
ok: true,
count: lines.length,
truncated,
lines
})
}
if (toolName === 'read_proc_file') {
const path = typeof args.path === 'string' ? args.path : ''
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 500_000)
: 256_000
if (!bareAgentProcReadPathAllowed(path)) {
return bareAgentJsonResult({
ok: false,
error: 'path_not_allowed',
hint: 'read_proc_file accepts any /proc path'
})
}
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('read_proc_file ' + path)
try {
const buf = await vfs.readFile(path)
if (!buf) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
let parsed = null
try {
parsed = JSON.parse(t)
} catch {
parsed = null
}
if (t.length > maxB) t = t.slice(0, maxB) + '\n… truncated'
return bareAgentJsonResult({
ok: true,
path,
text: t,
json: parsed
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'list_services') {
appendProgress('list_services')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
let readinessText = ''
/** @type {Record<string, unknown> | null} */
let readinessJson = null
try {
const b = await vfs.readFile('/proc/bare_os/initd_readiness.json')
if (b && b.length) {
readinessText =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
try {
const parsed = JSON.parse(readinessText)
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed))
readinessJson = /** @type {Record<string, unknown>} */ (parsed)
} catch {
/* ignore */
}
}
} catch {
/* ignore */
}
const units = Array.isArray(readinessJson?.units)
? /** @type {unknown[]} */ (readinessJson.units)
: []
const rows = units
.filter((u) => u && typeof u === 'object')
.map((u) => {
const o = /** @type {Record<string, unknown>} */ (u)
return {
name: String(o.name || ''),
phase: String(o.phase || ''),
startedAtMs:
typeof o.startedAtMs === 'number' ? o.startedAtMs : undefined,
error: typeof o.error === 'string' ? o.error : undefined
}
})
.filter((r) => r.name)
return bareAgentJsonResult({
ok: true,
source: '/proc/bare_os/initd_readiness.json',
count: rows.length,
units: rows,
note:
rows.length > 0
? 'Runtime units from initd readiness snapshot.'
: 'No parsed readiness units available.'
})
}
if (toolName === 'service_status') {
const name = typeof args.name === 'string' ? args.name.trim() : ''
if (!name) return bareAgentJsonResult({ ok: false, error: 'name_required' })
appendProgress('service_status ' + name)
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
try {
const b = await vfs.readFile('/proc/bare_os/initd_readiness.json')
const t =
b && b.length
? typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
: ''
const parsed = JSON.parse(t)
const units = Array.isArray(parsed?.units) ? parsed.units : []
const hit =
units.find((u) => u && typeof u === 'object' && String(u.name || '') === name) ||
null
if (!hit) {
return bareAgentJsonResult({
ok: false,
error: 'not_found',
source: '/proc/bare_os/initd_readiness.json'
})
}
const o = /** @type {Record<string, unknown>} */ (hit)
return bareAgentJsonResult({
ok: true,
status: {
name: String(o.name || ''),
phase: String(o.phase || ''),
startedAtMs:
typeof o.startedAtMs === 'number' ? o.startedAtMs : undefined,
error: typeof o.error === 'string' ? o.error : undefined
},
journal_hint: '/run/bare-os/unit-journal/' + name + '.ndjson'
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'list_timers') {
const includePreview = Boolean(args.include_preview)
const maxEntries =
typeof args.max_entries === 'number' && Number.isFinite(args.max_entries)
? Math.min(Math.max(Math.floor(args.max_entries), 1), 512)
: 128
appendProgress('list_timers')
if (!vfs || typeof vfs.readdir !== 'function' || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
const dir = home + '/.config/bare-os/timers'
let names = []
try {
names = await vfs.readdir(dir)
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg, path: dir })
}
const timerNames = names
.filter((n) => typeof n === 'string' && n.endsWith('.timer'))
.sort()
.slice(0, maxEntries)
/** @type {unknown[]} */
const timers = []
for (const name of timerNames) {
const path = dir + '/' + name
/** @type {Record<string, unknown>} */
const row = { name, path }
if (includePreview) {
try {
const b = await vfs.readFile(path)
const txt =
b && b.length
? typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
: ''
row.preview = bareAgentTruncateChars(txt, 1200)
} catch (e) {
row.preview_error =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
}
}
timers.push(row)
}
return bareAgentJsonResult({
ok: true,
path: dir,
count: timers.length,
timers
})
}
if (toolName === 'read_cron_log' || toolName === 'read_audit_log') {
const maxChars =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.max(Math.floor(args.max_chars), 200), 80_000)
: 12_000
const tailOnly = Boolean(args.tail_only)
const redact = toolName === 'read_audit_log' ? args.redact !== false : false
const path =
toolName === 'read_audit_log'
? '/var/log/bare-os/audit.log'
: '/var/log/bare-os/cron.log'
appendProgress(toolName + ' ' + path)
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
try {
const b = await vfs.readFile(path)
if (!b || !b.length) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
if (redact) {
t = t
.replace(/\bBearer\s+\S+/gi, 'Bearer <redacted>')
.replace(/\bsk-[a-zA-Z0-9]{16,}\b/g, '<redacted_sk>')
.replace(/\b([A-Z0-9_]*(KEY|TOKEN|SECRET|PASS)[A-Z0-9_]*)\s*=\s*([^\s]+)/gi, '$1=<redacted>')
}
const out = tailOnly ? t.slice(-maxChars) : bareAgentTruncateChars(t, maxChars)
return bareAgentJsonResult({
ok: true,
path,
text: out,
truncated: t.length > out.length
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'read_boot_policy' || toolName === 'read_kernel_extension_resolution') {
const maxChars =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.max(Math.floor(args.max_chars), 200), 120_000)
: 20_000
const path =
toolName === 'read_boot_policy'
? '/etc/bare-os/boot.policy.json'
: '/run/bare-os/kernel-ext-resolution.json'
appendProgress(toolName + ' ' + path)
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
try {
const b = await vfs.readFile(path)
if (!b || !b.length) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
/** @type {unknown} */
let json = null
try {
json = JSON.parse(t)
} catch {
json = null
}
return bareAgentJsonResult({
ok: true,
path,
text: bareAgentTruncateChars(t, maxChars),
json
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'get_initd_graph') {
appendProgress('get_initd_graph')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
/** @type {Record<string, unknown>} */
const out = { ok: true }
for (const p of ['/proc/bare_os/initd_dag.json', '/proc/bare_os/initd_readiness.json']) {
try {
const b = await vfs.readFile(p)
if (!b || !b.length) continue
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
try {
out[p] = JSON.parse(t)
} catch {
out[p] = bareAgentTruncateChars(t, 8000)
}
} catch {
/* ignore missing */
}
}
return bareAgentJsonResult(out)
}
if (toolName === 'read_unit_journal') {
const unit = typeof args.unit === 'string' ? args.unit.trim() : ''
if (!/^[a-zA-Z0-9._-]{1,96}$/.test(unit)) {
return bareAgentJsonResult({ ok: false, error: 'invalid_unit' })
}
const maxChars =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.max(Math.floor(args.max_chars), 200), 120_000)
: 12_000
const tailOnly = Boolean(args.tail_only)
const p = '/run/bare-os/unit-journal/' + unit + '.ndjson'
appendProgress('read_unit_journal ' + unit)
return bareAgentJsonResult(await readBoundedText(p, maxChars, tailOnly, true))
}
if (toolName === 'inspect_ipc_backpressure') {
appendProgress('inspect_ipc_backpressure')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
const candidates = [
'/proc/bare_os/ipc_backpressure.json',
'/proc/bare_os/replication_operator_sketch.json',
'/proc/bare_os/metrics_live.json'
]
/** @type {Record<string, unknown>} */
const out = { ok: true, sources: [] }
for (const p of candidates) {
try {
const b = await vfs.readFile(p)
if (!b || !b.length) continue
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
out.sources.push(p)
try {
out[p] = JSON.parse(t)
} catch {
out[p] = bareAgentTruncateChars(t, 6000)
}
} catch {
/* ignore */
}
}
return bareAgentJsonResult(out)
}
if (toolName === 'get_network_summary') {
appendProgress('get_network_summary')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
const candidates = [
'/proc/bare_os/net_summary.json',
'/proc/bare_os/swarm.json',
'/proc/bare_os/swarm_status.json',
'/proc/bare_os/swarm_connection_manager_status.json'
]
/** @type {Record<string, unknown>} */
const out = { ok: true, sources: [] }
for (const p of candidates) {
try {
const b = await vfs.readFile(p)
if (!b || !b.length) continue
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
out.sources.push(p)
try {
out[p] = JSON.parse(t)
} catch {
out[p] = bareAgentTruncateChars(t, 6000)
}
} catch {
/* ignore */
}
}
return bareAgentJsonResult(out)
}
if (toolName === 'tail_telemetry_streams') {
const maxChars =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.max(Math.floor(args.max_chars), 200), 60_000)
: 8000
appendProgress('tail_telemetry_streams')
const pathsToRead = [
'/var/log/bare-os/audit.log',
'/var/log/bare-os/logger.jsonl',
'/var/log/bare-os/initd.log',
'/var/log/bare-os/cron.log'
]
/** @type {Record<string, unknown>} */
const out = { ok: true, streams: {} }
for (const p of pathsToRead) {
out.streams[p] = await readBoundedText(p, maxChars, true, true)
}
return bareAgentJsonResult(out)
}
if (toolName === 'pkg_index_lookup') {
const key = typeof args.key === 'string' ? args.key.trim() : ''
if (!key) return bareAgentJsonResult({ ok: false, error: 'key_required' })
appendProgress('pkg_index_lookup ' + key.slice(0, 80))
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const cmd = 'pkg-swarm-index get --key ' + bareAgentShellQuote(key)
const r = await captureExec(cmd, 90000)
if (r.ok === false) return bareAgentJsonResult(r)
const txt = typeof r.stdout_stderr === 'string' ? r.stdout_stderr : ''
let json = null
try {
json = JSON.parse(txt)
} catch {
json = null
}
return bareAgentJsonResult({ ok: true, key, json, text: bareAgentTruncateChars(txt, 12000) })
}
if (toolName === 'list_verification_scripts') {
appendProgress('list_verification_scripts')
if (!vfs || typeof vfs.readdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
const out = []
for (const dir of ['/scripts', '/home/guest/scripts']) {
try {
const names = await vfs.readdir(dir)
const rows = names
.filter((n) => typeof n === 'string' && (n.endsWith('.mjs') || n.endsWith('.js')))
.sort()
.slice(0, 400)
.map((n) => dir + '/' + n)
out.push(...rows)
} catch {
/* ignore */
}
}
return bareAgentJsonResult({ ok: true, scripts: out })
}
if (toolName === 'run_maintenance_gate') {
const command = typeof args.command === 'string' ? args.command.trim() : ''
const timeoutMs =
typeof args.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
? Math.min(Math.max(Math.floor(args.timeout_ms), 1000), 900000)
: 180000
appendProgress('run_maintenance_gate ' + command)
const allow = {
'verify-kernel-seeder-parity': 'node scripts/verify-kernel-seeder-parity.mjs',
'verify-man-coverage': 'node scripts/verify-man-coverage.mjs',
'verify-ctx-api-feature-bits': 'node scripts/verify-ctx-api-feature-bits.mjs',
'coreutils-test': 'npm test -w bare-os-coreutils'
}
if (!Object.prototype.hasOwnProperty.call(allow, command)) {
return bareAgentJsonResult({ ok: false, error: 'command_not_allowlisted', allowlist: Object.keys(allow) })
}
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const cmd = /** @type {Record<string, string>} */ (allow)[command]
const r = await captureExec(cmd, timeoutMs, { captureExit: true })
return bareAgentJsonResult(r)
}
if (toolName === 'run_contract_checks') {
const profile = typeof args.profile === 'string' ? args.profile.trim() : ''
appendProgress('run_contract_checks ' + profile)
const mapping = {
core: 'node scripts/verify-kernel-seeder-parity.mjs && node scripts/verify-man-coverage.mjs',
docs: 'node scripts/verify-doc-links.mjs && node scripts/verify-doc-contracts.mjs',
parity: 'node scripts/verify-kernel-seeder-parity.mjs'
}
if (!Object.prototype.hasOwnProperty.call(mapping, profile)) {
return bareAgentJsonResult({ ok: false, error: 'unknown_profile', profiles: Object.keys(mapping) })
}
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const cmd = /** @type {Record<string, string>} */ (mapping)[profile]
const r = await captureExec(cmd, 300000, { captureExit: true })
return bareAgentJsonResult(r)
}
if (toolName === 'summarize_build_drift') {
appendProgress('summarize_build_drift')
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const r = await captureExec('git status --short', 60000)
return bareAgentJsonResult(r)
}
if (toolName === 'get_hrpc_bridge_health') {
appendProgress('get_hrpc_bridge_health')
/** @type {Record<string, unknown>} */
const out = {
ok: true,
hostCapabilities: {
hrpcBridge:
typeof ctx.bareOsHostCapability === 'function'
? Boolean(ctx.bareOsHostCapability('hrpcBridge'))
: false
}
}
if (vfs && typeof vfs.readFile === 'function') {
for (const p of ['/proc/bare_os/hrpc_route_table.json', '/proc/bare_os/hrpc_health.json']) {
try {
const b = await vfs.readFile(p)
if (!b || !b.length) continue
const t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(b)
: String(new TextDecoder().decode(b))
try {
out[p] = JSON.parse(t)
} catch {
out[p] = bareAgentTruncateChars(t, 6000)
}
} catch {
/* ignore */
}
}
}
return bareAgentJsonResult(out)
}
if (toolName === 'get_hrpc_allowlist_status') {
appendProgress('get_hrpc_allowlist_status')
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const r = await captureExec('hrpc probe', 60000)
return bareAgentJsonResult(r)
}
if (toolName === 'emit_host_notification' || toolName === 'request_host_action') {
const cfg = configRef.current || {}
if (cfg && cfg.emergency_stop_mutations) {
return bareAgentJsonResult({ ok: false, error: 'emergency_stop_mutations_enabled' })
}
if (toolName === 'emit_host_notification' && !cfg.allow_host_notifications) {
return bareAgentJsonResult({ ok: false, error: 'host_notifications_disabled' })
}
if (toolName === 'request_host_action' && !cfg.allow_host_actions) {
return bareAgentJsonResult({ ok: false, error: 'host_actions_disabled' })
}
if (!cfg.allow_bridge_mutations) {
return bareAgentJsonResult({ ok: false, error: 'bridge_mutations_disabled' })
}
if (typeof ctx.bareOsHrpcRequest !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'bareOsHrpcRequest unavailable' })
}
try {
if (toolName === 'emit_host_notification') {
const payload = {
title: String(args.title || '').slice(0, 200),
message: String(args.message || '').slice(0, 2000),
level: typeof args.level === 'string' ? args.level : 'info'
}
appendProgress('emit_host_notification ' + payload.title)
const res = await ctx.bareOsHrpcRequest('bare_os', 'host_notify', payload)
return bareAgentJsonResult({ ok: true, result: res })
}
const action = String(args.action || '').trim()
if (!/^[a-zA-Z0-9._-]{1,64}$/.test(action)) {
return bareAgentJsonResult({ ok: false, error: 'invalid_action' })
}
const payload =
args.payload && typeof args.payload === 'object' && !Array.isArray(args.payload)
? args.payload
: {}
appendProgress('request_host_action ' + action)
const res = await ctx.bareOsHrpcRequest('bare_os', 'host_action', {
action,
payload
})
return bareAgentJsonResult({ ok: true, result: res })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'get_swarm_peers') {
appendProgress('get_swarm_peers')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
try {
const buf = await vfs.readFile('/proc/bare_os/swarm.json')
if (!buf || !buf.length) {
return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
}
const txt =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
/** @type {unknown} */
let j = null
try {
j = JSON.parse(txt)
} catch {
return bareAgentJsonResult({ ok: true, raw: txt.slice(0, 120_000) })
}
return bareAgentJsonResult({ ok: true, swarm: j })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'get_resource_limits') {
appendProgress('get_resource_limits')
try {
if (typeof ctx.bareOsGetResourceStatus !== 'function') {
return bareAgentJsonResult({
ok: false,
error: 'bareOsGetResourceStatus unavailable'
})
}
const r = ctx.bareOsGetResourceStatus()
return bareAgentJsonResult({ ok: true, resources: r })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'run_js_script_at_path') {
const scriptPath = typeof args.path === 'string' ? args.path : ''
if (!bareAgentPathAllowed(scriptPath) || scriptPath.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs?.readFile || !execLine) {
return bareAgentJsonResult({ ok: false, error: 'vfs or execLine' })
}
appendProgress('run_js_script_at_path ' + scriptPath)
try {
await vfs.readFile(scriptPath)
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: 'cannot_read_script', detail: msg })
}
const cmd = bareAgentShellQuote(scriptPath)
const r = await captureExec(cmd, 60000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'verification_hints') {
const topic = typeof args.topic === 'string' ? args.topic : ''
const pathsTouch =
typeof args.paths_touched === 'string' ? args.paths_touched : ''
appendProgress('verification_hints')
const combined = topic + ' ' + pathsTouch.replace(/,/g, ' ')
const hints = bareAgentVerificationHintsList(combined)
return bareAgentJsonResult({
ok: true,
scope_note:
'Suggested commands apply to the Bare OS git checkout on the host (npm/node at repo root). They do not run automatically.',
suggested_commands: hints
})
}
if (toolName === 'runtime_diagnostic_bundle') {
appendProgress('runtime_diagnostic_bundle')
/** @type {Record<string, unknown>} */
const bundle = {}
bundle.bareOsCtxApiVersion =
typeof ctx.bareOsCtxApiVersion !== 'undefined' ? ctx.bareOsCtxApiVersion : null
try {
if (typeof ctx.bareOsGetResourceStatus === 'function')
bundle.resources = ctx.bareOsGetResourceStatus()
} catch {
bundle.resources_error = true
}
/** @type {Record<string, unknown>} */
const procParts = {}
/** @type {string[]} */
let list = [...BARE_AGENT_PROC_READ_ALLOWLIST]
if (vfs && typeof vfs.readdir === 'function') {
try {
const names = await vfs.readdir('/proc/bare_os')
if (Array.isArray(names)) {
for (let i = 0; i < names.length; i++) {
const n = String(names[i] || '')
if (!n || n === '.' || n === '..') continue
const full = '/proc/bare_os/' + n
if (list.indexOf(full) === -1) list.push(full)
}
}
} catch {
/* keep seed list */
}
}
if (vfs && typeof vfs.readFile === 'function') {
for (let i = 0; i < list.length; i++) {
const procPath = list[i]
try {
const buf = await vfs.readFile(procPath)
if (!buf || !buf.length) continue
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
if (t.length > 80_000) t = t.slice(0, 80_000) + '\n… truncated'
try {
procParts[procPath] = JSON.parse(t)
} catch {
procParts[procPath] = t
}
} catch {
/* missing path */
}
}
}
bundle.proc = procParts
return bareAgentJsonResult({ ok: true, bundle })
}
if (toolName === 'web_fetch') {
const url = typeof args.url === 'string' ? args.url : ''
let hostHint = ''
try {
hostHint = new URL(url).hostname
} catch {
hostHint = ''
}
appendProgress('web_fetch ' + (hostHint || url.slice(0, 80)))
try {
const out = await bareWebRunTool({
ctx,
url,
method: typeof args.method === 'string' ? args.method : undefined,
headers:
args.headers &&
typeof args.headers === 'object' &&
!Array.isArray(args.headers)
? /** @type {Record<string, unknown>} */ (args.headers)
: undefined,
body: typeof args.body === 'string' ? args.body : undefined,
content_type:
typeof args.content_type === 'string' ? args.content_type : undefined,
max_response_bytes:
typeof args.max_response_bytes === 'number'
? args.max_response_bytes
: undefined,
max_redirects:
typeof args.max_redirects === 'number' ? args.max_redirects : undefined,
timeout_ms:
typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
format: typeof args.format === 'string' ? args.format : undefined,
max_links:
typeof args.max_links === 'number' ? args.max_links : undefined,
signal
})
return bareAgentJsonResult(out)
} catch (e) {
const msg = bareWebFmtErr(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'grep') {
const pattern = typeof args.pattern === 'string' ? args.pattern : ''
if (!pattern) return bareAgentJsonResult({ ok: false, error: 'pattern_required' })
if (typeof bareAgentGrepFiles !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'grep_unavailable' })
}
const root =
(typeof args.path === 'string' && args.path.trim()) ||
(typeof args.root === 'string' && args.root.trim()) ||
home ||
'/home'
if (!bareAgentPathAllowed(root)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
appendProgress('grep ' + pattern + ' @ ' + root)
const out = await bareAgentGrepFiles(ctx, {
pattern,
root,
glob: typeof args.glob === 'string' ? args.glob : '',
ignore_case: Boolean(args.ignore_case),
before: args.before,
after: args.after,
context: args.context,
max_matches: args.max_matches,
files_with_matches: Boolean(args.files_with_matches),
output_mode: typeof args.output_mode === 'string' ? args.output_mode : ''
})
return bareAgentJsonResult(out)
}
if (toolName === 'apply_patch') {
const patch =
(typeof args.patch === 'string' && args.patch) ||
(typeof args.input === 'string' && args.input) ||
''
if (!patch.trim()) return bareAgentJsonResult({ ok: false, error: 'patch_required' })
if (typeof bareAgentParseApplyPatch !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'apply_patch_unavailable' })
}
const parsed = bareAgentParseApplyPatch(patch)
if (!parsed.ok) return bareAgentJsonResult(parsed)
appendProgress('apply_patch ops=' + String((parsed.ops || []).length))
if (typeof bareAgentPushEdit === 'function' && paths.edits && parsed.ops) {
for (let i = 0; i < parsed.ops.length; i++) {
const op = parsed.ops[i]
const p = String((op && op.path) || '')
if (!p) continue
try {
const prev = await bareAgentReadTextFile(ctx, p)
await bareAgentPushEdit(ctx, paths.edits, { path: p, prev, tool: 'apply_patch' })
} catch {
/* new file */
}
}
}
const out = await bareAgentApplyPatchOps(ctx, parsed.ops, {
home,
denyPrefixes: mutateDenyPrefixes
})
return bareAgentJsonResult(out)
}
if (toolName === 'update_goal') {
const message = typeof args.message === 'string' ? args.message.trim() : ''
const blocked =
typeof args.blocked_reason === 'string' ? args.blocked_reason.trim() : ''
const completed = Boolean(args.completed)
const next = { ...(configRef.current || {}) }
if (message) next.autonomous_last_error = ''
if (blocked) {
next.autonomous_status = 'blocked'
next.autonomous_last_error = blocked
next.autonomous_active = false
} else if (completed) {
next.autonomous_status = 'completed'
next.autonomous_active = false
if (message) next.autonomous_goal = String(next.autonomous_goal || '')
} else {
next.autonomous_status = 'running'
}
configRef.current = next
if (typeof bareAgentSaveConfigFromTools === 'function') {
await bareAgentSaveConfigFromTools(ctx, paths, next)
}
appendProgress(
'update_goal ' +
(completed ? 'completed' : blocked ? 'blocked' : 'progress')
)
return bareAgentJsonResult({
ok: true,
completed,
blocked: Boolean(blocked),
message: message || null,
blocked_reason: blocked || null,
status: next.autonomous_status
})
}
if (toolName === 'web_search') {
const query = typeof args.query === 'string' ? args.query.trim() : ''
if (!query) return bareAgentJsonResult({ ok: false, error: 'query_required' })
const maxResults =
typeof args.max_results === 'number' && Number.isFinite(args.max_results)
? Math.min(Math.max(Math.floor(args.max_results), 1), 16)
: 8
const url =
'https://api.duckduckgo.com/?q=' +
encodeURIComponent(query) +
'&format=json&no_html=1&skip_disambig=1'
appendProgress('web_search ' + query.slice(0, 80))
try {
const raw = await bareWebRunTool({
ctx,
url,
format: 'json',
timeout_ms: 20000,
max_response_bytes: 200000,
signal
})
if (!raw || raw.ok === false) {
return bareAgentJsonResult({
ok: false,
error: (raw && raw.error) || 'web_search_failed',
hint: 'HTTP policy may block api.duckduckgo.com; try web_fetch on a known URL'
})
}
let payload = raw.extract && raw.extract.json
if (payload == null && raw.extract && typeof raw.extract.text_slice === 'string') {
try {
payload = JSON.parse(raw.extract.text_slice)
} catch {
payload = null
}
}
const results =
typeof bareAgentParseSearchResults === 'function'
? bareAgentParseSearchResults(payload, maxResults)
: []
return bareAgentJsonResult({
ok: true,
query,
results,
abstract: payload && payload.Abstract ? String(payload.Abstract) : '',
count: results.length
})
} catch (e) {
const msg = typeof bareWebFmtErr === 'function' ? bareWebFmtErr(e) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'git_status') {
const cwd =
(typeof args.cwd === 'string' && args.cwd.trim()) ||
(ctx.env && typeof ctx.env === 'object'
? String(ctx.env.PWD || ctx.env.CWD || home || '').trim()
: '') ||
home ||
'/home'
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('git_status ' + cwd)
const cmd =
'git -C ' +
bareAgentShellQuote(cwd) +
' status --short && echo --- && git -C ' +
bareAgentShellQuote(cwd) +
' diff --stat && echo --- && git -C ' +
bareAgentShellQuote(cwd) +
' log --oneline -8'
const r = await captureExec(cmd, 30000)
return bareAgentJsonResult(
r.ok === false
? r
: { ok: true, cwd, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'memory_append') {
const text = typeof args.text === 'string' ? args.text.trim() : ''
if (!text) return bareAgentJsonResult({ ok: false, error: 'text_required' })
const kindRaw = String(args.kind || 'NOTE').trim().toUpperCase()
const kind =
kindRaw === 'FACT' ||
kindRaw === 'CHECK' ||
kindRaw === 'RISK' ||
kindRaw === 'HANDOFF' ||
kindRaw === 'NOTE'
? kindRaw
: 'NOTE'
const workspace =
typeof paths.workspace === 'string' ? paths.workspace : paths.dir + '/workspace'
const memDir =
typeof paths.workspaceMemory === 'string'
? paths.workspaceMemory
: workspace + '/memory'
const dest = args.daily
? memDir +
'/' +
(typeof bareAgentWorkspaceUtcYmd === 'function'
? bareAgentWorkspaceUtcYmd()
: new Date().toISOString().slice(0, 10)) +
'.md'
: workspace + '/MEMORY.md'
const stamp = new Date().toISOString()
const line = '- ' + kind + ' — ' + text.replace(/\s+/g, ' ').trim()
let prev = ''
try {
prev = await bareAgentReadTextFile(ctx, dest)
} catch {
prev = ''
}
const next =
(prev ? prev.replace(/\s*$/, '') + '\n' : '# Memory\n\n') +
line +
' \n _' +
stamp +
'_\n'
await bareAgentWriteTextFile(ctx, dest, next)
appendProgress('memory_append ' + dest)
return bareAgentJsonResult({ ok: true, path: dest, kind, appended: line })
}
if (toolName === 'list_skills') {
const max =
typeof args.max === 'number' && Number.isFinite(args.max)
? Math.min(Math.max(Math.floor(args.max), 1), 80)
: 40
if (typeof bareAgentDiscoverSkills !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'skills_unavailable' })
}
const skillPaths = {
workspaceSkills:
typeof paths.workspaceSkills === 'string'
? paths.workspaceSkills
: paths.dir + '/workspace/skills',
skillsGlobal:
typeof paths.skillsGlobal === 'string'
? paths.skillsGlobal
: paths.dir + '/skills'
}
const skills = await bareAgentDiscoverSkills(ctx, skillPaths)
appendProgress('list_skills ' + String(skills.length))
return bareAgentJsonResult({
ok: true,
count: Math.min(skills.length, max),
truncated: skills.length > max,
skills: skills.slice(0, max)
})
}
if (toolName === 'schedule_task') {
const prompt = typeof args.prompt === 'string' ? args.prompt.trim() : ''
if (!prompt) return bareAgentJsonResult({ ok: false, error: 'prompt_required' })
const parsed =
typeof bareAgentParseScheduleInterval === 'function'
? bareAgentParseScheduleInterval(args.interval)
: { error: 'schedule_unavailable' }
if (parsed.error) return bareAgentJsonResult({ ok: false, error: parsed.error, hint: parsed.hint })
const id =
typeof bareAgentScheduleId === 'function'
? bareAgentScheduleId(args.id || prompt.slice(0, 24))
: 'agent-task'
const dir = (home || '/home') + '/.config/bare-os/timers'
if (!vfs || typeof vfs.readdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
try {
if (typeof vfs.mkdir === 'function') await vfs.mkdir(dir, { recursive: true })
} catch {
/* exists */
}
let names = []
try {
names = await vfs.readdir(dir)
} catch {
names = []
}
const existing = Array.isArray(names)
? names.filter(function (n) {
return typeof n === 'string' && n.endsWith('.timer')
})
: []
const dest = dir + '/' + id + '.timer'
const already = existing.indexOf(id + '.timer') >= 0
if (!already && existing.length >= 8) {
return bareAgentJsonResult({
ok: false,
error: 'timer_limit',
max: 8,
hint: 'unschedule_task an existing id first'
})
}
const quoted = bareAgentShellQuote(prompt)
const line = args.auto === false ? 'agent ' + quoted : 'agent --auto ' + quoted
const body =
parsed.kind === 'everyMs'
? '[Timer]\nEveryMs=' + String(parsed.everyMs) + '\nExecLine=' + line + '\n'
: '[Timer]\nOnCalendar=' + parsed.onCalendar + '\nExecLine=' + line + '\n'
await bareAgentWriteTextFile(ctx, dest, body)
appendProgress('schedule_task ' + id)
return bareAgentJsonResult({
ok: true,
id,
path: dest,
interval: parsed,
exec: line
})
}
if (toolName === 'unschedule_task') {
const id =
typeof bareAgentScheduleId === 'function'
? bareAgentScheduleId(args.id)
: String(args.id || '')
if (!id) return bareAgentJsonResult({ ok: false, error: 'id_required' })
const dest = (home || '/home') + '/.config/bare-os/timers/' + id + '.timer'
if (!vfs || typeof vfs.unlink !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs.unlink unavailable' })
}
try {
await vfs.unlink(dest)
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg, path: dest })
}
appendProgress('unschedule_task ' + id)
return bareAgentJsonResult({ ok: true, id, path: dest })
}
if (toolName === 'list_scheduled') {
const dir = (home || '/home') + '/.config/bare-os/timers'
let names = []
try {
names = vfs && typeof vfs.readdir === 'function' ? await vfs.readdir(dir) : []
} catch {
names = []
}
const rows = []
const list = Array.isArray(names) ? names : []
for (let i = 0; i < list.length; i++) {
const name = String(list[i] || '')
if (!name.endsWith('.timer') || name.indexOf('agent-') !== 0) continue
const path = dir + '/' + name
const preview = await bareAgentReadTextFile(ctx, path)
rows.push({ id: name.replace(/\.timer$/, ''), path, preview: preview.slice(0, 400) })
}
appendProgress('list_scheduled ' + String(rows.length))
return bareAgentJsonResult({ ok: true, count: rows.length, timers: rows })
}
if (toolName === 'fuzzy_find') {
const query = typeof args.query === 'string' ? args.query.trim() : ''
if (!query) return bareAgentJsonResult({ ok: false, error: 'query_required' })
const root =
(typeof args.root === 'string' && args.root.trim()) || home || '/home'
if (typeof bareAgentFuzzyFind !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'fuzzy_unavailable' })
}
appendProgress('fuzzy_find ' + query)
const hits = await bareAgentFuzzyFind(ctx, root, query, { max: args.max })
return bareAgentJsonResult({ ok: true, query, root, hits })
}
if (toolName === 'read_many') {
const pathsIn = Array.isArray(args.paths) ? args.paths : []
if (!pathsIn.length) return bareAgentJsonResult({ ok: false, error: 'paths_required' })
const maxB =
typeof args.max_bytes_each === 'number' && Number.isFinite(args.max_bytes_each)
? Math.min(Math.max(Math.floor(args.max_bytes_each), 200), 200000)
: 32000
const files = []
for (let i = 0; i < pathsIn.length && i < 16; i++) {
const path = String(pathsIn[i] || '').trim()
if (!path || !bareAgentPathAllowed(path)) {
files.push({ path, ok: false, error: 'path_not_allowed' })
continue
}
let text = await bareAgentReadTextFile(ctx, path)
const truncated = text.length > maxB
if (truncated) text = text.slice(0, maxB) + '\n… truncated'
files.push({ path, ok: true, content: text, truncated })
}
appendProgress('read_many ' + String(files.length))
return bareAgentJsonResult({ ok: true, files })
}
if (toolName === 'wait_for') {
const pattern = typeof args.pattern === 'string' ? args.pattern : ''
if (!pattern) return bareAgentJsonResult({ ok: false, error: 'pattern_required' })
let re
try {
re = new RegExp(pattern, args.ignore_case ? 'i' : '')
} catch (e) {
return bareAgentJsonResult({ ok: false, error: 'bad_regex' })
}
const timeoutMs = Math.min(
120000,
Math.max(200, Math.floor(Number(args.timeout_ms) || 15000))
)
const intervalMs = Math.min(
5000,
Math.max(100, Math.floor(Number(args.interval_ms) || 400))
)
const path = typeof args.path === 'string' ? args.path.trim() : ''
const command = typeof args.command === 'string' ? args.command.trim() : ''
if (!path && !command) {
return bareAgentJsonResult({ ok: false, error: 'path_or_command_required' })
}
appendProgress('wait_for ' + pattern)
const deadline = Date.now() + timeoutMs
let last = ''
while (Date.now() <= deadline) {
if (path) last = await bareAgentReadTextFile(ctx, path)
else if (command && execLine) {
const r = await captureExec(command, Math.min(intervalMs + 2000, 15000))
last = r && r.stdout_stderr ? String(r.stdout_stderr) : ''
}
re.lastIndex = 0
if (re.test(last)) {
return bareAgentJsonResult({
ok: true,
matched: true,
elapsed_ms: timeoutMs - Math.max(0, deadline - Date.now()),
sample: last.slice(0, 800)
})
}
if (Date.now() + intervalMs > deadline) break
await new Promise(function (resolve) {
setTimeout(resolve, intervalMs)
})
}
return bareAgentJsonResult({
ok: false,
error: 'wait_timeout',
sample: last.slice(0, 400)
})
}
if (toolName === 'undo_last_edit') {
const editsPath = paths.edits || paths.dir + '/edits.json'
if (typeof bareAgentPopEdit !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'undo_unavailable' })
}
const rec = await bareAgentPopEdit(ctx, editsPath)
if (!rec || !rec.path) {
return bareAgentJsonResult({ ok: false, error: 'nothing_to_undo' })
}
await bareAgentWriteTextFile(ctx, String(rec.path), String(rec.prev || ''))
appendProgress('undo_last_edit ' + rec.path)
return bareAgentJsonResult({ ok: true, path: rec.path, tool: rec.tool || null })
}
if (toolName === 'git_diff') {
const cwd =
(typeof args.cwd === 'string' && args.cwd.trim()) ||
(ctx.env && typeof ctx.env === 'object'
? String(ctx.env.PWD || ctx.env.CWD || home || '').trim()
: '') ||
home ||
'/home'
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const p = typeof args.path === 'string' ? args.path.trim() : ''
const stat = Boolean(args.stat)
const cmd =
'git -C ' +
bareAgentShellQuote(cwd) +
' diff' +
(stat ? ' --stat' : '') +
(p ? ' -- ' + bareAgentShellQuote(p) : '')
appendProgress('git_diff ' + cwd)
const r = await captureExec(cmd, 30000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, cwd, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'history_search') {
const query = typeof args.query === 'string' ? args.query.trim() : ''
if (!query) return bareAgentJsonResult({ ok: false, error: 'query_required' })
const histPath = paths.history || paths.dir + '/history.json'
const raw = await bareAgentReadJsonFile(ctx, histPath, [])
const hits =
typeof bareAgentHistorySearch === 'function'
? bareAgentHistorySearch(Array.isArray(raw) ? raw : [], query, args.max)
: []
appendProgress('history_search ' + query)
return bareAgentJsonResult({ ok: true, query, hits })
}
if (toolName === 'git_log' || toolName === 'git_show' || toolName === 'git_blame') {
const cwd =
(typeof args.cwd === 'string' && args.cwd.trim()) ||
(ctx.env && typeof ctx.env === 'object'
? String(ctx.env.PWD || ctx.env.CWD || home || '').trim()
: '') ||
home ||
'/home'
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const quoted = bareAgentShellQuote(cwd)
let cmd = 'git -C ' + quoted + ' '
if (toolName === 'git_log') {
const max = Math.min(80, Math.max(1, Math.floor(Number(args.max) || 20)))
const p = typeof args.path === 'string' ? args.path.trim() : ''
cmd += 'log --oneline -' + String(max) + (p ? ' -- ' + bareAgentShellQuote(p) : '')
} else if (toolName === 'git_show') {
const rev = typeof args.rev === 'string' && args.rev.trim() ? args.rev.trim() : 'HEAD'
const p = typeof args.path === 'string' ? args.path.trim() : ''
cmd +=
'show ' +
(args.stat ? '--stat ' : '') +
bareAgentShellQuote(rev) +
(p ? ' -- ' + bareAgentShellQuote(p) : '')
} else {
const p = typeof args.path === 'string' ? args.path.trim() : ''
if (!p) return bareAgentJsonResult({ ok: false, error: 'path_required' })
cmd += 'blame -- ' + bareAgentShellQuote(p)
}
appendProgress(toolName + ' ' + cwd)
const r = await captureExec(cmd, 30000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, cwd, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'copy_path') {
const from = typeof args.from_path === 'string' ? args.from_path.trim() : ''
const to = typeof args.to_path === 'string' ? args.to_path.trim() : ''
if (
!from ||
!to ||
from.includes('..') ||
to.includes('..') ||
!bareAgentPathAllowed(from) ||
!bareAgentPathAllowedMutate(to, mutateDenyPrefixes)
) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (typeof bareAgentCopyPath !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'copy_unavailable' })
}
appendProgress('copy_path ' + from)
const out = await bareAgentCopyPath(ctx, from, to)
return bareAgentJsonResult(out)
}
if (toolName === 'diff_files') {
const from = typeof args.from_path === 'string' ? args.from_path.trim() : ''
const to = typeof args.to_path === 'string' ? args.to_path.trim() : ''
if (!from || !to || !bareAgentPathAllowed(from) || !bareAgentPathAllowed(to)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (typeof bareAgentUnifiedDiff !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'diff_unavailable' })
}
const a = await bareAgentReadTextFile(ctx, from)
const b = await bareAgentReadTextFile(ctx, to)
appendProgress('diff_files ' + from)
const out = bareAgentUnifiedDiff(a, b, {
from,
to,
context: args.context
})
return bareAgentJsonResult(out)
}
if (toolName === 'find_symbol') {
const name = typeof args.name === 'string' ? args.name.trim() : ''
if (!name) return bareAgentJsonResult({ ok: false, error: 'name_required' })
const root =
(typeof args.root === 'string' && args.root.trim()) || home || '/home'
if (!bareAgentPathAllowed(root) || typeof bareAgentFindSymbol !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'find_symbol_unavailable' })
}
appendProgress('find_symbol ' + name)
const out = await bareAgentFindSymbol(ctx, root, name, {
glob: typeof args.glob === 'string' ? args.glob : '',
max: args.max
})
return bareAgentJsonResult(out)
}
if (toolName === 'create_skill') {
const id = String(args.id || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9-]+/g, '-')
.replace(/^-+|-+$/g, '')
if (!id) return bareAgentJsonResult({ ok: false, error: 'id_required' })
const destRoot =
(typeof paths.workspaceSkills === 'string' && paths.workspaceSkills) ||
(typeof paths.workspace === 'string' ? paths.workspace + '/skills' : paths.dir + '/workspace/skills')
const dest = destRoot + '/' + id + '/SKILL.md'
if (!bareAgentPathAllowedMutate(dest, mutateDenyPrefixes)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const md =
typeof bareAgentSkillMarkdown === 'function'
? bareAgentSkillMarkdown({
name: typeof args.name === 'string' ? args.name : id,
description: typeof args.description === 'string' ? args.description : id,
body: typeof args.body === 'string' ? args.body : ''
})
: String(args.body || '')
await bareAgentWriteTextFile(ctx, dest, md)
appendProgress('create_skill ' + id)
return bareAgentJsonResult({ ok: true, id, path: dest })
}
if (toolName === 'rewind_session') {
const histPath = paths.history || paths.dir + '/history.json'
const raw = await bareAgentReadJsonFile(ctx, histPath, [])
if (typeof bareAgentRewindHistory !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'rewind_unavailable' })
}
const out = bareAgentRewindHistory(Array.isArray(raw) ? raw : [], {
steps: args.steps,
userIndex: args.user_index,
keep_user: args.keep_user
})
if (!out.ok) return bareAgentJsonResult(out)
await bareAgentSaveHistory(ctx, histPath, out.messages)
appendProgress('rewind_session dropped=' + String(out.dropped))
return bareAgentJsonResult({
ok: true,
dropped: out.dropped,
remaining: out.messages.length,
target: out.target || null,
keep_user: Boolean(out.keep_user)
})
}
if (toolName === 'export_session') {
const histPath = paths.history || paths.dir + '/history.json'
const dest =
(typeof args.path === 'string' && args.path.trim()) ||
(paths.dir ? paths.dir + '/export.md' : '/tmp/agent-export.md')
if (!bareAgentPathAllowedMutate(dest, mutateDenyPrefixes)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const raw = await bareAgentReadJsonFile(ctx, histPath, [])
const md =
typeof bareAgentExportTranscript === 'function'
? bareAgentExportTranscript(Array.isArray(raw) ? raw : [])
: ''
await bareAgentWriteTextFile(ctx, dest, md)
appendProgress('export_session ' + dest)
return bareAgentJsonResult({
ok: true,
path: dest,
messages: Array.isArray(raw) ? raw.length : 0
})
}
return bareAgentJsonResult({ ok: false, error: 'unknown_tool ' + toolName })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
appendProgress('tool_error ' + toolName + ': ' + msg.slice(0, 200))
return bareAgentJsonResult({ ok: false, error: msg })
}
}
/**
* @param {Record<string, unknown>} base
* @param {Record<string, unknown>} patch
*/
function bareAgentMergeConfigPatch(base, patch) {
const out = { ...base }
const keys = [
'backend',
'rest_base_url',
'rest_api_key',
'model',
'qvac_model',
'qvac_profile',
'qvac_ctx_size',
'qvac_device',
'qvac_main_gpu',
'qvac_gpu_layers',
'max_tokens',
'temperature',
'provider',
'max_iterations',
'stream',
'tool_parallelism',
'request_timeout_ms',
'access_policy',
'allow_delete',
'require_confirm_token',
'command_deny',
'mutate_deny_prefixes',
'owner_name',
'agent_label',
'show_reasoning',
'reasoning_mode',
'reasoning_max_chars',
'reasoning_include_tools',
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error',
'context_compaction',
'compaction_keep_recent',
'compaction_tool_chars',
'plan_mode_active',
'todo_nudge_enabled'
]
const numKeys = new Set([
'max_tokens',
'temperature',
'max_iterations',
'tool_parallelism',
'request_timeout_ms',
'reasoning_max_chars',
'compaction_keep_recent',
'compaction_tool_chars',
'qvac_ctx_size',
'qvac_gpu_layers',
'autonomous_max_runtime_ms',
'autonomous_started_at_ms'
])
for (const k of keys) {
if (Object.prototype.hasOwnProperty.call(patch, k)) {
/** @type {unknown} */
const v = patch[k]
if (numKeys.has(k)) {
const n = Number(v)
if (Number.isFinite(n)) out[k] = n
} else if (
k === 'autonomous_completion_required_checks' ||
k === 'autonomous_allow_paths' ||
k === 'autonomous_deny_ops' ||
k === 'command_deny' ||
k === 'mutate_deny_prefixes'
) {
out[k] = Array.isArray(v) ? v.map((x) => String(x ?? '')).filter(Boolean) : out[k]
} else if (
k === 'stream' ||
k === 'allow_delete' ||
k === 'show_reasoning' ||
k === 'reasoning_include_tools' ||
k === 'allow_bridge_mutations' ||
k === 'allow_host_notifications' ||
k === 'allow_host_actions' ||
k === 'emergency_stop_mutations' ||
k === 'autonomous_mode_enabled' ||
k === 'autonomous_active' ||
k === 'autonomous_stop_requested' ||
k === 'plan_mode_active' ||
k === 'todo_nudge_enabled'
) {
out[k] = Boolean(v)
} else if (k === 'reasoning_mode') {
const mode = String(v ?? '').trim().toLowerCase()
out[k] = mode === 'summary' || mode === 'trace' ? mode : 'off'
} else if (k === 'context_compaction') {
const mode = String(v ?? '').trim().toLowerCase()
out[k] = mode === 'off' || mode === 'aggressive' ? mode : 'auto'
} else if (k === 'require_confirm_token') {
out[k] = String(v ?? '')
} else if (k === 'access_policy') {
const pol = String(v ?? '').trim().toLowerCase()
out[k] = pol === 'restricted' ? 'restricted' : 'full'
} else {
out[k] = String(v ?? '')
}
}
}
if (
patch.extra_headers &&
typeof patch.extra_headers === 'object' &&
!Array.isArray(patch.extra_headers)
) {
out.extra_headers = { .../** @type {Record<string, string>} */ (patch.extra_headers) }
}
return out
}
/**
* @param {Record<string, unknown>} ctx
* @param {{ config: string }} paths
* @param {Record<string, unknown>} config
*/
async function bareAgentSaveConfigFromTools(ctx, paths, config) {
if (typeof bareAgentSaveConfig === 'function') {
await bareAgentSaveConfig(ctx, paths, config)
return
}
const vfs = ctx.vfs
if (!vfs?.writeFile) return
const json = JSON.stringify(config, null, 2) + '\n'
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(json)
: new TextEncoder().encode(json)
await vfs.writeFile(paths.config, body)
}
/**
* Markdown → ANSI for /bin/agent replies (TTY-friendly, no deps).
*/
/**
* @param {boolean} useColor
* @param {'bold'|'dim'|'italic'|'code'|'heading'|'quote'|'hr'|'bullet'|'link'} kind
*/
function bareAgentMdSgr(useColor, kind) {
if (!useColor) return ''
switch (kind) {
case 'bold':
return '\x1b[1m'
case 'dim':
return '\x1b[2m'
case 'italic':
return '\x1b[3m'
case 'code':
return '\x1b[36m'
case 'heading':
return '\x1b[1;36m'
case 'quote':
return '\x1b[2;37m'
case 'hr':
return '\x1b[2m'
case 'bullet':
return '\x1b[33m'
case 'link':
return '\x1b[4;36m'
default:
return ''
}
}
/**
* Inline markdown on a single line (no nested fences).
* @param {string} line
* @param {boolean} useColor
*/
function bareAgentMdInline(line, useColor) {
const reset = useColor ? '\x1b[0m' : ''
let s = String(line || '')
// escape-ish: leave raw backslashes mostly alone
/** @type {string[]} */
const out = []
let i = 0
while (i < s.length) {
// inline code
if (s[i] === '`') {
const end = s.indexOf('`', i + 1)
if (end > i) {
out.push(
bareAgentMdSgr(useColor, 'code') + s.slice(i + 1, end) + reset
)
i = end + 1
continue
}
}
// bold ** or __
if (
(s[i] === '*' && s[i + 1] === '*') ||
(s[i] === '_' && s[i + 1] === '_')
) {
const mark = s[i]
const end = s.indexOf(mark + mark, i + 2)
if (end > i) {
out.push(
bareAgentMdSgr(useColor, 'bold') +
bareAgentMdInline(s.slice(i + 2, end), useColor) +
reset
)
i = end + 2
continue
}
}
// italic * or _
if (s[i] === '*' || s[i] === '_') {
const mark = s[i]
// avoid matching list markers at start handled elsewhere
const end = s.indexOf(mark, i + 1)
if (end > i + 1) {
out.push(
bareAgentMdSgr(useColor, 'italic') +
s.slice(i + 1, end) +
reset
)
i = end + 1
continue
}
}
// strike ~~
if (s[i] === '~' && s[i + 1] === '~') {
const end = s.indexOf('~~', i + 2)
if (end > i) {
out.push('\x1b[9m' + s.slice(i + 2, end) + reset)
i = end + 2
continue
}
}
// links [text](url)
if (s[i] === '[') {
const mid = s.indexOf('](', i + 1)
const end = mid >= 0 ? s.indexOf(')', mid + 2) : -1
if (mid > i && end > mid) {
const label = s.slice(i + 1, mid)
const url = s.slice(mid + 2, end)
out.push(
bareAgentMdSgr(useColor, 'link') +
label +
reset +
bareAgentMdSgr(useColor, 'dim') +
' (' +
url +
')' +
reset
)
i = end + 1
continue
}
}
out.push(s[i])
i++
}
return out.join('')
}
/**
* Visible width ignoring ANSI CSI sequences.
* @param {string} s
*/
function bareAgentMdVisibleWidth(s) {
return String(s || '')
.replace(/\x1b\[[0-9;]*m/g, '')
.length
}
/**
* Wrap a string to width by visible columns (ANSI-aware, crude).
* @param {string} s
* @param {number} width
* @returns {string[]}
*/
function bareAgentMdWrapAnsi(s, width) {
const w = Math.max(8, width | 0)
const raw = String(s || '')
if (!raw) return ['']
/** @type {string[]} */
const lines = []
let cur = ''
let vis = 0
let i = 0
while (i < raw.length) {
if (raw[i] === '\x1b' && raw[i + 1] === '[') {
let j = i + 2
while (j < raw.length && raw[j] !== 'm') j++
cur += raw.slice(i, Math.min(j + 1, raw.length))
i = Math.min(j + 1, raw.length)
continue
}
if (raw[i] === '\n') {
lines.push(cur)
cur = ''
vis = 0
i++
continue
}
if (vis >= w) {
lines.push(cur)
cur = ''
vis = 0
}
cur += raw[i]
vis++
i++
}
if (cur || !lines.length) lines.push(cur)
return lines
}
/**
* Render markdown source to ANSI lines (no trailing newline join).
* @param {string} md
* @param {{ useColor?: boolean, width?: number }} [opts]
* @returns {string}
*/
function bareAgentRenderMarkdown(md, opts) {
const useColor = !opts || opts.useColor !== false
const width = Math.max(
40,
Math.min(500, (opts && opts.width) || 80)
)
const reset = useColor ? '\x1b[0m' : ''
const src = String(md || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n')
const rows = src.split('\n')
/** @type {string[]} */
const out = []
let i = 0
let inFence = false
let fenceLang = ''
while (i < rows.length) {
const line = rows[i]
// fenced code
const fenceOpen = /^```\s*(\S*)\s*$/.exec(line)
if (fenceOpen && !inFence) {
inFence = true
fenceLang = fenceOpen[1] || ''
const label = fenceLang
? bareAgentMdSgr(useColor, 'dim') + '┌─ ' + fenceLang + ' ' + reset
: bareAgentMdSgr(useColor, 'dim') + '┌──' + reset
out.push(label)
i++
continue
}
if (inFence) {
if (/^```\s*$/.test(line)) {
inFence = false
out.push(bareAgentMdSgr(useColor, 'dim') + '└──' + reset)
i++
continue
}
out.push(
bareAgentMdSgr(useColor, 'dim') +
'│ ' +
reset +
bareAgentMdSgr(useColor, 'code') +
line +
reset
)
i++
continue
}
// hr
if (/^\s*(?:---+|\*\*\*+|___+)\s*$/.test(line)) {
out.push(
bareAgentMdSgr(useColor, 'hr') + '─'.repeat(Math.min(width, 40)) + reset
)
i++
continue
}
// headings
const hm = /^(#{1,6})\s+(.*)$/.exec(line)
if (hm) {
const level = hm[1].length
const text = hm[2]
const prefix = level <= 2 ? '' : ' '.repeat(level - 2)
out.push(
prefix +
bareAgentMdSgr(useColor, 'heading') +
bareAgentMdInline(text, useColor) +
reset
)
if (level === 1) {
out.push(
bareAgentMdSgr(useColor, 'dim') +
'━'.repeat(Math.min(width, Math.max(8, text.length))) +
reset
)
}
i++
continue
}
// blockquote
const qm = /^>\s?(.*)$/.exec(line)
if (qm) {
out.push(
bareAgentMdSgr(useColor, 'quote') +
'┃ ' +
reset +
bareAgentMdInline(qm[1], useColor)
)
i++
continue
}
// unordered list
const ul = /^(\s*)([-*+])\s+(.*)$/.exec(line)
if (ul) {
const indent = Math.min(6, Math.floor(ul[1].length / 2))
const pad = ' '.repeat(indent)
out.push(
pad +
bareAgentMdSgr(useColor, 'bullet') +
'• ' +
reset +
bareAgentMdInline(ul[3], useColor)
)
i++
continue
}
// ordered list
const ol = /^(\s*)(\d+)\.\s+(.*)$/.exec(line)
if (ol) {
const indent = Math.min(6, Math.floor(ol[1].length / 2))
const pad = ' '.repeat(indent)
out.push(
pad +
bareAgentMdSgr(useColor, 'bullet') +
ol[2] +
'. ' +
reset +
bareAgentMdInline(ol[3], useColor)
)
i++
continue
}
// empty
if (!line.trim()) {
out.push('')
i++
continue
}
// paragraph — wrap
const rendered = bareAgentMdInline(line, useColor)
for (const wline of bareAgentMdWrapAnsi(rendered, width)) out.push(wline)
i++
}
// Ensure fence close if stream was truncated
if (inFence) out.push(bareAgentMdSgr(useColor, 'dim') + '└──' + reset)
return out.join('\n') + (out.length ? '\n' : '')
}
/**
* Live thinking viewport for /bin/agent — fixed-height auto-follow box with
* scrollbar + keyboard review, separated from the assistant reply.
* Also splits Qwen-style <think> tags out of content deltas.
*/
/**
* Resolve usable terminal width (full width; no artificial 80/100/120 cap).
* @param {Record<string, unknown>} [ctx]
* @param {import('stream').Writable | undefined} [stdout]
*/
function bareAgentResolveTermCols(ctx, stdout) {
/** @type {number[]} */
const cands = []
const push = (v) => {
const n = Number(v)
if (Number.isFinite(n) && n >= 20) cands.push(Math.floor(n))
}
if (stdout && typeof stdout === 'object') {
push(/** @type {{ columns?: number }} */ (stdout).columns)
}
if (ctx && typeof ctx === 'object') {
const rs = ctx.replStdout
if (rs && typeof rs === 'object') {
push(/** @type {{ columns?: number }} */ (rs).columns)
}
const env =
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<string, string>} */ (ctx.env)
: null
if (env) push(env.COLUMNS)
}
try {
if (globalThis.process && globalThis.process.stdout) {
push(globalThis.process.stdout.columns)
}
if (globalThis.process && globalThis.process.env) {
push(globalThis.process.env.COLUMNS)
}
} catch {
/* ignore */
}
if (!cands.length) return 80
// Prefer the largest reported size (stale COLUMNS=80 is common on wide TTYs).
return Math.min(500, Math.max(40, Math.max(...cands)))
}
/**
* @param {string} s
* @param {number} width
* @returns {string[]}
*/
function bareAgentThinkWrapLines(s, width) {
const w = Math.max(8, width | 0)
const raw = String(s || '').replace(/\r\n/g, '\n').replace(/\r/g, '\n')
/** @type {string[]} */
const out = []
for (const para of raw.split('\n')) {
if (!para) {
out.push('')
continue
}
let rest = para
while (rest.length > w) {
let cut = rest.lastIndexOf(' ', w)
if (cut < Math.floor(w * 0.5)) cut = w
out.push(rest.slice(0, cut).trimEnd())
rest = rest.slice(cut).trimStart()
}
if (rest.length || !out.length) out.push(rest)
}
return out.length ? out : ['']
}
/**
* @param {string} s
* @param {number} width
*/
function bareAgentThinkPad(s, width) {
const t = String(s || '')
if (t.length >= width) return t.slice(0, width)
return t + ' '.repeat(width - t.length)
}
/**
* @param {string} title
* @param {number} inner
* @param {boolean} fancy
*/
function bareAgentThinkTitleBar(title, inner, fancy) {
const label = String(title || ' thinking ')
const fill = Math.max(0, inner - label.length)
const left = Math.floor(fill / 2)
const right = fill - left
if (fancy) {
return '─'.repeat(left) + label + '─'.repeat(right)
}
return '-'.repeat(left) + label + '-'.repeat(right)
}
/**
* Scrollbar column chars for a viewport.
* @param {number} bodyLines
* @param {number} totalLines
* @param {number} viewStart
* @param {boolean} fancy
* @returns {string[]} length === bodyLines
*/
function bareAgentThinkScrollbar(bodyLines, totalLines, viewStart, fancy) {
/** @type {string[]} */
const col = []
const track = fancy ? '│' : '|'
const thumb = fancy ? '█' : '#'
const gap = fancy ? '░' : ':'
if (totalLines <= bodyLines) {
for (let i = 0; i < bodyLines; i++) col.push(track)
return col
}
const maxStart = totalLines - bodyLines
const thumbSize = Math.max(
1,
Math.round((bodyLines / totalLines) * bodyLines)
)
const thumbStart =
maxStart <= 0
? 0
: Math.round((viewStart / maxStart) * (bodyLines - thumbSize))
for (let i = 0; i < bodyLines; i++) {
col.push(i >= thumbStart && i < thumbStart + thumbSize ? thumb : gap)
}
return col
}
/**
* Fixed-height thinking panel with auto-follow + manual scroll review.
* @param {Record<string, unknown>} ctx
* @param {import('stream').Writable | undefined} stdout
* @param {{
* useColor?: boolean,
* bodyLines?: number,
* maxChars?: number,
* write?: (ctx: Record<string, unknown>, out: unknown, s: string) => void
* }} [opts]
*/
function bareAgentCreateThinkPanel(ctx, stdout, opts) {
const useColor = opts && opts.useColor !== undefined ? Boolean(opts.useColor) : true
const bodyLines = Math.min(
18,
Math.max(4, (opts && opts.bodyLines) || 8)
)
const maxChars = Math.min(
80_000,
Math.max(400, (opts && opts.maxChars) || 24_000)
)
const write =
opts && typeof opts.write === 'function'
? opts.write
: typeof bareAgentWriteOut === 'function'
? bareAgentWriteOut
: (c, o, s) => {
try {
if (o && typeof o.write === 'function') o.write(s)
} catch {
/* ignore */
}
}
const fancy =
useColor &&
!(
ctx.env &&
typeof ctx.env === 'object' &&
String(/** @type {Record<string, string>} */ (ctx.env).BARE_OS_AGENT_ASCII || '') ===
'1'
)
let text = ''
let drawn = false
let sealed = false
let height = 0
let startedAt = 0
/** Lines below the panel (status / reply) that must be preserved on redraw. */
let belowLines = 0
/** 0 = pinned to bottom (auto-follow); >0 = scrolled up from bottom. */
let scrollFromBottom = 0
let followTail = true
/** Optional status line under the box (e.g. answering…). */
let statusLine = ''
function cols() {
return bareAgentResolveTermCols(ctx, stdout)
}
function dim(s) {
if (!useColor) return s
const paint =
typeof bareEditSgr === 'function' ? bareEditSgr('dim', true) : '\x1b[90m'
const reset = typeof EDIT_ANSI_RESET === 'string' ? EDIT_ANSI_RESET : '\x1b[0m'
return paint + s + reset
}
function accent(s) {
if (!useColor) return s
const paint =
typeof bareEditSgr === 'function' ? bareEditSgr('comment', true) : '\x1b[90m'
const reset = typeof EDIT_ANSI_RESET === 'string' ? EDIT_ANSI_RESET : '\x1b[0m'
return paint + s + reset
}
function keyword(s) {
if (!useColor) return s
const paint =
typeof bareEditSgr === 'function' ? bareEditSgr('keyword', true) : '\x1b[36m'
const reset = typeof EDIT_ANSI_RESET === 'string' ? EDIT_ANSI_RESET : '\x1b[0m'
return paint + s + reset
}
function contentWidth(inner) {
// border + space + text + space + scrollbar + border
return Math.max(12, inner - 4)
}
/**
* @returns {{ lines: string[], wrappedCount: number, viewStart: number }}
*/
function frameLines() {
const c = cols()
const inner = Math.max(28, c - 2)
const textW = contentWidth(inner)
const wrapped = bareAgentThinkWrapLines(text, textW)
const maxStart = Math.max(0, wrapped.length - bodyLines)
if (followTail) scrollFromBottom = 0
const viewStart = Math.max(
0,
Math.min(maxStart, maxStart - scrollFromBottom)
)
const view = wrapped.slice(viewStart, viewStart + bodyLines)
while (view.length < bodyLines) view.push('')
const sb = bareAgentThinkScrollbar(
bodyLines,
wrapped.length,
viewStart,
fancy
)
const elapsed =
startedAt > 0 ? ((Date.now() - startedAt) / 1000).toFixed(1) + 's' : ''
const pos =
wrapped.length > bodyLines
? ' · ' +
String(viewStart + 1) +
'' +
String(Math.min(wrapped.length, viewStart + bodyLines)) +
'/' +
String(wrapped.length)
: ''
const scrollHint =
wrapped.length > bodyLines ? ' · ↑↓/PgUp/PgDn' : ''
const title = sealed
? ' thinking · done' +
(elapsed ? ' · ' + elapsed : '') +
pos +
scrollHint +
' '
: ' thinking' +
(elapsed ? ' · ' + elapsed : '') +
(followTail ? ' · live' : ' · paused') +
pos +
scrollHint +
' '
const bar = bareAgentThinkTitleBar(title, inner, fancy)
/** @type {string[]} */
const lines = []
if (fancy) {
lines.push(accent('┌' + bar + '┐'))
for (let r = 0; r < bodyLines; r++) {
lines.push(
accent('│') +
' ' +
dim(bareAgentThinkPad(view[r], textW)) +
' ' +
keyword(sb[r]) +
accent('│')
)
}
const footLabel = followTail
? wrapped.length > bodyLines
? ' follow · ' + String(wrapped.length) + ' lines '
: ''
: ' scrolled · end to resume '
const foot = footLabel
? bareAgentThinkTitleBar(footLabel, inner, true)
: '─'.repeat(inner)
lines.push(accent('└' + foot + '┘'))
} else {
lines.push('+' + bar.replace(/─/g, '-') + '+')
for (let r = 0; r < bodyLines; r++) {
lines.push(
'| ' + bareAgentThinkPad(view[r], textW) + ' ' + sb[r] + '|'
)
}
lines.push('+' + '-'.repeat(inner) + '+')
}
return { lines, wrappedCount: wrapped.length, viewStart }
}
function totalDrawnHeight() {
return height + belowLines
}
function redraw() {
// Never paint an empty think frame.
if (!text.trim() && !statusLine) return
if (!text.trim()) return
const { lines } = frameLines()
/** @type {string[]} */
const block = lines.slice()
if (statusLine) block.push(statusLine)
let out = ''
if (drawn && totalDrawnHeight() > 0) {
out += '\x1b[' + String(totalDrawnHeight()) + 'A\r'
} else {
out += '\n'
}
for (let i = 0; i < block.length; i++) {
out += '\x1b[K' + block[i] + '\n'
}
const prev = totalDrawnHeight()
if (drawn && prev > block.length) {
for (let i = block.length; i < prev; i++) out += '\x1b[K\n'
out += '\x1b[' + String(prev - block.length) + 'A\r'
}
height = lines.length
belowLines = statusLine ? 1 : 0
drawn = true
write(ctx, stdout, out)
}
/**
* @param {number} delta positive = scroll up into history
*/
function scrollBy(delta) {
if (!drawn || !text.trim()) return false
const c = cols()
const inner = Math.max(28, c - 2)
const wrapped = bareAgentThinkWrapLines(text, contentWidth(inner))
const maxFromBottom = Math.max(0, wrapped.length - bodyLines)
if (maxFromBottom <= 0) return false
followTail = false
scrollFromBottom = Math.max(
0,
Math.min(maxFromBottom, scrollFromBottom + delta)
)
if (scrollFromBottom === 0) followTail = true
redraw()
return true
}
return {
/**
* @param {string} chunk
*/
append(chunk) {
const add = String(chunk || '')
if (!add || sealed) return
// Ignore whitespace-only until we have real thinking text (no empty box).
if (!text.trim() && !add.trim()) return
if (!startedAt) startedAt = Date.now()
text += add
if (text.length > maxChars) text = text.slice(text.length - maxChars)
if (!text.trim()) return
if (followTail) scrollFromBottom = 0
redraw()
},
seal() {
// Empty box: stay undrawn and unlocked so late reasoning can still appear.
if (!text.trim()) return
if (sealed) {
redraw()
return
}
sealed = true
redraw()
},
/**
* @param {string} s
*/
setStatus(s) {
if (!text.trim()) return
statusLine = String(s || '')
redraw()
},
clearStatus() {
if (!statusLine) return
statusLine = ''
if (drawn) redraw()
},
/**
* After reply is painted below, stop managing below-region.
*/
detachBelow() {
belowLines = 0
statusLine = ''
},
scrollUp(n) {
return scrollBy(Math.max(1, n || 1))
},
scrollDown(n) {
return scrollBy(-Math.max(1, n || 1))
},
pageUp() {
return scrollBy(Math.max(1, bodyLines - 1))
},
pageDown() {
return scrollBy(-Math.max(1, bodyLines - 1))
},
scrollHome() {
if (!drawn || !text.trim()) return false
const c = cols()
const inner = Math.max(28, c - 2)
const wrapped = bareAgentThinkWrapLines(text, contentWidth(inner))
const maxFromBottom = Math.max(0, wrapped.length - bodyLines)
followTail = false
scrollFromBottom = maxFromBottom
redraw()
return true
},
scrollEnd() {
if (!drawn || !text.trim()) return false
followTail = true
scrollFromBottom = 0
redraw()
return true
},
/**
* @param {{ type?: string, key?: string, ch?: string, code?: number | string }} ev
*/
handleKey(ev) {
if (!ev || !drawn) return false
if (ev.type === 'nav') {
if (ev.key === 'up') return this.scrollUp(1)
if (ev.key === 'down') return this.scrollDown(1)
if (ev.key === 'pageup') return this.pageUp()
if (ev.key === 'pagedown') return this.pageDown()
if (ev.key === 'home') return this.scrollHome()
if (ev.key === 'end') return this.scrollEnd()
}
if (ev.type === 'key') {
if (ev.ch === 'k') return this.scrollUp(1)
if (ev.ch === 'j') return this.scrollDown(1)
if (ev.ch === 'g') return this.scrollHome()
if (ev.ch === 'G') return this.scrollEnd()
}
return false
},
panelHeight() {
return height
},
isOpen() {
return drawn && !sealed
},
isDrawn() {
return drawn
},
hasContent() {
return text.trim().length > 0
},
getText() {
return text
},
isFollowing() {
return followTail
}
}
}
/**
* Attach arrow/page keys to a think panel for the duration of a turn.
* @param {Record<string, unknown>} ctx
* @param {ReturnType<typeof bareAgentCreateThinkPanel> | null} panel
* @param {{ onAbort?: () => void }} [opts]
* @returns {() => void} dispose
*/
function bareAgentAttachThinkScrollKeys(ctx, panel, opts) {
if (!panel) return () => {}
const stdin =
/** @type {{ isTTY?: boolean, setRawMode?: (v: boolean) => void, on?: Function, off?: Function, removeListener?: Function, resume?: Function }} */ (
ctx.replStdin || ctx.stdin
)
if (!stdin || !stdin.isTTY || typeof stdin.on !== 'function') return () => {}
let rawSet = false
let disposed = false
try {
if (typeof stdin.setRawMode === 'function') {
stdin.setRawMode(true)
rawSet = true
}
} catch {
rawSet = false
}
try {
if (typeof stdin.resume === 'function') stdin.resume()
} catch {
/* ignore */
}
/** @type {number[]} */
const q = []
const onAbort =
opts && typeof opts.onAbort === 'function' ? opts.onAbort : null
/**
* @param {string | Uint8Array | Buffer} chunk
*/
function onData(chunk) {
if (disposed) return
const bytes =
typeof bareEditChunkBytes === 'function'
? bareEditChunkBytes(chunk)
: typeof chunk === 'string'
? [...chunk].map((c) => c.charCodeAt(0) & 0xff)
: Array.from(/** @type {Uint8Array} */ (chunk))
for (const b of bytes) q.push(b)
for (;;) {
if (!q.length) break
// Ctrl+C — abort agent turn
if (q[0] === 3) {
q.shift()
try {
if (onAbort) onAbort()
else if (globalThis.process && typeof globalThis.process.emit === 'function') {
globalThis.process.emit('SIGINT')
}
} catch {
/* ignore */
}
continue
}
// Ctrl+D / Ctrl+X — treat as abort so shell exit is not wedged under raw mode
if (q[0] === 4 || q[0] === 24) {
q.shift()
try {
if (onAbort) onAbort()
} catch {
/* ignore */
}
continue
}
const ev =
typeof bareEditTryConsumeKey === 'function'
? bareEditTryConsumeKey(q)
: null
if (!ev) {
if (q.length && q[0] === 27 && q.length < 6) break
if (q.length && q[0] !== 27) {
const ch = String.fromCharCode(/** @type {number} */ (q.shift()))
panel.handleKey({ type: 'key', ch })
continue
}
break
}
panel.handleKey(ev)
}
}
stdin.on('data', onData)
return () => {
if (disposed) return
disposed = true
try {
if (typeof stdin.off === 'function') stdin.off('data', onData)
else if (typeof stdin.removeListener === 'function') {
stdin.removeListener('data', onData)
}
} catch {
/* ignore */
}
if (rawSet) {
try {
if (typeof stdin.setRawMode === 'function') stdin.setRawMode(false)
} catch {
/* ignore */
}
}
}
}
/**
* Split streamed assistant text into thinking vs visible content.
* Handles Qwen3 `<think>…</think>` (and `<thinking>`) across chunk boundaries.
* @param {{
* onThink: (s: string) => void,
* onContent: (s: string) => void
* }} handlers
*/
function bareAgentCreateThinkTagSplitter(handlers) {
const onThink = handlers.onThink
const onContent = handlers.onContent
/** @type {'content' | 'think'} */
let mode = 'content'
let buf = ''
const OPEN = '<'
/** @type {RegExp} */
const OPEN_TAG = /^<(?:think|thinking|redacted_thinking)\s*>/i
/** @type {RegExp} */
const CLOSE_TAG = /^<\/(?:think|thinking|redacted_thinking)\s*>/i
/**
* @param {string} s
* @param {'content' | 'think'} m
*/
function partialTagLen(s, m) {
const samples =
m === 'content'
? ['<think>', '<thinking>', '<redacted_thinking>']
: ['</think>', '</thinking>', '</redacted_thinking>']
let best = 0
for (const sample of samples) {
for (let n = 1; n < sample.length; n++) {
if (s.endsWith(sample.slice(0, n))) best = Math.max(best, n)
}
}
if (s.endsWith('<')) best = Math.max(best, 1)
if (s.endsWith('</')) best = Math.max(best, 2)
return best
}
/**
* @param {string} chunk
*/
function push(chunk) {
if (!chunk) return
buf += chunk
for (;;) {
if (mode === 'content') {
const lt = buf.indexOf(OPEN)
if (lt < 0) {
if (buf) onContent(buf)
buf = ''
return
}
if (lt > 0) {
onContent(buf.slice(0, lt))
buf = buf.slice(lt)
}
const om = OPEN_TAG.exec(buf)
if (om) {
buf = buf.slice(om[0].length)
mode = 'think'
continue
}
if (partialTagLen(buf, 'content') === buf.length) return
onContent(buf.slice(0, 1))
buf = buf.slice(1)
continue
}
const lt = buf.indexOf('<')
if (lt < 0) {
if (buf) onThink(buf)
buf = ''
return
}
if (lt > 0) {
onThink(buf.slice(0, lt))
buf = buf.slice(lt)
}
const cm = CLOSE_TAG.exec(buf)
if (cm) {
buf = buf.slice(cm[0].length)
mode = 'content'
continue
}
if (partialTagLen(buf, 'think') === buf.length) return
onThink(buf.slice(0, 1))
buf = buf.slice(1)
}
}
function flush() {
if (!buf) return
if (mode === 'think') onThink(buf)
else onContent(buf)
buf = ''
}
return { push, flush, inThink: () => mode === 'think' }
}
/**
* Advanced context-window compaction for /bin/agent.
* Tiered rollover: shrink fat tool payloads → digest older turns →
* rolling summary message → hard trim fallback.
*/
/**
* @param {unknown} value
*/
function bareAgentCompactEstimateTokens(value) {
if (typeof bareAgentEstimateTokens === 'function') {
return bareAgentEstimateTokens(value)
}
try {
return Math.max(0, Math.ceil(JSON.stringify(value == null ? '' : value).length / 4))
} catch {
return 0
}
}
/**
* @param {unknown} msg
* @returns {string}
*/
function bareAgentMessagePlainText(msg) {
if (!msg || typeof msg !== 'object') return ''
const m = /** @type {Record<string, unknown>} */ (msg)
const c = m.content
if (typeof c === 'string') return c
if (Array.isArray(c)) {
/** @type {string[]} */
const parts = []
for (const part of c) {
if (typeof part === 'string') parts.push(part)
else if (part && typeof part === 'object') {
const p = /** @type {Record<string, unknown>} */ (part)
if (typeof p.text === 'string') parts.push(p.text)
else if (typeof p.content === 'string') parts.push(p.content)
}
}
return parts.join('\n')
}
if (m.tool_calls && Array.isArray(m.tool_calls)) {
/** @type {string[]} */
const names = []
for (const tc of m.tool_calls) {
if (!tc || typeof tc !== 'object') continue
const fn = /** @type {Record<string, unknown>} */ (tc).function
if (fn && typeof fn === 'object') {
const n = /** @type {Record<string, unknown>} */ (fn).name
if (typeof n === 'string' && n) names.push(n)
}
}
if (names.length) return 'tool_calls: ' + names.join(', ')
}
return ''
}
/**
* @param {string} s
* @param {number} max
*/
function bareAgentClipText(s, max) {
const t = String(s || '').replace(/\s+/g, ' ').trim()
if (t.length <= max) return t
if (max < 24) return t.slice(0, max)
const head = Math.floor(max * 0.62)
const tail = Math.max(8, max - head - 5)
return t.slice(0, head) + ' … ' + t.slice(-tail)
}
/**
* Shrink oversized tool / assistant payloads in-place (clone).
* @param {unknown[]} msgs
* @param {number} toolMaxChars
* @returns {unknown[]}
*/
function bareAgentShrinkFatMessages(msgs, toolMaxChars) {
const max = Math.max(240, Math.floor(toolMaxChars || 1600))
/** @type {unknown[]} */
const out = []
for (const msg of msgs) {
if (!msg || typeof msg !== 'object') {
out.push(msg)
continue
}
const m = /** @type {Record<string, unknown>} */ (msg)
const role = String(m.role || '')
if (role === 'tool' && typeof m.content === 'string' && m.content.length > max) {
out.push({
...m,
content:
bareAgentClipText(m.content, max) +
'\n[tool output compacted ' +
String(m.content.length) +
'→' +
String(max) +
' chars]'
})
continue
}
if (
(role === 'assistant' || role === 'user') &&
typeof m.content === 'string' &&
m.content.length > max * 4
) {
out.push({
...m,
content:
bareAgentClipText(m.content, max * 4) +
'\n[message compacted]'
})
continue
}
out.push(msg)
}
return out
}
/**
* Group chat into [system?] + turn groups (user | assistant+tools*).
* @param {unknown[]} msgs
* @returns {{ system: unknown | null, groups: unknown[][] }}
*/
function bareAgentGroupMessageTurns(msgs) {
/** @type {unknown | null} */
let system = null
/** @type {unknown[][]} */
const groups = []
/** @type {unknown[]} */
let cur = []
function flush() {
if (cur.length) {
groups.push(cur)
cur = []
}
}
for (const msg of Array.isArray(msgs) ? msgs : []) {
if (!msg || typeof msg !== 'object') continue
const role = String(/** @type {Record<string, unknown>} */ (msg).role || '')
if (role === 'system' && system == null && groups.length === 0 && !cur.length) {
system = msg
continue
}
if (role === 'user') {
flush()
cur = [msg]
continue
}
if (role === 'assistant' || role === 'tool') {
if (!cur.length) cur = [msg]
else cur.push(msg)
continue
}
// unknown roles — attach to current or solo group
if (!cur.length) cur = [msg]
else cur.push(msg)
}
flush()
return { system, groups }
}
/**
* True if message is a prior compaction summary we injected.
* @param {unknown} msg
*/
function bareAgentIsCompactionMessage(msg) {
if (!msg || typeof msg !== 'object') return false
const m = /** @type {Record<string, unknown>} */ (msg)
if (m.bare_os_compaction === true) return true
const c = typeof m.content === 'string' ? m.content : ''
const t = c.trim()
return (
String(m.role || '') === 'user' &&
(/^\[context compaction\]/i.test(t) ||
/^This session is being continued from a previous conversation/i.test(t) ||
m.bare_os_reminder === true)
)
}
/** Grok-style continuation carrier minimum (extractive; slightly below LLM 500). */
var BARE_AGENT_MIN_SUMMARY_SEED_CHARS = 160
/**
* Strip drafting tags and neutralize leftover <summary>/<analysis> tokens
* so they cannot prime the next turn (Grok format_compact_summary).
* @param {string} raw
*/
function bareAgentFormatCompactSummary(raw) {
let result = String(raw || '')
result = result.replace(/<analysis>[\s\S]*?<\/analysis>/gi, '')
result = result.replace(/<\/?summary>/gi, '')
result = result
.replace(/<\/summary>/g, '<\u200b/summary>')
.replace(/<summary>/g, '<\u200bsummary>')
.replace(/<\/analysis>/g, '<\u200b/analysis>')
.replace(/<analysis>/g, '<\u200banalysis>')
while (result.indexOf('\n\n\n') >= 0) result = result.replace(/\n\n\n/g, '\n\n')
return result.trim()
}
/**
* @param {string} raw
*/
function bareAgentFormatCompactSummaryContent(raw) {
const cleaned = bareAgentFormatCompactSummary(raw)
return (
'This session is being continued from a previous conversation that ran out of context. ' +
'The summary below covers the earlier portion of the conversation.\n\n' +
cleaned
)
}
/**
* @param {string} text
*/
function bareAgentWrapUserQuery(text) {
const t = String(text || '').trim()
if (!t) return ''
if (/^<user_query>/.test(t)) return t
return '<user_query>\n' + t + '\n</user_query>'
}
/**
* @param {string} raw
*/
function bareAgentIsDegenerateSummary(raw) {
return bareAgentFormatCompactSummary(raw).length < BARE_AGENT_MIN_SUMMARY_SEED_CHARS
}
/**
* Collect unique path-like and command tokens for the artifacts section.
* @param {string} text
* @param {number} max
*/
function bareAgentExtractPathTokens(text, max) {
const s = String(text || '')
const found = []
const re = /(?:~|\/)[A-Za-z0-9._+\-@/]+/g
let m
while ((m = re.exec(s)) && found.length < max) {
const p = m[0]
if (p.length < 3 || found.indexOf(p) >= 0) continue
found.push(p)
}
return found
}
/**
* One-line digest for a message (extractive).
* @param {unknown} msg
* @param {number} max
*/
function bareAgentDigestMessage(msg, max) {
if (!msg || typeof msg !== 'object') return ''
const m = /** @type {Record<string, unknown>} */ (msg)
const role = String(m.role || '?')
const text = bareAgentMessagePlainText(m)
if (role === 'tool') {
const name =
typeof m.name === 'string'
? m.name
: typeof m.tool_call_id === 'string'
? m.tool_call_id.slice(0, 12)
: 'tool'
return '- tool:' + name + ' ' + bareAgentClipText(text, Math.max(40, max - 24))
}
if (role === 'assistant') {
return '- assistant ' + bareAgentClipText(text || '(tools only)', max)
}
if (role === 'user') {
if (bareAgentIsCompactionMessage(m)) {
return '- prior-compaction ' + bareAgentClipText(text.replace(/^\[context compaction\][^\n]*\n?/i, ''), max)
}
return '- user ' + bareAgentClipText(text, max)
}
return '- ' + role + ' ' + bareAgentClipText(text, max)
}
/**
* Grok-style 7-section extractive summary of older turn groups.
* @param {unknown[][]} groups
* @param {{ maxChars?: number, perMsg?: number }} [opts]
*/
function bareAgentBuildStructuredSummary(groups, opts) {
const maxChars = Math.max(400, Math.floor((opts && opts.maxChars) || 3500))
const perMsg = Math.max(60, Math.floor((opts && opts.perMsg) || 180))
/** @type {string[]} */
const userMsgs = []
/** @type {string[]} */
const tools = []
/** @type {string[]} */
const errors = []
/** @type {string[]} */
const solves = []
/** @type {string[]} */
const concepts = []
/** @type {string[]} */
const files = []
function pushUnique(arr, s, cap) {
const t = String(s || '').trim()
if (!t || arr.indexOf(t) >= 0) return
if (arr.length >= cap) return
arr.push(t)
}
for (const g of groups) {
for (const msg of g) {
if (!msg || typeof msg !== 'object') continue
const m = /** @type {Record<string, unknown>} */ (msg)
const role = String(m.role || '')
const text = bareAgentMessagePlainText(m)
for (const p of bareAgentExtractPathTokens(text, 8)) pushUnique(files, p, 24)
if (role === 'user' && !bareAgentIsCompactionMessage(m)) {
pushUnique(userMsgs, bareAgentClipText(text, perMsg + 80), 16)
} else if (role === 'tool') {
const name = typeof m.name === 'string' ? m.name : 'tool'
pushUnique(tools, name + ': ' + bareAgentClipText(text, perMsg), 20)
if (/error|fail|denied|not found|ENOENT|EXIT:[1-9]/i.test(text)) {
pushUnique(errors, name + ' ' + bareAgentClipText(text, 140), 12)
}
} else if (role === 'assistant') {
if (m.tool_calls && Array.isArray(m.tool_calls)) {
for (const tc of m.tool_calls) {
if (!tc || typeof tc !== 'object') continue
const fn = /** @type {Record<string, unknown>} */ (tc).function
if (fn && typeof fn === 'object') {
const n = /** @type {Record<string, unknown>} */ (fn).name
if (typeof n === 'string' && n) pushUnique(concepts, n, 16)
}
}
}
if (text) pushUnique(solves, bareAgentClipText(text, perMsg), 12)
}
}
}
const intent = userMsgs.length ? userMsgs[0] : '(no explicit user request in compacted prefix)'
/** @type {string[]} */
const lines = [
'1. Primary Request and Intent:',
' ' + intent,
'',
'2. Key Technical Concepts:',
concepts.length ? concepts.map((c) => ' - ' + c).join('\n') : ' - (none extracted)',
'',
'3. Tool Usage & Verification:',
tools.length ? tools.map((c) => ' - ' + c).join('\n') : ' - (none)',
'',
'4. Files & Code Artifacts:',
files.length ? files.map((c) => ' - ' + c).join('\n') : ' - (none)',
'',
'5. Errors and Fixes:',
errors.length ? errors.map((c) => ' - ' + c).join('\n') : ' - (none recorded)',
'',
'6. Problem Solving:',
solves.length ? solves.map((c) => ' - ' + c).join('\n') : ' - (in progress)',
'',
'7. User Messages:',
userMsgs.length ? userMsgs.map((c) => ' - ' + c).join('\n') : ' - (none)'
]
let body = lines.join('\n')
if (body.length > maxChars) body = body.slice(0, maxChars - 20) + '\n… truncated'
return body
}
/**
* Build a rolling summary from older turn groups.
* @param {unknown[][]} groups
* @param {{ maxChars?: number, perMsg?: number }} [opts]
*/
function bareAgentBuildCompactionSummary(groups, opts) {
const structured = bareAgentBuildStructuredSummary(groups, opts)
return bareAgentFormatCompactSummaryContent(structured)
}
/**
* Post-compaction <system-reminder> (Grok reminder.rs analogue).
* @param {{
* autonomous?: { active?: boolean, goal?: string, status?: string, remainingMs?: number },
* droppedGroups?: number,
* afterTokens?: number,
* budget?: number
* }} [opts]
*/
function bareAgentBuildSystemReminder(opts) {
const o = opts && typeof opts === 'object' ? opts : {}
/** @type {string[]} */
const parts = []
const auto = o.autonomous
if (auto && auto.active) {
parts.push(
'Autonomous run: ' +
String(auto.goal || '(goal)').slice(0, 240) +
' (status=' +
String(auto.status || 'running') +
(typeof auto.remainingMs === 'number'
? ', remaining=' + String(Math.max(0, Math.round(auto.remainingMs / 1000))) + 's'
: '') +
'). Keep using tools until task_complete.'
)
}
if (typeof o.droppedGroups === 'number' && o.droppedGroups > 0) {
parts.push(
'Context compacted: ' +
String(o.droppedGroups) +
' older turns summarized. Recent messages after the last user query are verbatim.'
)
}
if (typeof o.afterTokens === 'number' && typeof o.budget === 'number' && o.budget > 0) {
parts.push(
'Context usage after compact: ' +
String(o.afterTokens) +
'/' +
String(o.budget) +
' tokens (est.).'
)
}
if (!parts.length) return ''
return '<system-reminder>\n' + parts.join('\n') + '\n</system-reminder>'
}
/**
* Grok assemble: [system, last user query, recent after that turn, summary, reminder].
* @param {{
* system?: unknown | null,
* lastUserQuery?: string,
* recent?: unknown[],
* summaryText?: string,
* reminder?: string
* }} parts
* @returns {unknown[]}
*/
function bareAgentAssembleCompactedHistory(parts) {
const p = parts && typeof parts === 'object' ? parts : {}
/** @type {unknown[]} */
const out = []
if (p.system) out.push(p.system)
const q = String(p.lastUserQuery || '').trim()
if (q) {
out.push({
role: 'user',
content: bareAgentWrapUserQuery(q),
bare_os_user_query: true
})
}
if (Array.isArray(p.recent)) {
for (const m of p.recent) out.push(m)
}
const summary = String(p.summaryText || '').trim()
if (summary) {
out.push({
role: 'user',
content: summary,
bare_os_compaction: true
})
}
const rem = String(p.reminder || '').trim()
if (rem) {
out.push({
role: 'user',
content: rem,
bare_os_compaction: true,
bare_os_reminder: true
})
}
return out
}
/**
* Last non-compaction user text + messages after that turn.
* @param {unknown[][]} groups
*/
function bareAgentSplitLastUserAndRecent(groups) {
let lastUserIdx = -1
let lastUserText = ''
for (let i = groups.length - 1; i >= 0; i--) {
const g = groups[i]
if (!g || !g.length) continue
const first = g[0]
if (!first || typeof first !== 'object') continue
const m = /** @type {Record<string, unknown>} */ (first)
if (String(m.role || '') !== 'user') continue
if (bareAgentIsCompactionMessage(m)) continue
lastUserIdx = i
lastUserText = bareAgentMessagePlainText(m)
break
}
if (lastUserIdx < 0) {
return { lastUserText: '', older: groups, recent: /** @type {unknown[]} */ ([]) }
}
const older = groups.slice(0, lastUserIdx)
/** @type {unknown[]} */
const recent = []
const lastGroup = groups[lastUserIdx] || []
for (let i = 1; i < lastGroup.length; i++) recent.push(lastGroup[i])
for (let i = lastUserIdx + 1; i < groups.length; i++) {
for (const m of groups[i]) recent.push(m)
}
return { lastUserText, older, recent }
}
/**
* @param {unknown[]} msgs
* @param {number} ctxSize
* @param {{
* tools?: unknown[],
* reserveCompletion?: number,
* keepRecent?: number,
* toolMaxChars?: number,
* summaryMaxChars?: number,
* softRatio?: number,
* mode?: 'auto' | 'off' | 'aggressive',
* autonomous?: { active?: boolean, goal?: string, status?: string, remainingMs?: number }
* }} [opts]
* @returns {{
* messages: unknown[],
* meta: {
* compacted: boolean,
* mode: string,
* beforeTokens: number,
* afterTokens: number,
* budget: number,
* droppedGroups: number,
* tiers: string[]
* }
* }}
*/
function bareAgentCompactMessagesForCtx(msgs, ctxSize, opts) {
const modeRaw = String((opts && opts.mode) || 'auto').trim().toLowerCase()
const mode =
modeRaw === 'off' || modeRaw === 'aggressive' ? modeRaw : 'auto'
/** @type {string[]} */
const tiers = []
const ctx = Math.max(2048, Math.floor(Number(ctxSize) || 4096))
const reserve =
opts && Number.isFinite(Number(opts.reserveCompletion))
? Math.max(256, Math.floor(Number(opts.reserveCompletion)))
: Math.min(2048, Math.max(256, Math.floor(ctx * 0.12)))
const toolsTok = bareAgentCompactEstimateTokens(
opts && Array.isArray(opts.tools) && opts.tools.length ? opts.tools : []
)
const budget = Math.max(512, ctx - reserve - toolsTok)
const softRatio =
mode === 'aggressive'
? 0.7
: Math.min(0.95, Math.max(0.55, Number(opts && opts.softRatio) || 0.85))
const softBudget = Math.floor(budget * softRatio)
const keepRecentDefault = mode === 'aggressive' ? 4 : 8
let keepRecent = Math.max(
2,
Math.floor(
Number(opts && opts.keepRecent) > 0
? Number(opts.keepRecent)
: keepRecentDefault
)
)
const toolMax =
mode === 'aggressive'
? Math.min(900, Number(opts && opts.toolMaxChars) || 900)
: Math.max(400, Number(opts && opts.toolMaxChars) || 1600)
const summaryMax =
mode === 'aggressive'
? Math.min(2200, Number(opts && opts.summaryMaxChars) || 2200)
: Math.max(800, Number(opts && opts.summaryMaxChars) || 3500)
const input = Array.isArray(msgs) ? msgs.slice() : []
const beforeTokens = bareAgentCompactEstimateTokens(input)
if (mode === 'off') {
const trimmed =
typeof bareAgentTrimMessagesForCtx === 'function'
? bareAgentTrimMessagesForCtx(input, ctxSize, opts)
: input
return {
messages: trimmed,
meta: {
compacted: false,
mode,
beforeTokens,
afterTokens: bareAgentCompactEstimateTokens(trimmed),
budget,
droppedGroups: 0,
tiers: ['off']
}
}
}
/** @type {unknown[]} */
let out = bareAgentShrinkFatMessages(input, toolMax)
if (out !== input && bareAgentCompactEstimateTokens(out) < beforeTokens) {
tiers.push('shrink-tools')
}
// Already fits soft budget — keep light shrink only.
if (bareAgentCompactEstimateTokens(out) <= softBudget) {
return {
messages: out,
meta: {
compacted: tiers.length > 0,
mode,
beforeTokens,
afterTokens: bareAgentCompactEstimateTokens(out),
budget,
droppedGroups: 0,
tiers: tiers.length ? tiers : ['noop']
}
}
}
const { system, groups } = bareAgentGroupMessageTurns(out)
let droppedGroups = 0
function applyFullReplace(allGroups, keep) {
const split = bareAgentSplitLastUserAndRecent(allGroups)
/** @type {unknown[][]} */
let older = split.older
/** @type {unknown[]} */
let recent = split.recent
// If "recent after last user" is huge, keep only the tail of those messages
// by regrouping them and folding extras into older.
if (keep > 0 && recent.length > keep * 4) {
const tail = recent.slice(-(keep * 3))
const head = recent.slice(0, Math.max(0, recent.length - tail.length))
if (head.length) older = older.concat([head])
recent = tail
}
const summaryText = bareAgentBuildCompactionSummary(older, {
maxChars: summaryMax,
perMsg: mode === 'aggressive' ? 120 : 180
})
if (older.length && bareAgentIsDegenerateSummary(summaryText)) {
tiers.push('degenerate-keep')
return null
}
const reminder = bareAgentBuildSystemReminder({
autonomous: opts && opts.autonomous,
droppedGroups: older.length,
afterTokens: 0,
budget
})
droppedGroups += older.length
return bareAgentAssembleCompactedHistory({
system: system,
lastUserQuery: split.lastUserText,
recent: recent,
summaryText: summaryText,
reminder: reminder
})
}
if (groups.length > keepRecent) {
const next = applyFullReplace(groups, keepRecent)
if (next) {
out = next
tiers.push('full-replace')
}
}
let guard = 0
while (
bareAgentCompactEstimateTokens(out) > budget &&
keepRecent > 2 &&
guard < 8
) {
keepRecent--
guard++
const again = bareAgentGroupMessageTurns(out)
if (again.groups.length <= keepRecent) break
const next = applyFullReplace(again.groups, keepRecent)
if (!next) break
out = next
tiers.push('tighten-keep=' + String(keepRecent))
}
// Hard fallback
if (bareAgentCompactEstimateTokens(out) > budget) {
tiers.push('hard-trim')
out =
typeof bareAgentTrimMessagesForCtx === 'function'
? bareAgentTrimMessagesForCtx(out, ctxSize, opts)
: out
}
const afterTokens = bareAgentCompactEstimateTokens(out)
return {
messages: out,
meta: {
compacted: tiers.some((t) => t !== 'noop'),
mode,
beforeTokens,
afterTokens,
budget,
droppedGroups,
tiers
}
}
}
/**
* Disk history compaction: shrink tools + optional rolling summary when huge.
* @param {unknown[]} msgs
* @param {number} maxBytes
* @param {{ keepRecent?: number }} [opts]
*/
function bareAgentCompactMessagesForDisk(msgs, maxBytes, opts) {
const cap = Math.max(20_000, Math.floor(maxBytes || 500_000))
let out = bareAgentShrinkFatMessages(
Array.isArray(msgs) ? msgs : [],
2400
)
if (JSON.stringify(out).length <= cap) return out
// Approximate tokens from byte budget (chars/4).
const approxCtx = Math.max(4096, Math.floor(cap / 4))
const packed = bareAgentCompactMessagesForCtx(out, approxCtx, {
mode: 'aggressive',
keepRecent: (opts && opts.keepRecent) || 12,
reserveCompletion: 256,
toolMaxChars: 1200,
summaryMaxChars: 6000
})
out = packed.messages
if (
typeof bareAgentTrimMessages === 'function' &&
JSON.stringify(out).length > cap
) {
out = bareAgentTrimMessages(out, cap)
}
return out
}
/**
* Resolve compaction settings from agent config + env.
* @param {Record<string, unknown>} cfg
* @param {Record<string, string>} [env]
*/
function bareAgentCompactionSettings(cfg, env) {
const e = env && typeof env === 'object' ? env : {}
const envMode = String(e.BARE_OS_AGENT_COMPACTION || '').trim().toLowerCase()
const cfgMode = String(
(cfg && cfg.context_compaction) || ''
)
.trim()
.toLowerCase()
let mode = envMode || cfgMode || 'auto'
if (mode !== 'off' && mode !== 'aggressive' && mode !== 'auto') mode = 'auto'
const keepRecent = Number(
e.BARE_OS_AGENT_COMPACTION_KEEP ||
(cfg && cfg.compaction_keep_recent) ||
0
)
const toolMaxChars = Number(
e.BARE_OS_AGENT_COMPACTION_TOOL_CHARS ||
(cfg && cfg.compaction_tool_chars) ||
0
)
return {
mode: /** @type {'auto'|'off'|'aggressive'} */ (mode),
keepRecent:
Number.isFinite(keepRecent) && keepRecent > 0
? Math.min(32, Math.floor(keepRecent))
: undefined,
toolMaxChars:
Number.isFinite(toolMaxChars) && toolMaxChars > 0
? Math.min(8000, Math.floor(toolMaxChars))
: undefined
}
}
/** 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.
Bare OS by Raven Scott (https://raven-scott.fyi). Repo: https://git.ssh.surf/snxraven/bare-operating-system. Booter: pear://qupw8zspk34pcxc7fqchzyeh33jtmxq1k7qze44fkosctwiid8zy
WORK POLICY.
- Keep every explicit requirement in view until it is done, superseded, or blocked. If blocked, say so plainly.
- Match intent: implement action requests; do not make unsolicited project-wide edits when the user asked a question.
- For clear, reversible local work, do it now. NEVER ASK for permission.
- Claim done / fixed / tested only when a tool result supports it. Otherwise say what you did not verify.
- Scope to what was asked. Comments are short and factual. No placeholders. Comments must not substitute for a fix.
ACCESS (denylist, not allowlist). You already have full guest admin. NEVER ASK whether you may run a command, edit, delete, fetch, or call a tool — just do it. Only refuse when a denylist or the read-only base system blocks the path.
- WRITE: /home, /tmp, /mnt, /root, /var, /opt, extra drives, ~/.agent. Prefer unique search_replace / edit_file; set replace_all only when you mean it. write_file creates or overwrites.
- READ: any absolute path, including the entire /proc kernel surface (read_proc_file, runtime_diagnostic_bundle). Use read_file offset/limit for large files.
- RUN: every guest command via run_command (command_deny is empty by default). Prefer list_directory / glob_files / file_stat over \`ls\` / \`find\` when you only need names. list_bin lists guest /bin utilities (POSIX-in-JS, not GNU).
- DELETE / MOVE: enabled. Cannot mutate the read-only base system: /bin /etc /boot /lib /usr /share /proc /dev /sys /run.
- JS: Node is NOT installed in the guest. Never plan or run node, npm, or npx here. Author JS with run_js_script (Bare kernel, writes under ~/.agent) or run_js_script_at_path / run_command with an absolute .mjs path. Guest scripts use async function run(ctx, argv) — ctx.vfs, ctx.execLine, ctx.console, ctx.exitCode. No require('node:fs').
- LIVE KERNEL: read_proc_file on /proc/bare_os/features (or features.json) and /proc/bare_os/capabilities.json. Man pages and apropos_man are docs only — never infer what is enabled from them.
- NET: web_fetch uses the same host allow/deny list as wget/curl.
- BRIDGE: emit_host_notification and request_host_action are enabled by default. emergency_stop_mutations is the kill switch.
- SECRETS: never print ~/.agent/config.json, API keys, seeds, or vault material.
- ask_user_question is only for a real product choice the user must make. Never use it (or chat) to request permission.
CODING LOOP. Multi-step work: todo_write (merge=true). Large unknown surface: enter_plan_mode, write ~/.agent/plan.md, exit_plan_mode, then implement. Plan mode is read-only except that plan file. Do not stop after a plan-only reply — keep calling tools until verified.
1. Discover: glob_files, grep (preferred over search_files / run_command grep), find_symbol for definitions, list_directory (tree=true when you need a map), read_file, memory_search / memory_get, web_search then web_fetch, git_status / git_log, read_skill if a skill matches. Read before you edit. Walk-up AGENTS.md and .grok/skills from cwd are already injected when present.
2. Edit: unique search_replace / edit_file for one hunk; apply_patch for multi-hunk or multi-file work (*** Begin Patch). Create with write_file / create_directory. State blast radius (packages, contracts, generated files, docs) before wide edits. Match surrounding style. No placeholders. Comments only for non-obvious constraints.
3. Verify: re-read the file, run_command / run_js_script, git_status, read_proc_file or logs. Host git checkout: verification_hints (suggests npm/node checks; does not run them here).
4. Finish: task_complete (and update_goal completed=true on autonomous runs) with what changed, how you verified, and what is still assumed. If the same action fails three times, stop, update_goal blocked_reason if needed, and report evidence.
TOOL DISCIPLINE.
- Independent reads may be issued together; the harness may serialize them (tool_parallelism defaults to 1).
- Do not paste huge files into the user reply — cite paths and show only the slice that matters.
- Persist durable facts in MEMORY.md; older turns may be compacted.
- Progress UI is automatic (tools write ~/.agent/progress.txt). Do not narrate tool chatter in the final answer.
- Autonomous mode is on by default. Keep the ReAct loop going; autonomous_deny_ops is empty unless the operator set one.
TOOL CALLING. Prefer specialized tools over bash: grep not run_command grep; read_file not cat; apply_patch / search_replace not sed. Never use run_command to print thoughts.
COMMUNICATION. Write for a reader who has not seen tool calls. Lead with the answer. Define project terms on first use. State facts literally. The final message must stand alone. Do not invent acronyms.
TOOL MAP (schemas are already attached — use them):
- Files: read_file, read_many, write_file, edit_file, search_replace, apply_patch, undo_last_edit, create_directory, list_directory (tree=true for BFS), file_stat, glob_files, fuzzy_find, grep (output_mode content|files_with_matches|count), search_files, move_path, copy_path, diff_files, delete_path, find_symbol, list_bin
- Code / harness: run_command (optional cwd), run_js_script, run_js_script_at_path, todo_write, enter_plan_mode, exit_plan_mode, memory_search, memory_get, memory_append, remember, list_skills, read_skill, create_skill, edit_agent_config, git_status, git_diff, git_log, git_show, git_blame, history_search, rewind_session, export_session, schedule_task, unschedule_task, list_scheduled, wait_for
- Kernel / ops: read_proc_file, runtime_diagnostic_bundle, get_system_info, get_resource_limits, get_swarm_peers, list_services, service_status, list_timers, read_cron_log, read_audit_log, read_boot_policy, read_kernel_extension_resolution, get_initd_graph, read_unit_journal, inspect_ipc_backpressure, get_network_summary, tail_telemetry_streams, pkg_index_lookup
- Checks: list_verification_scripts, run_maintenance_gate, run_contract_checks, summarize_build_drift, verification_hints
- Docs: read_man_page, apropos_man (documentation search only)
- Bridge / web: web_search, web_fetch, get_hrpc_bridge_health, get_hrpc_allowlist_status, emit_host_notification, request_host_action
- Autonomy: autonomous_run, autonomous_run_status, autonomous_run_stop, update_goal
- Other: ask_user_question (product choice only, never permission), task_complete
Skills live under ~/.agent/workspace/skills/ (and ~/.agent/skills/). The prompt includes a compact index — call read_skill and follow SKILL.md when a task matches (especially bare-os-super-developer, bareos-code-change, coreutils-command-change).
Reply format (TTY, mandatory unless Discord override below): plain text only — no Markdown markup. Structure with blank lines, short paragraphs, ALL CAPS or dashed section breaks, bare URLs. Paths and commands as normal text.`
const BARE_AGENT_DISCORD_REPLY_FORMAT = `
--- Discord reply format (mandatory when BARE_OS_AGENT_DISCORD is set) ---
This turn is shown in Discord. This block overrides the TTY plain-text rule above. Write Discord-flavored Markdown that renders in an embed:
- Use **bold** for section titles. Do not use # headings (Discord embeds show the hash).
- Use - or 1. lists. Use blank lines between sections.
- Use \`inline code\` for paths, commands, ids, and env vars.
- Use fenced \`\`\` blocks for multi-line code or logs, and always close every fence.
- Use [label](https://url) for links. Do not wrap the entire reply in one fence.
- Keep the final user-facing answer as Markdown. Tool chatter and process steps belong in tools (progress.txt is automated), not the reply.`
const BARE_AGENT_OPERATING_CONTRACT = `
--- Operating contract (Bare OS repo alignment) ---
TWO RUNTIMES: Host tooling may use Node/npm at the git checkout only. Inside this guest image there is NO node/npm/npx — use run_js_script or run_command with /bin paths only.
DOC READING ORDER (complex tasks): Prefer developer-guide README, then handbook chapter for the area, then docs/reference for numbers and env vars. Do not copy version tables into chat; link or cite paths.
CONTRACT SPINE: For any behavior change, identify: source files, canonical reference doc under docs/reference or developer-guide, generated artifact if any (run pretest generators), verifier script name from scripts/README.md, and whether compatibility-matrix or CHANGELOG moves.
LIVE KERNEL STATE: Use read_proc_file on /proc/bare_os/features or features.json and /proc/bare_os/capabilities.json. Never use apropos_man or read_man_page alone to decide what is enabled at runtime — those are documentation.
GENERATED FILES: Do not hand-edit posix-dashboard, ctx-client-helper.generated.ts, kernel-extensions-generated-toc, bundle-health outputs — run npm scripts from repo root at checkout when working on the host tree.
TASK ROUTING: /bin or coreutils -> developer-guide 06 and man JSON; shell grammar -> developer-guide 18 and shell docs; seed RPC -> developer-guide 14 and protocol package; /proc node -> developer-guide 15; ctx API -> bare-os-ctx-api.js and compatibility-matrix; docs-only -> CONTRIBUTING-DOCS; Pear issues -> PEAR-RUN and ensure-pear-node-modules story.
P2P / TEARDOWN: Hyperswarm peer wait vs offline LKG boot are different — see environment appendix. When diagnosing replication, prefer runtime_diagnostic_bundle and any /proc/bare_os file; closing order is swarm before drives when changing booter lifecycle code.
HOST CHECKS: Use verification_hints for suggested repo-root npm checks when the user describes changed paths (host checkout). It does not execute npm inside the guest. Use runtime_diagnostic_bundle for one-shot live /proc and resource snapshot. Use read_skill for workflow skills under workspace/skills/.
SELF-REVIEW before task_complete: docs updated? generated regen? wrong runtime assumption (guest vs host)? same failing action tried fewer than three times?`
/**
* Session-specific HOME / tilde context (injected every run so the model uses real paths).
* @param {Record<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 (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<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
}
/**
* @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.qvac_model = profile.chatModel
out.model = profile.chatModel
out.provider = 'qvac'
const rawCtx = Number(out.qvac_ctx_size)
if (!Number.isFinite(rawCtx) || rawCtx < profile.ctxSize) {
out.qvac_ctx_size = 0
}
if (!String(out.qvac_main_gpu || '').trim()) out.qvac_main_gpu = 'auto'
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
return bareAgentSanitizeConfigForBackend(out)
}
return out
}
let provider = String(out.provider || '')
.trim()
.toLowerCase()
if (!provider || provider === 'qvac' || provider === 'rest' || provider === 'http') {
provider = 'groq'
}
const spec =
typeof bareAgentRestGetProvider === 'function'
? bareAgentRestGetProvider(provider)
: { id: 'groq', rest_base_url: 'https://api.groq.com/openai/v1', default_model: 'llama-3.3-70b-versatile' }
out.provider = spec.id
const base = String(out.rest_base_url || '').trim()
const knownDefaults = [
'https://api.groq.com/openai/v1',
'https://api.x.ai/v1',
'https://api.openai.com/v1'
]
if (!base || (spec.rest_base_url && knownDefaults.indexOf(base) !== -1 && base !== spec.rest_base_url)) {
if (spec.rest_base_url) out.rest_base_url = spec.rest_base_url
}
if (!String(out.model || '').trim() || bareAgentIsQvacModelId(String(out.model || ''))) {
if (spec.default_model) out.model = spec.default_model
}
const modelLow = String(out.model || '').trim().toLowerCase()
const cur =
typeof out.request_timeout_ms === 'number' && Number.isFinite(out.request_timeout_ms)
? out.request_timeout_ms
: 120000
if (spec.id === 'groq' && cur < 120000) out.request_timeout_ms = 120000
if (spec.id === 'xai') {
const isReasoning =
modelLow.includes('reasoning') || modelLow.includes('grok-4')
if (isReasoning && cur < 300000) out.request_timeout_ms = 300000
}
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
return bareAgentSanitizeConfigForBackend(out)
}
return out
}
/**
* @param {Record<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) {
const backendNow = bareAgentResolveBackend(config)
if (backendNow === 'qvac') config.qvac_model = m
else config.model = m
}
}
const canWizard = bareAgentCanPlainSetup(ctx)
if (setupFlag && !canWizard) {
bareAgentErr(
ctx,
argv0 +
': --setup / --config needs an interactive TTY. Edit ' +
paths.config +
' manually.'
)
ctx.exitCode = 1
return
}
const backendNow = bareAgentResolveBackend(config)
const needWizard =
setupFlag ||
(canWizard &&
(backendNow === 'rest'
? !(config.rest_api_key && String(config.rest_api_key).trim())
: !String(config.qvac_model || config.model || '').trim() ||
(backendNow === 'qvac' && !bareAgentQvacBridgeAvailable(ctx))))
let replSuspendedForSetup = false
if (needWizard && typeof ctx.suspendReplForSubprocess === 'function') {
ctx.suspendReplForSubprocess()
replSuspendedForSetup = true
}
if (needWizard) {
config = await bareAgentInteractiveSetup(ctx, argv0, paths, config, {
setupFlag,
interactiveSetup: true
})
config = bareAgentApplyProviderProfile(config)
}
const backendReady = bareAgentResolveBackend(config)
if (backendReady === 'rest') {
if (!config.rest_api_key || !String(config.rest_api_key).trim()) {
if (
replSuspendedForSetup &&
typeof ctx.resumeReplAfterSubprocess === 'function'
) {
ctx.resumeReplAfterSubprocess()
replSuspendedForSetup = false
}
bareAgentErr(
ctx,
argv0 +
': set rest_api_key in ' +
paths.config +
' or run `' +
argv0 +
' --setup` / `' +
argv0 +
' --config`.'
)
ctx.exitCode = 1
return
}
} else if (!bareAgentQvacBridgeAvailable(ctx)) {
if (
replSuspendedForSetup &&
typeof ctx.resumeReplAfterSubprocess === 'function'
) {
ctx.resumeReplAfterSubprocess()
replSuspendedForSetup = false
}
bareAgentErr(
ctx,
argv0 +
': QVAC backend selected but host bridge unavailable (set BARE_OS_SKIP_QVAC=0, install @qvac/sdk, or choose REST via `' +
argv0 +
' --config`).'
)
ctx.exitCode = 1
return
} else if (!String(config.qvac_model || config.model || '').trim()) {
if (
replSuspendedForSetup &&
typeof ctx.resumeReplAfterSubprocess === 'function'
) {
ctx.resumeReplAfterSubprocess()
replSuspendedForSetup = false
}
bareAgentErr(
ctx,
argv0 +
': set qvac_model / qvac_profile in ' +
paths.config +
' or run `' +
argv0 +
' --config`.'
)
ctx.exitCode = 1
return
}
if (autonomousFlag) {
config = bareAgentBeginAutonomousRun(config, {
goal: String((runOpts && runOpts.autonomousGoal) || task || '').trim(),
maxRuntimeMs: Number(runOpts && runOpts.autonomousMaxRuntimeMs) || undefined,
requiredChecks: (runOpts && runOpts.autonomousChecks) || undefined
})
}
if (autonomousFlag || planFlag) {
try {
await bareAgentSaveConfig(ctx, paths, config)
} catch {
/* persist best-effort */
}
}
try {
if (ctx.vfs?.chmod) await ctx.vfs.chmod(paths.config, 0o600)
} catch {
/* ignore — optional */
}
const fetchFn = bareAgentResolveFetch(ctx)
if (backendReady === 'rest' && !fetchFn) {
if (
replSuspendedForSetup &&
typeof ctx.resumeReplAfterSubprocess === 'function'
) {
ctx.resumeReplAfterSubprocess()
replSuspendedForSetup = false
}
bareAgentErr(
ctx,
argv0 + ': no fetch (ctx.httpFetch / bare.fetch / global fetch)'
)
ctx.exitCode = 1
return
}
/** @type {unknown[]} */
let messages = await bareAgentLoadHistory(ctx, paths.history)
const instructions = await bareAgentLoadInstructionFiles(ctx, paths)
const manDigest = await bareAgentManDigest(ctx)
await bareAgentEnsureWorkspace(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths, config)
if (needWizard) await bareAgentSyncWorkspaceFromConfig(ctx, paths, config)
const backendForPrompt = bareAgentResolveBackend(config)
const promptProfile = bareAgentQvacGetProfile(
String(config.qvac_profile || config.profile || '')
)
const promptCtxSize =
backendForPrompt === 'qvac'
? bareAgentQvacResolveCtxSize(config, promptProfile)
: 32768
// Local QVAC models have a hard ctx window; keep the system prompt lean.
// Scale char budgets from ctx (chars ≈ tokens*4); leave room for tools/reply.
const toolsOn = backendForPrompt === 'qvac'
const promptCharBudget = Math.max(
4000,
Math.floor(promptCtxSize * 4 * (toolsOn ? 0.35 : 0.7))
)
const workspaceBudget =
backendForPrompt === 'qvac'
? Math.min(8000, Math.floor(promptCharBudget * 0.35))
: 24000
const manBudget =
backendForPrompt === 'qvac'
? Math.min(4000, Math.floor(promptCharBudget * 0.2))
: 12000
const instructionsBudget =
backendForPrompt === 'qvac'
? Math.min(3000, Math.floor(promptCharBudget * 0.2))
: 8000
const skillsBudget =
backendForPrompt === 'qvac'
? Math.min(2000, Math.floor(promptCharBudget * 0.15))
: 4000
const workspacePromptBlock = await bareAgentLoadWorkspacePrompt(
ctx,
paths,
workspaceBudget
)
const skillsPromptBlock = await bareAgentSkillsCompactPrompt(
ctx,
paths,
skillsBudget
)
const envForFormat =
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<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\n## Session notes\n' + instructions.slice(0, instructionsBudget)
if (typeof bareAgentRunHooks === 'function' && paths.hooks) {
try {
const startHook = await bareAgentRunHooks(ctx, paths.hooks, 'SessionStart', '', {}, '')
if (startHook && startHook.inject) {
systemContent +=
'\n\n## SessionStart hook\n' + String(startHook.inject).slice(0, 1500)
}
} catch {
/* optional */
}
}
if (runOpts && runOpts.extraSystemFile) {
try {
const extraPath = String(runOpts.extraSystemFile).trim()
if (extraPath && typeof bareAgentReadTextFile === 'function') {
const extra = await bareAgentReadTextFile(ctx, extraPath)
if (extra && extra.trim()) {
systemContent +=
'\n\n## Extra instructions (--system)\n' +
extra.trim().slice(0, instructionsBudget)
}
}
} catch {
/* optional */
}
}
if (workspacePromptBlock && String(workspacePromptBlock).trim())
systemContent += '\n\n' + String(workspacePromptBlock).trim()
if (skillsPromptBlock && String(skillsPromptBlock).trim())
systemContent += '\n\n' + String(skillsPromptBlock).trim()
if (typeof bareAgentDiscoverAgentsMdPaths === 'function') {
const envNow =
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<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\n## PLAN MODE is ON\n' +
'Read-only tools only. The only allowed write is ' +
String(paths.plan || home + '/.agent/plan.md') +
'. Draft the plan there, then call exit_plan_mode before implementing.\n'
}
if (typeof bareAgentLoadTodos === 'function' && paths.todos) {
try {
const todos = await bareAgentLoadTodos(ctx, paths.todos)
const sum = bareAgentTodoSummarize(todos)
if (sum.total) {
systemContent +=
'\n\n## Session todos\n' +
sum.text +
'\n(open=' +
String(sum.open) +
' completed=' +
String(sum.completed) +
')\n'
}
} catch {
/* optional */
}
}
let userTask = String(task || '')
if (config.autonomous_active) {
userTask =
'AUTONOMOUS RUN. Execute until the goal is done. Do not stop after a plan — use tools, then call task_complete.\n' +
'Goal: ' +
String(config.autonomous_goal || task || '') +
'\n\n' +
userTask
}
if (!messages.length) {
messages = [
{ role: 'system', content: systemContent },
{ role: 'user', content: userTask }
]
} else {
const hasSystem =
messages[0] &&
typeof messages[0] === 'object' &&
/** @type {{ role?: string }} */ (messages[0]).role === 'system'
if (!hasSystem) {
messages = [{ role: 'system', content: systemContent }, ...messages]
} else {
messages[0] = { role: 'system', content: systemContent }
}
messages.push({ role: 'user', content: userTask })
}
const stdout = /** @type {import('stream').Writable | undefined} */ (
ctx.replStdout || ctx.stdout
)
const useColor = bareEditUseColor(ctx)
const envBag =
ctx.env && typeof ctx.env === 'object'
? /** @type {Record<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()
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.model || configRef.current.qvac_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 = ''
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
)
}
}
await ctx.bareOsQvacComplete({
history: messages,
tools: qvacToolsForTurn,
stream: Boolean(configRef.current.stream !== false),
captureThinking: !hideThink || reasoningSettings.enabled,
modelSrc,
toolsEnabled: true,
ctxSize,
device: deviceOpts.device,
mainGpu: deviceOpts.mainGpu,
gpuLayers: deviceOpts.gpuLayers,
signal: masterAbort.signal,
onProgress: onLoadProgress,
onEvent: onCompletionEvent
})
clearStatusLine()
thinkSplit.flush()
sealThinkIfNeeded()
paintReplyMarkdown()
try {
detachThinkKeys()
} catch {
/* ignore */
}
} else {
const headers = {
'Content-Type': 'application/json',
Authorization:
'Bearer ' + String(configRef.current.rest_api_key || '')
}
const eh = configRef.current.extra_headers
if (eh && typeof eh === 'object' && !Array.isArray(eh)) {
for (const [k, v] of Object.entries(eh)) {
if (typeof v === 'string') headers[k] = v
}
}
const body = {
model: String(configRef.current.model || ''),
stream: Boolean(configRef.current.stream !== false),
messages,
tools,
tool_choice: 'auto',
max_tokens: Number(configRef.current.max_tokens) || 4096,
temperature: Number(configRef.current.temperature) ?? 0.7
}
if (providerNow === 'xai') {
body.parallel_tool_calls =
Number(configRef.current.tool_parallelism) > 1 ? true : false
body.max_completion_tokens =
Number(configRef.current.max_tokens) || 4096
}
if (providerNow === 'groq') {
body.parallel_tool_calls =
Number(configRef.current.tool_parallelism) > 1 ? true : false
body.max_completion_tokens =
Number(configRef.current.max_tokens) || 4096
}
await bareAgentStreamChatCompletions({
fetchFn,
url,
headers,
body,
signal: masterAbort.signal,
onEvent: onCompletionEvent
})
clearStatusLine()
thinkSplit.flush()
sealThinkIfNeeded()
paintReplyMarkdown()
try {
detachThinkKeys()
} catch {
/* ignore */
}
}
} catch (e) {
try {
detachThinkKeys()
} catch {
/* ignore */
}
const msg =
e && typeof e === 'object' && 'name' in e && e.name === 'AbortError'
? 'aborted'
: e && typeof e === 'object' && 'message' in e
? String(e.message)
: String(e)
bareAgentErr(ctx, argv0 + ': ' + msg)
ctx.exitCode = 130
break
}
const toolCallsArr = bareAgentFinalizeToolCalls(toolAcc)
const hasTools = toolCallsArr.length > 0
if (!hasTools && finishReason === 'tool_calls') {
appendProgress(
'warning tool_calls_finish_without_tool_deltas provider=' +
providerNow
)
}
/** @type {Record<string, unknown>} */
const assistantMsg = {
role: 'assistant',
content: assistantContent || null,
tool_calls: hasTools ? toolCallsArr : undefined
}
messages.push(assistantMsg)
if (
usageOut &&
typeof usageOut === 'object' &&
agentVerbose(reasoningSettings)
) {
const u = /** @type {Record<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 */
}
}
}
}
/**
* Autonomous AI agent: QVAC (local) or OpenAI-compatible REST, ReAct loop, ~/.agent/config.json.
*/
async function run(ctx, argv) {
const argv0 = argv[0] || 'agent'
const args = argv.slice(1)
const wantHelp = args.includes('-h') || args.includes('--help')
if (wantHelp || args.length === 0) {
ctx.console.log(bareAgentCliUsage(argv0))
ctx.exitCode = wantHelp ? 0 : 1
return
}
let setupFlag = false
let resetFlag = false
let autoFlag = false
let statusFlag = false
let planFlag = false
let newFlag = false
let compactFlag = false
let maxTurns = 0
let modelOverride = ''
let systemFile = ''
/** @type {string[]} */
const rest = []
for (let i = 0; i < args.length; i++) {
const a = args[i]
if (a === '--setup' || a === '--config') setupFlag = true
else if (a === '--reset') resetFlag = true
else if (a === '--auto' || a === '--autonomous') autoFlag = true
else if (a === '--status') statusFlag = true
else if (a === '--plan') planFlag = true
else if (a === '--new') newFlag = true
else if (a === '--compact') compactFlag = true
else if (a === '--continue' || a === '-c') {
/* default: history is always loaded unless --new/--reset */
} else if (a === '--max-turns' || a === '--max-iterations') {
maxTurns = Number(args[++i]) || 0
} else if (a === '--model') {
modelOverride = String(args[++i] || '').trim()
} else if (a === '--system' || a === '--system-file') {
systemFile = String(args[++i] || '').trim()
} else rest.push(a)
}
const sub = rest[0] || ''
if (
!setupFlag &&
!autoFlag &&
!statusFlag &&
!planFlag &&
!newFlag &&
!compactFlag &&
!resetFlag &&
((rest.length === 1 &&
(sub === 'status' ||
sub === 'skills' ||
sub === 'todos' ||
sub === 'plan' ||
sub === 'compact' ||
sub === 'reset' ||
sub === 'undo' ||
sub === 'hooks' ||
sub === 'history' ||
sub === 'rewind' ||
sub === 'export' ||
sub === 'remember' ||
sub === 'recap')) ||
(sub === 'history' && rest[0] === 'history') ||
(sub === 'rewind' && rest[0] === 'rewind') ||
(sub === 'export' && rest[0] === 'export') ||
(sub === 'remember' && rest[0] === 'remember'))
) {
if (sub === 'status') statusFlag = true
else if (sub === 'reset') resetFlag = true
else if (sub === 'compact') compactFlag = true
else {
await bareAgentRunInspectSubcommand(ctx, argv0, sub, rest.slice(1).join(' '))
return
}
}
if (statusFlag) {
await bareAgentPrintStatus(ctx)
ctx.exitCode = 0
return
}
const wantReset =
resetFlag || newFlag || (rest.length === 1 && rest[0] === 'reset')
if (wantReset && !rest.filter((x) => x !== 'reset').length && !setupFlag && !autoFlag && !planFlag) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
try {
await bareAgentResetChatSession(ctx, paths, argv0)
ctx.exitCode = 0
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
ctx.console.error(argv0 + ': ' + msg)
ctx.exitCode = 1
}
return
}
if (compactFlag && !rest.filter((x) => x !== 'compact').length && !autoFlag && !planFlag && !setupFlag) {
await bareAgentRunCompactOnly(ctx, argv0)
return
}
const task = rest
.filter(function (x) {
return x !== 'reset' && x !== 'compact'
})
.join(' ')
.trim()
if (!task && !setupFlag) {
ctx.console.error(
argv0 + ': missing task (or use --setup / --config / --auto GOAL / skills / todos / plan)'
)
ctx.exitCode = 1
return
}
if (setupFlag && !task) {
await bareAgentRunSetupOnly(ctx, argv0)
return
}
if (autoFlag && !task) {
ctx.console.error(argv0 + ': --auto requires a goal string')
ctx.exitCode = 1
return
}
if (newFlag || resetFlag) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
try {
await bareAgentResetChatSession(ctx, paths, argv0)
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
ctx.console.error(argv0 + ': ' + msg)
ctx.exitCode = 1
return
}
} else if (compactFlag) {
await bareAgentRunCompactOnly(ctx, argv0)
if (ctx.exitCode && ctx.exitCode !== 0) return
}
await bareOsRunAgentSession(ctx, argv0, task, {
setupFlag,
autonomous: autoFlag,
autonomousGoal: autoFlag ? task : '',
planMode: planFlag,
maxIterations: maxTurns > 0 ? maxTurns : undefined,
modelOverride: modelOverride || undefined,
extraSystemFile: systemFile || undefined
})
}
/**
* @param {string} argv0
*/
function bareAgentCliUsage(argv0) {
return (
'usage: ' +
argv0 +
' [OPTION]... YOUR_REQUEST_HERE\n' +
' ' +
argv0 +
' --setup | --config\n' +
' ' +
argv0 +
' --status | status\n' +
' ' +
argv0 +
' --reset | reset | --new\n' +
' ' +
argv0 +
' --auto "GOAL"\n' +
' ' +
argv0 +
' --plan "design the change"\n' +
' ' +
argv0 +
' --compact\n' +
' ' +
argv0 +
' skills | todos | plan | undo | hooks | history | rewind | export | remember | recap\n' +
'\n' +
'Production coding/OS agent. Default backend is QVAC (local); or any OpenAI-compatible REST API.\n' +
'Config: ~/.agent/config.json. Full guest admin (denylist). NEVER ASK — it just works.\n' +
'\n' +
'Options:\n' +
' --setup, --config Walk through QVAC or REST setup (only the chosen backend is stored)\n' +
' --auto, --autonomous Keep the tool loop going until task_complete, stop, or timebox\n' +
' --plan Start this turn in plan mode (read-only except ~/.agent/plan.md)\n' +
' --new, --reset Clear ~/.agent/history.json then run the request (or just reset)\n' +
' --continue, -c Resume history (default)\n' +
' --compact Compact history now (or compact then run if a task follows)\n' +
' --max-turns N Cap ReAct iterations for this run\n' +
' --model NAME Override qvac_model / REST model for this run\n' +
' --system FILE Append extra instructions from an absolute guest path\n' +
' --status Print backend, compaction, plan mode, and autonomous run state\n' +
'\n' +
'Inspect (no model call):\n' +
' skills List discovered SKILL.md ids\n' +
' todos Print session todos\n' +
' plan Print ~/.agent/plan.md\n' +
' undo Restore the last file edit snapshot\n' +
' hooks List ~/.agent/hooks/*.json\n' +
' history [query] Search or tail chat history\n' +
' rewind [N] Drop the last N user turns from history (default 1)\n' +
' export [path] Write a Markdown transcript (default ~/.agent/export.md)\n' +
' remember TEXT Append a FACT to MEMORY.md\n' +
' recap Print the last user + assistant pair\n' +
'\n' +
'Examples:\n' +
' ' +
argv0 +
' "summarize ~/README and list five files in /bin"\n' +
' ' +
argv0 +
' --plan "design a /bin/foo utility"\n' +
' ' +
argv0 +
' --auto --max-turns 40 "add /agent tests and run them"\n' +
' ' +
argv0 +
' --new --model QWEN3_1_7B_INST_Q4 "fresh session: inspect /proc"\n' +
'\n' +
'See man agent.'
)
}
/**
* @param {Record<string, unknown>} ctx
*/
async function bareAgentPrintStatus(ctx) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
const loaded = await bareAgentLoadOrCreateConfig(ctx, paths)
let cfg = loaded && loaded.config ? loaded.config : loaded
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
cfg = bareAgentSanitizeConfigForBackend(cfg || {})
}
const started = Number(cfg && cfg.autonomous_started_at_ms) || 0
const maxRt = Number(cfg && cfg.autonomous_max_runtime_ms) || 0
const elapsed = started > 0 ? Math.max(0, Date.now() - started) : 0
const backend =
typeof bareAgentResolveBackend === 'function'
? bareAgentResolveBackend(cfg || {})
: String((cfg && (cfg.backend || cfg.provider)) || 'qvac')
/** @type {string[]} */
const lines = ['agent status']
if (typeof bareAgentFormatConfigSummary === 'function') {
String(bareAgentFormatConfigSummary(cfg || {}))
.split('\n')
.forEach(function (row) {
lines.push(' ' + row)
})
} else {
lines.push(' backend: ' + backend)
}
lines.push(
' compaction: ' + String((cfg && cfg.context_compaction) || 'auto'),
' autonomous_enabled: ' + String(Boolean(cfg && cfg.autonomous_mode_enabled)),
' autonomous_active: ' + String(Boolean(cfg && cfg.autonomous_active)),
' status: ' + String((cfg && cfg.autonomous_status) || 'idle'),
' goal: ' + String((cfg && cfg.autonomous_goal) || ''),
' elapsed_s: ' + String(Math.round(elapsed / 1000)),
' remaining_s: ' +
String(maxRt > 0 ? Math.max(0, Math.round((maxRt - elapsed) / 1000)) : 0),
' last_error: ' + String((cfg && cfg.autonomous_last_error) || ''),
' plan_mode: ' + String(Boolean(cfg && cfg.plan_mode_active)),
' access_policy: ' + String((cfg && cfg.access_policy) || 'full'),
' allow_delete: ' + String(cfg && cfg.allow_delete !== false)
)
ctx.console.log(lines.join('\n'))
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} argv0
* @param {string} sub
*/
async function bareAgentRunInspectSubcommand(ctx, argv0, sub, extra) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
try {
if (sub === 'skills') {
if (typeof bareAgentEnsureSkillTemplates === 'function') {
const loaded = await bareAgentLoadOrCreateConfig(ctx, paths)
const cfg = loaded && loaded.config ? loaded.config : loaded
await bareAgentEnsureSkillTemplates(ctx, paths, cfg || {})
}
const block =
typeof bareAgentSkillsCompactPrompt === 'function'
? await bareAgentSkillsCompactPrompt(ctx, paths, 8000)
: ''
ctx.console.log(block && String(block).trim() ? String(block).trim() : argv0 + ': no skills')
ctx.exitCode = 0
return
}
if (sub === 'todos') {
const todos =
typeof bareAgentLoadTodos === 'function'
? await bareAgentLoadTodos(ctx, paths.todos)
: []
const sum =
typeof bareAgentTodoSummarize === 'function'
? bareAgentTodoSummarize(todos)
: { text: '', open: 0, total: 0 }
ctx.console.log(sum.text || argv0 + ': no todos')
ctx.exitCode = 0
return
}
if (sub === 'plan') {
const text =
typeof bareAgentReadTextFile === 'function'
? await bareAgentReadTextFile(ctx, paths.plan)
: ''
ctx.console.log(text && text.trim() ? text : argv0 + ': no plan (' + paths.plan + ')')
ctx.exitCode = 0
return
}
if (sub === 'undo') {
if (typeof bareAgentPopEdit !== 'function') {
ctx.console.error(argv0 + ': undo unavailable')
ctx.exitCode = 1
return
}
const rec = await bareAgentPopEdit(ctx, paths.edits)
if (!rec || !rec.path) {
ctx.console.log(argv0 + ': nothing to undo')
ctx.exitCode = 0
return
}
await bareAgentWriteTextFile(ctx, String(rec.path), String(rec.prev || ''))
ctx.console.log(argv0 + ': restored ' + rec.path)
ctx.exitCode = 0
return
}
if (sub === 'hooks') {
let names = []
try {
names = ctx.vfs && typeof ctx.vfs.readdir === 'function'
? await ctx.vfs.readdir(paths.hooks)
: []
} catch {
names = []
}
const json = (Array.isArray(names) ? names : []).filter(function (n) {
return /\.json$/i.test(String(n || ''))
})
ctx.console.log(
json.length
? json.map(function (n) {
return paths.hooks + '/' + n
}).join('\n')
: argv0 + ': no hooks in ' + paths.hooks
)
ctx.exitCode = 0
return
}
if (sub === 'history') {
const q = String(extra || '').trim()
const messages = await bareAgentLoadHistory(ctx, paths.history)
if (q && typeof bareAgentHistorySearch === 'function') {
const hits = bareAgentHistorySearch(messages, q, 12)
ctx.console.log(
hits.length
? hits
.map(function (h) {
return '[' + h.role + '] ' + h.snippet
})
.join('\n')
: argv0 + ': no history matches'
)
} else {
ctx.console.log(argv0 + ': ' + String(messages.length) + ' messages in history')
}
ctx.exitCode = 0
return
}
if (sub === 'rewind') {
const steps = Math.max(1, Math.floor(Number(extra) || 1))
const messages = await bareAgentLoadHistory(ctx, paths.history)
if (typeof bareAgentRewindHistory !== 'function') {
ctx.console.error(argv0 + ': rewind unavailable')
ctx.exitCode = 1
return
}
const out = bareAgentRewindHistory(messages, { steps })
if (!out.ok) {
ctx.console.log(argv0 + ': ' + (out.error || 'nothing to rewind'))
ctx.exitCode = 0
return
}
await bareAgentSaveHistory(ctx, paths.history, out.messages)
ctx.console.log(
argv0 +
': rewound ' +
String(out.dropped) +
' message(s), ' +
String(out.messages.length) +
' remain'
)
ctx.exitCode = 0
return
}
if (sub === 'export') {
const dest = String(extra || '').trim() || paths.dir + '/export.md'
const messages = await bareAgentLoadHistory(ctx, paths.history)
const md =
typeof bareAgentExportTranscript === 'function'
? bareAgentExportTranscript(messages)
: ''
await bareAgentWriteTextFile(ctx, dest, md)
ctx.console.log(argv0 + ': exported ' + String(messages.length) + ' messages to ' + dest)
ctx.exitCode = 0
return
}
if (sub === 'remember') {
const text = String(extra || '').trim()
if (!text) {
ctx.console.error(argv0 + ': remember requires a note')
ctx.exitCode = 1
return
}
const dest = paths.workspace + '/MEMORY.md'
const line = '- FACT — ' + text.replace(/\s+/g, ' ')
let prev = ''
try {
prev = await bareAgentReadTextFile(ctx, dest)
} catch {
prev = ''
}
await bareAgentWriteTextFile(ctx, dest, (prev ? prev.replace(/\s*$/, '') + '\n' : '') + line + '\n')
ctx.console.log(argv0 + ': remembered in ' + dest)
ctx.exitCode = 0
return
}
if (sub === 'recap') {
const messages = await bareAgentLoadHistory(ctx, paths.history)
let lastUser = ''
let lastAsst = ''
for (let i = messages.length - 1; i >= 0; i--) {
const m = messages[i] && typeof messages[i] === 'object' ? messages[i] : null
if (!m) continue
const role = String(m.role || '')
if (!lastAsst && role === 'assistant') lastAsst = String(m.content || '')
if (!lastUser && role === 'user') lastUser = String(m.content || '')
if (lastUser && lastAsst) break
}
ctx.console.log(
lastUser || lastAsst
? 'USER\n' +
lastUser.slice(0, 1200) +
'\n\nASSISTANT\n' +
lastAsst.slice(0, 2000)
: argv0 + ': no recap (empty history)'
)
ctx.exitCode = 0
return
}
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
ctx.console.error(argv0 + ': ' + msg)
ctx.exitCode = 1
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} argv0
*/
async function bareAgentRunCompactOnly(ctx, argv0) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
try {
const messages = await bareAgentLoadHistory(ctx, paths.history)
if (typeof bareAgentCompactMessagesForCtx !== 'function') {
ctx.console.error(argv0 + ': compact unavailable')
ctx.exitCode = 1
return
}
const packed = bareAgentCompactMessagesForCtx(messages, 32768, {
mode: 'aggressive',
keepRecent: 6,
toolMaxChars: 1200
})
await bareAgentSaveHistory(ctx, paths.history, packed.messages)
ctx.console.log(
argv0 +
': compacted ' +
String(packed.meta.beforeTokens) +
'→' +
String(packed.meta.afterTokens) +
' tok (dropped_groups=' +
String(packed.meta.droppedGroups) +
')'
)
ctx.exitCode = 0
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
ctx.console.error(argv0 + ': ' + msg)
ctx.exitCode = 1
}
}