Files
bare-operating-system/packages/bare-os-coreutils/lib/agent/agent-grok-port.js
T
2026-08-18 18:11:28 -04:00

1985 lines
58 KiB
JavaScript

/**
* Guest-safe Grok Build harness ports: todos, plan mode, memory, glob,
* AGENTS.md walk-up, JSON hooks, and search_replace uniqueness.
*/
var BARE_AGENT_PLAN_READONLY_TOOLS = Object.freeze({
read_file: 1,
list_directory: 1,
list_dir: 1,
file_stat: 1,
search_files: 1,
grep: 1,
glob_files: 1,
glob: 1,
get_system_info: 1,
list_bin: 1,
read_man_page: 1,
apropos_man: 1,
read_proc_file: 1,
get_swarm_peers: 1,
get_resource_limits: 1,
web_fetch: 1,
web_search: 1,
git_status: 1,
read_skill: 1,
list_services: 1,
service_status: 1,
list_timers: 1,
read_cron_log: 1,
read_audit_log: 1,
read_boot_policy: 1,
read_kernel_extension_resolution: 1,
get_initd_graph: 1,
read_unit_journal: 1,
inspect_ipc_backpressure: 1,
get_network_summary: 1,
tail_telemetry_streams: 1,
pkg_index_lookup: 1,
list_verification_scripts: 1,
verification_hints: 1,
runtime_diagnostic_bundle: 1,
memory_search: 1,
memory_get: 1,
memory_append: 1,
list_skills: 1,
fuzzy_find: 1,
wait_for: 1,
read_many: 1,
git_diff: 1,
git_log: 1,
git_show: 1,
git_blame: 1,
history_search: 1,
list_scheduled: 1,
find_symbol: 1,
diff_files: 1,
export_session: 1,
remember: 1,
todo_write: 1,
enter_plan_mode: 1,
exit_plan_mode: 1,
ask_user_question: 1,
update_goal: 1,
autonomous_run_status: 1,
get_hrpc_bridge_health: 1,
get_hrpc_allowlist_status: 1
})
var BARE_AGENT_PLAN_WRITE_TOOLS = Object.freeze({
write_file: 1,
edit_file: 1,
search_replace: 1,
apply_patch: 1
})
/**
* @param {string} pattern
* @returns {RegExp | null}
*/
function bareAgentGlobToRegExp(pattern) {
const src = String(pattern || '').trim()
if (!src) return null
let out = '^'
for (let i = 0; i < src.length; i++) {
const c = src.charAt(i)
if (c === '*' && src.charAt(i + 1) === '*') {
const next = src.charAt(i + 2)
if (next === '/' || next === '') {
out += '.*'
i += next === '/' ? 2 : 1
continue
}
}
if (c === '*') {
out += '[^/]*'
continue
}
if (c === '?') {
out += '[^/]'
continue
}
if ('\\^$+()[]{}|.'.indexOf(c) !== -1) out += '\\' + c
else out += c
}
out += '$'
try {
return new RegExp(out)
} catch {
return null
}
}
/**
* @param {string} rel
* @param {string} pattern
*/
function bareAgentGlobMatch(rel, pattern) {
const re = bareAgentGlobToRegExp(pattern)
if (!re) return false
const n = String(rel || '').replace(/^\/+/, '')
if (re.test(n)) return true
const base = n.split('/').pop() || n
return re.test(base)
}
/**
* @param {unknown} updates
* @param {{ merge?: boolean }} [opts]
* @param {{ id: string, content: string, status: string }[]} [prev]
*/
function bareAgentTodoApply(updates, opts, prev) {
const merge = Boolean(opts && opts.merge)
/** @type {{ id: string, content: string, status: string }[]} */
const list = merge && Array.isArray(prev) ? prev.slice() : []
const byId = Object.create(null)
for (let i = 0; i < list.length; i++) byId[list[i].id] = i
const rows = Array.isArray(updates) ? updates : []
const seen = Object.create(null)
for (let i = 0; i < rows.length; i++) {
const row = rows[i] && typeof rows[i] === 'object' ? rows[i] : {}
const id = String(row.id || '').trim()
if (!id) throw new Error('todo_id_required')
if (seen[id]) throw new Error('duplicate_todo_id:' + id)
seen[id] = 1
const statusRaw = String(row.status || 'pending').trim().toLowerCase()
const status =
statusRaw === 'in_progress' || statusRaw === 'completed' || statusRaw === 'cancelled'
? statusRaw
: 'pending'
const content = String(row.content == null ? '' : row.content).trim()
if (byId[id] != null) {
const cur = list[byId[id]]
if (content) cur.content = content
cur.status = status
} else {
list.push({ id, content: content || id, status })
byId[id] = list.length - 1
}
}
return list
}
/**
* @param {{ id: string, content: string, status: string }[]} todos
*/
function bareAgentTodoSummarize(todos) {
const rows = Array.isArray(todos) ? todos : []
let pending = 0
let inProgress = 0
let completed = 0
const lines = []
for (let i = 0; i < rows.length; i++) {
const t = rows[i]
const st = String(t.status || 'pending')
if (st === 'completed' || st === 'cancelled') completed++
else if (st === 'in_progress') inProgress++
else pending++
lines.push('- [' + st + '] ' + t.id + ': ' + String(t.content || '').slice(0, 160))
}
return {
total: rows.length,
pending,
in_progress: inProgress,
completed,
open: pending + inProgress,
text: lines.join('\n')
}
}
/**
* @param {{ open?: number, turnsSinceTodoWrite?: number, nudgeEnabled?: boolean }} opts
*/
function bareAgentTodoNudgeText(opts) {
const o = opts && typeof opts === 'object' ? opts : {}
if (o.nudgeEnabled === false) return ''
const turns = Number(o.turnsSinceTodoWrite) || 0
const open = Number(o.open) || 0
if (open > 0 && turns >= 3) {
return (
'Open todos remain (' +
String(open) +
'). Use todo_write to mark progress or complete items before stopping.'
)
}
if (open === 0 && turns >= 5) {
return 'Multi-step work: call todo_write to track remaining steps (merge=true to update one id).'
}
return ''
}
/**
* @param {string} toolName
* @param {Record<string, unknown>} args
* @param {{ plan?: string }} paths
*/
function bareAgentPlanModeToolAllowed(toolName, args, paths) {
const name = String(toolName || '')
if (BARE_AGENT_PLAN_READONLY_TOOLS[name]) return true
if (!BARE_AGENT_PLAN_WRITE_TOOLS[name]) return false
const plan = String((paths && paths.plan) || '')
const p = String(args.path || args.file_path || '')
return Boolean(plan && p && p === plan)
}
/**
* @param {string} hay
* @param {string} needle
*/
function bareAgentCountOccurrences(hay, needle) {
if (!needle) return 0
let n = 0
let from = 0
while (from <= hay.length) {
const i = hay.indexOf(needle, from)
if (i === -1) break
n++
from = i + Math.max(needle.length, 1)
}
return n
}
/**
* @param {string} text
* @param {string} oldStr
* @param {string} newStr
* @param {boolean} replaceAll
*/
function bareAgentSearchReplaceApply(text, oldStr, newStr, replaceAll) {
const prev = String(text == null ? '' : text)
if (!oldStr) {
if (prev.trim()) {
return { ok: false, error: 'empty_old_string_cannot_overwrite' }
}
return { ok: true, next: String(newStr == null ? '' : newStr), replacements: 1 }
}
const count = bareAgentCountOccurrences(prev, oldStr)
if (count === 0) return { ok: false, error: 'old_string not found', count: 0 }
if (count > 1 && !replaceAll) {
return {
ok: false,
error: 'old_string not unique',
count,
hint: 'add surrounding lines to make the match unique, or set replace_all true'
}
}
const next = replaceAll ? prev.split(oldStr).join(newStr) : prev.replace(oldStr, newStr)
return { ok: true, next, replacements: replaceAll ? count : 1 }
}
/**
* @param {string} text
* @param {{ offset?: number, limit?: number, numbered?: boolean }} [opts]
*/
function bareAgentSliceFileLines(text, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const raw = String(text == null ? '' : text)
const lines = raw.split('\n')
if (lines.length && lines[lines.length - 1] === '') lines.pop()
const offset =
typeof o.offset === 'number' && o.offset >= 1 ? Math.floor(o.offset) : 1
const limit =
typeof o.limit === 'number' && o.limit >= 1
? Math.floor(o.limit)
: lines.length
const start = Math.min(lines.length, offset - 1)
const end = Math.min(lines.length, start + limit)
const slice = lines.slice(start, end)
const numbered = o.numbered !== false
const body = numbered
? slice
.map(function (line, i) {
return String(start + i + 1) + '→' + line
})
.join('\n')
: slice.join('\n')
return {
content: body,
start_line: start + 1,
end_line: end,
total_lines: lines.length,
truncated: start > 0 || end < lines.length
}
}
/**
* @param {string} query
*/
function bareAgentMemoryTokens(query) {
return String(query || '')
.toLowerCase()
.split(/[^a-z0-9_./-]+/)
.filter(function (t) {
return t.length > 1
})
}
/**
* @param {string} text
* @param {string[]} tokens
*/
function bareAgentMemoryScore(text, tokens) {
const low = String(text || '').toLowerCase()
if (!tokens.length || !low) return 0
let hits = 0
for (let i = 0; i < tokens.length; i++) {
if (low.indexOf(tokens[i]) !== -1) hits++
}
return hits / tokens.length
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} startDir
* @param {number} [maxHops]
* @returns {Promise<string[]>}
*/
async function bareAgentDiscoverAgentsMdPaths(ctx, startDir, maxHops) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return []
/** @type {string[]} */
const found = []
const seen = Object.create(null)
let dir = String(startDir || '').replace(/\/+$/, '') || '/'
const hops = Math.min(Math.max(Number(maxHops) || 12, 1), 24)
const names = ['AGENTS.md', 'Claude.md', 'CLAUDE.md']
for (let n = 0; n < hops; n++) {
for (let i = 0; i < names.length; i++) {
const p = (dir === '/' ? '' : dir) + '/' + names[i]
if (seen[p]) continue
seen[p] = 1
try {
const buf = await vfs.readFile(p)
if (buf && buf.length) found.push(p)
} catch {
/* missing */
}
}
const rulesDirs = [dir + '/.grok/rules', dir + '/.agent/rules']
for (let r = 0; r < rulesDirs.length; r++) {
if (typeof vfs.readdir !== 'function') continue
let entries = []
try {
entries = await vfs.readdir(rulesDirs[r])
} catch {
continue
}
if (!Array.isArray(entries)) continue
entries = entries.slice().sort()
for (let e = 0; e < entries.length; e++) {
const name = String(entries[e] || '')
if (!/\.md$/i.test(name)) continue
const p = rulesDirs[r] + '/' + name
if (seen[p]) continue
seen[p] = 1
try {
const buf = await vfs.readFile(p)
if (buf && buf.length) found.push(p)
} catch {
/* skip */
}
}
}
if (dir === '/' || dir === '') break
const slash = dir.lastIndexOf('/')
dir = slash <= 0 ? '/' : dir.slice(0, slash)
}
return found
}
/**
* @param {Record<string, unknown>} ctx
* @param {string[]} files
* @param {number} [maxChars]
*/
async function bareAgentLoadAgentsMdPrompt(ctx, files, maxChars) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return ''
const cap = Math.min(Math.max(Number(maxChars) || 6000, 500), 16000)
const parts = []
let used = 0
for (let i = 0; i < files.length; i++) {
if (used >= cap) break
try {
const buf = await vfs.readFile(files[i])
if (!buf || !buf.length) continue
const t =
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
? ctx.b4a.toString(buf)
: String(new TextDecoder().decode(buf))
const body = String(t || '').trim()
if (!body) continue
const block = '### ' + files[i] + '\n' + body
const room = cap - used
parts.push(block.slice(0, room))
used += Math.min(block.length, room)
} catch {
/* skip */
}
}
if (!parts.length) return ''
return (
'## Discovered project agent files (cwd walk-up, Grok-style)\n' +
parts.join('\n\n')
)
}
/**
* @param {Record<string, unknown>} hook
* @param {string} toolName
* @param {Record<string, unknown>} args
*/
function bareAgentHookDenies(hook, toolName, args) {
if (!hook || typeof hook !== 'object') return ''
const event = String(hook.event || hook.type || 'PreToolUse')
if (event !== 'PreToolUse' && event !== 'pre') return ''
const tools = Array.isArray(hook.tools) ? hook.tools.map(String) : []
if (tools.length && tools.indexOf(toolName) === -1) return ''
const reSrc = String(hook.deny_regex || hook.denyRegex || '').trim()
if (!reSrc) return ''
let re
try {
re = new RegExp(reSrc, 'i')
} catch {
return ''
}
const blob =
toolName +
' ' +
String(args.command || args.path || args.file_path || args.pattern || '')
if (!re.test(blob)) return ''
return String(hook.reason || hook.message || 'blocked by ~/.agent/hooks').slice(0, 240)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} hooksDir
* @param {string} toolName
* @param {Record<string, unknown>} args
*/
async function bareAgentRunPreToolHooks(ctx, hooksDir, toolName, args) {
if (typeof bareAgentRunHooks === 'function') {
const out = await bareAgentRunHooks(ctx, hooksDir, 'PreToolUse', toolName, args, '')
return out.deny || ''
}
return ''
}
/**
* @param {Record<string, unknown>} ctx
* @param {unknown} buf
*/
function bareAgentPortDecode(ctx, buf) {
if (!buf || !buf.length) return ''
if (
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.toString === 'function'
) {
return ctx.b4a.toString(buf)
}
return String(new TextDecoder().decode(buf))
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} text
*/
function bareAgentPortEncode(ctx, text) {
if (
typeof ctx.b4a !== 'undefined' &&
ctx.b4a &&
typeof ctx.b4a.from === 'function'
) {
return ctx.b4a.from(String(text == null ? '' : text))
}
return new TextEncoder().encode(String(text == null ? '' : text))
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
*/
async function bareAgentReadTextFile(ctx, path) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readFile !== 'function') return ''
try {
const buf = await vfs.readFile(path)
return bareAgentPortDecode(ctx, buf)
} catch {
return ''
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {string} text
*/
async function bareAgentWriteTextFile(ctx, path, text) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.writeFile !== 'function') {
throw new Error('vfs.writeFile unavailable')
}
if (typeof vfs.mkdir === 'function') {
const dir = String(path || '').replace(/\/[^/]+$/, '')
if (dir && dir !== path) {
try {
await vfs.mkdir(dir, { recursive: true })
} catch {
/* parent may already exist */
}
}
}
await vfs.writeFile(path, bareAgentPortEncode(ctx, text))
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {unknown} fallback
*/
async function bareAgentReadJsonFile(ctx, path, fallback) {
const t = await bareAgentReadTextFile(ctx, path)
if (!t.trim()) return fallback
try {
return JSON.parse(t)
} catch {
return fallback
}
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {unknown} value
*/
async function bareAgentWriteJsonFile(ctx, path, value) {
await bareAgentWriteTextFile(ctx, path, JSON.stringify(value, null, 2) + '\n')
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
*/
async function bareAgentVfsIsDir(ctx, path) {
const vfs = ctx && ctx.vfs
if (!vfs) return false
if (typeof vfs.lstat === 'function' || typeof vfs.stat === 'function') {
try {
const st =
typeof vfs.lstat === 'function' ? await vfs.lstat(path) : await vfs.stat(path)
if (st && typeof st.isDirectory === 'function') return Boolean(st.isDirectory())
if (st && typeof st === 'object' && 'isDirectory' in st) {
return Boolean(/** @type {{ isDirectory?: unknown }} */ (st).isDirectory)
}
} catch {
return false
}
}
if (typeof vfs.readdir === 'function') {
try {
await vfs.readdir(path)
return true
} catch {
return false
}
}
return false
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} root
* @param {{ maxFiles?: number, maxDepth?: number }} [opts]
* @returns {Promise<string[]>}
*/
async function bareAgentVfsWalkFiles(ctx, root, opts) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readdir !== 'function') return []
const maxFiles = Math.min(Math.max(Number(opts && opts.maxFiles) || 400, 1), 4000)
const maxDepth = Math.min(Math.max(Number(opts && opts.maxDepth) || 8, 1), 16)
const skip = Object.create(null)
skip['.git'] = 1
skip['node_modules'] = 1
skip['.bare-os'] = 1
const rootN = String(root || '/').replace(/\/+$/, '') || '/'
const rules =
opts && Array.isArray(opts.ignore)
? opts.ignore
: typeof bareAgentLoadIgnoreRules === 'function'
? await bareAgentLoadIgnoreRules(ctx, rootN)
: []
/** @type {string[]} */
const out = []
/** @type {{ dir: string, depth: number }[]} */
const queue = [{ dir: rootN, depth: 0 }]
while (queue.length && out.length < maxFiles) {
const cur = queue.shift()
if (!cur) break
let names
try {
names = await vfs.readdir(cur.dir)
} catch {
continue
}
if (!Array.isArray(names)) continue
for (let i = 0; i < names.length && out.length < maxFiles; i++) {
const name = String(names[i] || '')
if (!name || name === '.' || name === '..' || skip[name]) continue
const full = (cur.dir === '/' ? '' : cur.dir) + '/' + name
let rel = full
if (rootN !== '/' && full.indexOf(rootN + '/') === 0) rel = full.slice(rootN.length + 1)
else if (full.charAt(0) === '/') rel = full.slice(1)
const isDir = await bareAgentVfsIsDir(ctx, full)
if (rules.length && bareAgentIgnoreMatch(rel, isDir, rules)) continue
if (isDir) {
if (cur.depth + 1 < maxDepth) queue.push({ dir: full, depth: cur.depth + 1 })
} else {
out.push(full)
}
}
}
return out
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} root
* @param {string} pattern
* @param {{ maxFiles?: number, maxDepth?: number }} [opts]
*/
async function bareAgentGlobFiles(ctx, root, pattern, opts) {
const files = await bareAgentVfsWalkFiles(ctx, root, opts)
const rootN = String(root || '/').replace(/\/+$/, '') || '/'
/** @type {string[]} */
const hits = []
for (let i = 0; i < files.length; i++) {
const abs = files[i]
let rel = abs
if (rootN !== '/' && abs.indexOf(rootN + '/') === 0) rel = abs.slice(rootN.length + 1)
else if (abs.charAt(0) === '/') rel = abs.slice(1)
if (bareAgentGlobMatch(rel, pattern) || bareAgentGlobMatch(abs, pattern)) {
hits.push(abs)
}
}
return hits
}
/**
* @param {Record<string, unknown>} ctx
* @param {string[]} files
* @param {string} query
* @param {{ maxHits?: number, snippet?: number }} [opts]
*/
async function bareAgentMemorySearchFiles(ctx, files, query, opts) {
const tokens = bareAgentMemoryTokens(query)
const maxHits = Math.min(Math.max(Number(opts && opts.maxHits) || 8, 1), 24)
const snippet = Math.min(Math.max(Number(opts && opts.snippet) || 280, 80), 1200)
/** @type {{ path: string, score: number, snippet: string }[]} */
const scored = []
for (let i = 0; i < files.length; i++) {
const path = files[i]
const text = await bareAgentReadTextFile(ctx, path)
if (!text) continue
const score = tokens.length ? bareAgentMemoryScore(text, tokens) : 0.15
if (score <= 0) continue
let snip = text.trim().replace(/\s+/g, ' ').slice(0, snippet)
if (tokens.length) {
const low = text.toLowerCase()
let at = -1
for (let t = 0; t < tokens.length; t++) {
const j = low.indexOf(tokens[t])
if (j !== -1 && (at === -1 || j < at)) at = j
}
if (at >= 0) {
const start = Math.max(0, at - 40)
snip =
(start > 0 ? '…' : '') +
text.slice(start, start + snippet).replace(/\s+/g, ' ')
}
}
scored.push({ path, score: Math.round(score * 1000) / 1000, snippet: snip })
}
scored.sort(function (a, b) {
return b.score - a.score
})
return scored.slice(0, maxHits)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
*/
async function bareAgentLoadTodos(ctx, path) {
const raw = await bareAgentReadJsonFile(ctx, path, [])
if (!Array.isArray(raw)) return []
/** @type {{ id: string, content: string, status: string }[]} */
const out = []
for (let i = 0; i < raw.length; i++) {
const row = raw[i] && typeof raw[i] === 'object' ? raw[i] : null
if (!row) continue
const id = String(row.id || '').trim()
if (!id) continue
const statusRaw = String(row.status || 'pending').trim().toLowerCase()
const status =
statusRaw === 'in_progress' || statusRaw === 'completed' || statusRaw === 'cancelled'
? statusRaw
: 'pending'
out.push({
id,
content: String(row.content == null ? id : row.content),
status
})
}
return out
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} path
* @param {{ id: string, content: string, status: string }[]} todos
*/
async function bareAgentSaveTodos(ctx, path, todos) {
await bareAgentWriteJsonFile(ctx, path, Array.isArray(todos) ? todos : [])
}
/**
* @param {Record<string, unknown>} args
*/
function bareAgentToolPathArg(args) {
if (!args || typeof args !== 'object') return ''
if (typeof args.path === 'string' && args.path.trim()) return args.path.trim()
if (typeof args.file_path === 'string' && args.file_path.trim()) {
return args.file_path.trim()
}
return ''
}
/**
* @param {string} text
*/
function bareAgentLooksBinaryText(text) {
const s = String(text || '')
if (!s) return false
if (s.indexOf('\0') !== -1) return true
let bad = 0
const n = Math.min(s.length, 800)
for (let i = 0; i < n; i++) {
const c = s.charCodeAt(i)
if (c === 9 || c === 10 || c === 13) continue
if (c < 32) bad++
}
return bad > 8
}
/**
* Guest-native grep (Grok-style). VFS walk + JS regex — no host rg/node.
* @param {Record<string, unknown>} ctx
* @param {{
* pattern: string,
* root?: string,
* glob?: string,
* ignore_case?: boolean,
* before?: number,
* after?: number,
* context?: number,
* max_matches?: number,
* files_with_matches?: boolean
* }} opts
*/
async function bareAgentGrepFiles(ctx, opts) {
const o = opts && typeof opts === 'object' ? opts : {}
const pattern = String(o.pattern || '')
if (!pattern) return { ok: false, error: 'pattern_required' }
let re
try {
re = new RegExp(pattern, o.ignore_case ? 'i' : '')
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
return { ok: false, error: 'bad_regex', detail: msg }
}
const root = String(o.root || '/').replace(/\/+$/, '') || '/'
const before = Math.min(
8,
Math.max(0, Math.floor(Number(o.before != null ? o.before : o.context) || 0))
)
const after = Math.min(
8,
Math.max(0, Math.floor(Number(o.after != null ? o.after : o.context) || 0))
)
const maxMatches = Math.min(
200,
Math.max(1, Math.floor(Number(o.max_matches) || 50))
)
const mode = String(o.output_mode || '').toLowerCase()
const filesOnly = Boolean(o.files_with_matches) || mode === 'files_with_matches'
const countOnly = Boolean(o.count) || mode === 'count'
const glob = typeof o.glob === 'string' && o.glob.trim() ? o.glob.trim() : ''
const files = glob
? await bareAgentGlobFiles(ctx, root, glob, { maxFiles: 800, maxDepth: 12 })
: await bareAgentVfsWalkFiles(ctx, root, { maxFiles: 800, maxDepth: 12 })
/** @type {{ path: string, line?: number, text?: string }[]} */
const matches = []
/** @type {string[]} */
const filesHit = []
/** @type {{ path: string, count: number }[]} */
const counts = []
let truncated = false
for (let f = 0; f < files.length; f++) {
const path = files[f]
const text = await bareAgentReadTextFile(ctx, path)
if (!text || bareAgentLooksBinaryText(text)) continue
const lines = text.split('\n')
if (lines.length && lines[lines.length - 1] === '') lines.pop()
let fileHit = false
let fileCount = 0
for (let i = 0; i < lines.length; i++) {
re.lastIndex = 0
if (!re.test(lines[i])) continue
fileHit = true
fileCount++
if (filesOnly || countOnly) continue
const start = Math.max(0, i - before)
const end = Math.min(lines.length, i + 1 + after)
const slice = lines.slice(start, end)
const body =
before || after
? slice
.map(function (ln, j) {
const n = start + j + 1
const mark = n === i + 1 ? ':' : '-'
return String(n) + mark + ln
})
.join('\n')
: String(i + 1) + ':' + lines[i]
matches.push({ path, line: i + 1, text: body })
if (matches.length >= maxMatches) {
truncated = true
break
}
}
if (fileHit && filesOnly) {
filesHit.push(path)
if (filesHit.length >= maxMatches) {
truncated = true
break
}
}
if (fileHit && countOnly) {
counts.push({ path, count: fileCount })
if (counts.length >= maxMatches) {
truncated = true
break
}
}
if (truncated) break
}
if (filesOnly) {
return {
ok: true,
pattern,
root,
glob: glob || null,
output_mode: 'files_with_matches',
files_with_matches: filesHit,
count: filesHit.length,
truncated
}
}
if (countOnly) {
let total = 0
for (let i = 0; i < counts.length; i++) total += counts[i].count
return {
ok: true,
pattern,
root,
glob: glob || null,
output_mode: 'count',
files: counts,
count: total,
truncated
}
}
return {
ok: true,
pattern,
root,
glob: glob || null,
output_mode: 'content',
matches,
count: matches.length,
truncated
}
}
/**
* Codex / Grok apply_patch parser (Begin Patch … End Patch).
* @param {string} text
* @returns {{ ok: boolean, error?: string, ops?: object[] }}
*/
function bareAgentParseApplyPatch(text) {
const raw = String(text || '').replace(/\r\n/g, '\n')
if (!raw.trim()) return { ok: false, error: 'empty_patch' }
const all = raw.split('\n')
let start = 0
let end = all.length
for (let i = 0; i < all.length; i++) {
if (/^\s*\*\*\*\s*Begin Patch\s*$/i.test(all[i])) {
start = i + 1
break
}
}
for (let i = all.length - 1; i >= 0; i--) {
if (/^\s*\*\*\*\s*End Patch\s*$/i.test(all[i])) {
end = i
break
}
}
const body = all.slice(start, end)
/** @type {object[]} */
const ops = []
/** @type {Record<string, unknown> | null} */
let cur = null
/** @type {{ context: string, old_lines: string[], new_lines: string[], is_end_of_file?: boolean } | null} */
let chunk = null
function flushChunk() {
if (cur && cur.type === 'update' && chunk) {
const chunks = Array.isArray(cur.chunks) ? cur.chunks : []
chunks.push(chunk)
cur.chunks = chunks
chunk = null
}
}
function flushOp() {
flushChunk()
if (cur) ops.push(cur)
cur = null
}
for (let i = 0; i < body.length; i++) {
const line = body[i]
if (/^\s*\*\*\*\s*Add File:\s*/i.test(line)) {
flushOp()
cur = {
type: 'add',
path: line.replace(/^\s*\*\*\*\s*Add File:\s*/i, '').trim(),
content: ''
}
continue
}
if (/^\s*\*\*\*\s*Delete File:\s*/i.test(line)) {
flushOp()
cur = {
type: 'delete',
path: line.replace(/^\s*\*\*\*\s*Delete File:\s*/i, '').trim()
}
continue
}
if (/^\s*\*\*\*\s*Update File:\s*/i.test(line)) {
flushOp()
cur = {
type: 'update',
path: line.replace(/^\s*\*\*\*\s*Update File:\s*/i, '').trim(),
move_to: '',
chunks: []
}
continue
}
if (/^\s*\*\*\*\s*Move to:\s*/i.test(line) && cur && cur.type === 'update') {
cur.move_to = line.replace(/^\s*\*\*\*\s*Move to:\s*/i, '').trim()
continue
}
if (/^\s*\*\*\*\s*End of File\s*$/i.test(line)) {
if (chunk) chunk.is_end_of_file = true
flushChunk()
continue
}
if (line.indexOf('@@') === 0 && cur && cur.type === 'update') {
flushChunk()
chunk = {
context: line.replace(/^@@\s?/, '').trim(),
old_lines: [],
new_lines: []
}
continue
}
if (cur && cur.type === 'add') {
const bodyLine = line.charAt(0) === '+' ? line.slice(1) : line
cur.content = cur.content ? String(cur.content) + '\n' + bodyLine : bodyLine
continue
}
if (cur && cur.type === 'update') {
if (!chunk) {
chunk = { context: '', old_lines: [], new_lines: [] }
}
const tag = line.charAt(0)
const rest = line.length ? line.slice(1) : ''
if (tag === '-') chunk.old_lines.push(rest)
else if (tag === '+') chunk.new_lines.push(rest)
else {
const ctxLine = tag === ' ' ? rest : line
chunk.old_lines.push(ctxLine)
chunk.new_lines.push(ctxLine)
}
}
}
flushOp()
if (!ops.length) return { ok: false, error: 'no_patch_ops' }
return { ok: true, ops }
}
/**
* @param {string} text
* @param {{ context: string, old_lines: string[], new_lines: string[], is_end_of_file?: boolean }} chunk
*/
function bareAgentApplyPatchChunk(text, chunk) {
const src = String(text == null ? '' : text)
const oldBlock = (chunk.old_lines || []).join('\n')
const newBlock = (chunk.new_lines || []).join('\n')
if (!oldBlock && !newBlock) {
return { ok: false, error: 'empty_chunk' }
}
if (!oldBlock) {
const next = src
? src.replace(/\s*$/, '') + (src.endsWith('\n') ? '' : '\n') + newBlock + '\n'
: newBlock + (newBlock.endsWith('\n') ? '' : '\n')
return { ok: true, next }
}
let from = 0
if (chunk.context) {
const at = src.indexOf(chunk.context)
if (at === -1) {
return { ok: false, error: 'chunk_context_not_found', context: chunk.context }
}
from = at
}
const hay = src.slice(from)
const count = bareAgentCountOccurrences(hay, oldBlock)
if (count === 0) return { ok: false, error: 'chunk_old_not_found' }
if (count > 1 && !chunk.context) {
return {
ok: false,
error: 'chunk_old_not_unique',
count,
hint: 'add @@ context or more surrounding lines'
}
}
const next = src.slice(0, from) + hay.replace(oldBlock, newBlock)
return { ok: true, next }
}
/**
* @param {Record<string, unknown>} ctx
* @param {object[]} ops
* @param {{ home?: string, denyPrefixes?: unknown }} [opts]
*/
async function bareAgentApplyPatchOps(ctx, ops, opts) {
const home = String((opts && opts.home) || '')
const deny = opts && opts.denyPrefixes
/** @type {object[]} */
const results = []
const rows = Array.isArray(ops) ? ops : []
for (let i = 0; i < rows.length; i++) {
const op = rows[i] && typeof rows[i] === 'object' ? rows[i] : {}
let path = String(op.path || '').trim()
if (path && path.charAt(0) !== '/' && home) {
path = home.replace(/\/+$/, '') + '/' + path.replace(/^\.\//, '')
}
if (!path) {
results.push({ ok: false, error: 'path_required' })
continue
}
if (
typeof bareAgentPathAllowedMutate === 'function' &&
!bareAgentPathAllowedMutate(path, deny)
) {
results.push({ ok: false, path, error: 'path_not_allowed' })
continue
}
if (op.type === 'add') {
const content = String(op.content == null ? '' : op.content)
await bareAgentWriteTextFile(
ctx,
path,
content.endsWith('\n') ? content : content + '\n'
)
results.push({ ok: true, op: 'add', path })
continue
}
if (op.type === 'delete') {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.unlink !== 'function') {
results.push({ ok: false, path, error: 'vfs.unlink unavailable' })
continue
}
try {
await vfs.unlink(path)
results.push({ ok: true, op: 'delete', path })
} catch (e) {
const msg =
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
results.push({ ok: false, path, error: msg })
}
continue
}
if (op.type === 'update') {
let text = await bareAgentReadTextFile(ctx, path)
if (!text && text !== '') {
results.push({ ok: false, path, error: 'missing_file' })
continue
}
const chunks = Array.isArray(op.chunks) ? op.chunks : []
let failed = null
for (let c = 0; c < chunks.length; c++) {
const applied = bareAgentApplyPatchChunk(text, chunks[c])
if (!applied.ok) {
failed = applied
break
}
text = applied.next
}
if (failed) {
results.push({ ok: false, path, error: failed.error, hint: failed.hint || null })
continue
}
const dest = String(op.move_to || '').trim() || path
const destAbs =
dest.charAt(0) === '/'
? dest
: home
? home.replace(/\/+$/, '') + '/' + dest.replace(/^\.\//, '')
: dest
if (
destAbs !== path &&
typeof bareAgentPathAllowedMutate === 'function' &&
!bareAgentPathAllowedMutate(destAbs, deny)
) {
results.push({ ok: false, path, error: 'move_path_not_allowed', dest: destAbs })
continue
}
await bareAgentWriteTextFile(ctx, destAbs, text)
if (destAbs !== path && ctx.vfs && typeof ctx.vfs.unlink === 'function') {
try {
await ctx.vfs.unlink(path)
} catch {
/* keep original if unlink fails */
}
}
results.push({
ok: true,
op: destAbs !== path ? 'move' : 'update',
path,
dest: destAbs !== path ? destAbs : undefined,
chunks: chunks.length
})
continue
}
results.push({ ok: false, path, error: 'unknown_op' })
}
const failed = results.filter(function (r) {
return !r.ok
})
return {
ok: failed.length === 0,
applied: results.length - failed.length,
failed: failed.length,
results
}
}
/**
* Parse DuckDuckGo instant-answer JSON into a compact result list.
* @param {unknown} payload
* @param {number} [max]
*/
function bareAgentParseSearchResults(payload, max) {
const cap = Math.min(Math.max(Number(max) || 8, 1), 16)
/** @type {{ title: string, url: string, snippet: string }[]} */
const out = []
const seen = Object.create(null)
function add(title, url, snippet) {
const u = String(url || '').trim()
if (!u || seen[u] || out.length >= cap) return
if (!/^https?:\/\//i.test(u)) return
seen[u] = 1
out.push({
title: String(title || u).slice(0, 160),
url: u,
snippet: String(snippet || '').replace(/\s+/g, ' ').trim().slice(0, 280)
})
}
const obj = payload && typeof payload === 'object' ? payload : {}
const rec = /** @type {Record<string, unknown>} */ (obj)
if (rec.AbstractURL || rec.Abstract) {
add(
String(rec.Heading || rec.AbstractSource || 'Abstract'),
String(rec.AbstractURL || ''),
String(rec.AbstractText || rec.Abstract || '')
)
}
const results = Array.isArray(rec.Results) ? rec.Results : []
for (let i = 0; i < results.length; i++) {
const row = results[i] && typeof results[i] === 'object' ? results[i] : {}
add(row.Text || row.Name, row.FirstURL, row.Text)
}
const related = Array.isArray(rec.RelatedTopics) ? rec.RelatedTopics : []
function walk(list) {
for (let i = 0; i < list.length && out.length < cap; i++) {
const row = list[i] && typeof list[i] === 'object' ? list[i] : {}
if (Array.isArray(row.Topics)) walk(row.Topics)
else add(row.Text, row.FirstURL, row.Text)
}
}
walk(related)
return out
}
/**
* Parse Grok-style intervals (5m / 2h / 1d) or a five-field cron line.
* @param {string} raw
* @returns {{ kind: 'everyMs', everyMs: number } | { kind: 'calendar', onCalendar: string } | { error: string }}
*/
function bareAgentParseScheduleInterval(raw) {
const s = String(raw || '').trim()
if (!s) return { error: 'interval_required' }
const compact = s.replace(/\s+/g, '')
const m = /^(\d+)(ms|s|m|h|d)$/i.exec(compact)
if (m) {
const n = Number(m[1])
const unit = m[2].toLowerCase()
let ms = 0
if (unit === 'ms') ms = n
else if (unit === 's') ms = n * 1000
else if (unit === 'm') ms = n * 60 * 1000
else if (unit === 'h') ms = n * 60 * 60 * 1000
else ms = n * 24 * 60 * 60 * 1000
if (ms < 1000) return { error: 'interval_too_short', min_ms: 1000 }
if (ms > 86400000) return { error: 'interval_too_long', max_ms: 86400000 }
return { kind: 'everyMs', everyMs: ms }
}
const fields = s.split(/\s+/)
if (fields.length === 5) return { kind: 'calendar', onCalendar: s }
return { error: 'bad_interval', hint: 'use 5m, 2h, 1d, or five cron fields' }
}
/**
* @param {string} id
*/
function bareAgentParseIgnoreRules(text) {
const lines = String(text || '').split(/\r?\n/)
/** @type {{ pattern: string, negate: boolean, dirOnly: boolean }[]} */
const rules = []
for (let i = 0; i < lines.length; i++) {
let line = String(lines[i] || '').trim()
if (!line || line.charAt(0) === '#') continue
let negate = false
if (line.charAt(0) === '!') {
negate = true
line = line.slice(1)
}
let dirOnly = false
if (line.charAt(line.length - 1) === '/') {
dirOnly = true
line = line.slice(0, -1)
}
if (line.charAt(0) === '/') line = line.slice(1)
if (!line) continue
rules.push({ pattern: line, negate, dirOnly })
}
return rules
}
/**
* Last matching gitignore-style rule wins.
* @param {string} rel
* @param {boolean} isDir
* @param {{ pattern: string, negate: boolean, dirOnly: boolean }[]} rules
*/
function bareAgentIgnoreMatch(rel, isDir, rules) {
const n = String(rel || '').replace(/^\/+/, '')
if (!n || !Array.isArray(rules) || !rules.length) return false
let ignored = false
for (let i = 0; i < rules.length; i++) {
const rule = rules[i]
if (rule.dirOnly && !isDir) continue
let hit = false
if (rule.pattern.indexOf('/') === -1) {
const parts = n.split('/')
for (let p = 0; p < parts.length; p++) {
if (bareAgentGlobMatch(parts[p], rule.pattern)) {
hit = true
break
}
}
} else {
hit = bareAgentGlobMatch(n, rule.pattern) || bareAgentGlobMatch(n, '**/' + rule.pattern)
}
if (!hit) continue
ignored = !rule.negate
}
return ignored
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} root
*/
async function bareAgentLoadIgnoreRules(ctx, root) {
const names = ['.gitignore', '.agentignore', '.grokignore']
/** @type {string[]} */
const chunks = []
for (let i = 0; i < names.length; i++) {
const t = await bareAgentReadTextFile(ctx, String(root || '').replace(/\/+$/, '') + '/' + names[i])
if (t && t.trim()) chunks.push(t)
}
return bareAgentParseIgnoreRules(chunks.join('\n'))
}
/**
* Simple Grok-style fuzzy score (basename + path subsequence). Higher is better.
* @param {string} query
* @param {string} path
*/
function bareAgentFuzzyScore(query, path) {
const q = String(query || '').toLowerCase().trim()
const p = String(path || '')
if (!q || !p) return 0
const low = p.toLowerCase()
const base = (p.split('/').pop() || p).toLowerCase()
if (base === q) return 1000
if (base.indexOf(q) === 0) return 800 - Math.min(base.length, 80)
if (base.indexOf(q) !== -1) return 600 - base.indexOf(q)
if (low.indexOf(q) !== -1) return 400
let qi = 0
let score = 0
let streak = 0
for (let i = 0; i < low.length && qi < q.length; i++) {
if (low.charAt(i) === q.charAt(qi)) {
qi++
streak++
score += 8 + streak * 4
} else streak = 0
}
if (qi < q.length) return 0
if (base.length && q.length / base.length > 0.5) score += 40
return score
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} root
* @param {string} query
* @param {{ max?: number, maxFiles?: number }} [opts]
*/
async function bareAgentFuzzyFind(ctx, root, query, opts) {
const files = await bareAgentVfsWalkFiles(ctx, root, {
maxFiles: Math.min(Math.max(Number(opts && opts.maxFiles) || 600, 40), 2000),
maxDepth: 12
})
const cap = Math.min(Math.max(Number(opts && opts.max) || 20, 1), 80)
/** @type {{ path: string, score: number }[]} */
const scored = []
for (let i = 0; i < files.length; i++) {
const score = bareAgentFuzzyScore(query, files[i])
if (score <= 0) continue
scored.push({ path: files[i], score })
}
scored.sort(function (a, b) {
return b.score - a.score
})
return scored.slice(0, cap)
}
/**
* @param {Record<string, unknown>} hook
* @param {string} event
* @param {string} toolName
* @param {Record<string, unknown>} args
* @param {string} [result]
*/
function bareAgentHookMatch(hook, event, toolName, args, result) {
if (!hook || typeof hook !== 'object') return null
const ev = String(hook.event || hook.type || 'PreToolUse')
const want = String(event || '')
if (ev !== want && ev.toLowerCase() !== want.toLowerCase()) return null
const tools = Array.isArray(hook.tools) ? hook.tools.map(String) : []
if (tools.length && toolName && tools.indexOf(toolName) === -1) return null
const reSrc = String(hook.deny_regex || hook.match_regex || hook.denyRegex || '').trim()
if (reSrc) {
let re
try {
re = new RegExp(reSrc, 'i')
} catch {
return null
}
const blob =
toolName +
' ' +
String((args && (args.command || args.path || args.file_path || args.pattern)) || '') +
' ' +
String(result || '').slice(0, 400)
if (!re.test(blob)) return null
}
return hook
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} hooksDir
* @param {string} event
* @param {string} [toolName]
* @param {Record<string, unknown>} [args]
* @param {string} [result]
*/
async function bareAgentRunHooks(ctx, hooksDir, event, toolName, args, result) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readdir !== 'function' || typeof vfs.readFile !== 'function') {
return { deny: '', inject: '' }
}
let names = []
try {
names = await vfs.readdir(hooksDir)
} catch {
return { deny: '', inject: '' }
}
if (!Array.isArray(names)) return { deny: '', inject: '' }
names = names.filter(function (n) {
return /\.json$/i.test(String(n || ''))
})
names.sort()
/** @type {string[]} */
const injects = []
for (let i = 0; i < names.length; i++) {
try {
const t = await bareAgentReadTextFile(ctx, hooksDir + '/' + names[i])
if (!t.trim()) continue
const hook = JSON.parse(t)
const matched = bareAgentHookMatch(hook, event, toolName || '', args || {}, result || '')
if (!matched) continue
if (event === 'PreToolUse' || event === 'pre') {
const reason = String(matched.reason || matched.message || '').trim()
if (reason && (matched.deny === true || matched.deny_regex || matched.denyRegex)) {
return { deny: reason.slice(0, 240), inject: '' }
}
if (typeof bareAgentHookDenies === 'function') {
const d = bareAgentHookDenies(matched, toolName || '', args || {})
if (d) return { deny: d, inject: '' }
}
}
const inj = String(matched.inject || matched.append || '').trim()
if (inj) injects.push(inj.slice(0, 800))
} catch {
/* ignore */
}
}
return { deny: '', inject: injects.join('\n') }
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} editsPath
* @param {{ path: string, prev: string, tool?: string }} rec
*/
async function bareAgentPushEdit(ctx, editsPath, rec) {
const prev = await bareAgentReadJsonFile(ctx, editsPath, [])
const list = Array.isArray(prev) ? prev : []
list.push({
path: String(rec.path || ''),
prev: String(rec.prev == null ? '' : rec.prev),
tool: String(rec.tool || ''),
ts: new Date().toISOString()
})
while (list.length > 20) list.shift()
await bareAgentWriteJsonFile(ctx, editsPath, list)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} editsPath
*/
async function bareAgentPopEdit(ctx, editsPath) {
const prev = await bareAgentReadJsonFile(ctx, editsPath, [])
const list = Array.isArray(prev) ? prev : []
const last = list.pop()
await bareAgentWriteJsonFile(ctx, editsPath, list)
return last && typeof last === 'object' ? last : null
}
/**
* @param {unknown[]} messages
* @param {string} query
* @param {number} [max]
*/
function bareAgentHistorySearch(messages, query, max) {
const tokens = bareAgentMemoryTokens(query)
const cap = Math.min(Math.max(Number(max) || 8, 1), 24)
if (!Array.isArray(messages) || !tokens.length) return []
/** @type {{ role: string, score: number, snippet: string }[]} */
const hits = []
for (let i = 0; i < messages.length; i++) {
const m = messages[i] && typeof messages[i] === 'object' ? messages[i] : null
if (!m) continue
const role = String(m.role || '')
if (role !== 'user' && role !== 'assistant') continue
const text = String(m.content || '')
const score = bareAgentMemoryScore(text, tokens)
if (score <= 0) continue
hits.push({
role,
score: Math.round(score * 1000) / 1000,
snippet: text.replace(/\s+/g, ' ').trim().slice(0, 280)
})
}
hits.sort(function (a, b) {
return b.score - a.score
})
return hits.slice(0, cap)
}
function bareAgentScheduleId(id) {
let s = String(id || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9-]+/g, '-')
.replace(/^-+|-+$/g, '')
if (!s) s = 'task'
if (s.indexOf('agent-') !== 0) s = 'agent-' + s
return s.slice(0, 40)
}
/**
* User-turn rewind points (Grok /rewind). Index 0 is the first user message.
* @param {unknown[]} messages
*/
function bareAgentRewindPoints(messages) {
/** @type {{ userIndex: number, messageIndex: number, preview: string }[]} */
const points = []
if (!Array.isArray(messages)) return points
for (let i = 0; i < messages.length; i++) {
const m = messages[i] && typeof messages[i] === 'object' ? messages[i] : null
if (!m || String(m.role || '') !== 'user') continue
points.push({
userIndex: points.length,
messageIndex: i,
preview: String(m.content || '')
.replace(/\s+/g, ' ')
.trim()
.slice(0, 140)
})
}
return points
}
/**
* Drop the last N user turns (and everything after that user message).
* userIndex rewinds to that user turn (keeps messages before it).
* keep_user keeps the target user message so the prompt can be retried.
* @param {unknown[]} messages
* @param {{ steps?: number, userIndex?: number, keep_user?: boolean }} [opts]
*/
function bareAgentRewindHistory(messages, opts) {
const list = Array.isArray(messages) ? messages.slice() : []
const points = bareAgentRewindPoints(list)
if (!points.length) return { ok: false, error: 'nothing_to_rewind', messages: list, dropped: 0 }
const o = opts && typeof opts === 'object' ? opts : {}
let target
if (o.userIndex != null && Number.isFinite(Number(o.userIndex))) {
target = points[Math.floor(Number(o.userIndex))]
if (!target) return { ok: false, error: 'bad_user_index', messages: list, dropped: 0 }
} else {
const steps = Math.min(points.length, Math.max(1, Math.floor(Number(o.steps) || 1)))
target = points[points.length - steps]
}
const keepUser = Boolean(o.keep_user)
const cut = keepUser ? target.messageIndex : target.messageIndex - 1
const next = cut < 0 ? [] : list.slice(0, cut + 1)
return {
ok: true,
messages: next,
dropped: list.length - next.length,
target: target,
keep_user: keepUser
}
}
/**
* Markdown transcript (Grok /export).
* @param {unknown[]} messages
*/
function bareAgentExportTranscript(messages) {
const lines = [
'# Agent session export',
'',
'Exported: ' + new Date().toISOString(),
''
]
const list = Array.isArray(messages) ? messages : []
for (let i = 0; i < list.length; i++) {
const m = list[i] && typeof list[i] === 'object' ? list[i] : {}
const role = String(m.role || 'unknown')
let content = String(m.content || '')
if (role === 'tool' && content.length > 1200) content = content.slice(0, 1200) + '\n… truncated'
const calls = Array.isArray(m.tool_calls) ? m.tool_calls : []
const names = []
for (let c = 0; c < calls.length; c++) {
const fn = calls[c] && calls[c].function ? calls[c].function : {}
if (fn && fn.name) names.push(String(fn.name))
}
lines.push('## ' + String(i + 1) + '. ' + role)
lines.push('')
if (names.length) lines.push('tools: ' + names.join(', '))
lines.push(content || '(empty)')
lines.push('')
}
return lines.join('\n')
}
/**
* Line-based unified diff (Grok-style file compare, no git required).
* @param {string} oldText
* @param {string} newText
* @param {{ from?: string, to?: string, context?: number }} [opts]
*/
function bareAgentUnifiedDiff(oldText, newText, opts) {
const pathA = String((opts && opts.from) || 'a')
const pathB = String((opts && opts.to) || 'b')
const ctxN = Math.min(8, Math.max(0, Math.floor(Number(opts && opts.context) || 3)))
const a = String(oldText || '')
.replace(/\r\n/g, '\n')
.split('\n')
const b = String(newText || '')
.replace(/\r\n/g, '\n')
.split('\n')
if (a.length && a[a.length - 1] === '') a.pop()
if (b.length && b[b.length - 1] === '') b.pop()
if (a.join('\n') === b.join('\n')) {
return { ok: true, identical: true, text: '', added: 0, removed: 0 }
}
const maxLines = 800
if (a.length > maxLines || b.length > maxLines) {
return {
ok: true,
truncated: true,
identical: false,
text:
'--- ' +
pathA +
'\n+++ ' +
pathB +
'\n@@ files too large for inline LCS diff (' +
a.length +
'/' +
b.length +
' lines) @@\n',
added: 0,
removed: 0
}
}
const n = a.length
const m = b.length
/** @type {number[][]} */
const dp = new Array(n + 1)
for (let i = 0; i <= n; i++) {
dp[i] = new Array(m + 1)
for (let j = 0; j <= m; j++) dp[i][j] = 0
}
for (let i = 1; i <= n; i++) {
for (let j = 1; j <= m; j++) {
dp[i][j] =
a[i - 1] === b[j - 1]
? dp[i - 1][j - 1] + 1
: dp[i - 1][j] >= dp[i][j - 1]
? dp[i - 1][j]
: dp[i][j - 1]
}
}
/** @type {{ op: string, line: string }[]} */
const ops = []
let i = n
let j = m
while (i > 0 || j > 0) {
if (i > 0 && j > 0 && a[i - 1] === b[j - 1]) {
ops.push({ op: ' ', line: a[i - 1] })
i--
j--
} else if (j > 0 && (i === 0 || dp[i][j - 1] >= dp[i - 1][j])) {
ops.push({ op: '+', line: b[j - 1] })
j--
} else {
ops.push({ op: '-', line: a[i - 1] })
i--
}
}
ops.reverse()
let added = 0
let removed = 0
for (let k = 0; k < ops.length; k++) {
if (ops[k].op === '+') added++
else if (ops[k].op === '-') removed++
}
const lines = ['--- ' + pathA, '+++ ' + pathB]
let idx = 0
while (idx < ops.length) {
while (idx < ops.length && ops[idx].op === ' ') idx++
if (idx >= ops.length) break
let start = Math.max(0, idx - ctxN)
let end = idx
while (end < ops.length) {
if (ops[end].op !== ' ') {
end++
continue
}
let run = 0
let p = end
while (p < ops.length && ops[p].op === ' ') {
run++
p++
}
if (run > ctxN * 2) {
end += ctxN
break
}
end = p
}
end = Math.min(ops.length, end)
let oldLine = 1
let newLine = 1
for (let k = 0; k < start; k++) {
if (ops[k].op !== '+') oldLine++
if (ops[k].op !== '-') newLine++
}
let oldCount = 0
let newCount = 0
for (let k = start; k < end; k++) {
if (ops[k].op !== '+') oldCount++
if (ops[k].op !== '-') newCount++
}
lines.push(
'@@ -' +
String(oldLine) +
',' +
String(oldCount) +
' +' +
String(newLine) +
',' +
String(newCount) +
' @@'
)
for (let k = start; k < end; k++) {
lines.push(ops[k].op + ops[k].line)
}
idx = end
}
return {
ok: true,
identical: false,
truncated: false,
text: lines.join('\n') + '\n',
added,
removed
}
}
/**
* Definition-oriented regex for a symbol name (JS / Python / Rust / C-like).
* @param {string} name
*/
function bareAgentSymbolRegex(name) {
const esc = String(name || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
if (!esc) return ''
return (
'(?:(?:export\\s+)?(?:default\\s+)?(?:async\\s+)?function\\s+' +
esc +
'\\b|(?:export\\s+)?(?:default\\s+)?class\\s+' +
esc +
'\\b|(?:export\\s+)?(?:const|let|var)\\s+' +
esc +
'\\b|def\\s+' +
esc +
'\\s*\\(|fn\\s+' +
esc +
'\\b|' +
esc +
'\\s*=\\s*(?:async\\s+)?(?:function|\\(|class))'
)
}
/**
* @param {Record<string, unknown>} ctx
* @param {string} root
* @param {string} name
* @param {{ glob?: string, max?: number }} [opts]
*/
async function bareAgentFindSymbol(ctx, root, name, opts) {
const pattern = bareAgentSymbolRegex(name)
if (!pattern) return { ok: false, error: 'name_required' }
const out = await bareAgentGrepFiles(ctx, {
pattern,
root,
glob: opts && opts.glob,
max_matches: opts && opts.max ? opts.max : 40
})
if (!out || out.ok === false) return out
return {
ok: true,
name,
root,
matches: out.matches || [],
count: out.count || 0,
truncated: Boolean(out.truncated)
}
}
/**
* Grok list_dir-style BFS tree (bounded).
* @param {Record<string, unknown>} ctx
* @param {string} root
* @param {{ max?: number, maxDepth?: number }} [opts]
*/
async function bareAgentRenderTree(ctx, root, opts) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readdir !== 'function') {
return { ok: false, error: 'readdir unavailable' }
}
const base = String(root || '/').replace(/\/+$/, '') || '/'
const maxItems = Math.min(Math.max(Number(opts && opts.max) || 200, 20), 800)
const maxDepth = Math.min(Math.max(Number(opts && opts.maxDepth) || 6, 1), 12)
const skip = Object.create(null)
skip['.git'] = 1
skip['node_modules'] = 1
skip['.bare-os'] = 1
/** @type {string[]} */
const lines = [base + '/']
/** @type {{ dir: string, prefix: string, depth: number }[]} */
const queue = [{ dir: base, prefix: '', depth: 0 }]
let count = 1
let truncated = false
while (queue.length && count < maxItems) {
const cur = queue.shift()
if (!cur) break
let names = []
try {
names = await vfs.readdir(cur.dir)
} catch {
continue
}
if (!Array.isArray(names)) continue
names = names
.map(function (n) {
return String(n || '')
})
.filter(function (n) {
return n && n !== '.' && n !== '..' && !skip[n]
})
.sort()
for (let i = 0; i < names.length && count < maxItems; i++) {
const name = names[i]
const full = (cur.dir === '/' ? '' : cur.dir) + '/' + name
const isDir = await bareAgentVfsIsDir(ctx, full)
const last = i === names.length - 1
const branch = last ? '`-- ' : '|-- '
lines.push(cur.prefix + branch + name + (isDir ? '/' : ''))
count++
if (isDir && cur.depth + 1 < maxDepth) {
queue.push({
dir: full,
prefix: cur.prefix + (last ? ' ' : '| '),
depth: cur.depth + 1
})
}
}
if (count >= maxItems) {
truncated = true
break
}
}
return { ok: true, path: base, tree: lines.join('\n'), count, truncated }
}
/**
* VFS copy (file or directory). Dest parents are created.
* @param {Record<string, unknown>} ctx
* @param {string} from
* @param {string} to
*/
async function bareAgentCopyPath(ctx, from, to) {
const src = String(from || '').replace(/\/+$/, '')
let dest = String(to || '')
if (!src || !dest) return { ok: false, error: 'from_and_to_required' }
const srcIsDir = await bareAgentVfsIsDir(ctx, src)
const destIsDir = await bareAgentVfsIsDir(ctx, dest.replace(/\/+$/, ''))
if (destIsDir) {
const base = src.split('/').pop() || 'copy'
dest = dest.replace(/\/+$/, '') + '/' + base
}
if (!srcIsDir) {
const text = await bareAgentReadTextFile(ctx, src)
await bareAgentWriteTextFile(ctx, dest, text)
return { ok: true, from: src, to: dest, kind: 'file' }
}
const files = await bareAgentVfsWalkFiles(ctx, src, { maxFiles: 400, maxDepth: 12 })
for (let i = 0; i < files.length; i++) {
const rel = files[i].slice(src.length)
const text = await bareAgentReadTextFile(ctx, files[i])
await bareAgentWriteTextFile(ctx, dest + rel, text)
}
return { ok: true, from: src, to: dest, kind: 'directory', files: files.length }
}
/**
* SKILL.md body with YAML frontmatter (Grok skill format).
* @param {{ name: string, description: string, body?: string }} spec
*/
function bareAgentSkillMarkdown(spec) {
const name = String((spec && spec.name) || '').trim() || 'skill'
const description = String((spec && spec.description) || '').trim() || name
const body = String((spec && spec.body) || '').trim() || '# ' + name + '\n'
return (
'---\nname: ' +
name.replace(/\n/g, ' ') +
'\ndescription: ' +
description.replace(/\n/g, ' ') +
'\n---\n\n' +
body +
'\n'
)
}
/**
* Walk-up Grok/Claude/Cursor skill roots (project-local).
* @param {Record<string, unknown>} ctx
* @param {string} startDir
*/
async function bareAgentDiscoverProjectSkillRoots(ctx, startDir) {
const vfs = ctx && ctx.vfs
if (!vfs || typeof vfs.readdir !== 'function') return []
/** @type {{ path: string, source: string }[]} */
const roots = []
const seen = Object.create(null)
let dir = String(startDir || '').replace(/\/+$/, '') || '/'
const suffixes = ['.grok/skills', '.agents/skills', '.claude/skills', '.cursor/skills']
for (let hop = 0; hop < 12; hop++) {
for (let i = 0; i < suffixes.length; i++) {
const p = (dir === '/' ? '' : dir) + '/' + suffixes[i]
if (seen[p]) continue
seen[p] = 1
try {
const names = await vfs.readdir(p)
if (Array.isArray(names) && names.length) roots.push({ path: p, source: 'project' })
} catch {
/* missing */
}
}
if (dir === '/') break
const parent = dir.replace(/\/[^/]+$/, '') || '/'
if (parent === dir) break
dir = parent
}
return roots
}