Files
bare-operating-system/packages/bare-os-seeder/kernel/bin/agent
T
Raven Scott 014c70ad09 feat: introduce ctx.pear surface and /bin/pear for in-OS Pear development
Complete the full planned effort for the detailed ctx.bare code audit
and the new ctx.pear surface, delivering the ability to create, stage,
and integrate real Pear applications from within a booted Bare OS.

### Audit (ctx.bare)
- Performed exhaustive code audit of bare-os-ctx-bare.js (host import
  path, drive bundle eval + require.addon wrappers, referrer workarounds).
- Inventoried all manifest/bundle verifiers and related scripts.
- Researched manifest format, implicit tiering model, and dual loading
  strategy (JSON + .data.mjs).
- Deep analysis of the local Holepunch clone (bare-* and pear-* packages)
  to identify realistic guest vs host-delegate boundaries.
- Full cross-reference of call sites, greps, and historical pain points
  (pear:// referrer resolution, nativeHint handling, addon stubs).

### Implementation (ctx.pear)
- Added `pearEntries` tier to bare-module-manifest.json with initial
  high-value packages (pear-build, pear-bundle, pear-ref, etc.).
- Implemented `loadPearModuleManifest()` and `buildPearCtxObjectFromHost()`.
- Wired ctx.pear exposure through the booter into the guest context.
- Updated TypeScript definitions (`bare-os-ctx.d.ts`).

### User-Facing Surface
- Created full `/bin/pear` command with `help`, `info`, `list`, `init`
  (functional skeleton creation), and improved `stage` subcommands.
- Registered as Tier-1 command (now 183 total commands).
- Added man page and rebuilt coreutils (kernel + seeder).

### Agent Autonomy
- Created production-quality `pear-dev` agent skill.
- Added to skill seed list with cross-references to the appstore skill.

### P2P App Store Integration
- Updated appstore skill with explicit Pear development synergy section.
- Updated p2p-app-store design doc to document the new closed loop.
- Added cross-references in both skills and design documents.

### Verification & Hygiene
- Created `scripts/verify-pear-module-manifest-data.mjs`.
- Enhanced `verify-pear-no-static-node-import.mjs` with explicit pear
  command coverage.
- Integrated new verifier into release-checklist and agent hints.
- Performed comprehensive zero-TODO/scaffolding sweep across all new
  Pear artifacts (clean).
- Multiple full verification harness runs (all green).

### Documentation & Governance
- Added complete "Pear Development Environment" thread to feature-roadmap.md.
- Updated developer guide (Chapter 12).
- Maintained living plan document and detailed audit notes with full
  Implementation Log throughout.
- Updated command counts across READMEs and supporting docs.

All changes follow project governance:
- Bare-only guest constraints strictly observed
- Verifier-first discipline maintained
- Living plan + audit documents kept as single source of truth
- Production quality bar matching the completed P2P App Store feature

Plan items 04–21 completed.

See:
- docs/design/ctx-pear-surface-and-bare-audit-plan.md
- docs/audit/ctx-bare-audit-notes.md (full audit + implementation log)
2026-05-26 17:54:06 -04:00

6996 lines
223 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
}
/** 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). */
/**
* Paths allowed for rename/delete via agent tools (stricter than general read paths).
* @param {string} absPath
* @returns {boolean}
*/
function bareAgentPathAllowedMutate(absPath) {
const p = String(absPath || '').replace(/\\/g, '/')
if (!p.startsWith('/') || p.includes('..')) return false
return (
p.startsWith('/home/') ||
p.startsWith('/tmp/') ||
p === '/tmp' ||
p.startsWith('/root/') ||
p.startsWith('/mnt/')
)
}
/** @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 {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 = String(absPath || '').replace(/\\/g, '/')
if (!p.startsWith('/') || p.includes('..')) return false
for (let i = 0; i < BARE_AGENT_PROC_READ_ALLOWLIST.length; i++) {
if (p === BARE_AGENT_PROC_READ_ALLOWLIST[i]) return true
}
return false
}
/** ~/.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',
cmdOut: base + '/last_command_out.txt',
workspace: base + '/workspace',
workspaceMemory: base + '/workspace/memory',
workspaceSkills: base + '/workspace/skills',
skillsGlobal: base + '/skills'
}
}
function bareAgentDefaultConfig() {
return {
rest_base_url: 'https://api.groq.com/openai/v1',
rest_api_key: '',
model: 'llama3-70b-8192',
max_tokens: 4096,
temperature: 0.7,
provider: 'groq',
max_iterations: 64,
stream: true,
tool_parallelism: 1,
request_timeout_ms: 120000,
extra_headers: /** @type {Record<string, string>} */ ({}),
allow_delete: false,
require_confirm_token: '',
owner_name: '',
agent_label: '',
show_reasoning: false,
reasoning_mode: 'off',
reasoning_max_chars: 4000,
reasoning_include_tools: true,
allow_bridge_mutations: false,
allow_host_notifications: false,
allow_host_actions: false,
emergency_stop_mutations: false,
autonomous_mode_enabled: false,
autonomous_max_runtime_ms: 1800000,
autonomous_completion_required_checks: [],
autonomous_allow_paths: ['*'],
autonomous_deny_ops: [
'delete_path',
'request_host_action',
'emit_host_notification',
'list_verification_scripts',
'run_maintenance_gate',
'run_contract_checks',
'summarize_build_drift'
],
autonomous_active: false,
autonomous_started_at_ms: 0,
autonomous_stop_requested: false,
autonomous_goal: '',
autonomous_status: 'idle',
autonomous_last_error: ''
}
}
/**
* @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([
'rest_base_url',
'rest_api_key',
'model',
'max_tokens',
'temperature',
'provider',
'max_iterations',
'stream',
'tool_parallelism',
'request_timeout_ms',
'extra_headers',
'allow_delete',
'require_confirm_token',
'owner_name',
'agent_label',
'show_reasoning',
'reasoning_mode',
'reasoning_max_chars',
'reasoning_include_tools',
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
])
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 === 'rest_base_url' ||
k === 'rest_api_key' ||
k === 'model' ||
k === 'provider' ||
k === 'owner_name' ||
k === 'agent_label' ||
k === 'autonomous_goal' ||
k === 'autonomous_status' ||
k === 'autonomous_last_error'
) {
out[k] = String(val ?? '')
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 === 'autonomous_max_runtime_ms' ||
k === 'autonomous_started_at_ms'
) {
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'
) {
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'
) {
out[k] = Boolean(val)
continue
}
if (k === 'require_confirm_token') {
out.require_confirm_token = String(val ?? '')
continue
}
}
return out
}
/**
* @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 = [
'rest_base_url',
'rest_api_key',
'model',
'max_tokens',
'temperature',
'provider',
'max_iterations',
'stream',
'tool_parallelism',
'request_timeout_ms',
'extra_headers',
'allow_delete',
'require_confirm_token',
'owner_name',
'agent_label',
'show_reasoning',
'reasoning_mode',
'reasoning_max_chars',
'reasoning_include_tools',
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
]
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()
await bareAgentSaveConfig(ctx, paths, /** @type {any} */ (raw))
return { config: /** @type {any} */ (raw), created: true }
}
bareAgentValidateConfigShape(raw)
const merged = bareAgentMergeConfig(bareAgentDefaultConfig(), raw)
return { config: /** @type {any} */ (merged), 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')
bareAgentValidateConfigShape(config)
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)
}
/**
* 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
}
/**
* 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 trimmed = bareAgentTrimMessages(messages, 500_000)
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 []
}
}
/**
* Clear persisted chat session (history + progress log; keeps config and instructions).
* @param {Record<string, unknown>} ctx
* @param {ReturnType<typeof bareAgentPaths>} paths
* @param {string} argv0
*/
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 */
}
}
/**
* 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')
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/`) 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
if (method === 'HEAD') {
return {
ok: true,
url_final,
status: st,
content_type: ct,
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,
redirect_chain: redirectChain.length > 1 ? redirectChain : undefined,
truncated: bodyRead.truncated,
extract,
raw_preview: fmt === 'raw' || fmt === 'json' ? undefined : raw_preview
}
}
}
/** OpenAI-style tool schemas + dispatch (preamble for /bin/agent). */
/**
* Host-checkout verification hints (keep loosely aligned with scripts/lib/agent-check-hints-data.mjs).
* @param {string} combined paths + topic
* @returns {string[]}
*/
function bareAgentVerificationHintsList(combined) {
const c = String(combined || '').toLowerCase()
/** @type {string[]} */
const hints = []
if (/(^|\/)kernel\/|kernel\\|\/boot\/init|lib\/init|lib\\init/.test(c)) {
hints.push('npm run bundle:kernel', 'node scripts/verify-kernel-seeder-parity.mjs')
}
if (/bare-os-coreutils|kernel\/bin|kernel\\bin/.test(c)) {
hints.push('npm run build -w bare-os-coreutils', 'node scripts/verify-man-coverage.mjs')
}
if (/bare-os-booter|bare-os-ctx-api/.test(c)) {
hints.push(
'npm run test -w bare-os-booter',
'node scripts/verify-ctx-api-feature-bits.mjs'
)
}
if (/bare-os-protocol|seed-rpc|channel\.js/.test(c)) {
hints.push('npm run test -w bare-os-protocol')
}
if (/bare-os-seeder/.test(c)) {
hints.push('node scripts/verify-kernel-seeder-parity.mjs')
}
if (/bare-os-bare-libs|kernel\/lib\/bare|kernel\\lib\\bare/.test(c)) {
hints.push('npm run build -w bare-os-bare-libs', 'node scripts/verify-bundle-health.mjs')
}
if (/shell|sh\.js|test\.js/.test(c) && /booter/.test(c)) {
hints.push('npm run test:shell-fast')
}
if (/docs\/|handbook\/|developer-guide\//.test(c)) {
hints.push('npm run pretest', 'node scripts/verify-doc-links.mjs')
}
if (!hints.length) hints.push('npm run pretest', 'npm test')
return [...new Set(hints)]
}
/**
* @param {string} s
*/
function bareAgentShellQuote(s) {
return "'" + String(s).replace(/'/g, "'\\''") + "'"
}
/**
* @param {unknown} v
* @returns {string}
*/
function bareAgentJsonResult(v) {
try {
return JSON.stringify(v)
} catch {
return '{"error":"json_stringify_failed"}'
}
}
/**
* @returns {unknown[]}
*/
function bareAgentToolDefinitions() {
return [
{
type: 'function',
function: {
name: 'read_file',
description:
'Read a UTF-8 text file from the VFS. Path must be absolute (e.g. /home/guest/...).',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Absolute file path' },
max_bytes: {
type: 'integer',
description: 'Max bytes to read (default 256000)'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'write_file',
description:
'Create or overwrite a file. Parent directories are created as needed.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
content: { type: 'string', description: 'Full file contents' }
},
required: ['path', 'content']
}
}
},
{
type: 'function',
function: {
name: 'edit_file',
description:
'Edit a text file: either replace entire content, or replace first occurrence of old_string with new_string.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
content: {
type: 'string',
description: 'If set (non-empty), full file replacement'
},
old_string: {
type: 'string',
description: 'Search string (used with new_string)'
},
new_string: { type: 'string', description: 'Replacement text' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'create_directory',
description: 'Create a directory (recursive).',
parameters: {
type: 'object',
properties: {
path: { type: 'string' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'search_files',
description:
'Run grep -R to list paths matching a pattern (bounded). Uses the shell.',
parameters: {
type: 'object',
properties: {
pattern: { type: 'string' },
root: {
type: 'string',
description: 'Directory to search (default /home)'
},
max_lines: { type: 'integer', description: 'Default 200' }
},
required: ['pattern']
}
}
},
{
type: 'function',
function: {
name: 'run_command',
description:
'Run a shell command line via ctx.execLine (same as interactive shell). Output is captured to a temp file. Set capture_exit true to append a final EXIT:<code> line.',
parameters: {
type: 'object',
properties: {
command: {
type: 'string',
description: 'Full command string (e.g. ls -la /bin)'
},
timeout_ms: { type: 'integer' },
capture_exit: {
type: 'boolean',
description: 'If true, append last line EXIT:<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)'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'file_stat',
description:
'Stat a path: size, mtime, type, mode. Uses lstat when follow_symlinks is false (default).',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
follow_symlinks: {
type: 'boolean',
description: 'If true, use stat (follow); if false, lstat (default false)'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'move_path',
description:
'Rename or move a file or directory via shell mv (same rules as mv). Paths must be under /home, /tmp, /mnt, or /root.',
parameters: {
type: 'object',
properties: {
from_path: { type: 'string' },
to_path: { type: 'string' }
},
required: ['from_path', 'to_path']
}
}
},
{
type: 'function',
function: {
name: 'delete_path',
description:
'Delete a file or directory (recursive optional). Requires ~/.agent/config.json allow_delete; optional confirm_token when require_confirm_token is set.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' },
recursive: {
type: 'boolean',
description: 'Remove directories recursively (default false)'
},
confirm_token: {
type: 'string',
description: 'Must match config require_confirm_token when that key is non-empty'
}
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'read_man_page',
description:
'Read one manual page from /share/man/man.json (bounded text). Prefer over parsing man output.',
parameters: {
type: 'object',
properties: {
topic: { type: 'string', description: 'Page name (e.g. grep, agent)' },
section: {
type: 'integer',
description: 'Manual section 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 a small allowlisted /proc/bare_os pseudo file (bounded). Canonical live kernel feature state: /proc/bare_os/features or /proc/bare_os/features.json (same content). Use instead of shelling cat.',
parameters: {
type: 'object',
properties: {
path: {
type: 'string',
description:
'Exact allowlisted path (metrics_live.json, features or features.json, capabilities.json, swarm*.json, swarm_*_status.json); bounded read.'
},
max_bytes: { type: 'integer', description: 'Default 256000' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'get_swarm_peers',
description: 'Return parsed /proc/bare_os/swarm.json when readable (P2P / Hyperswarm snapshot).',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_resource_limits',
description:
'Return ctx.bareOsGetResourceStatus() when available (pipeline / resource snapshot).',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'run_js_script_at_path',
description:
'Execute an existing .mjs script by absolute path (Bare kernel runner). Same as running that path with run_command but dedicated for clarity.',
parameters: {
type: 'object',
properties: {
path: { type: 'string', description: 'Absolute path to .mjs file' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'web_fetch',
description:
'Fetch live HTTP(S) URLs and return structured content for the assistant. Uses ctx.httpFetch (same policy as wget/curl: BARE_OS_HTTP_ALLOWLIST / DENYLIST). For official docs index use read_man_page / apropos_man — they are not web pages. Supports GET/HEAD/POST and extract modes: auto (JSON vs HTML vs text), markdownish plain text from HTML, links (anchor hrefs), meta (title/og:), raw UTF-8 slice, or json parse.',
parameters: {
type: 'object',
properties: {
url: { type: 'string', description: 'Absolute http(s) URL' },
method: {
type: 'string',
description: 'HTTP method (default GET)',
enum: ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
},
headers: {
type: 'object',
description: 'Optional header map (string values only)'
},
body: {
type: 'string',
description: 'Request body for non-GET (e.g. JSON string for APIs)'
},
content_type: {
type: 'string',
description: 'Content-Type when body is set (default application/octet-stream)'
},
format: {
type: 'string',
enum: ['auto', 'json', 'markdownish', 'text', 'links', 'meta', 'raw'],
description:
'auto: sniff Content-Type; json: parse JSON; markdownish/text: strip HTML to readable text; links: absolute http(s) links; meta: title/description/og tags; raw: bounded UTF-8 text'
},
max_response_bytes: {
type: 'integer',
description: 'Cap downloaded bytes (default 524288, max 2MiB)'
},
max_redirects: {
type: 'integer',
description: 'Max redirects to follow (default 5)'
},
timeout_ms: {
type: 'integer',
description: 'Per-request timeout ms (default 30000, max 120000)'
},
max_links: {
type: 'integer',
description: 'Max links when format=links (default 200)'
}
},
required: ['url']
}
}
},
{
type: 'function',
function: {
name: 'read_skill',
description:
'Load the full SKILL.md for a modular agent skill (folder id or frontmatter name, case-insensitive). Workspace ~/.agent/workspace/skills/ overrides ~/.agent/skills/. Use after checking the compact skills index in the system prompt.',
parameters: {
type: 'object',
properties: {
skill: {
type: 'string',
description: 'Skill folder name (e.g. p2p-os-status) or YAML frontmatter name'
},
max_bytes: {
type: 'integer',
description: 'Max bytes of SKILL.md (default 256000)'
}
},
required: ['skill']
}
}
},
{
type: 'function',
function: {
name: 'list_services',
description:
'List initd service definitions and current runtime phases from /proc/bare_os/initd_readiness.json when available.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'service_status',
description:
'Read one initd unit status by name from /proc/bare_os/initd_readiness.json and include journal hint paths when present.',
parameters: {
type: 'object',
properties: {
name: { type: 'string', description: 'Unit name, e.g. bare-cron' }
},
required: ['name']
}
}
},
{
type: 'function',
function: {
name: 'list_timers',
description:
'List user timer drop-ins from ~/.config/bare-os/timers and optionally include short file previews.',
parameters: {
type: 'object',
properties: {
include_preview: {
type: 'boolean',
description: 'Include bounded timer file text previews (default false)'
},
max_entries: {
type: 'integer',
description: 'Maximum timer files to return (default 128, max 512)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_cron_log',
description:
'Read /var/log/bare-os/cron.log with bounded output and optional tail mode.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 12000)'
},
tail_only: {
type: 'boolean',
description: 'When true, return only the trailing max_chars slice'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_audit_log',
description:
'Read /var/log/bare-os/audit.log with bounded output and best-effort secret redaction.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 12000)'
},
tail_only: {
type: 'boolean',
description: 'When true, return only the trailing max_chars slice'
},
redact: {
type: 'boolean',
description: 'Apply lightweight token redaction (default true)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_boot_policy',
description:
'Read /etc/bare-os/boot.policy.json and return text plus parsed JSON when available.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 20000)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'read_kernel_extension_resolution',
description:
'Read /run/bare-os/kernel-ext-resolution.json for extension ordering, conflicts, and pin outcomes.',
parameters: {
type: 'object',
properties: {
max_chars: {
type: 'integer',
description: 'Maximum returned characters (default 20000)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'get_initd_graph',
description:
'Read initd dependency DAG/readiness graph from /proc/bare_os/initd_dag.json and /proc/bare_os/initd_readiness.json.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'read_unit_journal',
description:
'Read a bounded/redacted tail of /run/bare-os/unit-journal/<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 allowlisted maintenance command with bounded capture for automation workflows.',
parameters: {
type: 'object',
properties: {
command: { type: 'string', description: 'Allowlisted command id' },
cwd: { type: 'string', description: 'Optional working directory' },
timeout_ms: { type: 'integer', description: 'Timeout in milliseconds' }
},
required: ['command']
}
}
},
{
type: 'function',
function: {
name: 'run_contract_checks',
description:
'Run a grouped set of contract checks by profile id (allowlisted) with bounded output.',
parameters: {
type: 'object',
properties: {
profile: { type: 'string', description: 'Check profile id, e.g. core, docs, parity' }
},
required: ['profile']
}
}
},
{
type: 'function',
function: {
name: 'summarize_build_drift',
description:
'Summarize build/generated drift by comparing git status and key generated artifacts.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_hrpc_bridge_health',
description:
'Read HRPC bridge/operator health from /proc/bare_os surfaces and include host capability hints when available.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'get_hrpc_allowlist_status',
description:
'Inspect effective HRPC allowlist/probe status using hrpc probe and operator snapshots.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'emit_host_notification',
description:
'Request an audited host notification via HRPC route (policy-gated; disabled by default).',
parameters: {
type: 'object',
properties: {
title: { type: 'string' },
message: { type: 'string' },
level: { type: 'string', enum: ['info', 'warn', 'error'] }
},
required: ['title', 'message']
}
}
},
{
type: 'function',
function: {
name: 'request_host_action',
description:
'Request a schema-validated host action through HRPC (policy-gated; disabled by default).',
parameters: {
type: 'object',
properties: {
action: { type: 'string', description: 'Host action id' },
payload: { type: 'object', description: 'Action payload object' }
},
required: ['action']
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run',
description:
'Start an autonomous coding run with goal, optional scope path, and runtime cap. This enables autonomous mode in config.',
parameters: {
type: 'object',
properties: {
goal: { type: 'string', description: 'Task goal the agent should complete autonomously' },
scope_path: { type: 'string', description: 'Preferred working scope path (optional)' },
max_runtime_ms: { type: 'integer', description: 'Optional runtime cap override' },
required_checks: {
type: 'array',
items: { type: 'string' },
description: 'Optional quality gates (allowlisted check ids)'
}
},
required: ['goal']
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_status',
description:
'Return current autonomous run state, elapsed/runtime budget, configured checks, and latest status.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'autonomous_run_stop',
description:
'Request manual stop for an autonomous run; loop will stop safely on next control checkpoint.',
parameters: {
type: 'object',
properties: {
reason: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
name: 'verification_hints',
description:
'Suggest npm/node verification commands for a developer working at the Bare OS git checkout on the host (paths or topic keywords). Does not run commands.',
parameters: {
type: 'object',
properties: {
topic: {
type: 'string',
description: 'Free-text task or area (e.g. kernel init, seed RPC, shell)'
},
paths_touched: {
type: 'string',
description:
'Optional comma-separated path-like strings from the repo (forward slashes ok)'
}
}
}
}
},
{
type: 'function',
function: {
name: 'runtime_diagnostic_bundle',
description:
'Non-secret snapshot: ctx API version, optional resource status, and allowlisted /proc/bare_os files that exist (features, swarm, metrics). Prefer over many separate read_proc_file calls.',
parameters: {
type: 'object',
properties: {}
}
}
},
{
type: 'function',
function: {
name: 'task_complete',
description:
'Call when the user task is fully done. Provide a concise summary.',
parameters: {
type: 'object',
properties: {
summary: { type: 'string' }
},
required: ['summary']
}
}
}
]
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} absPath
*/
function bareAgentPathAllowed(absPath) {
const p = String(absPath || '').replace(/\\/g, '/')
if (!p.startsWith('/')) return false
const ok =
p.startsWith('/home/') ||
p.startsWith('/tmp/') ||
p === '/tmp' ||
p.startsWith('/root/') ||
p.startsWith('/mnt/') ||
p.startsWith('/bin/') ||
p === '/bin' ||
p.startsWith('/etc/') ||
p.startsWith('/share/') ||
p.startsWith('/usr/') ||
p.startsWith('/var/') ||
p.startsWith('/proc/') ||
p.startsWith('/boot/') ||
p.startsWith('/lib/') ||
p.startsWith('/dev/')
return ok
}
/**
* @param {{
* ctx: Record<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 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 (
cfgNow.autonomous_active &&
Array.isArray(cfgNow.autonomous_deny_ops) &&
cfgNow.autonomous_deny_ops.map((x) => String(x)).includes(toolName)
) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_op_denied', tool: toolName })
}
const vfs = ctx.vfs
const AUTONOMOUS_DENY_PATH_PREFIXES = [
'/.git',
'/proc',
'/dev',
'/sys',
'/run',
'/boot',
'/lib',
'/usr/lib'
]
const AUTONOMOUS_CHECK_ALLOW = {
'coreutils-test': 'npm test -w bare-os-coreutils',
'verify-kernel-seeder-parity': 'node scripts/verify-kernel-seeder-parity.mjs',
'verify-man-coverage': 'node scripts/verify-man-coverage.mjs',
'verify-ctx-api-feature-bits': 'node scripts/verify-ctx-api-feature-bits.mjs'
}
const execLine =
typeof ctx.execLine === 'function'
? /** @type {(s: string, opts?: unknown) => Promise<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
if (!s.startsWith('/')) return false
for (const pref of AUTONOMOUS_DENY_PATH_PREFIXES) {
if (s === pref || s.startsWith(pref + '/')) return false
}
return true
}
/**
* @param {string} p
*/
function enforceAutonomousPath(p) {
const cfg = configRef.current || {}
if (!cfg.autonomous_active) return true
const path = String(p || '').trim()
if (!path) return true
if (!autonomousPathAllowed(path)) return false
const allowList = Array.isArray(cfg.autonomous_allow_paths)
? cfg.autonomous_allow_paths.map((x) => String(x || '').trim()).filter(Boolean)
: []
if (!allowList.length || allowList.includes('*')) return true
for (const pref of allowList) {
if (path === pref || path.startsWith(pref.endsWith('/') ? pref : pref + '/')) return true
}
return false
}
try {
if (toolName === 'read_skill') {
const skill = typeof args.skill === 'string' ? args.skill.trim() : ''
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 512_000)
: 256_000
if (!skill) {
return bareAgentJsonResult({ ok: false, error: 'skill_required' })
}
const skillPaths = {
workspaceSkills:
typeof paths.workspaceSkills === 'string'
? paths.workspaceSkills
: paths.dir + '/workspace/skills',
skillsGlobal:
typeof paths.skillsGlobal === 'string' ? paths.skillsGlobal : paths.dir + '/skills'
}
appendProgress('read_skill ' + skill)
const loaded = await bareAgentLoadSkillMarkdown(ctx, skillPaths, skill)
if (!loaded.ok) {
return bareAgentJsonResult({
ok: false,
error: loaded.error || 'load_failed',
skill
})
}
let content = loaded.content
if (content.length > maxB) content = content.slice(0, maxB) + '\n… truncated'
return bareAgentJsonResult({
ok: true,
id: loaded.id,
name: loaded.skill,
path: loaded.path,
source: loaded.source,
content
})
}
if (toolName === 'task_complete') {
const summary = typeof args.summary === 'string' ? args.summary : ''
appendProgress('task_complete: ' + summary.slice(0, 200))
onTaskComplete(summary || '(done)')
return bareAgentJsonResult({
ok: true,
completed: true,
summary
})
}
if (toolName === 'autonomous_run') {
const goal = typeof args.goal === 'string' ? args.goal.trim() : ''
const scopePath = typeof args.scope_path === 'string' ? args.scope_path.trim() : ''
const cfg = configRef.current || {}
if (!cfg.autonomous_mode_enabled) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_mode_disabled' })
}
if (!goal) return bareAgentJsonResult({ ok: false, error: 'goal_required' })
if (scopePath && !autonomousPathAllowed(scopePath)) {
return bareAgentJsonResult({ ok: false, error: 'scope_path_denied' })
}
const maxRuntimeMsRaw =
typeof args.max_runtime_ms === 'number' && Number.isFinite(args.max_runtime_ms)
? args.max_runtime_ms
: cfg.autonomous_max_runtime_ms
const maxRuntimeMs = Math.min(Math.max(Math.floor(Number(maxRuntimeMsRaw) || 0), 60000), 7_200_000)
const requiredChecks = Array.isArray(args.required_checks)
? args.required_checks.map((x) => String(x || '').trim()).filter(Boolean)
: []
const unknown = requiredChecks.filter((x) => !Object.prototype.hasOwnProperty.call(AUTONOMOUS_CHECK_ALLOW, x))
if (unknown.length) {
return bareAgentJsonResult({ ok: false, error: 'unknown_required_checks', unknown, allowlist: Object.keys(AUTONOMOUS_CHECK_ALLOW) })
}
const merged = bareAgentMergeConfigPatch(cfg, {
autonomous_active: true,
autonomous_stop_requested: false,
autonomous_started_at_ms: Date.now(),
autonomous_goal: goal,
autonomous_status: 'running',
autonomous_last_error: '',
autonomous_max_runtime_ms: maxRuntimeMs,
autonomous_completion_required_checks: requiredChecks
})
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run start goal=' + goal.slice(0, 160))
return bareAgentJsonResult({
ok: true,
active: true,
goal,
scope_path: scopePath || null,
max_runtime_ms: maxRuntimeMs,
required_checks: requiredChecks,
status: 'running'
})
}
if (toolName === 'autonomous_run_status') {
const cfg = configRef.current || {}
const started = Number(cfg.autonomous_started_at_ms) || 0
const elapsed = started > 0 ? Math.max(0, Date.now() - started) : 0
const maxRuntime = Number(cfg.autonomous_max_runtime_ms) || 0
return bareAgentJsonResult({
ok: true,
autonomous_mode_enabled: Boolean(cfg.autonomous_mode_enabled),
active: Boolean(cfg.autonomous_active),
stop_requested: Boolean(cfg.autonomous_stop_requested),
goal: String(cfg.autonomous_goal || ''),
status: String(cfg.autonomous_status || 'idle'),
last_error: String(cfg.autonomous_last_error || ''),
started_at_ms: started,
elapsed_ms: elapsed,
max_runtime_ms: maxRuntime,
remaining_ms: maxRuntime > 0 ? Math.max(0, maxRuntime - elapsed) : 0,
required_checks: Array.isArray(cfg.autonomous_completion_required_checks)
? cfg.autonomous_completion_required_checks
: []
})
}
if (toolName === 'autonomous_run_stop') {
const cfg = configRef.current || {}
const reason = typeof args.reason === 'string' ? args.reason.trim() : ''
const merged = bareAgentMergeConfigPatch(cfg, {
autonomous_stop_requested: true,
autonomous_status: 'stopped',
autonomous_last_error: reason || String(cfg.autonomous_last_error || '')
})
await bareAgentSaveConfigFromTools(ctx, paths, merged)
configRef.current = merged
appendProgress('autonomous_run_stop ' + (reason || 'requested'))
return bareAgentJsonResult({ ok: true, stop_requested: true, reason: reason || null })
}
if (toolName === 'read_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 1_000_000)
: 256_000
if (!bareAgentPathAllowed(path)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('read_file ' + path)
const buf = await vfs.readFile(path)
if (!buf) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
if (t.length > maxB) t = t.slice(0, maxB) + '\n… truncated'
return bareAgentJsonResult({ ok: true, path, content: t })
}
if (toolName === 'write_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
const content = typeof args.content === 'string' ? args.content : ''
if (!path.startsWith('/') || path.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
}
if (!vfs || typeof vfs.writeFile !== 'function' || typeof vfs.mkdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('write_file ' + path)
let dir = path.replace(/\/[^/]+$/, '')
if (dir && dir !== path) await vfs.mkdir(dir, { recursive: true })
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(content)
: new TextEncoder().encode(content)
await vfs.writeFile(path, body)
return bareAgentJsonResult({ ok: true, bytes: body.length })
}
if (toolName === 'edit_file') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
if (!bareAgentPathAllowed(path) || !vfs?.readFile || !vfs?.writeFile) {
return bareAgentJsonResult({ ok: false, error: 'path_or_vfs' })
}
appendProgress('edit_file ' + path)
const buf = await vfs.readFile(path)
let prev =
buf && buf.length
? typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
: ''
const full = typeof args.content === 'string' ? args.content : ''
const oldStr = typeof args.old_string === 'string' ? args.old_string : ''
const newStr = typeof args.new_string === 'string' ? args.new_string : ''
let next = prev
if (full.length > 0) next = full
else if (oldStr) {
if (!prev.includes(oldStr)) {
return bareAgentJsonResult({ ok: false, error: 'old_string not found' })
}
next = prev.replace(oldStr, newStr)
} else {
return bareAgentJsonResult({
ok: false,
error: 'need content or old_string+new_string'
})
}
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(next)
: new TextEncoder().encode(next)
let dir = path.replace(/\/[^/]+$/, '')
if (dir && dir !== path) await vfs.mkdir(dir, { recursive: true })
await vfs.writeFile(path, body)
return bareAgentJsonResult({ ok: true, bytes: body.length })
}
if (toolName === 'create_directory') {
const path = typeof args.path === 'string' ? args.path : ''
if (!enforceAutonomousPath(path)) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_path_denied' })
}
if (!path.startsWith('/')) {
return bareAgentJsonResult({ ok: false, error: 'bad_path' })
}
if (!vfs || typeof vfs.mkdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('mkdir ' + path)
await vfs.mkdir(path, { recursive: true })
return bareAgentJsonResult({ ok: true })
}
if (toolName === 'search_files') {
const pattern = typeof args.pattern === 'string' ? args.pattern : ''
const root = typeof args.root === 'string' ? args.root : '/home'
const maxLines =
typeof args.max_lines === 'number' && Number.isFinite(args.max_lines)
? Math.min(Math.floor(args.max_lines), 500)
: 200
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('search_files ' + pattern + ' @ ' + root)
const cmd =
'grep -Rnl -- ' +
bareAgentShellQuote(pattern) +
' ' +
bareAgentShellQuote(root) +
' 2>/dev/null | head -n ' +
maxLines
const r = await captureExec(cmd, 60000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'run_command') {
const command = typeof args.command === 'string' ? args.command : ''
if ((configRef.current || {}).autonomous_active) {
const cmd = command.trim().toLowerCase()
if (
cmd.includes('rm ') ||
cmd.includes(' git reset') ||
cmd.includes(' git clean') ||
cmd.startsWith('git ') ||
cmd.includes(' git ')
) {
return bareAgentJsonResult({ ok: false, error: 'autonomous_command_denied' })
}
}
const timeoutMs =
typeof args.timeout_ms === 'number' && Number.isFinite(args.timeout_ms)
? Math.min(Math.floor(args.timeout_ms), 600000)
: 120000
const captureExit = Boolean(args.capture_exit)
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('run_command ' + command.slice(0, 160))
const r = await captureExec(command, timeoutMs, { captureExit })
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'run_js_script') {
const code = typeof args.code === 'string' ? args.code : ''
const scriptPath = paths.dir + '/_tmp_agent_run.mjs'
if (!vfs?.writeFile || !execLine) {
return bareAgentJsonResult({ ok: false, error: 'vfs or execLine' })
}
appendProgress('run_js_script (' + code.length + ' chars)')
const body =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
? ctx.b4a.from(code)
: new TextEncoder().encode(code)
await vfs.writeFile(scriptPath, body)
/** Absolute path → kernel-runner runs .mjs like `./script.mjs` (no host `node` binary). captureExec adds stdout redirect. */
const cmd = bareAgentShellQuote(scriptPath)
const r = await captureExec(cmd, 60000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'get_system_info') {
/** @type {Record<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 (!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) ||
!bareAgentPathAllowedMutate(to) ||
from.includes('..') ||
to.includes('..')
) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('move_path')
const cmd =
'mv -- ' + bareAgentShellQuote(from) + ' ' + bareAgentShellQuote(to)
const r = await captureExec(cmd, 120000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'delete_path') {
const path = typeof args.path === 'string' ? args.path : ''
const recursive = Boolean(args.recursive)
const token = typeof args.confirm_token === 'string' ? args.confirm_token : ''
const cfg = configRef.current
const allowDel = Boolean(cfg && cfg.allow_delete)
const reqTok =
cfg && typeof cfg.require_confirm_token === 'string'
? String(cfg.require_confirm_token)
: ''
if (!allowDel) {
return bareAgentJsonResult({
ok: false,
error: 'delete_disabled',
hint: 'set allow_delete true in ~/.agent/config.json'
})
}
if (reqTok && token !== reqTok) {
return bareAgentJsonResult({ ok: false, error: 'confirm_token_required' })
}
if (!bareAgentPathAllowedMutate(path) || path.includes('..')) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (!vfs) {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('delete_path ' + path)
try {
/** @type {unknown} */
let st = null
if (typeof vfs.lstat === 'function') st = await vfs.lstat(path)
else if (typeof vfs.stat === 'function') st = await vfs.stat(path)
const isDir =
st &&
typeof st === 'object' &&
typeof /** @type {{ isDirectory?: () => boolean }} */ (st).isDirectory ===
'function' &&
st.isDirectory()
if (isDir && recursive && typeof vfs.rm === 'function') {
await vfs.rm(path, { recursive: true })
return bareAgentJsonResult({ ok: true, removed: 'directory', recursive: true })
}
if (isDir && !recursive) {
return bareAgentJsonResult({
ok: false,
error: 'is_directory',
hint: 'pass recursive true to remove a directory tree'
})
}
if (typeof vfs.unlink !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'unlink unavailable' })
}
await vfs.unlink(path)
return bareAgentJsonResult({ ok: true, removed: isDir ? 'directory' : 'file' })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'read_man_page') {
const topic = typeof args.topic === 'string' ? args.topic : ''
const maxC =
typeof args.max_chars === 'number' && Number.isFinite(args.max_chars)
? Math.min(Math.floor(args.max_chars), 64_000)
: 12_000
let secExplicit = null
if (
typeof args.section === 'number' &&
Number.isFinite(args.section) &&
args.section >= 1 &&
args.section <= 8
) {
secExplicit = Math.floor(args.section)
}
const db = await bareAgentManEnsureDbLoaded(ctx, vfs, manDbCache)
if (!db) {
return bareAgentJsonResult({ ok: false, error: 'man_db_unavailable' })
}
appendProgress('read_man_page ' + topic)
const resolved = bareAgentManResolvePage(db, topic, secExplicit)
if ('error' in resolved && resolved.error === 'wrong_section') {
return bareAgentJsonResult({
ok: false,
error: 'wrong_section',
foundSection: resolved.foundSection
})
}
if (!resolved.page) {
return bareAgentJsonResult({ ok: false, error: 'not_found' })
}
const slice = bareAgentManExtractPageSlice(resolved.page, maxC)
return bareAgentJsonResult({ ok: true, ...slice })
}
if (toolName === 'apropos_man') {
const kw = typeof args.keyword === 'string' ? args.keyword : ''
const maxRes =
typeof args.max_results === 'number' && Number.isFinite(args.max_results)
? Math.floor(args.max_results)
: 40
const db = await bareAgentManEnsureDbLoaded(ctx, vfs, manDbCache)
if (!db) {
return bareAgentJsonResult({ ok: false, error: 'man_db_unavailable' })
}
appendProgress('apropos_man ' + kw)
const { lines, truncated } = bareAgentManAproposHits(db, kw, maxRes)
return bareAgentJsonResult({
ok: true,
count: lines.length,
truncated,
lines
})
}
if (toolName === 'read_proc_file') {
const path = typeof args.path === 'string' ? args.path : ''
const maxB =
typeof args.max_bytes === 'number' && Number.isFinite(args.max_bytes)
? Math.min(Math.floor(args.max_bytes), 500_000)
: 256_000
if (!bareAgentProcReadPathAllowed(path)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed', allowlist: [...BARE_AGENT_PROC_READ_ALLOWLIST] })
}
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
appendProgress('read_proc_file ' + path)
try {
const buf = await vfs.readFile(path)
if (!buf) return bareAgentJsonResult({ ok: false, error: 'empty_or_missing' })
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
let parsed = null
try {
parsed = JSON.parse(t)
} catch {
parsed = null
}
if (t.length > maxB) t = t.slice(0, maxB) + '\n… truncated'
return bareAgentJsonResult({
ok: true,
path,
text: t,
json: parsed
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'list_services') {
appendProgress('list_services')
if (!vfs || typeof vfs.readFile !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'vfs unavailable' })
}
let readinessText = ''
/** @type {Record<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 = {}
const list = BARE_AGENT_PROC_READ_ALLOWLIST
if (vfs && typeof vfs.readFile === 'function') {
for (let i = 0; i < list.length; i++) {
const procPath = list[i]
try {
const buf = await vfs.readFile(procPath)
if (!buf || !buf.length) continue
let t =
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
if (t.length > 80_000) t = t.slice(0, 80_000) + '\n… truncated'
try {
procParts[procPath] = JSON.parse(t)
} catch {
procParts[procPath] = t
}
} catch {
/* missing path */
}
}
}
bundle.proc = procParts
return bareAgentJsonResult({ ok: true, bundle })
}
if (toolName === 'web_fetch') {
const url = typeof args.url === 'string' ? args.url : ''
let hostHint = ''
try {
hostHint = new URL(url).hostname
} catch {
hostHint = ''
}
appendProgress('web_fetch ' + (hostHint || url.slice(0, 80)))
try {
const out = await bareWebRunTool({
ctx,
url,
method: typeof args.method === 'string' ? args.method : undefined,
headers:
args.headers &&
typeof args.headers === 'object' &&
!Array.isArray(args.headers)
? /** @type {Record<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 })
}
}
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 = [
'rest_base_url',
'rest_api_key',
'model',
'max_tokens',
'temperature',
'provider',
'max_iterations',
'stream',
'tool_parallelism',
'request_timeout_ms',
'allow_delete',
'require_confirm_token',
'owner_name',
'agent_label',
'show_reasoning',
'reasoning_mode',
'reasoning_max_chars',
'reasoning_include_tools',
'allow_bridge_mutations',
'allow_host_notifications',
'allow_host_actions',
'emergency_stop_mutations',
'autonomous_mode_enabled',
'autonomous_max_runtime_ms',
'autonomous_completion_required_checks',
'autonomous_allow_paths',
'autonomous_deny_ops',
'autonomous_active',
'autonomous_started_at_ms',
'autonomous_stop_requested',
'autonomous_goal',
'autonomous_status',
'autonomous_last_error'
]
const numKeys = new Set([
'max_tokens',
'temperature',
'max_iterations',
'tool_parallelism',
'request_timeout_ms',
'reasoning_max_chars',
'autonomous_max_runtime_ms',
'autonomous_started_at_ms'
])
for (const k of keys) {
if (Object.prototype.hasOwnProperty.call(patch, k)) {
/** @type {unknown} */
const v = patch[k]
if (numKeys.has(k)) {
const n = Number(v)
if (Number.isFinite(n)) out[k] = n
} else if (
k === 'autonomous_completion_required_checks' ||
k === 'autonomous_allow_paths' ||
k === 'autonomous_deny_ops'
) {
out[k] = Array.isArray(v) ? v.map((x) => String(x ?? '')).filter(Boolean) : out[k]
} else if (
k === 'stream' ||
k === 'allow_delete' ||
k === 'show_reasoning' ||
k === 'reasoning_include_tools' ||
k === 'allow_bridge_mutations' ||
k === 'allow_host_notifications' ||
k === 'allow_host_actions' ||
k === 'emergency_stop_mutations' ||
k === 'autonomous_mode_enabled' ||
k === 'autonomous_active' ||
k === 'autonomous_stop_requested'
) {
out[k] = Boolean(v)
} else if (k === 'reasoning_mode') {
const mode = String(v ?? '').trim().toLowerCase()
out[k] = mode === 'summary' || mode === 'trace' ? mode : 'off'
} else if (k === 'require_confirm_token') {
out[k] = String(v ?? '')
} else {
out[k] = String(v ?? '')
}
}
}
if (
patch.extra_headers &&
typeof patch.extra_headers === 'object' &&
!Array.isArray(patch.extra_headers)
) {
out.extra_headers = { .../** @type {Record<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) {
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)
}
/** 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 autonomous agent inside a JavaScript POSIX-like environment on Hyperdrive + Hyperswarm (Pear/Bare runtime).
JavaScript execution on this OS: **Node.js is not installed.** The \`node\`, \`npm\`, and \`npx\` commands **do not exist** and must never appear in plans or in run_command. To run JS as part of your agent work, **you must call the run_js_script tool** (writes under ~/.agent and executes via the Bare kernel). Optional: once a script exists on disk, run_command may invoke it by **absolute path** (e.g. \`/home/.../script.mjs\`)—same mechanism as \`/bin\` scripts—not via \`node\`.
Bare OS Created by: Raven Scott (https://raven-scott.fyi)
Your Repo: https://git.ssh.surf/snxraven/bare-operating-system
Your booter address: pear://qupw8zspk34pcxc7fqchzyeh33jtmxq1k7qze44fkosctwiid8zy
Capabilities: ctx.execLine for shell lines; ctx.vfs readFile/writeFile/mkdir/readdir/chmod. Paths under /home (personal Hyperdrive), /mnt, /tmp are writable where policy allows; /bin, /etc are system drive.
Safety: never exfiltrate ~/.agent/config.json or API keys. Prefer least-privilege commands. Call task_complete(summary) when fully done.
Discovery: man <topic>, /share/man/man.json; Tier-1 utilities in /bin. Tools: list_directory, file_stat, read_man_page, apropos_man, read_proc_file, get_swarm_peers, get_resource_limits, web_fetch (live http(s) pages and APIs; same host allowlist as wget); use list_directory instead of \`ls\` in run_command when only listing.
Skills: modular instructions live under ~/.agent/workspace/skills/ (and optionally ~/.agent/skills/). The system message includes a compact skill index; use the read_skill tool to load full SKILL.md when a task matches a listed skill.
Kernel features / capabilities that are **actually enabled or disabled** in this runtime come from **read_proc_file** on \`/proc/bare_os/features\` (same payload as \`/proc/bare_os/features.json\`) and \`/proc/bare_os/capabilities.json\`. **Do not** infer current kernel state from \`apropos_man\` or man pages—that only searches documentation keywords.
Prefer tools over guessing for filesystem and shell facts.
Reply format (mandatory): Every message you stream to the user must be plain text only — readable in a terminal without a Markdown renderer. Do not use Markdown or similar markup: no emphasis/backtick/code-fence/link syntax, no # headings, no list markers used as markup. Structure with blank lines, short paragraphs, indentation, ALL CAPS or dashed lines for section breaks if needed, and bare URLs when linking. Paths and commands appear as normal text.`
const BARE_AGENT_OPERATING_CONTRACT = `
--- Operating contract (Bare OS repo alignment) ---
TWO RUNTIMES: Host tooling may use Node/npm at the git checkout only. Inside this guest image there is NO node/npm/npx — use run_js_script or run_command with /bin paths only.
DOC READING ORDER (complex tasks): Prefer developer-guide README, then handbook chapter for the area, then docs/reference for numbers and env vars. Do not copy version tables into chat; link or cite paths.
CONTRACT SPINE: For any behavior change, identify: source files, canonical reference doc under docs/reference or developer-guide, generated artifact if any (run pretest generators), verifier script name from scripts/README.md, and whether compatibility-matrix or CHANGELOG moves.
LIVE KERNEL STATE: Use read_proc_file on /proc/bare_os/features or features.json and /proc/bare_os/capabilities.json. Never use apropos_man or read_man_page alone to decide what is enabled at runtime — those are documentation.
GENERATED FILES: Do not hand-edit posix-dashboard, ctx-client-helper.generated.ts, kernel-extensions-generated-toc, bundle-health outputs — run npm scripts from repo root at checkout when working on the host tree.
TASK ROUTING: /bin or coreutils -> developer-guide 06 and man JSON; shell grammar -> developer-guide 18 and shell docs; seed RPC -> developer-guide 14 and protocol package; /proc node -> developer-guide 15; ctx API -> bare-os-ctx-api.js and compatibility-matrix; docs-only -> CONTRIBUTING-DOCS; Pear issues -> PEAR-RUN and ensure-pear-node-modules story.
ASK FIRST: Destructive deletes, bridge mutations (emit_host_notification, request_host_action), vault/identity exfil patterns, or widening HTTP allowlists — confirm with the user unless config already allows.
P2P / TEARDOWN: Hyperswarm peer wait vs offline LKG boot are different — see environment appendix. When diagnosing replication, prefer runtime_diagnostic_bundle and read_proc allowlisted swarm files; closing order is swarm before drives when changing booter lifecycle code.
WORKFLOW: Plan briefly, execute tools, verify with read_proc or logs, then task_complete. Before large edits state blast radius (packages, contracts, docs). Before task_complete, self-review: docs updated? generated regen? wrong runtime assumption?
STOP CONDITIONS: If the same failing action repeats three times, stop and summarize evidence; do not loop blindly.
TOOLS: Use verification_hints for suggested repo-root npm checks when the user describes changed paths (host checkout). Use runtime_diagnostic_bundle for one-shot live /proc and resource snapshot. Use read_skill for workflow skills under workspace/skills/.`
/**
* Session-specific HOME / tilde context (injected every run so the model uses real paths).
* @param {Record<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 = []
for (const i of indices) {
const c = acc.get(i)
if (!c || !c.name) continue
arr.push({
id:
c.id ||
'call_' +
i +
'_' +
String(Math.random()).slice(2, 10),
type: 'function',
function: {
name: c.name,
arguments: c.args || '{}'
}
})
}
return arr
}
/**
* @param {Record<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 provider = String(out.provider || '').trim().toLowerCase()
const model = String(out.model || '').trim().toLowerCase()
const defaultGroq = 'https://api.groq.com/openai/v1'
if (provider === 'groq') {
const base = String(out.rest_base_url || '').trim()
if (!base || base === 'https://api.x.ai/v1') out.rest_base_url = defaultGroq
const cur =
typeof out.request_timeout_ms === 'number' && Number.isFinite(out.request_timeout_ms)
? out.request_timeout_ms
: 120000
if (cur < 120000) out.request_timeout_ms = 120000
}
if (provider === 'xai') {
const base = String(out.rest_base_url || '').trim()
if (!base || base === defaultGroq) out.rest_base_url = 'https://api.x.ai/v1'
const isReasoningModel = model.includes('reasoning') || model.includes('grok-4.20')
if (isReasoningModel) {
const cur =
typeof out.request_timeout_ms === 'number' && Number.isFinite(out.request_timeout_ms)
? out.request_timeout_ms
: 120000
if (cur < 300000) out.request_timeout_ms = 300000
}
}
return out
}
/**
* @param {Record<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 }
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} argv0
* @param {object} opts
* @param {boolean} opts.setupFlag
* @param {boolean} opts.interactiveSetup
*/
async function bareAgentInteractiveSetup(ctx, argv0, paths, config, opts) {
void opts
bareAgentLog(ctx, argv0 + ': configuring ~/.agent/config.json')
if (!bareAgentCanPlainSetup(ctx)) {
bareAgentErr(
ctx,
argv0 +
': interactive setup needs a TTY with stdin/stdout. Edit ' +
paths.config +
' manually or run from an interactive shell.'
)
return config
}
const stdout = ctx.replStdout || ctx.stdout
bareAgentWriteOut(
ctx,
stdout,
'\n=== ' +
argv0 +
' configuration ===\n' +
'Answer each question (Enter keeps the [default]). Typed input is echoed by the terminal.\n\n'
)
try {
const owner =
(await bareAgentPromptSetupLine(
ctx,
'Owner / human name (who operates this agent) [' +
(String(config.owner_name || '').trim() || 'unset') +
']: '
)) || ''
if (owner.trim()) config.owner_name = owner.trim()
const label =
(await bareAgentPromptSetupLine(
ctx,
'Agent display name / label (shown in notes and logs) [' +
(String(config.agent_label || '').trim() || 'BareAgent') +
']: '
)) || ''
if (label.trim()) config.agent_label = label.trim()
const url =
(await bareAgentPromptSetupLine(
ctx,
'REST base URL [' + String(config.rest_base_url || '') + ']: '
)) || ''
if (url.trim()) config.rest_base_url = url.trim()
const keyRaw =
(await bareAgentPromptSetupLine(
ctx,
'REST API key [leave empty to skip]: ',
{ mask: true }
)) || ''
if (keyRaw.trim()) config.rest_api_key = keyRaw.trim()
const modelRaw =
(await bareAgentPromptSetupLine(
ctx,
'Model [' + String(config.model || '') + ']: '
)) || ''
if (modelRaw.trim()) config.model = modelRaw.trim()
const provRaw =
(await bareAgentPromptSetupLine(
ctx,
'Provider label [' + String(config.provider || '') + ']: '
)) || ''
if (provRaw.trim()) config.provider = provRaw.trim()
const autonomousModeRaw =
(await bareAgentPromptSetupLine(
ctx,
'Enable autonomous coding mode? (y/N) [' +
(config.autonomous_mode_enabled ? 'y' : 'n') +
']: '
)) || ''
{
const v = autonomousModeRaw.trim().toLowerCase()
if (v === 'y' || v === 'yes' || v === '1' || v === 'true') config.autonomous_mode_enabled = true
else if (v === 'n' || v === 'no' || v === '0' || v === 'false') config.autonomous_mode_enabled = false
}
const showReasoningRaw =
(await bareAgentPromptSetupLine(
ctx,
'Show thinking/process output? (y/N) [' +
(config.show_reasoning ? 'y' : 'n') +
']: '
)) || ''
{
const v = showReasoningRaw.trim().toLowerCase()
if (v === 'y' || v === 'yes' || v === '1' || v === 'true') config.show_reasoning = true
else if (v === 'n' || v === 'no' || v === '0' || v === 'false') config.show_reasoning = false
}
const modeRaw =
(await bareAgentPromptSetupLine(
ctx,
'Reasoning mode off|summary|trace [' +
String(config.reasoning_mode || 'off') +
']: '
)) || ''
if (modeRaw.trim()) {
const m = modeRaw.trim().toLowerCase()
if (m === 'off' || m === 'summary' || m === 'trace') config.reasoning_mode = m
}
const maxCharsRaw =
(await bareAgentPromptSetupLine(
ctx,
'Reasoning max chars [default ' +
String(config.reasoning_max_chars || 4000) +
']: '
)) || ''
if (maxCharsRaw.trim()) {
const n = Number(maxCharsRaw)
if (Number.isFinite(n) && n >= 200) config.reasoning_max_chars = Math.floor(n)
}
const includeToolsRaw =
(await bareAgentPromptSetupLine(
ctx,
'Include tool traces in process output? (Y/n) [' +
(config.reasoning_include_tools === false ? 'n' : 'y') +
']: '
)) || ''
{
const v = includeToolsRaw.trim().toLowerCase()
if (v === 'y' || v === 'yes' || v === '1' || v === 'true') config.reasoning_include_tools = true
else if (v === 'n' || v === 'no' || v === '0' || v === 'false') config.reasoning_include_tools = false
}
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
bareAgentErr(ctx, argv0 + ': setup input failed: ' + msg)
return config
}
await bareAgentSaveConfig(ctx, paths, config)
bareAgentLog(ctx, 'Configuration saved.')
return config
}
/**
* Interactive setup only (`agent --setup`).
* @param {Record<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 home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
let { config } = await bareAgentLoadOrCreateConfig(ctx, paths)
config = bareAgentApplyProviderProfile(config)
const canWizard = bareAgentCanPlainSetup(ctx)
if (setupFlag && !canWizard) {
bareAgentErr(
ctx,
argv0 +
': --setup / --config needs an interactive TTY. Edit ' +
paths.config +
' manually.'
)
ctx.exitCode = 1
return
}
const needWizard =
setupFlag ||
(!(config.rest_api_key && String(config.rest_api_key).trim()) && canWizard)
let replSuspendedForSetup = false
if (needWizard && typeof ctx.suspendReplForSubprocess === 'function') {
ctx.suspendReplForSubprocess()
replSuspendedForSetup = true
}
if (needWizard) {
config = await bareAgentInteractiveSetup(ctx, argv0, paths, config, {
setupFlag,
interactiveSetup: true
})
}
if (!config.rest_api_key || !String(config.rest_api_key).trim()) {
if (replSuspendedForSetup && typeof ctx.resumeReplAfterSubprocess === 'function') {
ctx.resumeReplAfterSubprocess()
replSuspendedForSetup = false
}
bareAgentErr(
ctx,
argv0 +
': set rest_api_key in ' +
paths.config +
' or run `' +
argv0 +
' --setup` / `' +
argv0 +
' --config`.'
)
ctx.exitCode = 1
return
}
try {
if (ctx.vfs?.chmod) await ctx.vfs.chmod(paths.config, 0o600)
} catch {
/* ignore — optional */
}
const fetchFn = bareAgentResolveFetch(ctx)
if (!fetchFn) {
if (replSuspendedForSetup && typeof ctx.resumeReplAfterSubprocess === 'function') {
ctx.resumeReplAfterSubprocess()
replSuspendedForSetup = false
}
bareAgentErr(ctx, argv0 + ': no fetch (ctx.httpFetch / bare.fetch / global fetch)')
ctx.exitCode = 1
return
}
/** @type {unknown[]} */
let messages = await bareAgentLoadHistory(ctx, paths.history)
const instructions = await bareAgentLoadInstructionFiles(ctx, paths)
const manDigest = await bareAgentManDigest(ctx)
await bareAgentEnsureWorkspace(ctx, paths)
await bareAgentEnsureSkillTemplates(ctx, paths, config)
if (needWizard) await bareAgentSyncWorkspaceFromConfig(ctx, paths, config)
const workspacePromptBlock = await bareAgentLoadWorkspacePrompt(
ctx,
paths,
24000
)
const skillsPromptBlock = await bareAgentSkillsCompactPrompt(ctx, paths, 4000)
let systemContent =
BARE_AGENT_STATIC_SYSTEM +
BARE_AGENT_OPERATING_CONTRACT +
'\n\n' +
bareAgentSessionHomeBlock(ctx, home, paths) +
'\n\n' +
manDigest.slice(0, 12000)
if (instructions)
systemContent += '\n\n## Session notes\n' + instructions.slice(0, 8000)
if (workspacePromptBlock && String(workspacePromptBlock).trim())
systemContent += '\n\n' + String(workspacePromptBlock).trim()
if (skillsPromptBlock && String(skillsPromptBlock).trim())
systemContent += '\n\n' + String(skillsPromptBlock).trim()
if (!messages.length) {
messages = [
{ role: 'system', content: systemContent },
{ role: 'user', content: task }
]
} else {
const hasSystem =
messages[0] &&
typeof messages[0] === 'object' &&
/** @type {{ role?: string }} */ (messages[0]).role === 'system'
if (!hasSystem) {
messages = [{ role: 'system', content: systemContent }, ...messages]
} else {
messages[0] = { role: 'system', content: systemContent }
}
messages.push({ role: 'user', content: task })
}
const stdout =
/** @type {import('stream').Writable | undefined} */ (
ctx.replStdout || ctx.stdout
)
const useColor = bareEditUseColor(ctx)
/** @type {{ current: Record<string, unknown> }} */
const configRef = { current: { ...config } }
/** @type {{ db: unknown | null }} */
const manCacheRef = { db: null }
let completed = false
let taskSummary = ''
function onTaskComplete(summary) {
completed = true
taskSummary = summary
}
function appendProgress(line) {
void bareAgentAppendProgress(ctx, paths.progress, line)
}
const url =
bareAgentNormalizeBaseUrl(String(configRef.current.rest_base_url || '')) +
'/chat/completions'
const tools = bareAgentToolDefinitions()
let reasoningSettings = bareAgentReasoningSettings(configRef.current)
let autonomousSettings = bareAgentAutonomousSettings(configRef.current)
let reasoningCharCount = 0
let suspended = replSuspendedForSetup
try {
if (typeof ctx.suspendReplForSubprocess === 'function' && !suspended) {
ctx.suspendReplForSubprocess()
suspended = true
}
const masterAbort = new AbortController()
/** @type {(() => void) | null} */
let offSigint = null
if (globalThis.process && typeof globalThis.process.on === 'function') {
const fn = () => {
masterAbort.abort()
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) + '^C' + EDIT_ANSI_RESET + '\n'
)
}
globalThis.process.on('SIGINT', fn)
offSigint = () => {
try {
globalThis.process.off('SIGINT', fn)
} catch {
/* ignore */
}
}
}
const maxIter = Number(configRef.current.max_iterations) || 64
let iter = 0
for (;;) {
reasoningSettings = bareAgentReasoningSettings(configRef.current)
autonomousSettings = bareAgentAutonomousSettings(configRef.current)
if (completed) break
if (autonomousSettings.enabled && autonomousSettings.active) {
const now = Date.now()
if (autonomousSettings.stopRequested) {
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'stopped'
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous stopped by manual request')
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) + '\n[process] autonomous run stopped by request' + EDIT_ANSI_RESET + '\n'
)
break
}
if (
autonomousSettings.startedAtMs > 0 &&
now - autonomousSettings.startedAtMs >= autonomousSettings.maxRuntimeMs
) {
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'timebox_expired'
configRef.current.autonomous_last_error = 'timebox_expired'
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous timebox expired')
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) + '\n[process] autonomous run stopped (timebox expired)' + EDIT_ANSI_RESET + '\n'
)
break
}
}
iter++
if (iter > maxIter) {
bareAgentErr(ctx, argv0 + ': max_iterations exceeded')
ctx.exitCode = 1
break
}
messages = bareAgentTrimMessages(messages, 450_000)
const headers = {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + String(configRef.current.rest_api_key || '')
}
const eh = configRef.current.extra_headers
if (eh && typeof eh === 'object' && !Array.isArray(eh)) {
for (const [k, v] of Object.entries(eh)) {
if (typeof v === 'string') headers[k] = v
}
}
/** @type {Record<string, unknown>} */
const providerNow = String(configRef.current.provider || '').trim().toLowerCase()
const body = {
model: String(configRef.current.model || ''),
stream: Boolean(configRef.current.stream !== false),
messages,
tools,
tool_choice: 'auto',
max_tokens: Number(configRef.current.max_tokens) || 4096,
temperature: Number(configRef.current.temperature) ?? 0.7
}
if (providerNow === 'xai') {
body.parallel_tool_calls =
Number(configRef.current.tool_parallelism) > 1 ? true : false
body.max_completion_tokens = Number(configRef.current.max_tokens) || 4096
}
if (providerNow === 'groq') {
body.parallel_tool_calls =
Number(configRef.current.tool_parallelism) > 1 ? true : false
body.max_completion_tokens = Number(configRef.current.max_tokens) || 4096
}
if (reasoningSettings.enabled && reasoningSettings.mode === 'trace') {
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) +
'\n[process] request provider=' +
(providerNow || 'unknown') +
' model=' +
String(configRef.current.model || '') +
EDIT_ANSI_RESET +
'\n'
)
}
let assistantContent = ''
/** @type {Map<number, { id: string, name: string, args: string }>} */
const toolAcc = new Map()
/** @type {unknown} */
let usageOut = null
let finishReason = ''
try {
await bareAgentStreamChatCompletions({
fetchFn,
url,
headers,
body,
signal: masterAbort.signal,
onEvent: (ev) => {
const e = /** @type {Record<string, unknown>} */ (ev)
if (e.type === 'delta_content') {
const chunk = typeof e.content === 'string' ? e.content : ''
assistantContent += chunk
bareAgentWriteOut(ctx, stdout, chunk)
} else if (e.type === 'delta_tool_calls') {
const arr = e.tool_calls
if (Array.isArray(arr)) {
for (const tc of arr) bareAgentMergeToolCallDelta(toolAcc, tc)
}
} else if (e.type === 'delta_reasoning' && reasoningSettings.enabled) {
const chunk = typeof e.reasoning === 'string' ? e.reasoning : ''
if (chunk && reasoningCharCount < reasoningSettings.maxChars) {
const remain = reasoningSettings.maxChars - reasoningCharCount
const out = chunk.slice(0, remain)
reasoningCharCount += out.length
if (out.length) {
bareAgentWriteOut(
ctx,
stdout,
'\n' +
bareEditSgr('dim', useColor) +
'[thinking] ' +
out +
EDIT_ANSI_RESET +
'\n'
)
}
}
} else if (e.type === 'response_shape_keys') {
const keys = Array.isArray(e.keys)
? e.keys.map((k) => String(k)).join(',')
: ''
appendProgress('provider_shape_keys ' + keys.slice(0, 200))
} else if (e.type === 'usage') {
usageOut = e.usage
} else if (e.type === 'finish_reason') {
finishReason = String(e.finish_reason || '')
}
}
})
} catch (e) {
const msg =
e && typeof e === 'object' && 'name' in e && e.name === 'AbortError'
? 'aborted'
: e && typeof e === 'object' && 'message' in e
? String(e.message)
: String(e)
bareAgentErr(ctx, argv0 + ': ' + msg)
ctx.exitCode = 130
break
}
const toolCallsArr = bareAgentFinalizeToolCalls(toolAcc)
const hasTools = toolCallsArr.length > 0
if (!hasTools && finishReason === 'tool_calls') {
appendProgress('warning tool_calls_finish_without_tool_deltas provider=' + providerNow)
}
/** @type {Record<string, unknown>} */
const assistantMsg = {
role: 'assistant',
content: assistantContent || null,
tool_calls: hasTools ? toolCallsArr : undefined
}
messages.push(assistantMsg)
if (usageOut && typeof usageOut === 'object') {
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 (autonomousSettings.enabled && autonomousSettings.active && autonomousSettings.requiredChecks.length) {
/** @type {string[]} */
const failedChecks = []
for (const check of autonomousSettings.requiredChecks) {
const res = await bareAgentDispatchTool({
ctx,
toolName: 'run_maintenance_gate',
argsJson: JSON.stringify({ command: check, timeout_ms: 300000 }),
paths,
signal: masterAbort.signal,
appendProgress,
home,
configRef,
manCacheRef,
onTaskComplete
})
let ok = false
try {
const j = JSON.parse(res)
const body = typeof j.stdout_stderr === 'string' ? j.stdout_stderr : ''
ok = Boolean(j.ok) && !/EXIT:[1-9]/.test(body)
} catch {
ok = false
}
if (!ok) failedChecks.push(check)
}
if (failedChecks.length) {
configRef.current.autonomous_status = 'needs_fixups'
configRef.current.autonomous_last_error = 'failed_checks:' + failedChecks.join(',')
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous checks failed ' + failedChecks.join(','))
messages.push({
role: 'user',
content:
'Autonomous completion gates failed for checks: ' +
failedChecks.join(', ') +
'. Fix the issues, rerun required checks, and only call task_complete when all pass.'
})
continue
}
configRef.current.autonomous_active = false
configRef.current.autonomous_status = 'completed'
configRef.current.autonomous_last_error = ''
await bareAgentSaveConfig(ctx, paths, configRef.current)
appendProgress('autonomous completion gates passed')
}
await bareAgentSaveHistory(ctx, paths.history, messages)
bareAgentWriteOut(ctx, stdout, '\n')
break
}
appendProgress(
'iteration ' +
iter +
' tools ' +
toolCallsArr.map((x) =>
/** @type {{ function?: { name?: string } }} */ (x).function?.name
).join(',')
)
if (reasoningSettings.enabled && reasoningSettings.mode === 'trace') {
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) +
'\n[process] iteration ' +
String(iter) +
' finish_reason=' +
(finishReason || 'unknown') +
EDIT_ANSI_RESET +
'\n'
)
}
for (const tc of toolCallsArr) {
const fn = /** @type {{ id?: string, function?: { name?: string, arguments?: string } }} */ (
tc
).function
const id = /** @type {{ id?: string }} */ (tc).id || ''
const name = fn?.name || ''
const argsStr = fn?.arguments || '{}'
if (reasoningSettings.enabled && reasoningSettings.mode === 'trace' && reasoningSettings.includeTools) {
const argsPreview = argsStr.length > 280 ? argsStr.slice(0, 280) + '…' : argsStr
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) +
'[process] tool_call ' +
name +
' args=' +
argsPreview +
EDIT_ANSI_RESET +
'\n'
)
}
bareAgentWriteOut(
ctx,
stdout,
'\n' +
bareEditSgr('keyword', useColor) +
'[tool] ' +
name +
EDIT_ANSI_RESET +
'\n'
)
const spinner = bareEditSgr('dim', useColor) + '… running ' + name + EDIT_ANSI_RESET
bareAgentWriteOut(ctx, stdout, spinner + '\r')
const resultStr = await bareAgentDispatchTool({
ctx,
toolName: name,
argsJson: argsStr,
paths,
signal: masterAbort.signal,
appendProgress,
home,
configRef,
manCacheRef,
onTaskComplete
})
bareAgentWriteOut(ctx, stdout, '\x1b[K')
messages.push({
role: 'tool',
tool_call_id: id,
content: resultStr
})
if (reasoningSettings.enabled && reasoningSettings.mode === 'trace' && reasoningSettings.includeTools) {
const resPreview =
resultStr.length > 360 ? resultStr.slice(0, 360) + '…' : resultStr
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) +
'[process] tool_result ' +
name +
' ' +
resPreview +
EDIT_ANSI_RESET +
'\n'
)
}
if (completed) break
}
await bareAgentSaveHistory(ctx, paths.history, messages)
if (completed) {
bareAgentWriteOut(
ctx,
stdout,
bareEditSgr('dim', useColor) +
'\nDone: ' +
taskSummary +
EDIT_ANSI_RESET +
'\n'
)
break
}
}
if (offSigint) offSigint()
} finally {
if (suspended && typeof ctx.resumeReplAfterSubprocess === 'function') {
ctx.resumeReplAfterSubprocess()
}
}
}
/**
* Autonomous AI agent: 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(
'usage: ' +
argv0 +
' [--setup | --config | --reset] YOUR_REQUEST_HERE\n' +
' ' +
argv0 +
' --setup\n' +
' ' +
argv0 +
' --config\n' +
' ' +
argv0 +
' --reset\n' +
' ' +
argv0 +
' reset\n' +
'\n' +
'Runs an autonomous coding/OS agent against any OpenAI-compatible HTTPS API.\n' +
'Configuration: ~/.agent/config.json on your personal drive (created on first run).\n' +
'Use --setup or --config to interactively set owner name, agent label, API URL, key, model, and provider (plain TTY prompts).\n' +
'Use --reset or `reset` to clear ~/.agent/history.json and start a fresh chat session.\n' +
'\n' +
'Examples:\n' +
' ' +
argv0 +
' "summarize ~/README and list five files in /bin"\n' +
' ' +
argv0 +
' --setup\n' +
' ' +
argv0 +
' --config\n' +
' ' +
argv0 +
' --reset\n' +
'\n' +
'See man agent.'
)
ctx.exitCode = wantHelp ? 0 : 1
return
}
let setupFlag = false
let resetFlag = false
/** @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 rest.push(a)
}
const wantReset =
resetFlag || (rest.length === 1 && rest[0] === 'reset')
if (wantReset) {
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
}
const task = rest.join(' ').trim()
if (!task && !setupFlag) {
ctx.console.error(argv0 + ': missing task (or use --setup / --config)')
ctx.exitCode = 1
return
}
if (setupFlag && !task) {
await bareAgentRunSetupOnly(ctx, argv0)
return
}
await bareOsRunAgentSession(ctx, argv0, task, {
setupFlag
})
}