Update Agent Shape

This commit is contained in:
2026-08-18 15:13:39 -04:00
parent 613f05aaec
commit 187c8c4df8
27 changed files with 3476 additions and 338 deletions
@@ -9,6 +9,7 @@ var BARE_AGENT_PLAN_READONLY_TOOLS = Object.freeze({
list_dir: 1,
file_stat: 1,
search_files: 1,
grep: 1,
glob_files: 1,
glob: 1,
get_system_info: 1,
@@ -19,6 +20,8 @@ var BARE_AGENT_PLAN_READONLY_TOOLS = Object.freeze({
get_swarm_peers: 1,
get_resource_limits: 1,
web_fetch: 1,
web_search: 1,
git_status: 1,
read_skill: 1,
list_services: 1,
service_status: 1,
@@ -51,7 +54,8 @@ var BARE_AGENT_PLAN_READONLY_TOOLS = Object.freeze({
var BARE_AGENT_PLAN_WRITE_TOOLS = Object.freeze({
write_file: 1,
edit_file: 1,
search_replace: 1
search_replace: 1,
apply_patch: 1
})
/**
@@ -757,3 +761,455 @@ function bareAgentToolPathArg(args) {
}
return ''
}
/**
* @param {string} text
*/
function bareAgentLooksBinaryText(text) {
const s = String(text || '')
if (!s) return false
if (s.indexOf('\0') !== -1) return true
let bad = 0
const n = Math.min(s.length, 800)
for (let i = 0; i < n; i++) {
const c = s.charCodeAt(i)
if (c === 9 || c === 10 || c === 13) continue
if (c < 32) bad++
}
return bad > 8
}
/**
* Guest-native grep (Grok-style). VFS walk + JS regex — no host rg/node.
* @param {Record<string, unknown>} ctx
* @param {{
* pattern: string,
* root?: string,
* glob?: string,
* ignore_case?: boolean,
* before?: number,
* after?: number,
* context?: number,
* max_matches?: number,
* files_with_matches?: boolean
* }} opts
*/
async function bareAgentGrepFiles(ctx, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const pattern = String(o.pattern || '')
if (!pattern) return { ok: false, error: 'pattern_required' }
let re
try {
re = new RegExp(pattern, o.ignore_case ? 'i' : '')
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return { ok: false, error: 'bad_regex', detail: msg }
}
const root = String(o.root || '/').replace(/\/+$/, '') || '/'
const before = Math.min(
8,
Math.max(0, Math.floor(Number(o.before != null ? o.before : o.context) || 0))
)
const after = Math.min(
8,
Math.max(0, Math.floor(Number(o.after != null ? o.after : o.context) || 0))
)
const maxMatches = Math.min(
200,
Math.max(1, Math.floor(Number(o.max_matches) || 50))
)
const filesOnly = Boolean(o.files_with_matches)
const glob = typeof o.glob === 'string' && o.glob.trim() ? o.glob.trim() : ''
const files = glob
? await bareAgentGlobFiles(ctx, root, glob, { maxFiles: 800, maxDepth: 12 })
: await bareAgentVfsWalkFiles(ctx, root, { maxFiles: 800, maxDepth: 12 })
/** @type {{ path: string, line?: number, text?: string }[]} */
const matches = []
/** @type {string[]} */
const filesHit = []
let truncated = false
for (let f = 0; f < files.length; f++) {
const path = files[f]
const text = await bareAgentReadTextFile(ctx, path)
if (!text || bareAgentLooksBinaryText(text)) continue
const lines = text.split('\n')
if (lines.length && lines[lines.length - 1] === '') lines.pop()
let fileHit = false
for (let i = 0; i < lines.length; i++) {
re.lastIndex = 0
if (!re.test(lines[i])) continue
fileHit = true
if (filesOnly) break
const start = Math.max(0, i - before)
const end = Math.min(lines.length, i + 1 + after)
const slice = lines.slice(start, end)
const body =
before || after
? slice
.map(function (ln, j) {
const n = start + j + 1
const mark = n === i + 1 ? ':' : '-'
return String(n) + mark + ln
})
.join('\n')
: String(i + 1) + ':' + lines[i]
matches.push({ path, line: i + 1, text: body })
if (matches.length >= maxMatches) {
truncated = true
break
}
}
if (fileHit && filesOnly) {
filesHit.push(path)
if (filesHit.length >= maxMatches) {
truncated = true
break
}
}
if (truncated) break
}
if (filesOnly) {
return {
ok: true,
pattern,
root,
glob: glob || null,
files_with_matches: filesHit,
count: filesHit.length,
truncated
}
}
return {
ok: true,
pattern,
root,
glob: glob || null,
matches,
count: matches.length,
truncated
}
}
/**
* Codex / Grok apply_patch parser (Begin Patch … End Patch).
* @param {string} text
* @returns {{ ok: boolean, error?: string, ops?: object[] }}
*/
function bareAgentParseApplyPatch(text) {
const raw = String(text || '').replace(/\r\n/g, '\n')
if (!raw.trim()) return { ok: false, error: 'empty_patch' }
const all = raw.split('\n')
let start = 0
let end = all.length
for (let i = 0; i < all.length; i++) {
if (/^\s*\*\*\*\s*Begin Patch\s*$/i.test(all[i])) {
start = i + 1
break
}
}
for (let i = all.length - 1; i >= 0; i--) {
if (/^\s*\*\*\*\s*End Patch\s*$/i.test(all[i])) {
end = i
break
}
}
const body = all.slice(start, end)
/** @type {object[]} */
const ops = []
/** @type {Record<string, unknown> | null} */
let cur = null
/** @type {{ context: string, old_lines: string[], new_lines: string[], is_end_of_file?: boolean } | null} */
let chunk = null
function flushChunk() {
if (cur && cur.type === 'update' && chunk) {
const chunks = Array.isArray(cur.chunks) ? cur.chunks : []
chunks.push(chunk)
cur.chunks = chunks
chunk = null
}
}
function flushOp() {
flushChunk()
if (cur) ops.push(cur)
cur = null
}
for (let i = 0; i < body.length; i++) {
const line = body[i]
if (/^\s*\*\*\*\s*Add File:\s*/i.test(line)) {
flushOp()
cur = {
type: 'add',
path: line.replace(/^\s*\*\*\*\s*Add File:\s*/i, '').trim(),
content: ''
}
continue
}
if (/^\s*\*\*\*\s*Delete File:\s*/i.test(line)) {
flushOp()
cur = {
type: 'delete',
path: line.replace(/^\s*\*\*\*\s*Delete File:\s*/i, '').trim()
}
continue
}
if (/^\s*\*\*\*\s*Update File:\s*/i.test(line)) {
flushOp()
cur = {
type: 'update',
path: line.replace(/^\s*\*\*\*\s*Update File:\s*/i, '').trim(),
move_to: '',
chunks: []
}
continue
}
if (/^\s*\*\*\*\s*Move to:\s*/i.test(line) && cur && cur.type === 'update') {
cur.move_to = line.replace(/^\s*\*\*\*\s*Move to:\s*/i, '').trim()
continue
}
if (/^\s*\*\*\*\s*End of File\s*$/i.test(line)) {
if (chunk) chunk.is_end_of_file = true
flushChunk()
continue
}
if (line.indexOf('@@') === 0 && cur && cur.type === 'update') {
flushChunk()
chunk = {
context: line.replace(/^@@\s?/, '').trim(),
old_lines: [],
new_lines: []
}
continue
}
if (cur && cur.type === 'add') {
const bodyLine = line.charAt(0) === '+' ? line.slice(1) : line
cur.content = cur.content ? String(cur.content) + '\n' + bodyLine : bodyLine
continue
}
if (cur && cur.type === 'update') {
if (!chunk) {
chunk = { context: '', old_lines: [], new_lines: [] }
}
const tag = line.charAt(0)
const rest = line.length ? line.slice(1) : ''
if (tag === '-') chunk.old_lines.push(rest)
else if (tag === '+') chunk.new_lines.push(rest)
else {
const ctxLine = tag === ' ' ? rest : line
chunk.old_lines.push(ctxLine)
chunk.new_lines.push(ctxLine)
}
}
}
flushOp()
if (!ops.length) return { ok: false, error: 'no_patch_ops' }
return { ok: true, ops }
}
/**
* @param {string} text
* @param {{ context: string, old_lines: string[], new_lines: string[], is_end_of_file?: boolean }} chunk
*/
function bareAgentApplyPatchChunk(text, chunk) {
const src = String(text == null ? '' : text)
const oldBlock = (chunk.old_lines || []).join('\n')
const newBlock = (chunk.new_lines || []).join('\n')
if (!oldBlock && !newBlock) {
return { ok: false, error: 'empty_chunk' }
}
if (!oldBlock) {
const next = src
? src.replace(/\s*$/, '') + (src.endsWith('\n') ? '' : '\n') + newBlock + '\n'
: newBlock + (newBlock.endsWith('\n') ? '' : '\n')
return { ok: true, next }
}
let from = 0
if (chunk.context) {
const at = src.indexOf(chunk.context)
if (at === -1) {
return { ok: false, error: 'chunk_context_not_found', context: chunk.context }
}
from = at
}
const hay = src.slice(from)
const count = bareAgentCountOccurrences(hay, oldBlock)
if (count === 0) return { ok: false, error: 'chunk_old_not_found' }
if (count > 1 && !chunk.context) {
return {
ok: false,
error: 'chunk_old_not_unique',
count,
hint: 'add @@ context or more surrounding lines'
}
}
const next = src.slice(0, from) + hay.replace(oldBlock, newBlock)
return { ok: true, next }
}
/**
* @param {Record<string, unknown>} ctx
* @param {object[]} ops
* @param {{ home?: string, denyPrefixes?: unknown }} [opts]
*/
async function bareAgentApplyPatchOps(ctx, ops, opts) {
const home = String((opts && opts.home) || '')
const deny = opts && opts.denyPrefixes
/** @type {object[]} */
const results = []
const rows = Array.isArray(ops) ? ops : []
for (let i = 0; i < rows.length; i++) {
const op = rows[i] && typeof rows[i] === 'object' ? rows[i] : {}
let path = String(op.path || '').trim()
if (path && path.charAt(0) !== '/' && home) {
path = home.replace(/\/+$/, '') + '/' + path.replace(/^\.\//, '')
}
if (!path) {
results.push({ ok: false, error: 'path_required' })
continue
}
if (
typeof bareAgentPathAllowedMutate === 'function' &&
!bareAgentPathAllowedMutate(path, deny)
) {
results.push({ ok: false, path, error: 'path_not_allowed' })
continue
}
if (op.type === 'add') {
const content = String(op.content == null ? '' : op.content)
await bareAgentWriteTextFile(
ctx,
path,
content.endsWith('\n') ? content : content + '\n'
)
results.push({ ok: true, op: 'add', path })
continue
}
if (op.type === 'delete') {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.unlink !== 'function') {
results.push({ ok: false, path, error: 'vfs.unlink unavailable' })
continue
}
try {
await vfs.unlink(path)
results.push({ ok: true, op: 'delete', path })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
results.push({ ok: false, path, error: msg })
}
continue
}
if (op.type === 'update') {
let text = await bareAgentReadTextFile(ctx, path)
if (!text && text !== '') {
results.push({ ok: false, path, error: 'missing_file' })
continue
}
const chunks = Array.isArray(op.chunks) ? op.chunks : []
let failed = null
for (let c = 0; c < chunks.length; c++) {
const applied = bareAgentApplyPatchChunk(text, chunks[c])
if (!applied.ok) {
failed = applied
break
}
text = applied.next
}
if (failed) {
results.push({ ok: false, path, error: failed.error, hint: failed.hint || null })
continue
}
const dest = String(op.move_to || '').trim() || path
const destAbs =
dest.charAt(0) === '/'
? dest
: home
? home.replace(/\/+$/, '') + '/' + dest.replace(/^\.\//, '')
: dest
if (
destAbs !== path &&
typeof bareAgentPathAllowedMutate === 'function' &&
!bareAgentPathAllowedMutate(destAbs, deny)
) {
results.push({ ok: false, path, error: 'move_path_not_allowed', dest: destAbs })
continue
}
await bareAgentWriteTextFile(ctx, destAbs, text)
if (destAbs !== path && ctx.vfs && typeof ctx.vfs.unlink === 'function') {
try {
await ctx.vfs.unlink(path)
} catch {
/* keep original if unlink fails */
}
}
results.push({
ok: true,
op: destAbs !== path ? 'move' : 'update',
path,
dest: destAbs !== path ? destAbs : undefined,
chunks: chunks.length
})
continue
}
results.push({ ok: false, path, error: 'unknown_op' })
}
const failed = results.filter(function (r) {
return !r.ok
})
return {
ok: failed.length === 0,
applied: results.length - failed.length,
failed: failed.length,
results
}
}
/**
* Parse DuckDuckGo instant-answer JSON into a compact result list.
* @param {unknown} payload
* @param {number} [max]
*/
function bareAgentParseSearchResults(payload, max) {
const cap = Math.min(Math.max(Number(max) || 8, 1), 16)
/** @type {{ title: string, url: string, snippet: string }[]} */
const out = []
const seen = Object.create(null)
function add(title, url, snippet) {
const u = String(url || '').trim()
if (!u || seen[u] || out.length >= cap) return
if (!/^https?:\/\//i.test(u)) return
seen[u] = 1
out.push({
title: String(title || u).slice(0, 160),
url: u,
snippet: String(snippet || '').replace(/\s+/g, ' ').trim().slice(0, 280)
})
}
const obj = payload && typeof payload === 'object' ? payload : {}
const rec = /** @type {Record<string, unknown>} */ (obj)
if (rec.AbstractURL || rec.Abstract) {
add(
String(rec.Heading || rec.AbstractSource || 'Abstract'),
String(rec.AbstractURL || ''),
String(rec.AbstractText || rec.Abstract || '')
)
}
const results = Array.isArray(rec.Results) ? rec.Results : []
for (let i = 0; i < results.length; i++) {
const row = results[i] && typeof results[i] === 'object' ? results[i] : {}
add(row.Text || row.Name, row.FirstURL, row.Text)
}
const related = Array.isArray(rec.RelatedTopics) ? rec.RelatedTopics : []
function walk(list) {
for (let i = 0; i < list.length && out.length < cap; i++) {
const row = list[i] && typeof list[i] === 'object' ? list[i] : {}
if (Array.isArray(row.Topics)) walk(row.Topics)
else add(row.Text, row.FirstURL, row.Text)
}
}
walk(related)
return out
}
@@ -1083,6 +1083,107 @@ function bareAgentToolDefinitions() {
}
}
},
{
type: 'function',
function: {
name: 'grep',
description:
'Search file contents with a JS regular expression (Grok-style, VFS-native). Prefer this over run_command grep. Supports glob, ignore_case, context lines, and files_with_matches.',
parameters: {
type: 'object',
properties: {
pattern: {
type: 'string',
description: 'JavaScript regular expression (no surrounding slashes)'
},
path: {
type: 'string',
description: 'Directory or file to search (default session home)'
},
root: { type: 'string', description: 'Alias for path' },
glob: {
type: 'string',
description: 'Optional file glob such as **/*.js'
},
ignore_case: { type: 'boolean' },
before: { type: 'integer', description: 'Context lines before each match' },
after: { type: 'integer', description: 'Context lines after each match' },
context: { type: 'integer', description: 'Context lines before and after' },
max_matches: { type: 'integer', description: 'Default 50, cap 200' },
files_with_matches: {
type: 'boolean',
description: 'Return matching paths only'
}
},
required: ['pattern']
}
}
},
{
type: 'function',
function: {
name: 'apply_patch',
description:
'Apply a Codex/Grok multi-file patch (*** Begin Patch … *** End Patch). Supports *** Add File, *** Delete File, *** Update File, optional *** Move to, and @@ hunks with + / - / context lines. Prefer this for multi-hunk or multi-file edits.',
parameters: {
type: 'object',
properties: {
patch: { type: 'string', description: 'Full apply_patch document' },
input: { type: 'string', description: 'Alias for patch' }
},
required: ['patch']
}
}
},
{
type: 'function',
function: {
name: 'update_goal',
description:
'Report progress on the current autonomous/session goal. completed=true ends the goal. blocked_reason is a failure signal after 3+ failed attempts — never use it for success.',
parameters: {
type: 'object',
properties: {
completed: { type: 'boolean' },
message: { type: 'string', description: 'Short progress or completion note' },
blocked_reason: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
name: 'web_search',
description:
'Search the public web (DuckDuckGo instant answers via the same HTTP policy as web_fetch / wget). Use for current facts, docs, and error lookup. Then web_fetch promising URLs.',
parameters: {
type: 'object',
properties: {
query: { type: 'string' },
max_results: { type: 'integer', description: 'Default 8, cap 16' }
},
required: ['query']
}
}
},
{
type: 'function',
function: {
name: 'git_status',
description:
'Guest git status --short plus diff --stat (isomorphic-git /bin/git). Prefer this over parsing git via run_command when you only need a snapshot.',
parameters: {
type: 'object',
properties: {
cwd: {
type: 'string',
description: 'Repo directory (default PWD or home)'
}
}
}
}
},
{
type: 'function',
function: {
@@ -1202,6 +1303,9 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'glob') {
return bareAgentDispatchTool({ ...o, toolName: 'glob_files' })
}
if (toolName === 'ripgrep') {
return bareAgentDispatchTool({ ...o, toolName: 'grep' })
}
const vfs = ctx.vfs
const mutateDenyPrefixes = (function () {
@@ -3152,6 +3256,171 @@ async function bareAgentDispatchTool(o) {
}
}
if (toolName === 'grep') {
const pattern = typeof args.pattern === 'string' ? args.pattern : ''
if (!pattern) return bareAgentJsonResult({ ok: false, error: 'pattern_required' })
if (typeof bareAgentGrepFiles !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'grep_unavailable' })
}
const root =
(typeof args.path === 'string' && args.path.trim()) ||
(typeof args.root === 'string' && args.root.trim()) ||
home ||
'/home'
if (!bareAgentPathAllowed(root)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
appendProgress('grep ' + pattern + ' @ ' + root)
const out = await bareAgentGrepFiles(ctx, {
pattern,
root,
glob: typeof args.glob === 'string' ? args.glob : '',
ignore_case: Boolean(args.ignore_case),
before: args.before,
after: args.after,
context: args.context,
max_matches: args.max_matches,
files_with_matches: Boolean(args.files_with_matches)
})
return bareAgentJsonResult(out)
}
if (toolName === 'apply_patch') {
const patch =
(typeof args.patch === 'string' && args.patch) ||
(typeof args.input === 'string' && args.input) ||
''
if (!patch.trim()) return bareAgentJsonResult({ ok: false, error: 'patch_required' })
if (typeof bareAgentParseApplyPatch !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'apply_patch_unavailable' })
}
const parsed = bareAgentParseApplyPatch(patch)
if (!parsed.ok) return bareAgentJsonResult(parsed)
appendProgress('apply_patch ops=' + String((parsed.ops || []).length))
const out = await bareAgentApplyPatchOps(ctx, parsed.ops, {
home,
denyPrefixes: mutateDenyPrefixes
})
return bareAgentJsonResult(out)
}
if (toolName === 'update_goal') {
const message = typeof args.message === 'string' ? args.message.trim() : ''
const blocked =
typeof args.blocked_reason === 'string' ? args.blocked_reason.trim() : ''
const completed = Boolean(args.completed)
const next = { ...(configRef.current || {}) }
if (message) next.autonomous_last_error = ''
if (blocked) {
next.autonomous_status = 'blocked'
next.autonomous_last_error = blocked
next.autonomous_active = false
} else if (completed) {
next.autonomous_status = 'completed'
next.autonomous_active = false
if (message) next.autonomous_goal = String(next.autonomous_goal || '')
} else {
next.autonomous_status = 'running'
}
configRef.current = next
if (typeof bareAgentSaveConfigFromTools === 'function') {
await bareAgentSaveConfigFromTools(ctx, paths, next)
}
appendProgress(
'update_goal ' +
(completed ? 'completed' : blocked ? 'blocked' : 'progress')
)
return bareAgentJsonResult({
ok: true,
completed,
blocked: Boolean(blocked),
message: message || null,
blocked_reason: blocked || null,
status: next.autonomous_status
})
}
if (toolName === 'web_search') {
const query = typeof args.query === 'string' ? args.query.trim() : ''
if (!query) return bareAgentJsonResult({ ok: false, error: 'query_required' })
const maxResults =
typeof args.max_results === 'number' && Number.isFinite(args.max_results)
? Math.min(Math.max(Math.floor(args.max_results), 1), 16)
: 8
const url =
'https://api.duckduckgo.com/?q=' +
encodeURIComponent(query) +
'&format=json&no_html=1&skip_disambig=1'
appendProgress('web_search ' + query.slice(0, 80))
try {
const raw = await bareWebRunTool({
ctx,
url,
format: 'json',
timeout_ms: 20000,
max_response_bytes: 200000,
signal
})
if (!raw || raw.ok === false) {
return bareAgentJsonResult({
ok: false,
error: (raw && raw.error) || 'web_search_failed',
hint: 'HTTP policy may block api.duckduckgo.com; try web_fetch on a known URL'
})
}
let payload = raw.extract && raw.extract.json
if (payload == null && raw.extract && typeof raw.extract.text_slice === 'string') {
try {
payload = JSON.parse(raw.extract.text_slice)
} catch {
payload = null
}
}
const results =
typeof bareAgentParseSearchResults === 'function'
? bareAgentParseSearchResults(payload, maxResults)
: []
return bareAgentJsonResult({
ok: true,
query,
results,
abstract: payload && payload.Abstract ? String(payload.Abstract) : '',
count: results.length
})
} catch (e) {
const msg = typeof bareWebFmtErr === 'function' ? bareWebFmtErr(e) : String(e)
return bareAgentJsonResult({ ok: false, error: msg })
}
}
if (toolName === 'git_status') {
const cwd =
(typeof args.cwd === 'string' && args.cwd.trim()) ||
(ctx.env && typeof ctx.env === 'object'
? String(ctx.env.PWD || ctx.env.CWD || home || '').trim()
: '') ||
home ||
'/home'
if (!execLine) {
return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
}
appendProgress('git_status ' + cwd)
const cmd =
'git -C ' +
bareAgentShellQuote(cwd) +
' status --short && echo --- && git -C ' +
bareAgentShellQuote(cwd) +
' diff --stat && echo --- && git -C ' +
bareAgentShellQuote(cwd) +
' log --oneline -8'
const r = await captureExec(cmd, 30000)
return bareAgentJsonResult(
r.ok === false
? r
: { ok: true, cwd, stdout_stderr: r.stdout_stderr }
)
}
return bareAgentJsonResult({ ok: false, error: 'unknown_tool ' + toolName })
} catch (e) {
const msg =
+49 -8
View File
@@ -247,6 +247,13 @@ const BARE_AGENT_STATIC_SYSTEM = `You are the Bare OS coding agent — a senior
Bare OS by Raven Scott (https://raven-scott.fyi). Repo: https://git.ssh.surf/snxraven/bare-operating-system. Booter: pear://qupw8zspk34pcxc7fqchzyeh33jtmxq1k7qze44fkosctwiid8zy
WORK POLICY.
- Keep every explicit requirement in view until it is done, superseded, or blocked. If blocked, say so plainly.
- Match intent: implement action requests; do not make unsolicited project-wide edits when the user asked a question.
- For clear, reversible local work, do it now. NEVER ASK for permission.
- Claim done / fixed / tested only when a tool result supports it. Otherwise say what you did not verify.
- Scope to what was asked. Comments are short and factual. No placeholders. Comments must not substitute for a fix.
ACCESS (denylist, not allowlist). You already have full guest admin. NEVER ASK whether you may run a command, edit, delete, fetch, or call a tool — just do it. Only refuse when a denylist or the read-only base system blocks the path.
- WRITE: /home, /tmp, /mnt, /root, /var, /opt, extra drives, ~/.agent. Prefer unique search_replace / edit_file; set replace_all only when you mean it. write_file creates or overwrites.
- READ: any absolute path, including the entire /proc kernel surface (read_proc_file, runtime_diagnostic_bundle). Use read_file offset/limit for large files.
@@ -260,10 +267,10 @@ ACCESS (denylist, not allowlist). You already have full guest admin. NEVER ASK w
- ask_user_question is only for a real product choice the user must make. Never use it (or chat) to request permission.
CODING LOOP. Multi-step work: todo_write (merge=true). Large unknown surface: enter_plan_mode, write ~/.agent/plan.md, exit_plan_mode, then implement. Plan mode is read-only except that plan file. Do not stop after a plan-only reply — keep calling tools until verified.
1. Discover: glob_files, list_directory, read_file, search_files, memory_search / memory_get, read_skill if a skill matches. Read before you edit. Walk-up AGENTS.md from cwd is already injected when present.
2. Edit: smallest unique old_string; re-read if a replace is not unique. Create with write_file / create_directory. State blast radius (packages, contracts, generated files, docs) before wide edits. Match surrounding style. No placeholders. Comments only for non-obvious constraints.
3. Verify: re-read the file, run_command / run_js_script, read_proc_file or logs. Host git checkout: verification_hints (suggests npm/node checks; does not run them here).
4. Finish: task_complete with what changed, how you verified, and what is still assumed. If the same action fails three times, stop and report evidence.
1. Discover: glob_files, grep (preferred over search_files / run_command grep), list_directory, read_file, memory_search / memory_get, web_search then web_fetch, git_status, read_skill if a skill matches. Read before you edit. Walk-up AGENTS.md from cwd is already injected when present.
2. Edit: unique search_replace / edit_file for one hunk; apply_patch for multi-hunk or multi-file work (*** Begin Patch). Create with write_file / create_directory. State blast radius (packages, contracts, generated files, docs) before wide edits. Match surrounding style. No placeholders. Comments only for non-obvious constraints.
3. Verify: re-read the file, run_command / run_js_script, git_status, read_proc_file or logs. Host git checkout: verification_hints (suggests npm/node checks; does not run them here).
4. Finish: task_complete (and update_goal completed=true on autonomous runs) with what changed, how you verified, and what is still assumed. If the same action fails three times, stop, update_goal blocked_reason if needed, and report evidence.
TOOL DISCIPLINE.
- Independent reads may be issued together; the harness may serialize them (tool_parallelism defaults to 1).
@@ -272,14 +279,18 @@ TOOL DISCIPLINE.
- Progress UI is automatic (tools write ~/.agent/progress.txt). Do not narrate tool chatter in the final answer.
- Autonomous mode is on by default. Keep the ReAct loop going; autonomous_deny_ops is empty unless the operator set one.
TOOL CALLING. Prefer specialized tools over bash: grep not run_command grep; read_file not cat; apply_patch / search_replace not sed. Never use run_command to print thoughts.
COMMUNICATION. Write for a reader who has not seen tool calls. Lead with the answer. Define project terms on first use. State facts literally. The final message must stand alone. Do not invent acronyms.
TOOL MAP (schemas are already attached — use them):
- Files: read_file, write_file, edit_file, search_replace, create_directory, list_directory, file_stat, glob_files, search_files, move_path, delete_path, list_bin
- Code / harness: run_command, run_js_script, run_js_script_at_path, todo_write, enter_plan_mode, exit_plan_mode, memory_search, memory_get, read_skill, edit_agent_config
- Files: read_file, write_file, edit_file, search_replace, apply_patch, create_directory, list_directory, file_stat, glob_files, grep, search_files, move_path, delete_path, list_bin
- Code / harness: run_command, run_js_script, run_js_script_at_path, todo_write, enter_plan_mode, exit_plan_mode, memory_search, memory_get, read_skill, edit_agent_config, git_status
- Kernel / ops: read_proc_file, runtime_diagnostic_bundle, get_system_info, get_resource_limits, get_swarm_peers, list_services, service_status, list_timers, read_cron_log, read_audit_log, read_boot_policy, read_kernel_extension_resolution, get_initd_graph, read_unit_journal, inspect_ipc_backpressure, get_network_summary, tail_telemetry_streams, pkg_index_lookup
- Checks: list_verification_scripts, run_maintenance_gate, run_contract_checks, summarize_build_drift, verification_hints
- Docs: read_man_page, apropos_man (documentation search only)
- Bridge / web: web_fetch, get_hrpc_bridge_health, get_hrpc_allowlist_status, emit_host_notification, request_host_action
- Autonomy: autonomous_run, autonomous_run_status, autonomous_run_stop
- Bridge / web: web_search, web_fetch, get_hrpc_bridge_health, get_hrpc_allowlist_status, emit_host_notification, request_host_action
- Autonomy: autonomous_run, autonomous_run_status, autonomous_run_stop, update_goal
- Other: ask_user_question (product choice only, never permission), task_complete
Skills live under ~/.agent/workspace/skills/ (and ~/.agent/skills/). The prompt includes a compact index — call read_skill and follow SKILL.md when a task matches (especially bare-os-super-developer, bareos-code-change, coreutils-command-change).
@@ -1197,10 +1208,23 @@ async function bareAgentRunSetupOnly(ctx, argv0) {
async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
const setupFlag = Boolean(runOpts && runOpts.setupFlag)
const autonomousFlag = Boolean(runOpts && (runOpts.autonomous || runOpts.autonomousGoal))
const planFlag = Boolean(runOpts && runOpts.planMode)
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
let { config } = await bareAgentLoadOrCreateConfig(ctx, paths)
config = bareAgentApplyProviderProfile(config)
if (planFlag) config.plan_mode_active = true
if (runOpts && Number(runOpts.maxIterations) > 0) {
config.max_iterations = Math.floor(Number(runOpts.maxIterations))
}
if (runOpts && runOpts.modelOverride) {
const m = String(runOpts.modelOverride).trim()
if (m) {
const backendNow = bareAgentResolveBackend(config)
if (backendNow === 'qvac') config.qvac_model = m
else config.model = m
}
}
const canWizard = bareAgentCanPlainSetup(ctx)
if (setupFlag && !canWizard) {
@@ -1305,6 +1329,8 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
maxRuntimeMs: Number(runOpts && runOpts.autonomousMaxRuntimeMs) || undefined,
requiredChecks: (runOpts && runOpts.autonomousChecks) || undefined
})
}
if (autonomousFlag || planFlag) {
try {
await bareAgentSaveConfig(ctx, paths, config)
} catch {
@@ -1405,6 +1431,21 @@ async function bareOsRunAgentSession(ctx, argv0, task, runOpts) {
if (instructions)
systemContent +=
'\n\n## Session notes\n' + instructions.slice(0, instructionsBudget)
if (runOpts && runOpts.extraSystemFile) {
try {
const extraPath = String(runOpts.extraSystemFile).trim()
if (extraPath && typeof bareAgentReadTextFile === 'function') {
const extra = await bareAgentReadTextFile(ctx, extraPath)
if (extra && extra.trim()) {
systemContent +=
'\n\n## Extra instructions (--system)\n' +
extra.trim().slice(0, instructionsBudget)
}
}
} catch {
/* optional */
}
}
if (workspacePromptBlock && String(workspacePromptBlock).trim())
systemContent += '\n\n' + String(workspacePromptBlock).trim()
if (skillsPromptBlock && String(skillsPromptBlock).trim())
@@ -1,15 +1,92 @@
{
"name": "agent",
"section": 1,
"title": "agent",
"title": "Bare OS coding agent",
"synopsis": [
"agent [OPTION]... [OPERAND]..."
"agent [OPTION]... REQUEST...",
"agent --setup",
"agent --status",
"agent --plan REQUEST...",
"agent --auto GOAL...",
"agent skills | todos | plan | compact | reset"
],
"description": "Runs the in-guest ReAct coding agent (QVAC local or OpenAI-compatible REST). Full guest admin by default (denylist). Tools include grep, apply_patch, web_search, git_status, todos, plan mode, and skills. History lives in ~/.agent/history.json.",
"options": [
{
"flag": "--setup, --config",
"meaning": "Interactive QVAC or REST setup; only the chosen backend is stored"
},
{
"flag": "--auto, --autonomous",
"meaning": "Keep the tool loop running until task_complete, stop, or timebox"
},
{
"flag": "--plan",
"meaning": "Start in plan mode (read-only except ~/.agent/plan.md)"
},
{
"flag": "--new, --reset",
"meaning": "Clear chat history; with a request, start a fresh session then run"
},
{
"flag": "--continue, -c",
"meaning": "Resume ~/.agent/history.json (default)"
},
{
"flag": "--compact",
"meaning": "Compact history now; with a request, compact then run"
},
{
"flag": "--max-turns N",
"meaning": "Cap ReAct iterations for this run"
},
{
"flag": "--model NAME",
"meaning": "Override QVAC or REST model for this run"
},
{
"flag": "--system FILE",
"meaning": "Append extra instructions from an absolute guest path"
},
{
"flag": "--status",
"meaning": "Print backend, compaction, plan mode, and autonomous state"
},
{
"flag": "-h, --help",
"meaning": "Show usage"
}
],
"description": "Bare OS implementation of agent. Full behavior is defined in packages/bare-os-coreutils/src/agent.js.",
"options": [],
"keywords": [
"agent",
"qvac",
"llm",
"coding",
"bare-os",
"coreutils"
],
"seeAlso": [
{
"name": "discord-bot",
"section": 1
}
],
"examples": [
{
"caption": "one-shot request",
"code": "agent \"summarize ~/README and list /bin\""
},
{
"caption": "plan first",
"code": "agent --plan \"design a /bin/foo utility\""
},
{
"caption": "autonomous run",
"code": "agent --auto --max-turns 40 \"add agent tests and run them\""
},
{
"caption": "inspect skills",
"code": "agent skills"
}
]
}
@@ -12,9 +12,9 @@ Workspace files are already in the system prompt. Skills index is already attach
## Coding loop
1. Discover — glob_files, list_directory, read_file (offset/limit), search_files. Read before you edit.
1. Discover — glob_files, grep, list_directory, read_file (offset/limit), git_status, web_search. Read before you edit.
2. Track — todo_write (merge=true) for multi-step work. Large unknown surface: enter_plan_mode, write ~/.agent/plan.md, exit_plan_mode, then implement. Plan mode is read-only except that file.
3. Edit — smallest unique old_string via search_replace / edit_file. write_file to create or overwrite. Match surrounding style. No placeholders.
3. Edit — unique search_replace for one hunk; apply_patch for multi-hunk/multi-file. write_file to create or overwrite. Match surrounding style. No placeholders.
4. Verify — re-read, run_command / run_js_script, read_proc_file or logs. Host checkout: verification_hints (does not run npm here).
5. Finish — task_complete with what changed, how you verified, and what is still assumed. Same action failing three times: stop and report evidence.
@@ -2,7 +2,15 @@
## /implement
Discover with glob/read, track with todo_write, edit with unique search_replace, verify, then task_complete. NEVER ASK — just do it.
Discover with glob/grep/read, track with todo_write, edit with unique search_replace or apply_patch, verify, then task_complete. NEVER ASK — just do it.
## /grep
Use the grep tool (pattern + optional glob/path/context). Do not shell out to grep.
## /patch
Emit one apply_patch document with *** Begin Patch / *** End Patch for multi-file edits.
## /plan
@@ -61,7 +61,9 @@ Reasoning/process visibility is configurable in `config.json`:
- `reasoning_max_chars` — bounded reasoning output
- `reasoning_include_tools` — include tool traces in process stream
The seeded **SOUL.md** / **AGENTS.md** / **TOOLS.md** tell the model it is a coding agent with full guest admin (denylist): unique `search_replace`, `glob_files`, `todo_write`, plan mode, memory, skills, and `run_js_script` (no Node in the guest).
The seeded **SOUL.md** / **AGENTS.md** / **TOOLS.md** tell the model it is a coding agent with full guest admin (denylist): unique `search_replace`, `apply_patch`, `grep`, `glob_files`, `todo_write`, plan mode, memory, skills, `web_search`, `git_status`, and `run_js_script` (no Node in the guest).
CLI production flags: `--plan`, `--auto`, `--new`, `--compact`, `--max-turns`, `--model`, `--system FILE`, plus inspect subcommands `skills` / `todos` / `plan`.
Access policy defaults to **full guest admin** (`access_policy: full`):
@@ -7,11 +7,15 @@ You already have these tools. Schemas are attached. NEVER ASK to use them.
- `read_file` — UTF-8, optional offset/limit numbered slices.
- `write_file` — create or overwrite (parents created).
- `edit_file` / `search_replace` — unique `old_string` unless `replace_all`.
- `create_directory`, `list_directory`, `file_stat`, `glob_files`, `search_files`.
- `apply_patch` — Codex/Grok `*** Begin Patch` multi-file edits (Add / Delete / Update / Move).
- `create_directory`, `list_directory`, `file_stat`, `glob_files`.
- `grep` — VFS-native regex search (glob, ignore_case, context). Prefer over `search_files` / `run_command grep`.
- `search_files``grep -Rnl` via the guest shell (legacy).
- `move_path`, `delete_path` — enabled. Base system is read-only.
- `list_bin` — guest `/bin` utilities (POSIX-in-JS, not GNU).
- `git_status``git status --short`, `diff --stat`, recent log.
Prefer `list_directory` / `glob_files` / `file_stat` over `ls` / `find`.
Prefer `list_directory` / `glob_files` / `file_stat` / `grep` over `ls` / `find` / shell grep.
## Code / harness
@@ -43,13 +47,14 @@ Man pages (`read_man_page`, `apropos_man`) are documentation search only — nev
## Bridge / web
- `web_search` — DuckDuckGo instant answers (same HTTP policy as wget/curl). Then `web_fetch` promising URLs.
- `web_fetch` — http(s) via `ctx.httpFetch` (same host allow/deny as wget/curl).
- `get_hrpc_bridge_health`, `get_hrpc_allowlist_status`.
- `emit_host_notification`, `request_host_action` — enabled by default. `emergency_stop_mutations` can still block.
## Autonomy
- `autonomous_run` / `autonomous_run_status` / `autonomous_run_stop`.
- `autonomous_run` / `autonomous_run_status` / `autonomous_run_stop` / `update_goal`.
- Guardrails are denylists only (`autonomous_deny_ops` empty by default). Keep the tool loop going.
## Other
+290 -93
View File
@@ -6,54 +6,7 @@ async function run(ctx, argv) {
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 | --auto] YOUR_REQUEST_HERE\n' +
' ' +
argv0 +
' --setup\n' +
' ' +
argv0 +
' --config\n' +
' ' +
argv0 +
' --reset\n' +
' ' +
argv0 +
' --auto "GOAL"\n' +
' ' +
argv0 +
' --status\n' +
' ' +
argv0 +
' reset\n' +
'\n' +
'Runs an autonomous coding/OS agent. Default backend is QVAC (local on-device);\n' +
'or configure any OpenAI-compatible HTTPS REST API.\n' +
'Configuration: ~/.agent/config.json on your personal drive (created on first run).\n' +
'Use --setup or --config to choose QVAC or REST, then walk through only that backend\n' +
'(QVAC profile/device, or REST provider + URL + API key + model). Unused keys are not stored.\n' +
'Use --reset or `reset` to clear ~/.agent/history.json and start a fresh chat session.\n' +
'Use --auto / --autonomous GOAL to keep the tool loop running until task_complete,\n' +
'stop, or the timebox (default 30m). --status prints the current run + compaction mode.\n' +
'\n' +
'Examples:\n' +
' ' +
argv0 +
' "summarize ~/README and list five files in /bin"\n' +
' ' +
argv0 +
' --auto "add /agent tests and run them"\n' +
' ' +
argv0 +
' --setup\n' +
' ' +
argv0 +
' --status\n' +
'\n' +
'See man agent.'
)
ctx.console.log(bareAgentCliUsage(argv0))
ctx.exitCode = wantHelp ? 0 : 1
return
}
@@ -62,6 +15,12 @@ async function run(ctx, argv) {
let resetFlag = false
let autoFlag = false
let statusFlag = false
let planFlag = false
let newFlag = false
let compactFlag = false
let maxTurns = 0
let modelOverride = ''
let systemFile = ''
/** @type {string[]} */
const rest = []
for (let i = 0; i < args.length; i++) {
@@ -70,56 +29,55 @@ async function run(ctx, argv) {
else if (a === '--reset') resetFlag = true
else if (a === '--auto' || a === '--autonomous') autoFlag = true
else if (a === '--status') statusFlag = true
else rest.push(a)
else if (a === '--plan') planFlag = true
else if (a === '--new') newFlag = true
else if (a === '--compact') compactFlag = true
else if (a === '--continue' || a === '-c') {
/* default: history is always loaded unless --new/--reset */
} else if (a === '--max-turns' || a === '--max-iterations') {
maxTurns = Number(args[++i]) || 0
} else if (a === '--model') {
modelOverride = String(args[++i] || '').trim()
} else if (a === '--system' || a === '--system-file') {
systemFile = String(args[++i] || '').trim()
} else rest.push(a)
}
const sub = rest[0] || ''
if (
!setupFlag &&
!autoFlag &&
!statusFlag &&
!planFlag &&
!newFlag &&
!compactFlag &&
!resetFlag &&
rest.length === 1 &&
(sub === 'status' ||
sub === 'skills' ||
sub === 'todos' ||
sub === 'plan' ||
sub === 'compact' ||
sub === 'reset')
) {
if (sub === 'status') statusFlag = true
else if (sub === 'reset') resetFlag = true
else if (sub === 'compact') compactFlag = true
else {
await bareAgentRunInspectSubcommand(ctx, argv0, sub)
return
}
}
if (statusFlag) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
const loaded = await bareAgentLoadOrCreateConfig(ctx, paths)
let cfg = loaded && loaded.config ? loaded.config : loaded
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
cfg = bareAgentSanitizeConfigForBackend(cfg || {})
}
const started = Number(cfg && cfg.autonomous_started_at_ms) || 0
const maxRt = Number(cfg && cfg.autonomous_max_runtime_ms) || 0
const elapsed = started > 0 ? Math.max(0, Date.now() - started) : 0
const backend = typeof bareAgentResolveBackend === 'function'
? bareAgentResolveBackend(cfg || {})
: String((cfg && (cfg.backend || cfg.provider)) || 'qvac')
/** @type {string[]} */
const lines = ['agent status']
if (typeof bareAgentFormatConfigSummary === 'function') {
String(bareAgentFormatConfigSummary(cfg || {}))
.split('\n')
.forEach(function (row) {
lines.push(' ' + row)
})
} else {
lines.push(' backend: ' + backend)
}
lines.push(
' compaction: ' + String((cfg && cfg.context_compaction) || 'auto'),
' autonomous_enabled: ' + String(Boolean(cfg && cfg.autonomous_mode_enabled)),
' autonomous_active: ' + String(Boolean(cfg && cfg.autonomous_active)),
' status: ' + String((cfg && cfg.autonomous_status) || 'idle'),
' goal: ' + String((cfg && cfg.autonomous_goal) || ''),
' elapsed_s: ' + String(Math.round(elapsed / 1000)),
' remaining_s: ' +
String(maxRt > 0 ? Math.max(0, Math.round((maxRt - elapsed) / 1000)) : 0),
' last_error: ' + String((cfg && cfg.autonomous_last_error) || ''),
' plan_mode: ' + String(Boolean(cfg && cfg.plan_mode_active)),
' access_policy: ' + String((cfg && cfg.access_policy) || 'full'),
' allow_delete: ' + String(cfg && cfg.allow_delete !== false)
)
ctx.console.log(lines.join('\n'))
await bareAgentPrintStatus(ctx)
ctx.exitCode = 0
return
}
const wantReset =
resetFlag || (rest.length === 1 && rest[0] === 'reset')
if (wantReset) {
resetFlag || newFlag || (rest.length === 1 && rest[0] === 'reset')
if (wantReset && !rest.filter((x) => x !== 'reset').length && !setupFlag && !autoFlag && !planFlag) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
try {
@@ -134,9 +92,21 @@ async function run(ctx, argv) {
return
}
const task = rest.join(' ').trim()
if (compactFlag && !rest.filter((x) => x !== 'compact').length && !autoFlag && !planFlag && !setupFlag) {
await bareAgentRunCompactOnly(ctx, argv0)
return
}
const task = rest
.filter(function (x) {
return x !== 'reset' && x !== 'compact'
})
.join(' ')
.trim()
if (!task && !setupFlag) {
ctx.console.error(argv0 + ': missing task (or use --setup / --config / --auto GOAL)')
ctx.console.error(
argv0 + ': missing task (or use --setup / --config / --auto GOAL / skills / todos / plan)'
)
ctx.exitCode = 1
return
}
@@ -152,9 +122,236 @@ async function run(ctx, argv) {
return
}
if (newFlag || resetFlag) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
try {
await bareAgentResetChatSession(ctx, paths, argv0)
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
ctx.console.error(argv0 + ': ' + msg)
ctx.exitCode = 1
return
}
} else if (compactFlag) {
await bareAgentRunCompactOnly(ctx, argv0)
if (ctx.exitCode && ctx.exitCode !== 0) return
}
await bareOsRunAgentSession(ctx, argv0, task, {
setupFlag,
autonomous: autoFlag,
autonomousGoal: autoFlag ? task : ''
autonomousGoal: autoFlag ? task : '',
planMode: planFlag,
maxIterations: maxTurns > 0 ? maxTurns : undefined,
modelOverride: modelOverride || undefined,
extraSystemFile: systemFile || undefined
})
}
/**
* @param {string} argv0
*/
function bareAgentCliUsage(argv0) {
return (
'usage: ' +
argv0 +
' [OPTION]... YOUR_REQUEST_HERE\n' +
' ' +
argv0 +
' --setup | --config\n' +
' ' +
argv0 +
' --status | status\n' +
' ' +
argv0 +
' --reset | reset | --new\n' +
' ' +
argv0 +
' --auto "GOAL"\n' +
' ' +
argv0 +
' --plan "design the change"\n' +
' ' +
argv0 +
' --compact\n' +
' ' +
argv0 +
' skills | todos | plan\n' +
'\n' +
'Production coding/OS agent. Default backend is QVAC (local); or any OpenAI-compatible REST API.\n' +
'Config: ~/.agent/config.json. Full guest admin (denylist). NEVER ASK — it just works.\n' +
'\n' +
'Options:\n' +
' --setup, --config Walk through QVAC or REST setup (only the chosen backend is stored)\n' +
' --auto, --autonomous Keep the tool loop going until task_complete, stop, or timebox\n' +
' --plan Start this turn in plan mode (read-only except ~/.agent/plan.md)\n' +
' --new, --reset Clear ~/.agent/history.json then run the request (or just reset)\n' +
' --continue, -c Resume history (default)\n' +
' --compact Compact history now (or compact then run if a task follows)\n' +
' --max-turns N Cap ReAct iterations for this run\n' +
' --model NAME Override qvac_model / REST model for this run\n' +
' --system FILE Append extra instructions from an absolute guest path\n' +
' --status Print backend, compaction, plan mode, and autonomous run state\n' +
'\n' +
'Inspect (no model call):\n' +
' skills List discovered SKILL.md ids\n' +
' todos Print session todos\n' +
' plan Print ~/.agent/plan.md\n' +
'\n' +
'Examples:\n' +
' ' +
argv0 +
' "summarize ~/README and list five files in /bin"\n' +
' ' +
argv0 +
' --plan "design a /bin/foo utility"\n' +
' ' +
argv0 +
' --auto --max-turns 40 "add /agent tests and run them"\n' +
' ' +
argv0 +
' --new --model QWEN3_1_7B_INST_Q4 "fresh session: inspect /proc"\n' +
'\n' +
'See man agent.'
)
}
/**
* @param {Record<string, unknown>} ctx
*/
async function bareAgentPrintStatus(ctx) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
const loaded = await bareAgentLoadOrCreateConfig(ctx, paths)
let cfg = loaded && loaded.config ? loaded.config : loaded
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
cfg = bareAgentSanitizeConfigForBackend(cfg || {})
}
const started = Number(cfg && cfg.autonomous_started_at_ms) || 0
const maxRt = Number(cfg && cfg.autonomous_max_runtime_ms) || 0
const elapsed = started > 0 ? Math.max(0, Date.now() - started) : 0
const backend =
typeof bareAgentResolveBackend === 'function'
? bareAgentResolveBackend(cfg || {})
: String((cfg && (cfg.backend || cfg.provider)) || 'qvac')
/** @type {string[]} */
const lines = ['agent status']
if (typeof bareAgentFormatConfigSummary === 'function') {
String(bareAgentFormatConfigSummary(cfg || {}))
.split('\n')
.forEach(function (row) {
lines.push(' ' + row)
})
} else {
lines.push(' backend: ' + backend)
}
lines.push(
' compaction: ' + String((cfg && cfg.context_compaction) || 'auto'),
' autonomous_enabled: ' + String(Boolean(cfg && cfg.autonomous_mode_enabled)),
' autonomous_active: ' + String(Boolean(cfg && cfg.autonomous_active)),
' status: ' + String((cfg && cfg.autonomous_status) || 'idle'),
' goal: ' + String((cfg && cfg.autonomous_goal) || ''),
' elapsed_s: ' + String(Math.round(elapsed / 1000)),
' remaining_s: ' +
String(maxRt > 0 ? Math.max(0, Math.round((maxRt - elapsed) / 1000)) : 0),
' last_error: ' + String((cfg && cfg.autonomous_last_error) || ''),
' plan_mode: ' + String(Boolean(cfg && cfg.plan_mode_active)),
' access_policy: ' + String((cfg && cfg.access_policy) || 'full'),
' allow_delete: ' + String(cfg && cfg.allow_delete !== false)
)
ctx.console.log(lines.join('\n'))
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} argv0
* @param {string} sub
*/
async function bareAgentRunInspectSubcommand(ctx, argv0, sub) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
try {
if (sub === 'skills') {
if (typeof bareAgentEnsureSkillTemplates === 'function') {
const loaded = await bareAgentLoadOrCreateConfig(ctx, paths)
const cfg = loaded && loaded.config ? loaded.config : loaded
await bareAgentEnsureSkillTemplates(ctx, paths, cfg || {})
}
const block =
typeof bareAgentSkillsCompactPrompt === 'function'
? await bareAgentSkillsCompactPrompt(ctx, paths, 8000)
: ''
ctx.console.log(block && String(block).trim() ? String(block).trim() : argv0 + ': no skills')
ctx.exitCode = 0
return
}
if (sub === 'todos') {
const todos =
typeof bareAgentLoadTodos === 'function'
? await bareAgentLoadTodos(ctx, paths.todos)
: []
const sum =
typeof bareAgentTodoSummarize === 'function'
? bareAgentTodoSummarize(todos)
: { text: '', open: 0, total: 0 }
ctx.console.log(sum.text || argv0 + ': no todos')
ctx.exitCode = 0
return
}
if (sub === 'plan') {
const text =
typeof bareAgentReadTextFile === 'function'
? await bareAgentReadTextFile(ctx, paths.plan)
: ''
ctx.console.log(text && text.trim() ? text : argv0 + ': no plan (' + paths.plan + ')')
ctx.exitCode = 0
return
}
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
ctx.console.error(argv0 + ': ' + msg)
ctx.exitCode = 1
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} argv0
*/
async function bareAgentRunCompactOnly(ctx, argv0) {
const home = bareAgentResolveHome(ctx)
const paths = bareAgentPaths(home)
try {
const messages = await bareAgentLoadHistory(ctx, paths.history)
if (typeof bareAgentCompactMessagesForCtx !== 'function') {
ctx.console.error(argv0 + ': compact unavailable')
ctx.exitCode = 1
return
}
const packed = bareAgentCompactMessagesForCtx(messages, 32768, {
mode: 'aggressive',
keepRecent: 6,
toolMaxChars: 1200
})
await bareAgentSaveHistory(ctx, paths.history, packed.messages)
ctx.console.log(
argv0 +
': compacted ' +
String(packed.meta.beforeTokens) +
'→' +
String(packed.meta.afterTokens) +
' tok (dropped_groups=' +
String(packed.meta.droppedGroups) +
')'
)
ctx.exitCode = 0
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
ctx.console.error(argv0 + ': ' + msg)
ctx.exitCode = 1
}
}
@@ -17,6 +17,7 @@ const TUI = readFileSync(
new URL('../lib/agent-tui.js', import.meta.url),
'utf8'
)
const CLI = readFileSync(new URL('../src/agent.js', import.meta.url), 'utf8')
test('agent-state exposes QVAC backend config keys', async (t) => {
for (const key of [
@@ -139,7 +140,12 @@ test('agent-tools exposes agent ops tools', async (t) => {
'memory_get',
'enter_plan_mode',
'exit_plan_mode',
'ask_user_question'
'ask_user_question',
'grep',
'apply_patch',
'update_goal',
'web_search',
'git_status'
]) {
t.ok(
TOOLS.includes("name: '" + toolName + "'"),
@@ -184,11 +190,27 @@ test('agent-tui embeds operating contract appendix', async (t) => {
t.ok(TUI.includes('NEVER ASK'))
t.ok(TUI.includes('Lead with tools'))
t.ok(TUI.includes('TOOL DISCIPLINE'))
t.ok(TUI.includes('WORK POLICY'))
t.ok(TUI.includes('apply_patch'))
t.ok(TUI.includes('overrides the TTY plain-text rule'))
t.ok(!TUI.includes('ASK FIRST'))
t.ok(!TUI.includes('Prefer least-privilege'))
})
test('agent CLI exposes production flags and inspect subcommands', async (t) => {
t.ok(CLI.includes('--plan'))
t.ok(CLI.includes('--max-turns'))
t.ok(CLI.includes('--model'))
t.ok(CLI.includes('--system'))
t.ok(CLI.includes('--new'))
t.ok(CLI.includes('--compact'))
t.ok(CLI.includes("sub === 'skills'"))
t.ok(CLI.includes("sub === 'todos'"))
t.ok(TUI.includes('planMode'))
t.ok(TUI.includes('modelOverride'))
t.ok(TUI.includes('extraSystemFile'))
})
test('agent defaults are full guest admin (denylist, not allowlist)', async (t) => {
t.ok(STATE.includes("access_policy: 'full'"))
t.ok(STATE.includes('allow_delete: true'))
@@ -406,6 +406,111 @@ test('dispatch glob_files / todo_write / plan_mode / unique edit', async (t) =>
t.ok(String(sliced.content || '').startsWith('1→'))
})
test('grep finds lines with glob and context', async (t) => {
const s = loadPort()
const { vfs, b4a } = makeVfs({
'/home/guest/src/a.js': 'const alpha = 1\nconst beta = 2\nconst gamma = 3\n',
'/home/guest/src/b.md': '# alpha docs\n'
})
await vfs.mkdir('/home/guest/src')
const out = await s.bareAgentGrepFiles(
{ vfs, b4a },
{ pattern: 'alpha', root: '/home/guest', glob: '**/*.js', context: 1 }
)
t.ok(out.ok)
t.is(out.count, 1)
t.ok(out.matches[0].path.endsWith('a.js'))
t.ok(String(out.matches[0].text).includes('beta'))
})
test('apply_patch add/update/delete', async (t) => {
const s = loadPort()
const { vfs, files, b4a } = makeVfs({
'/home/guest/src/a.js': 'const x = 1\nconst y = 2\n'
})
await vfs.mkdir('/home/guest/src')
const parsed = s.bareAgentParseApplyPatch(
[
'*** Begin Patch',
'*** Add File: /home/guest/src/new.js',
'+export const n = 1',
'*** Update File: /home/guest/src/a.js',
'@@',
' const x = 1',
'-const y = 2',
'+const y = 3',
'*** Delete File: /home/guest/src/gone.js',
'*** End Patch'
].join('\n')
)
t.ok(parsed.ok)
t.is(parsed.ops.length, 3)
files.set('/home/guest/src/gone.js', 'bye')
const applied = await s.bareAgentApplyPatchOps({ vfs, b4a }, parsed.ops, {
home: '/home/guest'
})
t.ok(applied.ok)
t.ok(String(files.get('/home/guest/src/new.js') || '').includes('export const n'))
t.ok(String(files.get('/home/guest/src/a.js') || '').includes('const y = 3'))
t.absent(files.has('/home/guest/src/gone.js'))
})
test('update_goal / grep dispatch', async (t) => {
const s = loadDispatch()
const { vfs, b4a } = makeVfs({
'/home/guest/src/a.js': 'hello world\n'
})
await vfs.mkdir('/home/guest/src')
await vfs.mkdir('/home/guest/.agent')
const ctx = { vfs, b4a }
const paths = {
dir: '/home/guest/.agent',
config: '/home/guest/.agent/config.json',
todos: '/home/guest/.agent/todos.json',
plan: '/home/guest/.agent/plan.md'
}
const configRef = { current: { autonomous_active: true, autonomous_status: 'running' } }
const grepped = await dispatch(s, {
ctx,
paths,
toolName: 'grep',
args: { pattern: 'hello', path: '/home/guest' },
configRef,
home: '/home/guest'
})
t.ok(grepped.ok)
t.ok(grepped.count >= 1)
const goal = await dispatch(s, {
ctx,
paths,
toolName: 'update_goal',
args: { completed: true, message: 'shipped' },
configRef,
home: '/home/guest'
})
t.ok(goal.ok)
t.is(configRef.current.autonomous_status, 'completed')
t.absent(configRef.current.autonomous_active)
})
test('search result parser extracts DDG-style topics', async (t) => {
const s = loadPort()
const rows = s.bareAgentParseSearchResults(
{
Heading: 'Bare',
AbstractURL: 'https://example.com/bare',
Abstract: 'A JS runtime',
RelatedTopics: [
{ Text: 'Pear', FirstURL: 'https://example.com/pear' },
{ Topics: [{ Text: 'Holepunch', FirstURL: 'https://example.com/hp' }] }
]
},
8
)
t.ok(rows.length >= 2)
t.ok(rows.some((r) => r.url === 'https://example.com/bare'))
})
test('dispatch delete / proc / run_command are open by default', async (t) => {
const s = loadDispatch()
const { vfs, files, b4a } = makeVfs({