Orginize
This commit is contained in:
@@ -0,0 +1,803 @@
|
||||
/** ~/.agent paths, config, history trim, man digest (preamble for /bin/agent). */
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @returns {string}
|
||||
*/
|
||||
function bareAgentResolveHome(ctx) {
|
||||
const env =
|
||||
ctx && typeof ctx === 'object' && ctx.env && typeof ctx.env === 'object'
|
||||
? /** @type {Record<string, string>} */ (ctx.env)
|
||||
: {}
|
||||
const vfsHome =
|
||||
ctx &&
|
||||
typeof ctx === 'object' &&
|
||||
ctx.vfs &&
|
||||
typeof ctx.vfs === 'object' &&
|
||||
typeof /** @type {{ home?: string }} */ (ctx.vfs).home === 'string'
|
||||
? String(/** @type {{ home?: string }} */ (ctx.vfs).home)
|
||||
: ''
|
||||
const h = env.HOME || vfsHome || '/home/guest'
|
||||
return h.replace(/\/+$/, '') || '/home/guest'
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} home
|
||||
*/
|
||||
function bareAgentPaths(home) {
|
||||
const base = home + '/.agent'
|
||||
return {
|
||||
dir: base,
|
||||
config: base + '/config.json',
|
||||
history: base + '/history.json',
|
||||
progress: base + '/progress.txt',
|
||||
instructions: base + '/instructions.md',
|
||||
context: base + '/context.md',
|
||||
compact: base + '/compact.md',
|
||||
cmdOut: base + '/last_command_out.txt',
|
||||
workspace: base + '/workspace',
|
||||
workspaceMemory: base + '/workspace/memory',
|
||||
workspaceSkills: base + '/workspace/skills',
|
||||
skillsGlobal: base + '/skills',
|
||||
todos: base + '/todos.json',
|
||||
plan: base + '/plan.md',
|
||||
hooks: base + '/hooks',
|
||||
ask: base + '/ask.json',
|
||||
edits: base + '/edits.json',
|
||||
lastCwd: base + '/last_cwd'
|
||||
}
|
||||
}
|
||||
|
||||
function bareAgentDefaultConfig() {
|
||||
return {
|
||||
backend: 'qvac',
|
||||
rest_base_url: 'https://api.groq.com/openai/v1',
|
||||
rest_api_key: '',
|
||||
model: 'QWEN3_1_7B_INST_Q4',
|
||||
qvac_model: 'QWEN3_1_7B_INST_Q4',
|
||||
qvac_profile: 'recommended',
|
||||
qvac_ctx_size: 0,
|
||||
qvac_device: '',
|
||||
qvac_main_gpu: 'auto',
|
||||
qvac_gpu_layers: -1,
|
||||
max_tokens: 4096,
|
||||
temperature: 0.7,
|
||||
provider: 'qvac',
|
||||
max_iterations: 64,
|
||||
stream: true,
|
||||
tool_parallelism: 1,
|
||||
request_timeout_ms: 120000,
|
||||
extra_headers: /** @type {Record<string, string>} */ ({}),
|
||||
access_policy: 'full',
|
||||
allow_delete: true,
|
||||
require_confirm_token: '',
|
||||
owner_name: '',
|
||||
agent_label: '',
|
||||
show_reasoning: false,
|
||||
reasoning_mode: 'off',
|
||||
reasoning_max_chars: 4000,
|
||||
reasoning_include_tools: true,
|
||||
allow_bridge_mutations: true,
|
||||
allow_host_notifications: true,
|
||||
allow_host_actions: true,
|
||||
emergency_stop_mutations: false,
|
||||
autonomous_mode_enabled: true,
|
||||
autonomous_max_runtime_ms: 1800000,
|
||||
autonomous_completion_required_checks: [],
|
||||
autonomous_allow_paths: ['*'],
|
||||
autonomous_deny_ops: [],
|
||||
command_deny: [],
|
||||
mutate_deny_prefixes: [
|
||||
'/bin',
|
||||
'/etc',
|
||||
'/boot',
|
||||
'/lib',
|
||||
'/usr',
|
||||
'/share',
|
||||
'/proc',
|
||||
'/dev',
|
||||
'/sys',
|
||||
'/run'
|
||||
],
|
||||
autonomous_active: false,
|
||||
autonomous_started_at_ms: 0,
|
||||
autonomous_stop_requested: false,
|
||||
autonomous_goal: '',
|
||||
autonomous_status: 'idle',
|
||||
autonomous_last_error: '',
|
||||
context_compaction: 'auto',
|
||||
compaction_keep_recent: 8,
|
||||
compaction_tool_chars: 1600,
|
||||
plan_mode_active: false,
|
||||
todo_nudge_enabled: true
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {unknown} v
|
||||
* @returns {v is Record<string, unknown>}
|
||||
*/
|
||||
function bareAgentIsPlainObject(v) {
|
||||
return v != null && typeof v === 'object' && !Array.isArray(v)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shallow merge known keys from src into defaults.
|
||||
* @param {Record<string, unknown>} defaults
|
||||
* @param {Record<string, unknown>} src
|
||||
*/
|
||||
function bareAgentMergeConfig(defaults, src) {
|
||||
const out = { ...defaults }
|
||||
const known = new Set([
|
||||
'backend',
|
||||
'rest_base_url',
|
||||
'rest_api_key',
|
||||
'model',
|
||||
'qvac_model',
|
||||
'qvac_profile',
|
||||
'qvac_ctx_size',
|
||||
'qvac_device',
|
||||
'qvac_main_gpu',
|
||||
'qvac_gpu_layers',
|
||||
'max_tokens',
|
||||
'temperature',
|
||||
'provider',
|
||||
'max_iterations',
|
||||
'stream',
|
||||
'tool_parallelism',
|
||||
'request_timeout_ms',
|
||||
'extra_headers',
|
||||
'access_policy',
|
||||
'allow_delete',
|
||||
'require_confirm_token',
|
||||
'command_deny',
|
||||
'mutate_deny_prefixes',
|
||||
'owner_name',
|
||||
'agent_label',
|
||||
'show_reasoning',
|
||||
'reasoning_mode',
|
||||
'reasoning_max_chars',
|
||||
'reasoning_include_tools',
|
||||
'allow_bridge_mutations',
|
||||
'allow_host_notifications',
|
||||
'allow_host_actions',
|
||||
'emergency_stop_mutations',
|
||||
'autonomous_mode_enabled',
|
||||
'autonomous_max_runtime_ms',
|
||||
'autonomous_completion_required_checks',
|
||||
'autonomous_allow_paths',
|
||||
'autonomous_deny_ops',
|
||||
'autonomous_active',
|
||||
'autonomous_started_at_ms',
|
||||
'autonomous_stop_requested',
|
||||
'autonomous_goal',
|
||||
'autonomous_status',
|
||||
'autonomous_last_error',
|
||||
'context_compaction',
|
||||
'compaction_keep_recent',
|
||||
'compaction_tool_chars',
|
||||
'plan_mode_active',
|
||||
'todo_nudge_enabled'
|
||||
])
|
||||
for (const k of Object.keys(src)) {
|
||||
if (k.startsWith('x-')) continue
|
||||
if (!known.has(k)) continue
|
||||
const val = src[k]
|
||||
if (k === 'extra_headers' && bareAgentIsPlainObject(val)) {
|
||||
out.extra_headers = /** @type {Record<string, string>} */ ({ ...val })
|
||||
continue
|
||||
}
|
||||
if (k === 'backend') {
|
||||
const b = String(val ?? '').trim().toLowerCase()
|
||||
out.backend = b === 'rest' || b === 'openai' || b === 'http' ? 'rest' : 'qvac'
|
||||
continue
|
||||
}
|
||||
if (
|
||||
k === 'rest_base_url' ||
|
||||
k === 'rest_api_key' ||
|
||||
k === 'model' ||
|
||||
k === 'qvac_model' ||
|
||||
k === 'qvac_profile' ||
|
||||
k === 'provider' ||
|
||||
k === 'owner_name' ||
|
||||
k === 'agent_label' ||
|
||||
k === 'autonomous_goal' ||
|
||||
k === 'autonomous_status' ||
|
||||
k === 'autonomous_last_error' ||
|
||||
k === 'qvac_device' ||
|
||||
k === 'qvac_main_gpu' ||
|
||||
k === 'require_confirm_token'
|
||||
) {
|
||||
out[k] = String(val ?? '')
|
||||
continue
|
||||
}
|
||||
if (k === 'context_compaction') {
|
||||
const mode = String(val ?? '').trim().toLowerCase()
|
||||
out.context_compaction =
|
||||
mode === 'off' || mode === 'aggressive' ? mode : 'auto'
|
||||
continue
|
||||
}
|
||||
if (k === 'reasoning_mode') {
|
||||
const mode = String(val ?? '').trim().toLowerCase()
|
||||
out.reasoning_mode =
|
||||
mode === 'summary' || mode === 'trace' ? mode : 'off'
|
||||
continue
|
||||
}
|
||||
if (
|
||||
k === 'max_tokens' ||
|
||||
k === 'temperature' ||
|
||||
k === 'max_iterations' ||
|
||||
k === 'tool_parallelism' ||
|
||||
k === 'request_timeout_ms' ||
|
||||
k === 'reasoning_max_chars' ||
|
||||
k === 'qvac_ctx_size' ||
|
||||
k === 'qvac_gpu_layers' ||
|
||||
k === 'autonomous_max_runtime_ms' ||
|
||||
k === 'autonomous_started_at_ms' ||
|
||||
k === 'compaction_keep_recent' ||
|
||||
k === 'compaction_tool_chars'
|
||||
) {
|
||||
const n = Number(val)
|
||||
out[k] = Number.isFinite(n) ? n : defaults[k]
|
||||
continue
|
||||
}
|
||||
if (
|
||||
k === 'autonomous_completion_required_checks' ||
|
||||
k === 'autonomous_allow_paths' ||
|
||||
k === 'autonomous_deny_ops' ||
|
||||
k === 'command_deny' ||
|
||||
k === 'mutate_deny_prefixes'
|
||||
) {
|
||||
out[k] = Array.isArray(val) ? val.map((x) => String(x ?? '')).filter(Boolean) : defaults[k]
|
||||
continue
|
||||
}
|
||||
if (
|
||||
k === 'stream' ||
|
||||
k === 'allow_delete' ||
|
||||
k === 'show_reasoning' ||
|
||||
k === 'reasoning_include_tools' ||
|
||||
k === 'allow_bridge_mutations' ||
|
||||
k === 'allow_host_notifications' ||
|
||||
k === 'allow_host_actions' ||
|
||||
k === 'emergency_stop_mutations' ||
|
||||
k === 'autonomous_mode_enabled' ||
|
||||
k === 'autonomous_active' ||
|
||||
k === 'autonomous_stop_requested' ||
|
||||
k === 'plan_mode_active' ||
|
||||
k === 'todo_nudge_enabled'
|
||||
) {
|
||||
out[k] = Boolean(val)
|
||||
continue
|
||||
}
|
||||
if (k === 'require_confirm_token') {
|
||||
out.require_confirm_token = String(val ?? '')
|
||||
continue
|
||||
}
|
||||
if (k === 'access_policy') {
|
||||
const pol = String(val ?? '').trim().toLowerCase()
|
||||
out.access_policy = pol === 'restricted' ? 'restricted' : 'full'
|
||||
continue
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Old configs persisted restrictive defaults. Missing access_policy means
|
||||
* upgrade onto full guest admin (denylist-only) so existing homes match.
|
||||
* @param {Record<string, unknown>} raw
|
||||
* @param {Record<string, unknown>} merged
|
||||
*/
|
||||
function bareAgentApplyAccessPolicyUpgrade(raw, merged) {
|
||||
const src = bareAgentIsPlainObject(raw) ? raw : {}
|
||||
if (Object.prototype.hasOwnProperty.call(src, 'access_policy')) {
|
||||
return { config: merged, upgraded: false }
|
||||
}
|
||||
const next = { ...merged }
|
||||
next.access_policy = 'full'
|
||||
next.allow_delete = true
|
||||
next.require_confirm_token = ''
|
||||
next.allow_bridge_mutations = true
|
||||
next.allow_host_notifications = true
|
||||
next.allow_host_actions = true
|
||||
next.emergency_stop_mutations = false
|
||||
next.autonomous_deny_ops = []
|
||||
next.autonomous_allow_paths = ['*']
|
||||
next.command_deny = Array.isArray(next.command_deny) ? next.command_deny : []
|
||||
next.autonomous_mode_enabled = true
|
||||
if (!Array.isArray(next.mutate_deny_prefixes) || !next.mutate_deny_prefixes.length) {
|
||||
next.mutate_deny_prefixes = bareAgentDefaultConfig().mutate_deny_prefixes
|
||||
}
|
||||
return { config: next, upgraded: true }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} raw
|
||||
*/
|
||||
function bareAgentValidateConfigShape(raw) {
|
||||
if (!bareAgentIsPlainObject(raw)) throw new Error('config must be a JSON object')
|
||||
for (const k of Object.keys(raw)) {
|
||||
if (k.startsWith('x-')) continue
|
||||
const known = [
|
||||
'backend',
|
||||
'rest_base_url',
|
||||
'rest_api_key',
|
||||
'model',
|
||||
'qvac_model',
|
||||
'qvac_profile',
|
||||
'qvac_ctx_size',
|
||||
'qvac_device',
|
||||
'qvac_main_gpu',
|
||||
'qvac_gpu_layers',
|
||||
'max_tokens',
|
||||
'temperature',
|
||||
'provider',
|
||||
'max_iterations',
|
||||
'stream',
|
||||
'tool_parallelism',
|
||||
'request_timeout_ms',
|
||||
'extra_headers',
|
||||
'access_policy',
|
||||
'allow_delete',
|
||||
'require_confirm_token',
|
||||
'command_deny',
|
||||
'mutate_deny_prefixes',
|
||||
'owner_name',
|
||||
'agent_label',
|
||||
'show_reasoning',
|
||||
'reasoning_mode',
|
||||
'reasoning_max_chars',
|
||||
'reasoning_include_tools',
|
||||
'allow_bridge_mutations',
|
||||
'allow_host_notifications',
|
||||
'allow_host_actions',
|
||||
'emergency_stop_mutations',
|
||||
'autonomous_mode_enabled',
|
||||
'autonomous_max_runtime_ms',
|
||||
'autonomous_completion_required_checks',
|
||||
'autonomous_allow_paths',
|
||||
'autonomous_deny_ops',
|
||||
'autonomous_active',
|
||||
'autonomous_started_at_ms',
|
||||
'autonomous_stop_requested',
|
||||
'autonomous_goal',
|
||||
'autonomous_status',
|
||||
'autonomous_last_error',
|
||||
'context_compaction',
|
||||
'compaction_keep_recent',
|
||||
'compaction_tool_chars',
|
||||
'plan_mode_active',
|
||||
'todo_nudge_enabled'
|
||||
]
|
||||
if (!known.includes(k)) {
|
||||
throw new Error('unknown config key: ' + k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {{ dir: string, config: string }} paths
|
||||
* @returns {Promise<{ config: ReturnType<typeof bareAgentDefaultConfig>, created: boolean }>}
|
||||
*/
|
||||
async function bareAgentLoadOrCreateConfig(ctx, paths) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.mkdir !== 'function')
|
||||
throw new Error('agent: vfs.mkdir unavailable')
|
||||
await vfs.mkdir(paths.dir, { recursive: true })
|
||||
|
||||
let missingOrEmpty = false
|
||||
/** @type {Record<string, unknown>} */
|
||||
let raw = {}
|
||||
try {
|
||||
if (typeof vfs.readFile === 'function') {
|
||||
const buf = await vfs.readFile(paths.config)
|
||||
if (!buf || !buf.length) missingOrEmpty = true
|
||||
else {
|
||||
const t =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
|
||||
? ctx.b4a.toString(buf)
|
||||
: String(new TextDecoder().decode(buf))
|
||||
raw = JSON.parse(t)
|
||||
}
|
||||
} else missingOrEmpty = true
|
||||
} catch {
|
||||
missingOrEmpty = true
|
||||
raw = {}
|
||||
}
|
||||
|
||||
if (!bareAgentIsPlainObject(raw)) raw = {}
|
||||
|
||||
if (missingOrEmpty || Object.keys(raw).length === 0) {
|
||||
raw = bareAgentDefaultConfig()
|
||||
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
|
||||
raw = bareAgentSanitizeConfigForBackend(raw)
|
||||
}
|
||||
await bareAgentSaveConfig(ctx, paths, /** @type {any} */ (raw))
|
||||
return { config: /** @type {any} */ (raw), created: true }
|
||||
}
|
||||
|
||||
bareAgentValidateConfigShape(raw)
|
||||
const merged = bareAgentMergeConfig(bareAgentDefaultConfig(), raw)
|
||||
const applied = bareAgentApplyAccessPolicyUpgrade(raw, merged)
|
||||
let next = applied.config
|
||||
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
|
||||
next = bareAgentSanitizeConfigForBackend(next)
|
||||
}
|
||||
const dirty =
|
||||
applied.upgraded ||
|
||||
JSON.stringify(next) !== JSON.stringify(merged)
|
||||
if (dirty) {
|
||||
try {
|
||||
await bareAgentSaveConfig(ctx, paths, /** @type {any} */ (next))
|
||||
} catch {
|
||||
/* keep upgraded in-memory even if persist fails */
|
||||
}
|
||||
}
|
||||
return { config: /** @type {any} */ (next), created: false }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {{ config: string }} paths
|
||||
* @param {Record<string, unknown>} config
|
||||
*/
|
||||
async function bareAgentSaveConfig(ctx, paths, config) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.writeFile !== 'function')
|
||||
throw new Error('agent: vfs.writeFile unavailable')
|
||||
let next = config
|
||||
if (typeof bareAgentSanitizeConfigForBackend === 'function') {
|
||||
next = bareAgentSanitizeConfigForBackend(config)
|
||||
if (config && typeof config === 'object' && config !== next) {
|
||||
for (const k of Object.keys(config)) {
|
||||
if (!Object.prototype.hasOwnProperty.call(next, k)) delete config[k]
|
||||
}
|
||||
Object.assign(config, next)
|
||||
}
|
||||
}
|
||||
bareAgentValidateConfigShape(next)
|
||||
const json = JSON.stringify(next, null, 2) + '\n'
|
||||
const body =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
|
||||
? ctx.b4a.from(json)
|
||||
: new TextEncoder().encode(json)
|
||||
await vfs.writeFile(paths.config, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* Drop middle messages until JSON size fits (keep first system + recent tail).
|
||||
* @param {unknown[]} msgs
|
||||
* @param {number} maxBytes
|
||||
*/
|
||||
function bareAgentTrimMessages(msgs, maxBytes) {
|
||||
if (!Array.isArray(msgs) || maxBytes <= 0) return []
|
||||
/** @type {unknown[]} */
|
||||
let out = msgs.slice()
|
||||
while (JSON.stringify(out).length > maxBytes && out.length > 3) {
|
||||
out.splice(2, 1)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Rough token estimate (chars/4). Good enough for local ctx budgeting.
|
||||
* @param {unknown} value
|
||||
*/
|
||||
function bareAgentEstimateTokens(value) {
|
||||
try {
|
||||
const n = JSON.stringify(value == null ? '' : value).length
|
||||
return Math.max(0, Math.ceil(n / 4))
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trim history + shrink system content so prompt fits a QVAC ctx window.
|
||||
* Reserves room for completion and optional tools JSON.
|
||||
* @param {unknown[]} msgs
|
||||
* @param {number} ctxSize
|
||||
* @param {{ tools?: unknown[], reserveCompletion?: number }} [opts]
|
||||
*/
|
||||
function bareAgentTrimMessagesForCtx(msgs, ctxSize, opts) {
|
||||
const ctx = Math.max(2048, Math.floor(Number(ctxSize) || 4096))
|
||||
const reserve =
|
||||
opts && Number.isFinite(Number(opts.reserveCompletion))
|
||||
? Math.max(256, Math.floor(Number(opts.reserveCompletion)))
|
||||
: Math.min(1024, Math.max(256, Math.floor(ctx * 0.15)))
|
||||
const toolsTok = bareAgentEstimateTokens(
|
||||
opts && Array.isArray(opts.tools) && opts.tools.length ? opts.tools : []
|
||||
)
|
||||
const budget = Math.max(512, ctx - reserve - toolsTok)
|
||||
|
||||
/** @type {unknown[]} */
|
||||
let out = Array.isArray(msgs) ? msgs.slice() : []
|
||||
// Drop middle turns first (keep system + recent).
|
||||
while (bareAgentEstimateTokens(out) > budget && out.length > 3) {
|
||||
out.splice(2, 1)
|
||||
}
|
||||
// Shrink system blob if still over (workspace/man/skills dominate).
|
||||
if (bareAgentEstimateTokens(out) > budget && out[0] && typeof out[0] === 'object') {
|
||||
const sys = /** @type {Record<string, unknown>} */ (out[0])
|
||||
if (sys.role === 'system' && typeof sys.content === 'string') {
|
||||
let content = sys.content
|
||||
let guard = 0
|
||||
while (
|
||||
bareAgentEstimateTokens(out) > budget &&
|
||||
content.length > 800 &&
|
||||
guard < 24
|
||||
) {
|
||||
content =
|
||||
content.slice(0, Math.floor(content.length * 0.82)) + '\n… truncated'
|
||||
out[0] = { ...sys, content }
|
||||
guard++
|
||||
}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* Best-effort load instruction files.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {{ instructions: string, context: string }} paths
|
||||
*/
|
||||
async function bareAgentLoadInstructionFiles(ctx, paths) {
|
||||
const vfs = ctx.vfs
|
||||
const parts = []
|
||||
if (!vfs || typeof vfs.readFile !== 'function') return ''
|
||||
try {
|
||||
const b = await vfs.readFile(paths.instructions)
|
||||
if (b && b.length) {
|
||||
const t =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
|
||||
? ctx.b4a.toString(b)
|
||||
: String(new TextDecoder().decode(b))
|
||||
if (t.trim()) parts.push('## User instructions (~/.agent/instructions.md)\n' + t.trim())
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
const b = await vfs.readFile(paths.context)
|
||||
if (b && b.length) {
|
||||
const t =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
|
||||
? ctx.b4a.toString(b)
|
||||
: String(new TextDecoder().decode(b))
|
||||
if (t.trim()) parts.push('## Agent context (~/.agent/context.md)\n' + t.trim())
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return parts.join('\n\n')
|
||||
}
|
||||
|
||||
/**
|
||||
* One-time compact index from man.json for system prompt.
|
||||
* @param {Record<string, unknown>} ctx
|
||||
*/
|
||||
async function bareAgentManDigest(ctx) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.readFile !== 'function') return ''
|
||||
try {
|
||||
const buf = await vfs.readFile('/share/man/man.json')
|
||||
if (!buf || !buf.length) return ''
|
||||
const t =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
|
||||
? ctx.b4a.toString(buf)
|
||||
: String(new TextDecoder().decode(buf))
|
||||
const j = JSON.parse(t)
|
||||
const pages = Array.isArray(j.pages) ? j.pages : []
|
||||
/** @type {string[]} */
|
||||
const lines = []
|
||||
const max = Math.min(pages.length, 400)
|
||||
for (let i = 0; i < max; i++) {
|
||||
const p = pages[i]
|
||||
if (!p || typeof p !== 'object') continue
|
||||
const name = typeof p.name === 'string' ? p.name : ''
|
||||
const title = typeof p.title === 'string' ? p.title : ''
|
||||
if (name) lines.push('- ' + name + (title ? ': ' + title : ''))
|
||||
}
|
||||
let s = lines.join('\n')
|
||||
if (s.length > 24000) s = s.slice(0, 24000) + '\n…'
|
||||
return (
|
||||
'Manual page index (see `man <name>` for full text). Sample entries:\n' + s
|
||||
)
|
||||
} catch {
|
||||
return '(man digest unavailable)'
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} path
|
||||
*/
|
||||
async function bareAgentAppendProgress(ctx, path, line) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.readFile !== 'function' || typeof vfs.writeFile !== 'function')
|
||||
return
|
||||
let prev = ''
|
||||
try {
|
||||
const b = await vfs.readFile(path)
|
||||
if (b && b.length) {
|
||||
prev =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
|
||||
? ctx.b4a.toString(b)
|
||||
: String(new TextDecoder().decode(b))
|
||||
}
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
const chunk =
|
||||
new Date().toISOString() +
|
||||
' ' +
|
||||
line.replace(/\r?\n/g, ' ') +
|
||||
'\n'
|
||||
const maxKeep = 120_000
|
||||
let next = prev + chunk
|
||||
if (next.length > maxKeep) next = next.slice(-maxKeep)
|
||||
const body =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
|
||||
? ctx.b4a.from(next)
|
||||
: new TextEncoder().encode(next)
|
||||
await vfs.writeFile(path, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} path
|
||||
* @param {unknown[]} messages
|
||||
*/
|
||||
async function bareAgentSaveHistory(ctx, path, messages) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.writeFile !== 'function') return
|
||||
const compacted =
|
||||
typeof bareAgentCompactMessagesForDisk === 'function'
|
||||
? bareAgentCompactMessagesForDisk(messages, 500_000, { keepRecent: 16 })
|
||||
: bareAgentTrimMessages(messages, 500_000)
|
||||
const trimmed =
|
||||
typeof bareAgentTrimMessages === 'function'
|
||||
? bareAgentTrimMessages(compacted, 500_000)
|
||||
: compacted
|
||||
const json = JSON.stringify(trimmed, null, 2) + '\n'
|
||||
const body =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
|
||||
? ctx.b4a.from(json)
|
||||
: new TextEncoder().encode(json)
|
||||
await vfs.writeFile(path, body)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @param {string} path
|
||||
* @returns {Promise<unknown[]>}
|
||||
*/
|
||||
async function bareAgentLoadHistory(ctx, path) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.readFile !== 'function') return []
|
||||
try {
|
||||
const buf = await vfs.readFile(path)
|
||||
if (!buf || !buf.length) return []
|
||||
const t =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.toString === 'function'
|
||||
? ctx.b4a.toString(buf)
|
||||
: String(new TextDecoder().decode(buf))
|
||||
const j = JSON.parse(t)
|
||||
return Array.isArray(j) ? j : []
|
||||
} catch {
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start an autonomous run on a config object (does not persist).
|
||||
* @param {Record<string, unknown>} cfg
|
||||
* @param {{ goal: string, maxRuntimeMs?: number, requiredChecks?: string[], scopePath?: string }} opts
|
||||
*/
|
||||
function bareAgentBeginAutonomousRun(cfg, opts) {
|
||||
const goal = String((opts && opts.goal) || '').trim()
|
||||
const maxRuntimeMs = Math.min(
|
||||
Math.max(Math.floor(Number((opts && opts.maxRuntimeMs) || cfg.autonomous_max_runtime_ms) || 0), 60000),
|
||||
7_200_000
|
||||
)
|
||||
const requiredChecks = Array.isArray(opts && opts.requiredChecks)
|
||||
? opts.requiredChecks.map((x) => String(x || '').trim()).filter(Boolean)
|
||||
: Array.isArray(cfg.autonomous_completion_required_checks)
|
||||
? cfg.autonomous_completion_required_checks.map((x) => String(x || '').trim()).filter(Boolean)
|
||||
: []
|
||||
return {
|
||||
...cfg,
|
||||
autonomous_mode_enabled: true,
|
||||
autonomous_active: true,
|
||||
autonomous_stop_requested: false,
|
||||
autonomous_started_at_ms: Date.now(),
|
||||
autonomous_goal: goal,
|
||||
autonomous_status: 'running',
|
||||
autonomous_last_error: '',
|
||||
autonomous_max_runtime_ms: maxRuntimeMs,
|
||||
autonomous_completion_required_checks: requiredChecks
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} cfg
|
||||
* @param {string} [reason]
|
||||
*/
|
||||
function bareAgentStopAutonomousRun(cfg, reason) {
|
||||
return {
|
||||
...cfg,
|
||||
autonomous_stop_requested: true,
|
||||
autonomous_status: 'stopping',
|
||||
autonomous_last_error: reason || String(cfg.autonomous_last_error || '')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} cfg
|
||||
* @param {{ remainingMs?: number, lastError?: string }} [extra]
|
||||
*/
|
||||
function bareAgentAutonomousContinuationPrompt(cfg, extra) {
|
||||
const goal = String(cfg.autonomous_goal || '').trim() || '(goal unset)'
|
||||
const remain =
|
||||
extra && typeof extra.remainingMs === 'number'
|
||||
? Math.max(0, Math.round(extra.remainingMs / 1000))
|
||||
: 0
|
||||
return (
|
||||
'AUTONOMOUS RUN still active. Do not stop with a plan — take the next concrete tool action.\n' +
|
||||
'Goal: ' +
|
||||
goal +
|
||||
'\n' +
|
||||
(remain > 0 ? 'Time remaining: ' + String(remain) + 's.\n' : '') +
|
||||
(extra && extra.lastError ? 'Last gate error: ' + extra.lastError + '\n' : '') +
|
||||
'Call task_complete(summary) only when the goal is actually finished and verified.'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} cfg
|
||||
* @param {{ completed?: boolean, hasTools?: boolean, stopRequested?: boolean }} state
|
||||
*/
|
||||
function bareAgentAutonomousShouldContinue(cfg, state) {
|
||||
if (!cfg || !cfg.autonomous_active) return false
|
||||
if (state && state.stopRequested) return false
|
||||
if (state && state.completed) return false
|
||||
if (state && state.hasTools) return false
|
||||
return true
|
||||
}
|
||||
|
||||
async function bareAgentResetChatSession(ctx, paths, argv0) {
|
||||
const vfs = ctx.vfs
|
||||
if (!vfs || typeof vfs.mkdir !== 'function' || typeof vfs.writeFile !== 'function') {
|
||||
throw new Error('agent reset: vfs unavailable')
|
||||
}
|
||||
await vfs.mkdir(paths.dir, { recursive: true })
|
||||
const emptyHist =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
|
||||
? ctx.b4a.from('[]\n')
|
||||
: new TextEncoder().encode('[]\n')
|
||||
await vfs.writeFile(paths.history, emptyHist)
|
||||
const stamp = new Date().toISOString() + ' chat session reset\n'
|
||||
const progBody =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
|
||||
? ctx.b4a.from(stamp)
|
||||
: new TextEncoder().encode(stamp)
|
||||
await vfs.writeFile(paths.progress, progBody)
|
||||
try {
|
||||
const z =
|
||||
typeof ctx.b4a !== 'undefined' && ctx.b4a && typeof ctx.b4a.from === 'function'
|
||||
? ctx.b4a.from('')
|
||||
: new TextEncoder().encode('')
|
||||
await vfs.writeFile(paths.cmdOut, z)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
try {
|
||||
ctx.console.log(
|
||||
argv0 + ': chat session cleared (' + paths.history + ', ' + paths.progress + ')'
|
||||
)
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user