Agent Updates
This commit is contained in:
+648
-1
@@ -1663,6 +1663,555 @@ async function bareAgentCompleteOnce(opts) {
|
||||
return j
|
||||
}
|
||||
|
||||
/** HTTP fetch + HTML extract helpers for agent `web_fetch` tool (preamble for /bin/agent). */
|
||||
|
||||
/**
|
||||
* @param {Record<string, unknown>} ctx
|
||||
* @returns {typeof fetch | null}
|
||||
*/
|
||||
function bareWebResolveFetch(ctx) {
|
||||
if (typeof ctx.httpFetch === 'function')
|
||||
return /** @type {typeof fetch} */ (ctx.httpFetch.bind(ctx))
|
||||
const bare = ctx.bare && typeof ctx.bare === 'object' ? ctx.bare : null
|
||||
let f =
|
||||
bare && typeof bare.fetch === 'function'
|
||||
? bare.fetch
|
||||
: bare &&
|
||||
bare.default &&
|
||||
typeof bare.default === 'object' &&
|
||||
typeof bare.default.fetch === 'function'
|
||||
? bare.default.fetch
|
||||
: null
|
||||
if (typeof f === 'function') return /** @type {typeof fetch} */ (f.bind(bare))
|
||||
if (typeof globalThis.fetch === 'function')
|
||||
return globalThis.fetch.bind(globalThis)
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {AbortSignal | null | undefined} a
|
||||
* @param {AbortSignal | null | undefined} b
|
||||
*/
|
||||
function bareWebUnionAbort(a, b) {
|
||||
if (!a) return b || undefined
|
||||
if (!b) return a
|
||||
if (typeof AbortSignal.any === 'function') return AbortSignal.any([a, b])
|
||||
const c = new AbortController()
|
||||
const fn = () => {
|
||||
try {
|
||||
c.abort()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
try {
|
||||
if (a.aborted) fn()
|
||||
else a.addEventListener('abort', fn, { once: true })
|
||||
if (b.aborted) fn()
|
||||
else b.addEventListener('abort', fn, { once: true })
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return c.signal
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array[]} parts
|
||||
*/
|
||||
function bareWebConcatUint8(parts) {
|
||||
let n = 0
|
||||
for (const p of parts) n += p.length
|
||||
const out = new Uint8Array(n)
|
||||
let o = 0
|
||||
for (const p of parts) {
|
||||
out.set(p, o)
|
||||
o += p.length
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Response} res
|
||||
* @param {number} maxBytes
|
||||
* @param {AbortSignal | undefined} signal
|
||||
*/
|
||||
async function bareWebReadBodyLimited(res, maxBytes, signal) {
|
||||
if (!res.body || typeof res.body.getReader !== 'function') {
|
||||
try {
|
||||
const ab = await res.arrayBuffer()
|
||||
const u8 = new Uint8Array(ab)
|
||||
return {
|
||||
bytes: u8.byteLength > maxBytes ? u8.slice(0, maxBytes) : u8,
|
||||
truncated: u8.byteLength > maxBytes
|
||||
}
|
||||
} catch {
|
||||
return { bytes: new Uint8Array(0), truncated: false }
|
||||
}
|
||||
}
|
||||
const reader = res.body.getReader()
|
||||
/** @type {Uint8Array[]} */
|
||||
const chunks = []
|
||||
let total = 0
|
||||
try {
|
||||
for (;;) {
|
||||
if (signal && signal.aborted) {
|
||||
try {
|
||||
await reader.cancel()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
break
|
||||
}
|
||||
const { done, value } = await reader.read()
|
||||
if (done) break
|
||||
if (!value || !value.length) continue
|
||||
total += value.length
|
||||
if (total > maxBytes) {
|
||||
const prev = total - value.length
|
||||
const take = Math.max(0, maxBytes - prev)
|
||||
if (take > 0) chunks.push(value.subarray(0, take))
|
||||
try {
|
||||
await reader.cancel()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
return { bytes: bareWebConcatUint8(chunks), truncated: true }
|
||||
}
|
||||
chunks.push(value)
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
reader.releaseLock()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
return { bytes: bareWebConcatUint8(chunks), truncated: false }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string | null | undefined} ct
|
||||
*/
|
||||
function bareWebCharsetFromContentType(ct) {
|
||||
const m = /charset\s*=\s*["']?([^"';\s]+)/i.exec(String(ct || ''))
|
||||
return (m ? m[1] : 'utf-8').trim().toLowerCase()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} bytes
|
||||
* @param {string} label
|
||||
*/
|
||||
function bareWebDecodeBytes(bytes, label) {
|
||||
try {
|
||||
const dec = new TextDecoder(label || 'utf-8', { fatal: false, ignoreBOM: true })
|
||||
return dec.decode(bytes)
|
||||
} catch {
|
||||
return new TextDecoder('utf-8', { fatal: false }).decode(bytes)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} s
|
||||
*/
|
||||
function bareWebDecodeHtmlEntities(s) {
|
||||
let t = String(s || '')
|
||||
t = t.replace(/ /gi, ' ')
|
||||
t = t.replace(/"/gi, '"')
|
||||
t = t.replace(/'/g, "'")
|
||||
t = t.replace(/'/gi, "'")
|
||||
t = t.replace(/&/gi, '&')
|
||||
t = t.replace(/</gi, '<')
|
||||
t = t.replace(/>/gi, '>')
|
||||
t = t.replace(/&#x([0-9a-f]+);/gi, (_, h) => {
|
||||
const c = parseInt(h, 16)
|
||||
return Number.isFinite(c) ? String.fromCodePoint(c) : _
|
||||
})
|
||||
t = t.replace(/&#(\d+);/g, (_, d) => {
|
||||
const c = parseInt(d, 10)
|
||||
return Number.isFinite(c) ? String.fromCodePoint(c) : _
|
||||
})
|
||||
return t
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
*/
|
||||
function bareWebExtractHtmlText(html) {
|
||||
let s = String(html || '')
|
||||
s = s.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
|
||||
s = s.replace(/<style\b[^<]*(?:(?!<\/style>)<[^<]*)*<\/style>/gi, '')
|
||||
s = s.replace(/<noscript\b[^<]*(?:(?!<\/noscript>)<[^<]*)*<\/noscript>/gi, '')
|
||||
s = s.replace(/<!--[\s\S]*?-->/g, '')
|
||||
s = s.replace(/<[^>]+>/g, ' ')
|
||||
s = bareWebDecodeHtmlEntities(s)
|
||||
s = s.replace(/\s+/g, ' ').trim()
|
||||
return s
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
* @param {string} baseUrl
|
||||
* @param {number} maxLinks
|
||||
*/
|
||||
function bareWebExtractLinks(html, baseUrl, maxLinks) {
|
||||
const cap = Math.min(Math.max(Number(maxLinks) || 200, 1), 500)
|
||||
let uBase = null
|
||||
try {
|
||||
uBase = baseUrl ? new URL(String(baseUrl)) : null
|
||||
} catch {
|
||||
uBase = null
|
||||
}
|
||||
const seen = new Set()
|
||||
/** @type {string[]} */
|
||||
const out = []
|
||||
const re =
|
||||
/<a\b[^>]*\bhref\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/gi
|
||||
let m
|
||||
const h = String(html || '')
|
||||
while ((m = re.exec(h)) !== null) {
|
||||
const href = (m[1] || m[2] || m[3] || '').trim()
|
||||
if (!href || href.startsWith('javascript:') || href.startsWith('#')) continue
|
||||
try {
|
||||
const abs = uBase ? new URL(href, uBase).href : new URL(href).href
|
||||
const proto = new URL(abs).protocol
|
||||
if (proto !== 'http:' && proto !== 'https:') continue
|
||||
if (!seen.has(abs)) {
|
||||
seen.add(abs)
|
||||
out.push(abs)
|
||||
}
|
||||
} catch {
|
||||
/* skip */
|
||||
}
|
||||
if (out.length >= cap) break
|
||||
}
|
||||
return { links: out, links_truncated: out.length >= cap }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} tag
|
||||
*/
|
||||
function bareWebMetaContent(tag) {
|
||||
const q =
|
||||
/content\s*=\s*"([^"]*)"/i.exec(tag) ||
|
||||
/content\s*=\s*'([^']*)'/i.exec(tag) ||
|
||||
/content\s*=\s*([^\s>]+)/i.exec(tag)
|
||||
return q ? bareWebDecodeHtmlEntities(q[1]).trim() : ''
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} html
|
||||
*/
|
||||
function bareWebExtractMeta(html) {
|
||||
const h = String(html || '')
|
||||
const titleM = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(h)
|
||||
const title = titleM
|
||||
? bareWebDecodeHtmlEntities(titleM[1].replace(/<[^>]+>/g, ' ')).trim()
|
||||
: ''
|
||||
let description = ''
|
||||
const metaDescRe =
|
||||
/<meta[^>]*\bname\s*=\s*["']description["'][^>]*>/i.exec(h)
|
||||
if (metaDescRe) description = bareWebMetaContent(metaDescRe[0])
|
||||
|
||||
let og_title = ''
|
||||
const ogT = /<meta[^>]*\bproperty\s*=\s*["']og:title["'][^>]*>/i.exec(h)
|
||||
if (ogT) og_title = bareWebMetaContent(ogT[0])
|
||||
|
||||
let og_description = ''
|
||||
const ogD =
|
||||
/<meta[^>]*\bproperty\s*=\s*["']og:description["'][^>]*>/i.exec(h)
|
||||
if (ogD) og_description = bareWebMetaContent(ogD[0])
|
||||
|
||||
return {
|
||||
title,
|
||||
description,
|
||||
og_title,
|
||||
og_description
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} text
|
||||
*/
|
||||
function bareWebMaybeParseJson(text) {
|
||||
try {
|
||||
return { ok: true, value: JSON.parse(String(text)) }
|
||||
} catch {
|
||||
return { ok: false }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} ct
|
||||
*/
|
||||
function bareWebLooksLikeHtml(ct) {
|
||||
return /\btext\/html\b/i.test(String(ct || ''))
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} ct
|
||||
*/
|
||||
function bareWebLooksLikeJson(ct) {
|
||||
const s = String(ct || '').toLowerCase()
|
||||
return (
|
||||
/\bapplication\/json\b/.test(s) ||
|
||||
/\bapplication\/.*\+json\b/.test(s) ||
|
||||
/\btext\/json\b/.test(s)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {{
|
||||
* ctx: Record<string, unknown>,
|
||||
* url: string,
|
||||
* method?: string,
|
||||
* headers?: Record<string, unknown>,
|
||||
* body?: string,
|
||||
* content_type?: string,
|
||||
* max_response_bytes?: number,
|
||||
* max_redirects?: number,
|
||||
* timeout_ms?: number,
|
||||
* format?: string,
|
||||
* max_links?: number,
|
||||
* signal?: AbortSignal | null
|
||||
* }} o
|
||||
*/
|
||||
async function bareWebRunTool(o) {
|
||||
const ctx = o.ctx
|
||||
const fetchFn = bareWebResolveFetch(ctx)
|
||||
if (!fetchFn) {
|
||||
return {
|
||||
ok: false,
|
||||
error:
|
||||
'web_fetch: no HTTP client (set ctx.httpFetch, bare.fetch, or global fetch)'
|
||||
}
|
||||
}
|
||||
|
||||
let startUrl = String(o.url || '').trim()
|
||||
let method = String(o.method || 'GET').toUpperCase()
|
||||
if (
|
||||
!['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'].includes(
|
||||
method
|
||||
)
|
||||
) {
|
||||
return { ok: false, error: 'web_fetch: unsupported method' }
|
||||
}
|
||||
|
||||
let body =
|
||||
o.body != null && method !== 'GET' && method !== 'HEAD'
|
||||
? String(o.body)
|
||||
: undefined
|
||||
|
||||
let u0
|
||||
try {
|
||||
u0 = new URL(startUrl)
|
||||
} catch {
|
||||
return { ok: false, error: 'web_fetch: invalid URL' }
|
||||
}
|
||||
if (u0.protocol !== 'http:' && u0.protocol !== 'https:') {
|
||||
return { ok: false, error: 'web_fetch: only http(s) URLs are allowed' }
|
||||
}
|
||||
|
||||
const maxRedirects = Math.min(Math.max(Number(o.max_redirects) ?? 5, 0), 20)
|
||||
const maxBytes = Math.min(
|
||||
Math.max(Number(o.max_response_bytes) ?? 524288, 1024),
|
||||
2 * 1024 * 1024
|
||||
)
|
||||
const timeoutMs = Math.min(
|
||||
Math.max(Number(o.timeout_ms) ?? 30000, 500),
|
||||
120000
|
||||
)
|
||||
const fmtRaw = String(o.format || 'auto').toLowerCase()
|
||||
const maxLinks = Number(o.max_links) || 200
|
||||
|
||||
/** @type {string[]} */
|
||||
const redirectChain = [startUrl]
|
||||
let currentUrl = startUrl
|
||||
let redirectsUsed = 0
|
||||
|
||||
for (;;) {
|
||||
const controller = new AbortController()
|
||||
const timer = setTimeout(() => {
|
||||
try {
|
||||
controller.abort()
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}, timeoutMs)
|
||||
const signal = bareWebUnionAbort(o.signal || undefined, controller.signal)
|
||||
|
||||
/** @type {Record<string, string>} */
|
||||
const hdrObj = {}
|
||||
const hin = o.headers
|
||||
if (hin && typeof hin === 'object' && !Array.isArray(hin)) {
|
||||
for (const [k, v] of Object.entries(hin)) {
|
||||
if (typeof v === 'string' && k) hdrObj[k] = v
|
||||
}
|
||||
}
|
||||
if (
|
||||
body != null &&
|
||||
method !== 'GET' &&
|
||||
method !== 'HEAD' &&
|
||||
!Object.keys(hdrObj).some((k) => k.toLowerCase() === 'content-type')
|
||||
) {
|
||||
hdrObj['Content-Type'] =
|
||||
typeof o.content_type === 'string' && o.content_type.trim()
|
||||
? o.content_type.trim()
|
||||
: 'application/octet-stream'
|
||||
}
|
||||
|
||||
/** @type {RequestInit} */
|
||||
const init = {
|
||||
method,
|
||||
headers: hdrObj,
|
||||
signal: signal || undefined,
|
||||
redirect: 'manual'
|
||||
}
|
||||
if (body != null && method !== 'GET' && method !== 'HEAD') {
|
||||
init.body = body
|
||||
}
|
||||
|
||||
let res
|
||||
try {
|
||||
res = await fetchFn(currentUrl, init)
|
||||
} catch (e) {
|
||||
clearTimeout(timer)
|
||||
const msg = e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
|
||||
return {
|
||||
ok: false,
|
||||
error: 'web_fetch: request failed: ' + msg.slice(0, 400),
|
||||
url_final: currentUrl,
|
||||
redirect_chain: redirectChain
|
||||
}
|
||||
}
|
||||
clearTimeout(timer)
|
||||
|
||||
const st = res.status
|
||||
if (st >= 300 && st < 400) {
|
||||
if (redirectsUsed >= maxRedirects) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'web_fetch: too many redirects',
|
||||
status: st,
|
||||
url_final: currentUrl,
|
||||
redirect_chain: redirectChain
|
||||
}
|
||||
}
|
||||
const loc = res.headers.get('Location')
|
||||
if (!loc) {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'web_fetch: redirect without Location',
|
||||
status: st,
|
||||
url_final: currentUrl,
|
||||
redirect_chain: redirectChain
|
||||
}
|
||||
}
|
||||
let nextUrl
|
||||
try {
|
||||
nextUrl = new URL(loc, currentUrl).href
|
||||
} catch {
|
||||
return {
|
||||
ok: false,
|
||||
error: 'web_fetch: bad redirect URL',
|
||||
status: st,
|
||||
url_final: currentUrl,
|
||||
redirect_chain: redirectChain
|
||||
}
|
||||
}
|
||||
redirectChain.push(nextUrl)
|
||||
currentUrl = nextUrl
|
||||
redirectsUsed++
|
||||
if (st === 301 || st === 302 || st === 303) {
|
||||
method = 'GET'
|
||||
body = undefined
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
const ct = res.headers.get('content-type') || ''
|
||||
const url_final = currentUrl
|
||||
|
||||
if (method === 'HEAD') {
|
||||
return {
|
||||
ok: true,
|
||||
url_final,
|
||||
status: st,
|
||||
content_type: ct,
|
||||
redirect_chain: redirectChain.length > 1 ? redirectChain : undefined,
|
||||
truncated: false,
|
||||
extract: { note: 'HEAD — body omitted' }
|
||||
}
|
||||
}
|
||||
|
||||
let bodyRead
|
||||
try {
|
||||
bodyRead = await bareWebReadBodyLimited(res, maxBytes, signal || undefined)
|
||||
} catch (e) {
|
||||
const msg = e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
|
||||
return {
|
||||
ok: false,
|
||||
error: 'web_fetch: read body failed: ' + msg.slice(0, 400),
|
||||
url_final,
|
||||
status: st,
|
||||
redirect_chain: redirectChain.length > 1 ? redirectChain : undefined
|
||||
}
|
||||
}
|
||||
|
||||
const charset = bareWebCharsetFromContentType(ct)
|
||||
const text = bareWebDecodeBytes(bodyRead.bytes, charset)
|
||||
|
||||
const fmt =
|
||||
fmtRaw === 'auto'
|
||||
? bareWebLooksLikeJson(ct)
|
||||
? 'json'
|
||||
: bareWebLooksLikeHtml(ct)
|
||||
? 'markdownish'
|
||||
: 'raw'
|
||||
: fmtRaw
|
||||
|
||||
/** @type {unknown} */
|
||||
let extract
|
||||
|
||||
if (fmt === 'json') {
|
||||
const p = bareWebMaybeParseJson(text)
|
||||
extract = p.ok ? { json: p.value } : { parse_error: true, text_slice: text.slice(0, 8000) }
|
||||
} else if (fmt === 'links') {
|
||||
extract = bareWebExtractLinks(text, url_final, maxLinks)
|
||||
} else if (fmt === 'meta') {
|
||||
extract = bareWebExtractMeta(text)
|
||||
} else if (fmt === 'markdownish' || fmt === 'text') {
|
||||
const plain = bareWebExtractHtmlText(text)
|
||||
extract = {
|
||||
text: plain,
|
||||
approx_chars: plain.length
|
||||
}
|
||||
} else if (fmt === 'raw') {
|
||||
extract = {
|
||||
raw_text: text.length > 12000 ? text.slice(0, 12000) + '\n…' : text,
|
||||
char_count: text.length
|
||||
}
|
||||
} else {
|
||||
extract = {
|
||||
text: text.length > 12000 ? text.slice(0, 12000) + '\n…' : text,
|
||||
char_count: text.length
|
||||
}
|
||||
}
|
||||
|
||||
const raw_preview = text.slice(0, 2000)
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
url_final,
|
||||
status: st,
|
||||
content_type: ct,
|
||||
redirect_chain: redirectChain.length > 1 ? redirectChain : undefined,
|
||||
truncated: bodyRead.truncated,
|
||||
extract,
|
||||
raw_preview: fmt === 'raw' || fmt === 'json' ? undefined : raw_preview
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** OpenAI-style tool schemas + dispatch (preamble for /bin/agent). */
|
||||
|
||||
/**
|
||||
@@ -2042,6 +2591,60 @@ function bareAgentToolDefinitions() {
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
name: 'web_fetch',
|
||||
description:
|
||||
'Fetch live HTTP(S) URLs and return structured content for the assistant. Uses ctx.httpFetch (same policy as wget/curl: BARE_OS_HTTP_ALLOWLIST / DENYLIST). For official docs index use read_man_page / apropos_man — they are not web pages. Supports GET/HEAD/POST and extract modes: auto (JSON vs HTML vs text), markdownish plain text from HTML, links (anchor hrefs), meta (title/og:), raw UTF-8 slice, or json parse.',
|
||||
parameters: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
url: { type: 'string', description: 'Absolute http(s) URL' },
|
||||
method: {
|
||||
type: 'string',
|
||||
description: 'HTTP method (default GET)',
|
||||
enum: ['GET', 'HEAD', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
|
||||
},
|
||||
headers: {
|
||||
type: 'object',
|
||||
description: 'Optional header map (string values only)'
|
||||
},
|
||||
body: {
|
||||
type: 'string',
|
||||
description: 'Request body for non-GET (e.g. JSON string for APIs)'
|
||||
},
|
||||
content_type: {
|
||||
type: 'string',
|
||||
description: 'Content-Type when body is set (default application/octet-stream)'
|
||||
},
|
||||
format: {
|
||||
type: 'string',
|
||||
enum: ['auto', 'json', 'markdownish', 'text', 'links', 'meta', 'raw'],
|
||||
description:
|
||||
'auto: sniff Content-Type; json: parse JSON; markdownish/text: strip HTML to readable text; links: absolute http(s) links; meta: title/description/og tags; raw: bounded UTF-8 text'
|
||||
},
|
||||
max_response_bytes: {
|
||||
type: 'integer',
|
||||
description: 'Cap downloaded bytes (default 524288, max 2MiB)'
|
||||
},
|
||||
max_redirects: {
|
||||
type: 'integer',
|
||||
description: 'Max redirects to follow (default 5)'
|
||||
},
|
||||
timeout_ms: {
|
||||
type: 'integer',
|
||||
description: 'Per-request timeout ms (default 30000, max 120000)'
|
||||
},
|
||||
max_links: {
|
||||
type: 'integer',
|
||||
description: 'Max links when format=links (default 200)'
|
||||
}
|
||||
},
|
||||
required: ['url']
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
type: 'function',
|
||||
function: {
|
||||
@@ -2797,6 +3400,50 @@ async function bareAgentDispatchTool(o) {
|
||||
)
|
||||
}
|
||||
|
||||
if (toolName === 'web_fetch') {
|
||||
const url = typeof args.url === 'string' ? args.url : ''
|
||||
let hostHint = ''
|
||||
try {
|
||||
hostHint = new URL(url).hostname
|
||||
} catch {
|
||||
hostHint = ''
|
||||
}
|
||||
appendProgress('web_fetch ' + (hostHint || url.slice(0, 80)))
|
||||
try {
|
||||
const out = await bareWebRunTool({
|
||||
ctx,
|
||||
url,
|
||||
method: typeof args.method === 'string' ? args.method : undefined,
|
||||
headers:
|
||||
args.headers &&
|
||||
typeof args.headers === 'object' &&
|
||||
!Array.isArray(args.headers)
|
||||
? /** @type {Record<string, unknown>} */ (args.headers)
|
||||
: undefined,
|
||||
body: typeof args.body === 'string' ? args.body : undefined,
|
||||
content_type:
|
||||
typeof args.content_type === 'string' ? args.content_type : undefined,
|
||||
max_response_bytes:
|
||||
typeof args.max_response_bytes === 'number'
|
||||
? args.max_response_bytes
|
||||
: undefined,
|
||||
max_redirects:
|
||||
typeof args.max_redirects === 'number' ? args.max_redirects : undefined,
|
||||
timeout_ms:
|
||||
typeof args.timeout_ms === 'number' ? args.timeout_ms : undefined,
|
||||
format: typeof args.format === 'string' ? args.format : undefined,
|
||||
max_links:
|
||||
typeof args.max_links === 'number' ? args.max_links : undefined,
|
||||
signal
|
||||
})
|
||||
return bareAgentJsonResult(out)
|
||||
} catch (e) {
|
||||
const msg =
|
||||
e && typeof e === 'object' && 'message' in e ? String(e.message) : String(e)
|
||||
return bareAgentJsonResult({ ok: false, error: msg })
|
||||
}
|
||||
}
|
||||
|
||||
return bareAgentJsonResult({ ok: false, error: 'unknown_tool ' + toolName })
|
||||
} catch (e) {
|
||||
const msg =
|
||||
@@ -2929,7 +3576,7 @@ Capabilities: ctx.execLine for shell lines; ctx.vfs readFile/writeFile/mkdir/rea
|
||||
|
||||
Safety: never exfiltrate ~/.agent/config.json or API keys. Prefer least-privilege commands. Call task_complete(summary) when fully done.
|
||||
|
||||
Discovery: man <topic>, /share/man/man.json; Tier-1 utilities in /bin. Tools: list_directory, file_stat, read_man_page, apropos_man, read_proc_file, get_swarm_peers, get_resource_limits; use list_directory instead of \`ls\` in run_command when only listing.
|
||||
Discovery: man <topic>, /share/man/man.json; Tier-1 utilities in /bin. Tools: list_directory, file_stat, read_man_page, apropos_man, read_proc_file, get_swarm_peers, get_resource_limits, web_fetch (live http(s) pages and APIs; same host allowlist as wget); use list_directory instead of \`ls\` in run_command when only listing.
|
||||
|
||||
Kernel features / capabilities that are **actually enabled or disabled** in this runtime come from **read_proc_file** on \`/proc/bare_os/features\` (same payload as \`/proc/bare_os/features.json\`) and \`/proc/bare_os/capabilities.json\`. **Do not** infer current kernel state from \`apropos_man\` or man pages—that only searches documentation keywords.
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"schema": 2,
|
||||
"profileId": "bare-os-posix-like",
|
||||
"generatedAt": "2026-04-22T02:53:20.497Z",
|
||||
"generatedAt": "2026-04-22T03:27:54.120Z",
|
||||
"note": "Sparse POSIX Issue 7 coverage hints for /bin utilities. Omitted command names are not yet profiled here.",
|
||||
"commandIndex": [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"schema": 1,
|
||||
"atMs": 1776826400497,
|
||||
"atMs": 1776828474119,
|
||||
"commands": [
|
||||
"agent",
|
||||
"arch",
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user