Further Agent updates

This commit is contained in:
2026-08-18 15:51:13 -04:00
parent a94de5be1d
commit 2ad8ae1666
22 changed files with 3043 additions and 60 deletions
+353 -2
View File
@@ -281,7 +281,12 @@ function bareAgentToolDefinitions() {
include_stat: {
type: 'boolean',
description: 'If true, call stat on each entry (slower; default false)'
}
},
tree: {
type: 'boolean',
description: 'Grok-style bounded BFS tree instead of a flat listing'
},
max_depth: { type: 'integer', description: 'Tree depth (default 6)' }
},
required: ['path']
}
@@ -1117,6 +1122,10 @@ function bareAgentToolDefinitions() {
files_with_matches: {
type: 'boolean',
description: 'Return matching paths only'
},
output_mode: {
type: 'string',
description: 'content (default) | files_with_matches | count'
}
},
required: ['pattern']
@@ -1362,6 +1371,168 @@ function bareAgentToolDefinitions() {
}
}
},
{
type: 'function',
function: {
name: 'git_log',
description:
'git log --oneline (optional -N / path) via /bin/git. Prefer over raw run_command.',
parameters: {
type: 'object',
properties: {
cwd: { type: 'string' },
max: { type: 'integer', description: 'Default 20, cap 80' },
path: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
name: 'git_show',
description: 'git show <rev> (optional path / --stat) via /bin/git.',
parameters: {
type: 'object',
properties: {
cwd: { type: 'string' },
rev: { type: 'string', description: 'Commit-ish (default HEAD)' },
path: { type: 'string' },
stat: { type: 'boolean' }
}
}
}
},
{
type: 'function',
function: {
name: 'git_blame',
description: 'git blame a file via /bin/git.',
parameters: {
type: 'object',
properties: {
cwd: { type: 'string' },
path: { type: 'string' }
},
required: ['path']
}
}
},
{
type: 'function',
function: {
name: 'copy_path',
description:
'Copy a file or directory on the VFS (parents created). Prefer this over run_command cp.',
parameters: {
type: 'object',
properties: {
from_path: { type: 'string' },
to_path: { type: 'string' }
},
required: ['from_path', 'to_path']
}
}
},
{
type: 'function',
function: {
name: 'diff_files',
description:
'Unified diff of two UTF-8 files (no git). Prefer this when comparing two paths that are not a git hunk.',
parameters: {
type: 'object',
properties: {
from_path: { type: 'string' },
to_path: { type: 'string' },
context: { type: 'integer', description: 'Context lines (default 3)' }
},
required: ['from_path', 'to_path']
}
}
},
{
type: 'function',
function: {
name: 'find_symbol',
description:
'Find likely definitions of a symbol (function/class/const/def/fn) under a root. Prefer over ad-hoc grep for go-to-definition.',
parameters: {
type: 'object',
properties: {
name: { type: 'string' },
root: { type: 'string' },
glob: { type: 'string' },
max: { type: 'integer' }
},
required: ['name']
}
}
},
{
type: 'function',
function: {
name: 'create_skill',
description:
'Write a Grok-style SKILL.md (YAML frontmatter + body) under ~/.agent/workspace/skills/<id>/.',
parameters: {
type: 'object',
properties: {
id: { type: 'string', description: 'Folder name (kebab-case)' },
name: { type: 'string' },
description: { type: 'string' },
body: { type: 'string', description: 'Markdown instructions after frontmatter' }
},
required: ['id', 'description']
}
}
},
{
type: 'function',
function: {
name: 'remember',
description:
'Save a durable FACT to MEMORY.md now (Grok /remember). Same store as memory_append.',
parameters: {
type: 'object',
properties: {
text: { type: 'string' },
daily: { type: 'boolean' }
},
required: ['text']
}
}
},
{
type: 'function',
function: {
name: 'rewind_session',
description:
'Grok /rewind: drop the last N user turns from ~/.agent/history.json (and everything after). Default 1 turn.',
parameters: {
type: 'object',
properties: {
steps: { type: 'integer', description: 'How many user turns to drop (default 1)' },
user_index: { type: 'integer', description: 'Rewind to this 0-based user turn instead' },
keep_user: { type: 'boolean', description: 'Keep the target user prompt' }
}
}
}
},
{
type: 'function',
function: {
name: 'export_session',
description:
'Grok /export: write the current chat history as Markdown. Default ~/.agent/export.md.',
parameters: {
type: 'object',
properties: {
path: { type: 'string' }
}
}
}
},
{
type: 'function',
function: {
@@ -1484,6 +1655,13 @@ async function bareAgentDispatchTool(o) {
if (toolName === 'ripgrep') {
return bareAgentDispatchTool({ ...o, toolName: 'grep' })
}
if (toolName === 'remember') {
return bareAgentDispatchTool({
...o,
toolName: 'memory_append',
argsJson: JSON.stringify({ ...args, kind: 'FACT' })
})
}
const vfs = ctx.vfs
const mutateDenyPrefixes = (function () {
@@ -2487,6 +2665,14 @@ async function bareAgentDispatchTool(o) {
if (!bareAgentPathAllowed(dir)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (Boolean(args.tree) && typeof bareAgentRenderTree === 'function') {
appendProgress('list_directory tree ' + dir)
const tree = await bareAgentRenderTree(ctx, dir, {
max: maxEnt,
maxDepth: args.max_depth
})
return bareAgentJsonResult(tree)
}
if (!vfs || typeof vfs.readdir !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'readdir unavailable' })
}
@@ -3509,7 +3695,8 @@ async function bareAgentDispatchTool(o) {
after: args.after,
context: args.context,
max_matches: args.max_matches,
files_with_matches: Boolean(args.files_with_matches)
files_with_matches: Boolean(args.files_with_matches),
output_mode: typeof args.output_mode === 'string' ? args.output_mode : ''
})
return bareAgentJsonResult(out)
}
@@ -3976,6 +4163,170 @@ async function bareAgentDispatchTool(o) {
return bareAgentJsonResult({ ok: true, query, hits })
}
if (toolName === 'git_log' || toolName === 'git_show' || toolName === 'git_blame') {
const cwd =
(typeof args.cwd === 'string' && args.cwd.trim()) ||
(ctx.env && typeof ctx.env === 'object'
? String(ctx.env.PWD || ctx.env.CWD || home || '').trim()
: '') ||
home ||
'/home'
if (!execLine) return bareAgentJsonResult({ ok: false, error: 'execLine unavailable' })
const quoted = bareAgentShellQuote(cwd)
let cmd = 'git -C ' + quoted + ' '
if (toolName === 'git_log') {
const max = Math.min(80, Math.max(1, Math.floor(Number(args.max) || 20)))
const p = typeof args.path === 'string' ? args.path.trim() : ''
cmd += 'log --oneline -' + String(max) + (p ? ' -- ' + bareAgentShellQuote(p) : '')
} else if (toolName === 'git_show') {
const rev = typeof args.rev === 'string' && args.rev.trim() ? args.rev.trim() : 'HEAD'
const p = typeof args.path === 'string' ? args.path.trim() : ''
cmd +=
'show ' +
(args.stat ? '--stat ' : '') +
bareAgentShellQuote(rev) +
(p ? ' -- ' + bareAgentShellQuote(p) : '')
} else {
const p = typeof args.path === 'string' ? args.path.trim() : ''
if (!p) return bareAgentJsonResult({ ok: false, error: 'path_required' })
cmd += 'blame -- ' + bareAgentShellQuote(p)
}
appendProgress(toolName + ' ' + cwd)
const r = await captureExec(cmd, 30000)
return bareAgentJsonResult(
r.ok === false ? r : { ok: true, cwd, stdout_stderr: r.stdout_stderr }
)
}
if (toolName === 'copy_path') {
const from = typeof args.from_path === 'string' ? args.from_path.trim() : ''
const to = typeof args.to_path === 'string' ? args.to_path.trim() : ''
if (
!from ||
!to ||
from.includes('..') ||
to.includes('..') ||
!bareAgentPathAllowed(from) ||
!bareAgentPathAllowedMutate(to, mutateDenyPrefixes)
) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (typeof bareAgentCopyPath !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'copy_unavailable' })
}
appendProgress('copy_path ' + from)
const out = await bareAgentCopyPath(ctx, from, to)
return bareAgentJsonResult(out)
}
if (toolName === 'diff_files') {
const from = typeof args.from_path === 'string' ? args.from_path.trim() : ''
const to = typeof args.to_path === 'string' ? args.to_path.trim() : ''
if (!from || !to || !bareAgentPathAllowed(from) || !bareAgentPathAllowed(to)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
if (typeof bareAgentUnifiedDiff !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'diff_unavailable' })
}
const a = await bareAgentReadTextFile(ctx, from)
const b = await bareAgentReadTextFile(ctx, to)
appendProgress('diff_files ' + from)
const out = bareAgentUnifiedDiff(a, b, {
from,
to,
context: args.context
})
return bareAgentJsonResult(out)
}
if (toolName === 'find_symbol') {
const name = typeof args.name === 'string' ? args.name.trim() : ''
if (!name) return bareAgentJsonResult({ ok: false, error: 'name_required' })
const root =
(typeof args.root === 'string' && args.root.trim()) || home || '/home'
if (!bareAgentPathAllowed(root) || typeof bareAgentFindSymbol !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'find_symbol_unavailable' })
}
appendProgress('find_symbol ' + name)
const out = await bareAgentFindSymbol(ctx, root, name, {
glob: typeof args.glob === 'string' ? args.glob : '',
max: args.max
})
return bareAgentJsonResult(out)
}
if (toolName === 'create_skill') {
const id = String(args.id || '')
.trim()
.toLowerCase()
.replace(/[^a-z0-9-]+/g, '-')
.replace(/^-+|-+$/g, '')
if (!id) return bareAgentJsonResult({ ok: false, error: 'id_required' })
const destRoot =
(typeof paths.workspaceSkills === 'string' && paths.workspaceSkills) ||
(typeof paths.workspace === 'string' ? paths.workspace + '/skills' : paths.dir + '/workspace/skills')
const dest = destRoot + '/' + id + '/SKILL.md'
if (!bareAgentPathAllowedMutate(dest, mutateDenyPrefixes)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const md =
typeof bareAgentSkillMarkdown === 'function'
? bareAgentSkillMarkdown({
name: typeof args.name === 'string' ? args.name : id,
description: typeof args.description === 'string' ? args.description : id,
body: typeof args.body === 'string' ? args.body : ''
})
: String(args.body || '')
await bareAgentWriteTextFile(ctx, dest, md)
appendProgress('create_skill ' + id)
return bareAgentJsonResult({ ok: true, id, path: dest })
}
if (toolName === 'rewind_session') {
const histPath = paths.history || paths.dir + '/history.json'
const raw = await bareAgentReadJsonFile(ctx, histPath, [])
if (typeof bareAgentRewindHistory !== 'function') {
return bareAgentJsonResult({ ok: false, error: 'rewind_unavailable' })
}
const out = bareAgentRewindHistory(Array.isArray(raw) ? raw : [], {
steps: args.steps,
userIndex: args.user_index,
keep_user: args.keep_user
})
if (!out.ok) return bareAgentJsonResult(out)
await bareAgentSaveHistory(ctx, histPath, out.messages)
appendProgress('rewind_session dropped=' + String(out.dropped))
return bareAgentJsonResult({
ok: true,
dropped: out.dropped,
remaining: out.messages.length,
target: out.target || null,
keep_user: Boolean(out.keep_user)
})
}
if (toolName === 'export_session') {
const histPath = paths.history || paths.dir + '/history.json'
const dest =
(typeof args.path === 'string' && args.path.trim()) ||
(paths.dir ? paths.dir + '/export.md' : '/tmp/agent-export.md')
if (!bareAgentPathAllowedMutate(dest, mutateDenyPrefixes)) {
return bareAgentJsonResult({ ok: false, error: 'path_not_allowed' })
}
const raw = await bareAgentReadJsonFile(ctx, histPath, [])
const md =
typeof bareAgentExportTranscript === 'function'
? bareAgentExportTranscript(Array.isArray(raw) ? raw : [])
: ''
await bareAgentWriteTextFile(ctx, dest, md)
appendProgress('export_session ' + dest)
return bareAgentJsonResult({
ok: true,
path: dest,
messages: Array.isArray(raw) ? raw.length : 0
})
}
return bareAgentJsonResult({ ok: false, error: 'unknown_tool ' + toolName })
} catch (e) {
const msg =