agent.libx.js 0.89.9 → 0.92.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,4 +1,4 @@
1
- import { A as AgentTool } from './tools-Ch-OzOU8.js';
1
+ import { A as AgentTool } from './tools-CeK5AquG.js';
2
2
 
3
3
  /**
4
4
  * MCP bridge — adapt a Model Context Protocol tool list into the agent's AgentTool[],
@@ -16,6 +16,14 @@ interface McpToolSpec {
16
16
  }
17
17
  /** Perform an MCP tool call; return its textual result. Throw to surface an error to the model. */
18
18
  type McpCall = (name: string, args: any) => Promise<unknown>;
19
+ interface McpImage {
20
+ mimeType: string;
21
+ data: string;
22
+ }
23
+ interface McpToolResult {
24
+ text: string;
25
+ images?: McpImage[];
26
+ }
19
27
  /** Adapt one MCP tool spec into an AgentTool backed by `callTool`. */
20
28
  declare function mcpToolToAgentTool(spec: McpToolSpec, callTool: McpCall, prefix?: string): AgentTool;
21
29
  /** Adapt a whole MCP tool list into AgentTool[] (names prefixed to avoid collisions).
@@ -39,4 +47,4 @@ interface McpToolSearchOptions {
39
47
  */
40
48
  declare function makeMcpToolSearch(specs: McpToolSpec[], callTool: McpCall, options?: McpToolSearchOptions): AgentTool[];
41
49
 
42
- export { type McpCall as M, type McpToolSearchOptions as a, type McpToolSpec as b, mcpToolToAgentTool as c, mcpToolsToAgentTools as d, makeMcpToolSearch as m };
50
+ export { type McpCall as M, type McpImage as a, type McpToolResult as b, type McpToolSearchOptions as c, type McpToolSpec as d, mcpToolToAgentTool as e, mcpToolsToAgentTools as f, makeMcpToolSearch as m };
@@ -1,5 +1,5 @@
1
- import { b as McpToolSpec } from './mcp-Dg3vA1Uj.js';
2
- import { A as AgentTool } from './tools-Ch-OzOU8.js';
1
+ import { d as McpToolSpec } from './mcp-Bn5TlRbV.js';
2
+ import { A as AgentTool } from './tools-CeK5AquG.js';
3
3
  import '@livx.cc/wcli/core';
4
4
 
5
5
  /** A JSON-RPC 2.0 transport: request (awaits a result), notify (fire-and-forget), close. */
@@ -6,15 +6,26 @@ import { log } from "libx.js/src/modules/log";
6
6
  var forComponent = (name) => log.forComponent(name);
7
7
 
8
8
  // src/mcp.ts
9
- function toText(result) {
10
- if (result == null) return "";
11
- if (typeof result === "string") return result;
9
+ function toResult(result) {
10
+ if (result == null) return { text: "" };
11
+ if (typeof result === "string") return { text: result };
12
12
  const content = result.content;
13
13
  if (Array.isArray(content)) {
14
- const text = content.map((c) => typeof c?.text === "string" ? c.text : JSON.stringify(c)).join("\n");
15
- if (text) return text;
14
+ const texts = [];
15
+ const images = [];
16
+ for (const c of content) {
17
+ if (c?.type === "image" && typeof c.data === "string" && c.mimeType) {
18
+ images.push({ mimeType: c.mimeType, data: c.data });
19
+ } else if (typeof c?.text === "string") {
20
+ texts.push(c.text);
21
+ } else {
22
+ texts.push(JSON.stringify(c));
23
+ }
24
+ }
25
+ const text = texts.join("\n");
26
+ if (text || images.length) return { text, ...images.length ? { images } : {} };
16
27
  }
17
- return JSON.stringify(result);
28
+ return { text: JSON.stringify(result) };
18
29
  }
19
30
  function mcpToolToAgentTool(spec, callTool, prefix = "mcp__") {
20
31
  return {
@@ -22,7 +33,8 @@ function mcpToolToAgentTool(spec, callTool, prefix = "mcp__") {
22
33
  description: spec.description ?? `MCP tool ${spec.name}`,
23
34
  parameters: spec.inputSchema ?? { type: "object", properties: {} },
24
35
  async run(args, _ctx) {
25
- return toText(await callTool(spec.name, args ?? {}));
36
+ const r = toResult(await callTool(spec.name, args ?? {}));
37
+ return r.images?.length ? r : r.text;
26
38
  }
27
39
  };
28
40
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/mcp.client.ts","../src/logging.ts","../src/mcp.ts"],"sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process';\nimport { forComponent } from './logging';\nimport { mcpToolsToAgentTools, type McpToolSpec } from './mcp';\nimport type { AgentTool } from './tools';\n\nconst log = forComponent('mcp');\n\n/**\n * Real MCP (Model Context Protocol) client — node-only, deliberately kept OUT of the edge-safe\n * `src/index.ts` entry. It speaks JSON-RPC 2.0 over a transport (stdio child process or HTTP),\n * performs the `initialize` handshake + `tools/list` discovery, then hands `(specs, callTool)` to\n * the pure `src/mcp.ts` adapter — so the browser/edge build keeps working without importing this.\n */\n\nconst PROTOCOL_VERSION = '2025-06-18';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/** A JSON-RPC 2.0 transport: request (awaits a result), notify (fire-and-forget), close. */\nexport interface McpTransport {\n start(): Promise<void>;\n request(method: string, params?: unknown): Promise<any>;\n notify(method: string, params?: unknown): Promise<void>;\n close(): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// stdio transport — newline-delimited JSON-RPC over a spawned child's stdin/stdout.\n// ---------------------------------------------------------------------------\nexport interface StdioServerSpec {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n timeoutMs?: number;\n}\n\nexport class StdioTransport implements McpTransport {\n private proc?: ChildProcess;\n private buf = '';\n private nextId = 1;\n private pending = new Map<number, { resolve: (v: any) => void; reject: (e: any) => void; timer: ReturnType<typeof setTimeout> }>();\n\n constructor(private spec: StdioServerSpec) {}\n\n async start(): Promise<void> {\n const { command, args = [], env, cwd } = this.spec;\n const proc = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, ...env }, cwd });\n this.proc = proc;\n proc.stdout!.setEncoding('utf8');\n proc.stdout!.on('data', (chunk: string) => this.onData(chunk));\n proc.stderr!.setEncoding('utf8');\n proc.stderr!.on('data', (chunk: string) => log.debug(`[${command}] stderr:`, chunk.trimEnd())); // server diagnostics\n proc.on('exit', (code) => this.failAll(new Error(`MCP server \"${command}\" exited (code ${code})`)));\n proc.on('error', (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));\n }\n\n private onData(chunk: string): void {\n this.buf += chunk;\n let nl: number;\n while ((nl = this.buf.indexOf('\\n')) >= 0) {\n const line = this.buf.slice(0, nl).trim();\n this.buf = this.buf.slice(nl + 1);\n if (!line) continue;\n try { this.dispatch(JSON.parse(line)); }\n catch (e) { log.debug('dropping non-JSON line from MCP server:', line, e); } // never throw out of the stream handler\n }\n }\n\n private dispatch(msg: any): void {\n // We're a minimal client: only correlate responses to our requests; ignore server→client calls.\n if (msg?.id == null || !this.pending.has(msg.id)) return;\n const p = this.pending.get(msg.id)!;\n this.pending.delete(msg.id);\n clearTimeout(p.timer);\n if (msg.error) p.reject(new Error(msg.error?.message ?? JSON.stringify(msg.error)));\n else p.resolve(msg.result);\n }\n\n private failAll(e: Error): void {\n for (const p of this.pending.values()) { clearTimeout(p.timer); p.reject(e); }\n this.pending.clear();\n }\n\n private write(msg: unknown): void {\n if (!this.proc?.stdin) throw new Error('MCP stdio transport not started');\n this.proc.stdin.write(JSON.stringify(msg) + '\\n');\n }\n\n request(method: string, params?: unknown): Promise<any> {\n const id = this.nextId++;\n const timeoutMs = this.spec.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`MCP request \"${method}\" timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n this.pending.set(id, { resolve, reject, timer });\n try { this.write({ jsonrpc: '2.0', id, method, params }); }\n catch (e) { clearTimeout(timer); this.pending.delete(id); reject(e); }\n });\n }\n\n async notify(method: string, params?: unknown): Promise<void> {\n this.write({ jsonrpc: '2.0', method, params });\n }\n\n async close(): Promise<void> {\n this.failAll(new Error('MCP transport closed'));\n try { this.proc?.stdin?.end(); } catch (e) { log.debug('stdin end failed', e); }\n this.proc?.kill();\n }\n}\n\n// ---------------------------------------------------------------------------\n// HTTP transport — Streamable HTTP: POST JSON-RPC, parse JSON or an SSE response.\n// ---------------------------------------------------------------------------\nexport interface HttpServerSpec {\n url: string;\n headers?: Record<string, string>;\n /** Sugar over `headers` → `Authorization: Bearer <token>`. The headless-friendly path (no OAuth loopback);\n * inject a static token (from env / VFS secret) per our CLAUDE.md rule preferring API keys for cron/edge. */\n bearerToken?: string;\n timeoutMs?: number;\n}\n\n/** Thrown when an HTTP MCP server rejects auth (401/403). Carries `needsAuth` so callers can surface a hint\n * (\"set bearerToken / headers\") instead of a generic mount failure — without attempting an interactive flow. */\nexport class McpAuthError extends Error {\n needsAuth = true;\n constructor(public status: number, public serverName?: string) {\n super(`MCP server${serverName ? ` \"${serverName}\"` : ''} requires authentication (HTTP ${status}) — set bearerToken or headers`);\n this.name = 'McpAuthError';\n }\n}\n\nexport class HttpTransport implements McpTransport {\n private nextId = 1;\n private sessionId?: string;\n private fetchImpl: typeof fetch;\n\n constructor(private spec: HttpServerSpec, fetchImpl?: typeof fetch) {\n this.fetchImpl = fetchImpl ?? fetch;\n }\n\n async start(): Promise<void> {}\n\n request(method: string, params?: unknown): Promise<any> {\n return this.post({ jsonrpc: '2.0', id: this.nextId++, method, params }, false);\n }\n\n async notify(method: string, params?: unknown): Promise<void> {\n await this.post({ jsonrpc: '2.0', method, params }, true);\n }\n\n private async post(msg: unknown, isNotify: boolean): Promise<any> {\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n accept: 'application/json, text/event-stream',\n ...(this.spec.bearerToken ? { authorization: `Bearer ${this.spec.bearerToken}` } : {}),\n ...this.spec.headers, // explicit headers win — lets a caller override the bearer sugar if needed\n ...(this.sessionId ? { 'mcp-session-id': this.sessionId } : {}),\n };\n const resp = await this.fetchImpl(this.spec.url, {\n method: 'POST', headers, body: JSON.stringify(msg),\n signal: AbortSignal.timeout(this.spec.timeoutMs ?? DEFAULT_TIMEOUT_MS),\n });\n const sid = resp.headers.get('mcp-session-id');\n if (sid) this.sessionId = sid; // adopt the server-assigned session for subsequent calls\n if (resp.status === 401 || resp.status === 403) throw new McpAuthError(resp.status);\n if (!resp.ok) throw new Error(`MCP HTTP ${resp.status} ${resp.statusText}`);\n if (isNotify) return;\n const ct = resp.headers.get('content-type') ?? '';\n const data = ct.includes('text/event-stream') ? parseSseResponse(await resp.text()) : await resp.json();\n if (data?.error) throw new Error(data.error?.message ?? JSON.stringify(data.error));\n return data?.result;\n }\n\n async close(): Promise<void> {}\n}\n\n/** Pull the JSON-RPC response object out of an SSE body (the first `data:` payload that carries one). */\nfunction parseSseResponse(body: string): any {\n for (const line of body.split('\\n')) {\n const trimmed = line.trimStart();\n if (!trimmed.startsWith('data:')) continue;\n try {\n const obj = JSON.parse(trimmed.slice(5).trim());\n if (obj && (obj.result !== undefined || obj.error !== undefined)) return obj;\n } catch (e) { log.debug('skipping unparseable SSE data line', e); }\n }\n return {};\n}\n\n// ---------------------------------------------------------------------------\n// Client — handshake + discovery on top of a transport.\n// ---------------------------------------------------------------------------\nexport class McpClient {\n constructor(public transport: McpTransport, private clientInfo = { name: 'agent.libx.js', version: '0' }) {}\n\n /** `initialize` handshake → `notifications/initialized`. Returns the server's init result. */\n async connect(): Promise<{ serverInfo?: { name?: string; version?: string }; capabilities?: any }> {\n await this.transport.start();\n const result = await this.transport.request('initialize', {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: {},\n clientInfo: this.clientInfo,\n });\n await this.transport.notify('notifications/initialized');\n return result ?? {};\n }\n\n async listTools(): Promise<McpToolSpec[]> {\n const r = await this.transport.request('tools/list');\n return Array.isArray(r?.tools) ? r.tools : [];\n }\n\n async callTool(name: string, args: unknown): Promise<unknown> {\n return this.transport.request('tools/call', { name, arguments: args ?? {} });\n }\n\n async listResources(): Promise<Array<{ uri: string; name?: string; mimeType?: string }>> {\n const r = await this.transport.request('resources/list');\n return Array.isArray(r?.resources) ? r.resources : [];\n }\n\n async readResource(uri: string): Promise<any> {\n return this.transport.request('resources/read', { uri });\n }\n\n async close(): Promise<void> {\n await this.transport.close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Config-driven mounting — Claude-style `mcpServers: { name: {...} }`.\n// ---------------------------------------------------------------------------\n/** One server: stdio (`command`+`args`+`env`) OR http (`url`+`headers`). `disabled` skips it. */\nexport interface McpServerConfig {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n url?: string;\n headers?: Record<string, string>;\n /** `Authorization: Bearer <token>` sugar for http servers (headless-friendly; no OAuth flow). */\n bearerToken?: string;\n /** Auth strategy for http servers. `'oauth'` → the CLI resolves a token via the attended OAuth flow\n * (cli/mcpOAuth.ts) and injects it as `bearerToken` at mount; the core never runs the flow. Default none. */\n auth?: 'oauth' | 'none';\n timeoutMs?: number;\n disabled?: boolean;\n}\n\nexport interface MountedMcp {\n name: string;\n client: McpClient;\n tools: AgentTool[];\n serverInfo?: { name?: string; version?: string };\n}\n\n/** Connect one server, discover its tools, and adapt them to prefixed AgentTools (`mcp__<name>__`). */\nexport async function mountMcpServer(name: string, cfg: McpServerConfig): Promise<MountedMcp> {\n const transport: McpTransport = cfg.url\n ? new HttpTransport({ url: cfg.url, headers: cfg.headers, bearerToken: cfg.bearerToken, timeoutMs: cfg.timeoutMs })\n : new StdioTransport({ command: cfg.command!, args: cfg.args, env: cfg.env, cwd: cfg.cwd, timeoutMs: cfg.timeoutMs });\n const client = new McpClient(transport);\n const init = await client.connect();\n const specs = await client.listTools();\n const tools = mcpToolsToAgentTools(specs, (tool, a) => client.callTool(tool, a), `mcp__${name}__`);\n return { name, client, tools, serverInfo: init?.serverInfo };\n}\n\n/**\n * Mount every configured server. A server that fails (bad command, no handshake, timeout) is\n * logged and skipped — one broken server can never block the agent from starting.\n */\nexport async function mountMcpServers(servers: Record<string, McpServerConfig> = {}): Promise<MountedMcp[]> {\n const out: MountedMcp[] = [];\n for (const [name, cfg] of Object.entries(servers)) {\n if (!cfg || cfg.disabled) continue;\n if (!cfg.command && !cfg.url) { log.warn(`MCP server \"${name}\" needs a command (stdio) or url (http) — skipping`); continue; }\n try {\n const m = await mountMcpServer(name, cfg);\n out.push(m);\n log.info(`MCP \"${name}\" mounted — ${m.tools.length} tool(s)${m.serverInfo?.name ? ` from ${m.serverInfo.name}` : ''}`);\n } catch (e: any) {\n if (e instanceof McpAuthError) log.warn(`MCP \"${name}\" needs-auth: HTTP ${e.status} — set bearerToken or headers in its config; skipping`);\n else log.error(`MCP server \"${name}\" failed to mount: ${e?.message ?? e}`); // logged, then skipped\n }\n }\n return out;\n}\n","// Import the log module directly from libx.js source: libx.js's main bundle\n// doesn't re-export `log` as a named ESM export, and source-importing keeps\n// libx.js patches live (no rebuild) — matching the `bun link` workflow.\nimport { log } from 'libx.js/src/modules/log';\n\n/** Component-scoped logger (libx.js). debug/verbose gated via DEBUG env/localStorage. */\nexport const forComponent = (name: string) => log.forComponent(name);\nexport { log };\n","import type { AgentTool } from './tools';\nimport { topByRelevance } from './relevance';\n\n/**\n * MCP bridge — adapt a Model Context Protocol tool list into the agent's AgentTool[],\n * so any MCP server's tools become first-class agent tools (edge-safe: you supply the\n * transport via `callTool`; this module has no node/network dependency of its own).\n *\n * Pass the server's advertised tools + a `callTool(name, args)` that performs the call\n * (over stdio/HTTP/whatever you wire up); each becomes an AgentTool the Agent can use.\n */\nexport interface McpToolSpec {\n name: string;\n description?: string;\n /** JSON Schema for the tool's arguments (MCP's `inputSchema`). */\n inputSchema?: object;\n}\n\n/** Perform an MCP tool call; return its textual result. Throw to surface an error to the model. */\nexport type McpCall = (name: string, args: any) => Promise<unknown>;\n\n/** Normalize an MCP call result (string | content blocks | object) into a tool-result string. */\nfunction toText(result: unknown): string {\n if (result == null) return '';\n if (typeof result === 'string') return result;\n // MCP content shape: { content: [{ type:'text', text }] }\n const content = (result as any).content;\n if (Array.isArray(content)) {\n const text = content.map((c: any) => (typeof c?.text === 'string' ? c.text : JSON.stringify(c))).join('\\n');\n if (text) return text;\n }\n return JSON.stringify(result);\n}\n\n/** Adapt one MCP tool spec into an AgentTool backed by `callTool`. */\nexport function mcpToolToAgentTool(spec: McpToolSpec, callTool: McpCall, prefix = 'mcp__'): AgentTool {\n return {\n name: `${prefix}${spec.name}`,\n description: spec.description ?? `MCP tool ${spec.name}`,\n parameters: spec.inputSchema ?? { type: 'object', properties: {} },\n async run(args, _ctx) {\n return toText(await callTool(spec.name, args ?? {}));\n },\n };\n}\n\n/** Adapt a whole MCP tool list into AgentTool[] (names prefixed to avoid collisions).\n * Optional `filter` pre-narrows the set at mount time (host allowlist), so a server with\n * hundreds of tools needn't mount them all eagerly. */\nexport function mcpToolsToAgentTools(specs: McpToolSpec[], callTool: McpCall, prefix = 'mcp__', filter?: (spec: McpToolSpec) => boolean): AgentTool[] {\n return (filter ? specs.filter(filter) : specs).map((s) => mcpToolToAgentTool(s, callTool, prefix));\n}\n\nexport interface McpToolSearchOptions {\n /** Prefix stripped from / shown on tool names (cosmetic; mirrors the adapter prefix). Default 'mcp__'. */\n prefix?: string;\n /** Max tools returned per `ToolSearch` query. Default 10. */\n maxResults?: number;\n}\n\n/** Render one spec for the search result: name, description, and its argument schema (so the\n * model knows how to call it via `McpCall`). */\nfunction describeSpec(s: McpToolSpec): string {\n const schema = s.inputSchema ? `\\n args: ${JSON.stringify(s.inputSchema)}` : '';\n return `${s.name} — ${s.description ?? '(no description)'}${schema}`;\n}\n\n/**\n * Deferred-mount mode (ToolSearch-equivalent) for large MCP tool sets. Instead of mounting N\n * tools into the wire schema (cost + latency + model confusion past a few dozen), mount exactly\n * TWO bounded tools regardless of N:\n * - `ToolSearch({ query })` — ranks the catalog by relevance and returns the top matches with\n * their argument schemas, so the model discovers what's available on demand.\n * - `McpCall({ name, args })` — invokes any catalog tool by name through the same transport.\n * Keep `mcpToolsToAgentTools` (eager) as the default for small sets.\n */\nexport function makeMcpToolSearch(specs: McpToolSpec[], callTool: McpCall, options: McpToolSearchOptions = {}): AgentTool[] {\n const maxResults = options.maxResults ?? 10;\n const byName = new Map(specs.map((s) => [s.name, s]));\n const catalogLine = `${specs.length} MCP tool(s) available — search by keyword, then call by exact name.`;\n\n const searchTool: AgentTool = {\n name: 'ToolSearch',\n description: `Search the available MCP tools by keyword (${catalogLine}). Returns matching tool names + their argument schemas; call one with \\`McpCall\\`.`,\n parameters: { type: 'object', required: ['query'], properties: { query: { type: 'string', description: 'keywords to match against tool name + description' } } },\n async run({ query }) {\n const q = String(query ?? '').trim();\n if (!q) return catalogLine;\n const { kept } = topByRelevance(specs, q, (s) => `${s.name} ${s.description ?? ''}`, maxResults);\n if (!kept.length) return `(no MCP tool matches \"${q}\" — try broader keywords)`;\n return kept.map(describeSpec).join('\\n');\n },\n };\n\n const callMcpTool: AgentTool = {\n name: 'McpCall',\n description: 'Call an MCP tool discovered via `ToolSearch`, by its exact name. Pass its arguments as `args`.',\n parameters: {\n type: 'object',\n required: ['name'],\n properties: {\n name: { type: 'string', description: 'exact tool name from ToolSearch' },\n args: { type: 'object', description: 'arguments object for the tool (per its schema)' },\n },\n },\n async run({ name, args }) {\n const n = String(name ?? '');\n if (!byName.has(n)) return `Error: unknown MCP tool '${n}'. Use ToolSearch to find valid names.`;\n return toText(await callTool(n, args ?? {}));\n },\n };\n\n return [searchTool, callMcpTool];\n}\n"],"mappings":"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","names":["log"]}
1
+ {"version":3,"sources":["../src/mcp.client.ts","../src/logging.ts","../src/mcp.ts"],"sourcesContent":["import { spawn, type ChildProcess } from 'node:child_process';\nimport { forComponent } from './logging';\nimport { mcpToolsToAgentTools, type McpToolSpec } from './mcp';\nimport type { AgentTool } from './tools';\n\nconst log = forComponent('mcp');\n\n/**\n * Real MCP (Model Context Protocol) client — node-only, deliberately kept OUT of the edge-safe\n * `src/index.ts` entry. It speaks JSON-RPC 2.0 over a transport (stdio child process or HTTP),\n * performs the `initialize` handshake + `tools/list` discovery, then hands `(specs, callTool)` to\n * the pure `src/mcp.ts` adapter — so the browser/edge build keeps working without importing this.\n */\n\nconst PROTOCOL_VERSION = '2025-06-18';\nconst DEFAULT_TIMEOUT_MS = 30_000;\n\n/** A JSON-RPC 2.0 transport: request (awaits a result), notify (fire-and-forget), close. */\nexport interface McpTransport {\n start(): Promise<void>;\n request(method: string, params?: unknown): Promise<any>;\n notify(method: string, params?: unknown): Promise<void>;\n close(): Promise<void>;\n}\n\n// ---------------------------------------------------------------------------\n// stdio transport — newline-delimited JSON-RPC over a spawned child's stdin/stdout.\n// ---------------------------------------------------------------------------\nexport interface StdioServerSpec {\n command: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n timeoutMs?: number;\n}\n\nexport class StdioTransport implements McpTransport {\n private proc?: ChildProcess;\n private buf = '';\n private nextId = 1;\n private pending = new Map<number, { resolve: (v: any) => void; reject: (e: any) => void; timer: ReturnType<typeof setTimeout> }>();\n\n constructor(private spec: StdioServerSpec) {}\n\n async start(): Promise<void> {\n const { command, args = [], env, cwd } = this.spec;\n const proc = spawn(command, args, { stdio: ['pipe', 'pipe', 'pipe'], env: { ...process.env, ...env }, cwd });\n this.proc = proc;\n proc.stdout!.setEncoding('utf8');\n proc.stdout!.on('data', (chunk: string) => this.onData(chunk));\n proc.stderr!.setEncoding('utf8');\n proc.stderr!.on('data', (chunk: string) => log.debug(`[${command}] stderr:`, chunk.trimEnd())); // server diagnostics\n proc.on('exit', (code) => this.failAll(new Error(`MCP server \"${command}\" exited (code ${code})`)));\n proc.on('error', (e) => this.failAll(e instanceof Error ? e : new Error(String(e))));\n }\n\n private onData(chunk: string): void {\n this.buf += chunk;\n let nl: number;\n while ((nl = this.buf.indexOf('\\n')) >= 0) {\n const line = this.buf.slice(0, nl).trim();\n this.buf = this.buf.slice(nl + 1);\n if (!line) continue;\n try { this.dispatch(JSON.parse(line)); }\n catch (e) { log.debug('dropping non-JSON line from MCP server:', line, e); } // never throw out of the stream handler\n }\n }\n\n private dispatch(msg: any): void {\n // We're a minimal client: only correlate responses to our requests; ignore server→client calls.\n if (msg?.id == null || !this.pending.has(msg.id)) return;\n const p = this.pending.get(msg.id)!;\n this.pending.delete(msg.id);\n clearTimeout(p.timer);\n if (msg.error) p.reject(new Error(msg.error?.message ?? JSON.stringify(msg.error)));\n else p.resolve(msg.result);\n }\n\n private failAll(e: Error): void {\n for (const p of this.pending.values()) { clearTimeout(p.timer); p.reject(e); }\n this.pending.clear();\n }\n\n private write(msg: unknown): void {\n if (!this.proc?.stdin) throw new Error('MCP stdio transport not started');\n this.proc.stdin.write(JSON.stringify(msg) + '\\n');\n }\n\n request(method: string, params?: unknown): Promise<any> {\n const id = this.nextId++;\n const timeoutMs = this.spec.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n this.pending.delete(id);\n reject(new Error(`MCP request \"${method}\" timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n this.pending.set(id, { resolve, reject, timer });\n try { this.write({ jsonrpc: '2.0', id, method, params }); }\n catch (e) { clearTimeout(timer); this.pending.delete(id); reject(e); }\n });\n }\n\n async notify(method: string, params?: unknown): Promise<void> {\n this.write({ jsonrpc: '2.0', method, params });\n }\n\n async close(): Promise<void> {\n this.failAll(new Error('MCP transport closed'));\n try { this.proc?.stdin?.end(); } catch (e) { log.debug('stdin end failed', e); }\n this.proc?.kill();\n }\n}\n\n// ---------------------------------------------------------------------------\n// HTTP transport — Streamable HTTP: POST JSON-RPC, parse JSON or an SSE response.\n// ---------------------------------------------------------------------------\nexport interface HttpServerSpec {\n url: string;\n headers?: Record<string, string>;\n /** Sugar over `headers` → `Authorization: Bearer <token>`. The headless-friendly path (no OAuth loopback);\n * inject a static token (from env / VFS secret) per our CLAUDE.md rule preferring API keys for cron/edge. */\n bearerToken?: string;\n timeoutMs?: number;\n}\n\n/** Thrown when an HTTP MCP server rejects auth (401/403). Carries `needsAuth` so callers can surface a hint\n * (\"set bearerToken / headers\") instead of a generic mount failure — without attempting an interactive flow. */\nexport class McpAuthError extends Error {\n needsAuth = true;\n constructor(public status: number, public serverName?: string) {\n super(`MCP server${serverName ? ` \"${serverName}\"` : ''} requires authentication (HTTP ${status}) — set bearerToken or headers`);\n this.name = 'McpAuthError';\n }\n}\n\nexport class HttpTransport implements McpTransport {\n private nextId = 1;\n private sessionId?: string;\n private fetchImpl: typeof fetch;\n\n constructor(private spec: HttpServerSpec, fetchImpl?: typeof fetch) {\n this.fetchImpl = fetchImpl ?? fetch;\n }\n\n async start(): Promise<void> {}\n\n request(method: string, params?: unknown): Promise<any> {\n return this.post({ jsonrpc: '2.0', id: this.nextId++, method, params }, false);\n }\n\n async notify(method: string, params?: unknown): Promise<void> {\n await this.post({ jsonrpc: '2.0', method, params }, true);\n }\n\n private async post(msg: unknown, isNotify: boolean): Promise<any> {\n const headers: Record<string, string> = {\n 'content-type': 'application/json',\n accept: 'application/json, text/event-stream',\n ...(this.spec.bearerToken ? { authorization: `Bearer ${this.spec.bearerToken}` } : {}),\n ...this.spec.headers, // explicit headers win — lets a caller override the bearer sugar if needed\n ...(this.sessionId ? { 'mcp-session-id': this.sessionId } : {}),\n };\n const resp = await this.fetchImpl(this.spec.url, {\n method: 'POST', headers, body: JSON.stringify(msg),\n signal: AbortSignal.timeout(this.spec.timeoutMs ?? DEFAULT_TIMEOUT_MS),\n });\n const sid = resp.headers.get('mcp-session-id');\n if (sid) this.sessionId = sid; // adopt the server-assigned session for subsequent calls\n if (resp.status === 401 || resp.status === 403) throw new McpAuthError(resp.status);\n if (!resp.ok) throw new Error(`MCP HTTP ${resp.status} ${resp.statusText}`);\n if (isNotify) return;\n const ct = resp.headers.get('content-type') ?? '';\n const data = ct.includes('text/event-stream') ? parseSseResponse(await resp.text()) : await resp.json();\n if (data?.error) throw new Error(data.error?.message ?? JSON.stringify(data.error));\n return data?.result;\n }\n\n async close(): Promise<void> {}\n}\n\n/** Pull the JSON-RPC response object out of an SSE body (the first `data:` payload that carries one). */\nfunction parseSseResponse(body: string): any {\n for (const line of body.split('\\n')) {\n const trimmed = line.trimStart();\n if (!trimmed.startsWith('data:')) continue;\n try {\n const obj = JSON.parse(trimmed.slice(5).trim());\n if (obj && (obj.result !== undefined || obj.error !== undefined)) return obj;\n } catch (e) { log.debug('skipping unparseable SSE data line', e); }\n }\n return {};\n}\n\n// ---------------------------------------------------------------------------\n// Client — handshake + discovery on top of a transport.\n// ---------------------------------------------------------------------------\nexport class McpClient {\n constructor(public transport: McpTransport, private clientInfo = { name: 'agent.libx.js', version: '0' }) {}\n\n /** `initialize` handshake → `notifications/initialized`. Returns the server's init result. */\n async connect(): Promise<{ serverInfo?: { name?: string; version?: string }; capabilities?: any }> {\n await this.transport.start();\n const result = await this.transport.request('initialize', {\n protocolVersion: PROTOCOL_VERSION,\n capabilities: {},\n clientInfo: this.clientInfo,\n });\n await this.transport.notify('notifications/initialized');\n return result ?? {};\n }\n\n async listTools(): Promise<McpToolSpec[]> {\n const r = await this.transport.request('tools/list');\n return Array.isArray(r?.tools) ? r.tools : [];\n }\n\n async callTool(name: string, args: unknown): Promise<unknown> {\n return this.transport.request('tools/call', { name, arguments: args ?? {} });\n }\n\n async listResources(): Promise<Array<{ uri: string; name?: string; mimeType?: string }>> {\n const r = await this.transport.request('resources/list');\n return Array.isArray(r?.resources) ? r.resources : [];\n }\n\n async readResource(uri: string): Promise<any> {\n return this.transport.request('resources/read', { uri });\n }\n\n async close(): Promise<void> {\n await this.transport.close();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Config-driven mounting — Claude-style `mcpServers: { name: {...} }`.\n// ---------------------------------------------------------------------------\n/** One server: stdio (`command`+`args`+`env`) OR http (`url`+`headers`). `disabled` skips it. */\nexport interface McpServerConfig {\n command?: string;\n args?: string[];\n env?: Record<string, string>;\n cwd?: string;\n url?: string;\n headers?: Record<string, string>;\n /** `Authorization: Bearer <token>` sugar for http servers (headless-friendly; no OAuth flow). */\n bearerToken?: string;\n /** Auth strategy for http servers. `'oauth'` → the CLI resolves a token via the attended OAuth flow\n * (cli/mcpOAuth.ts) and injects it as `bearerToken` at mount; the core never runs the flow. Default none. */\n auth?: 'oauth' | 'none';\n timeoutMs?: number;\n disabled?: boolean;\n}\n\nexport interface MountedMcp {\n name: string;\n client: McpClient;\n tools: AgentTool[];\n serverInfo?: { name?: string; version?: string };\n}\n\n/** Connect one server, discover its tools, and adapt them to prefixed AgentTools (`mcp__<name>__`). */\nexport async function mountMcpServer(name: string, cfg: McpServerConfig): Promise<MountedMcp> {\n const transport: McpTransport = cfg.url\n ? new HttpTransport({ url: cfg.url, headers: cfg.headers, bearerToken: cfg.bearerToken, timeoutMs: cfg.timeoutMs })\n : new StdioTransport({ command: cfg.command!, args: cfg.args, env: cfg.env, cwd: cfg.cwd, timeoutMs: cfg.timeoutMs });\n const client = new McpClient(transport);\n const init = await client.connect();\n const specs = await client.listTools();\n const tools = mcpToolsToAgentTools(specs, (tool, a) => client.callTool(tool, a), `mcp__${name}__`);\n return { name, client, tools, serverInfo: init?.serverInfo };\n}\n\n/**\n * Mount every configured server. A server that fails (bad command, no handshake, timeout) is\n * logged and skipped — one broken server can never block the agent from starting.\n */\nexport async function mountMcpServers(servers: Record<string, McpServerConfig> = {}): Promise<MountedMcp[]> {\n const out: MountedMcp[] = [];\n for (const [name, cfg] of Object.entries(servers)) {\n if (!cfg || cfg.disabled) continue;\n if (!cfg.command && !cfg.url) { log.warn(`MCP server \"${name}\" needs a command (stdio) or url (http) — skipping`); continue; }\n try {\n const m = await mountMcpServer(name, cfg);\n out.push(m);\n log.info(`MCP \"${name}\" mounted — ${m.tools.length} tool(s)${m.serverInfo?.name ? ` from ${m.serverInfo.name}` : ''}`);\n } catch (e: any) {\n if (e instanceof McpAuthError) log.warn(`MCP \"${name}\" needs-auth: HTTP ${e.status} — set bearerToken or headers in its config; skipping`);\n else log.error(`MCP server \"${name}\" failed to mount: ${e?.message ?? e}`); // logged, then skipped\n }\n }\n return out;\n}\n","// Import the log module directly from libx.js source: libx.js's main bundle\n// doesn't re-export `log` as a named ESM export, and source-importing keeps\n// libx.js patches live (no rebuild) — matching the `bun link` workflow.\nimport { log } from 'libx.js/src/modules/log';\n\n/** Component-scoped logger (libx.js). debug/verbose gated via DEBUG env/localStorage. */\nexport const forComponent = (name: string) => log.forComponent(name);\nexport { log };\n","import type { AgentTool } from './tools';\nimport { topByRelevance } from './relevance';\n\n/**\n * MCP bridge — adapt a Model Context Protocol tool list into the agent's AgentTool[],\n * so any MCP server's tools become first-class agent tools (edge-safe: you supply the\n * transport via `callTool`; this module has no node/network dependency of its own).\n *\n * Pass the server's advertised tools + a `callTool(name, args)` that performs the call\n * (over stdio/HTTP/whatever you wire up); each becomes an AgentTool the Agent can use.\n */\nexport interface McpToolSpec {\n name: string;\n description?: string;\n /** JSON Schema for the tool's arguments (MCP's `inputSchema`). */\n inputSchema?: object;\n}\n\n/** Perform an MCP tool call; return its textual result. Throw to surface an error to the model. */\nexport type McpCall = (name: string, args: any) => Promise<unknown>;\n\nexport interface McpImage { mimeType: string; data: string }\nexport interface McpToolResult { text: string; images?: McpImage[] }\n\n/** Normalize an MCP call result into text + optional image blocks. */\nfunction toResult(result: unknown): McpToolResult {\n if (result == null) return { text: '' };\n if (typeof result === 'string') return { text: result };\n const content = (result as any).content;\n if (Array.isArray(content)) {\n const texts: string[] = [];\n const images: McpImage[] = [];\n for (const c of content) {\n if (c?.type === 'image' && typeof c.data === 'string' && c.mimeType) {\n images.push({ mimeType: c.mimeType, data: c.data });\n } else if (typeof c?.text === 'string') {\n texts.push(c.text);\n } else {\n texts.push(JSON.stringify(c));\n }\n }\n const text = texts.join('\\n');\n if (text || images.length) return { text, ...(images.length ? { images } : {}) };\n }\n return { text: JSON.stringify(result) };\n}\n\nfunction toText(result: unknown): string { return toResult(result).text; }\n\n/** Adapt one MCP tool spec into an AgentTool backed by `callTool`. */\nexport function mcpToolToAgentTool(spec: McpToolSpec, callTool: McpCall, prefix = 'mcp__'): AgentTool {\n return {\n name: `${prefix}${spec.name}`,\n description: spec.description ?? `MCP tool ${spec.name}`,\n parameters: spec.inputSchema ?? { type: 'object', properties: {} },\n async run(args, _ctx) {\n const r = toResult(await callTool(spec.name, args ?? {}));\n return r.images?.length ? r : r.text;\n },\n };\n}\n\n/** Adapt a whole MCP tool list into AgentTool[] (names prefixed to avoid collisions).\n * Optional `filter` pre-narrows the set at mount time (host allowlist), so a server with\n * hundreds of tools needn't mount them all eagerly. */\nexport function mcpToolsToAgentTools(specs: McpToolSpec[], callTool: McpCall, prefix = 'mcp__', filter?: (spec: McpToolSpec) => boolean): AgentTool[] {\n return (filter ? specs.filter(filter) : specs).map((s) => mcpToolToAgentTool(s, callTool, prefix));\n}\n\nexport interface McpToolSearchOptions {\n /** Prefix stripped from / shown on tool names (cosmetic; mirrors the adapter prefix). Default 'mcp__'. */\n prefix?: string;\n /** Max tools returned per `ToolSearch` query. Default 10. */\n maxResults?: number;\n}\n\n/** Render one spec for the search result: name, description, and its argument schema (so the\n * model knows how to call it via `McpCall`). */\nfunction describeSpec(s: McpToolSpec): string {\n const schema = s.inputSchema ? `\\n args: ${JSON.stringify(s.inputSchema)}` : '';\n return `${s.name} — ${s.description ?? '(no description)'}${schema}`;\n}\n\n/**\n * Deferred-mount mode (ToolSearch-equivalent) for large MCP tool sets. Instead of mounting N\n * tools into the wire schema (cost + latency + model confusion past a few dozen), mount exactly\n * TWO bounded tools regardless of N:\n * - `ToolSearch({ query })` — ranks the catalog by relevance and returns the top matches with\n * their argument schemas, so the model discovers what's available on demand.\n * - `McpCall({ name, args })` — invokes any catalog tool by name through the same transport.\n * Keep `mcpToolsToAgentTools` (eager) as the default for small sets.\n */\nexport function makeMcpToolSearch(specs: McpToolSpec[], callTool: McpCall, options: McpToolSearchOptions = {}): AgentTool[] {\n const maxResults = options.maxResults ?? 10;\n const byName = new Map(specs.map((s) => [s.name, s]));\n const catalogLine = `${specs.length} MCP tool(s) available — search by keyword, then call by exact name.`;\n\n const searchTool: AgentTool = {\n name: 'ToolSearch',\n description: `Search the available MCP tools by keyword (${catalogLine}). Returns matching tool names + their argument schemas; call one with \\`McpCall\\`.`,\n parameters: { type: 'object', required: ['query'], properties: { query: { type: 'string', description: 'keywords to match against tool name + description' } } },\n async run({ query }) {\n const q = String(query ?? '').trim();\n if (!q) return catalogLine;\n const { kept } = topByRelevance(specs, q, (s) => `${s.name} ${s.description ?? ''}`, maxResults);\n if (!kept.length) return `(no MCP tool matches \"${q}\" — try broader keywords)`;\n return kept.map(describeSpec).join('\\n');\n },\n };\n\n const callMcpTool: AgentTool = {\n name: 'McpCall',\n description: 'Call an MCP tool discovered via `ToolSearch`, by its exact name. Pass its arguments as `args`.',\n parameters: {\n type: 'object',\n required: ['name'],\n properties: {\n name: { type: 'string', description: 'exact tool name from ToolSearch' },\n args: { type: 'object', description: 'arguments object for the tool (per its schema)' },\n },\n },\n async run({ name, args }) {\n const n = String(name ?? '');\n if (!byName.has(n)) return `Error: unknown MCP tool '${n}'. Use ToolSearch to find valid names.`;\n const r = toResult(await callTool(n, args ?? {}));\n return r.images?.length ? r : r.text;\n },\n };\n\n return [searchTool, callMcpTool];\n}\n"],"mappings":"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","names":["log"]}
@@ -231,7 +231,13 @@ interface AgentTool {
231
231
  name: string;
232
232
  description: string;
233
233
  parameters: object;
234
- run(args: any, ctx: ToolContext): Promise<string>;
234
+ run(args: any, ctx: ToolContext): Promise<string | {
235
+ text: string;
236
+ images?: {
237
+ mimeType: string;
238
+ data: string;
239
+ }[];
240
+ }>;
235
241
  }
236
242
  /** Build a tool context bound to a filesystem backend (Mem / Disk / …) and an optional host. */
237
243
  declare function makeContext(fs: IFilesystem, host?: HostBridge): ToolContext;
@@ -243,6 +249,9 @@ declare const bashTool: AgentTool;
243
249
  declare const readTool: AgentTool;
244
250
  /** Replace an exact, unique substring; requires a prior Read and guards against stale edits. */
245
251
  declare const editTool: AgentTool;
252
+ /** Session-exit tool: the model calls this when the user wants to end the conversation.
253
+ * The `onExit` callback is injected by the host (CLI sets it to flip a flag that breaks the REPL loop). */
254
+ declare function exitSessionTool(onExit: () => void): AgentTool;
246
255
  declare function defaultTools(): AgentTool[];
247
256
  /**
248
257
  * The full catalog of selectable tools, keyed by name. The evolve loop's mutation
@@ -252,4 +261,4 @@ declare function toolRegistry(): Record<string, AgentTool>;
252
261
  /** Resolve a list of tool names against the registry (unknown names throw). */
253
262
  declare function toolsByName(names: string[]): AgentTool[];
254
263
 
255
- export { type AgentTool as A, type ChatLike as C, type HostBridge as H, type Message as M, type Role as R, SandboxJobRegistry as S, type TodoItem as T, type UserQuestion as U, type ChatOptions as a, type ChatResponse as b, type ContentPart as c, type HostEvent as d, type MessageContent as e, type StreamChunk as f, type Tool as g, type ToolCall as h, type ToolContext as i, bashTool as j, contentText as k, defaultTools as l, editTool as m, imagePart as n, makeContext as o, makeJobTools as p, todoWriteTool as q, readTool as r, toolRegistry as s, toWireTools as t, toolsByName as u };
264
+ export { type AgentTool as A, type ChatLike as C, type HostBridge as H, type Message as M, type Role as R, SandboxJobRegistry as S, type TodoItem as T, type UserQuestion as U, type ChatOptions as a, type ChatResponse as b, type ContentPart as c, type HostEvent as d, type MessageContent as e, type StreamChunk as f, type Tool as g, type ToolCall as h, type ToolContext as i, bashTool as j, contentText as k, defaultTools as l, editTool as m, exitSessionTool as n, imagePart as o, makeContext as p, makeJobTools as q, readTool as r, todoWriteTool as s, toWireTools as t, toolRegistry as u, toolsByName as v };
@@ -1,4 +1,4 @@
1
- import { A as AgentTool } from './tools-Ch-OzOU8.js';
1
+ import { A as AgentTool } from './tools-CeK5AquG.js';
2
2
  import '@livx.cc/wcli/core';
3
3
 
4
4
  /** The slice of node's `child_process.spawn` we depend on — injectable so tests supply a fake. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/tools.ts","../src/redact.ts","../src/logging.ts","../src/tools.web.ts","../src/tools.shell.ts"],"sourcesContent":["import type { IFilesystem } from '@livx.cc/wcli/core';\nimport { CommandExecutor, registerHeadlessCommands } from '@livx.cc/wcli/core';\nimport type { Tool, ChatLike } from './llm';\nimport { grepTool, globTool, writeTool, multiEditTool, applyEditsTool, repoMapTool, reviewTool, fuzzyLineReplace } from './tools.structured';\nimport { todoWriteTool, type TodoItem } from './todo';\nimport { webFetchTool, webSearchTool } from './tools.web';\nimport { OverlayFilesystem } from './OverlayFilesystem';\nimport { redactSecrets, CONFIG_FILE_RE } from './redact';\nimport type { SandboxJobRegistry } from './tools.jobs';\n\n/** A structured multiple-choice question the model can pose to a human. */\nexport interface UserQuestion {\n question: string;\n header?: string;\n options: { label: string; description?: string }[];\n multiSelect?: boolean;\n}\n\n/**\n * The host / human-in-the-loop seam (the \"third seam\" beyond LLM + filesystem).\n * Injected per host: a CLI reads stdin, a browser renders a dialog, edge/headless\n * omits it. Unifies user-questions, permission/plan approvals, and notifications.\n */\nexport type HostEvent =\n | { kind: 'text_delta'; message: string }\n | { kind: 'thinking_delta'; message: string }\n | { kind: 'tool_use'; id: string; name: string; input: unknown }\n | { kind: 'tool_result'; id: string; output: string; isError?: boolean }\n | { kind: 'tool_result_image'; id: string; dataUrl: string }\n | { kind: string; message: string; data?: unknown };\n\nexport interface HostBridge {\n /** Ask the user a structured question; resolve to the chosen label(s) / free text. */\n ask?(q: UserQuestion): Promise<string>;\n /** Request approval for a sensitive action (permission 'ask' / plan approval). */\n confirm?(prompt: string, meta?: { tool: string; input: unknown }): Promise<boolean>;\n /** Emit a progress / notification event to the host UI (non-blocking). */\n notify?(event: HostEvent): void;\n}\n\nexport interface ToolContext {\n fs: IFilesystem;\n exec: CommandExecutor;\n /** path -> content snapshot at last Read/Edit; powers the read-before-edit staleness guard. */\n readState: Map<string, string>;\n /** optional host interaction channel; absent => autonomous/headless. */\n host?: HostBridge;\n /** optional run-cancellation signal (mirrors AgentOptions.signal); lets abort-aware tools\n * (e.g. the real shell) kill in-flight work when the run is cancelled. */\n signal?: AbortSignal;\n /** the agent's working todo list (TodoWrite planning aid); replaced wholesale per call. */\n todos: TodoItem[];\n /** optional syntax guardrail: if set, write-class tools refuse to persist a broken result. */\n lint?: (path: string, content: string) => string | null;\n /** optional model handle for tools that run their own LLM pass (e.g. Review, a self-critique).\n * Populated by the Agent from its own ai/model; absent => such tools degrade to a no-op. */\n ai?: ChatLike;\n model?: string;\n /** optional sandbox background-job registry; enables `bash({background:true})`. Absent => no backgrounding. */\n jobs?: SandboxJobRegistry;\n}\n\nexport interface AgentTool {\n name: string;\n description: string;\n parameters: object; // JSON Schema for the function's arguments\n run(args: any, ctx: ToolContext): Promise<string>;\n}\n\n/** Build a tool context bound to a filesystem backend (Mem / Disk / …) and an optional host. */\nexport function makeContext(fs: IFilesystem, host?: HostBridge): ToolContext {\n const exec = new CommandExecutor(fs);\n registerHeadlessCommands(exec);\n return { fs, exec, readState: new Map(), host, todos: [] };\n}\n\n/** Convert AgentTools into the ai.libx.js `tools` array for chat(). */\nexport function toWireTools(tools: AgentTool[]): Tool[] {\n return tools.map((t) => ({\n type: 'function',\n function: { name: t.name, description: t.description, parameters: t.parameters },\n }));\n}\n\nconst numberLines = (content: string, offset = 0, limit?: number): string => {\n const lines = content.split('\\n');\n const start = Math.max(0, offset);\n const end = limit != null ? start + limit : lines.length;\n return lines\n .slice(start, end)\n .map((l, i) => `${start + i + 1}\\t${l}`)\n .join('\\n');\n};\n\n/** Keep huge tool output high-signal: head+tail with an omission marker (cuts re-runs to \"parse the wall\"). */\nexport function truncateOutput(s: string, headLines = 80, tailLines = 20): string {\n const lines = s.split('\\n');\n if (lines.length <= headLines + tailLines + 1) return s;\n const omitted = lines.length - headLines - tailLines;\n return [...lines.slice(0, headLines), `… (${omitted} lines omitted — narrow the command to see more) …`, ...lines.slice(-tailLines)].join('\\n');\n}\n\n/** Run any shell command line over the VFS (ls/cat/grep/find/head/tail/echo/mkdir/rm/mv/wc, pipes, redirects, &&/||/;). */\nexport const bashTool: AgentTool = {\n name: 'bash',\n description:\n 'Run a shell command. Supports ls, cat, grep, find, head, tail, echo, mkdir, rm, mv, cp, wc, pipes (|), redirects (>, >>), and chaining (&&, ||, ;). Best for: running tests/builds, file operations (mkdir/mv/rm), and piped workflows. For searching file contents, prefer `Grep` (structured results, no re-parse). For finding files by name, prefer `Glob`.',\n parameters: {\n type: 'object',\n required: ['command'],\n properties: {\n command: { type: 'string', description: 'the command line to execute' },\n background: { type: 'boolean', description: 'run detached over an isolated overlay (writes commit when it finishes); returns a job id to poll with JobOutput. Only worth it for slow work (remote VFS / long pipelines).' },\n },\n },\n async run({ command, background }, ctx) {\n if (background && ctx.jobs) return startBashJob(String(command ?? ''), ctx);\n const r = await ctx.exec.execute(String(command ?? ''));\n const out = truncateOutput((r.output ?? '').replace(/\\n+$/, ''));\n if (r.exitCode !== 0) {\n const err = (r.error ?? '').trim();\n return `[exit ${r.exitCode}]${err ? ' ' + err : ''}${out ? '\\n' + out : ''}`;\n }\n return out || '(command succeeded, no output)'; // explicit sentinel: don't re-run to \"check\"\n },\n};\n\n/** Kick a bash command into the background over an isolated overlay; its writes commit only on success.\n * A kill (abort) before completion skips the commit — the parent VFS is never touched mid-flight. */\nfunction startBashJob(command: string, ctx: ToolContext): string {\n const baseFs = ctx.fs;\n const id = ctx.jobs!.start(\n async ({ signal }) => {\n const overlay = new OverlayFilesystem(baseFs);\n const exec = new CommandExecutor(overlay);\n registerHeadlessCommands(exec);\n const r = await exec.execute(command); // wcli is sync-to-completion; abort can only gate the commit below\n if (signal.aborted) return '[killed before commit]';\n await overlay.commit(); // atomically flush this job's writes down into the parent VFS\n const out = truncateOutput((r.output ?? '').replace(/\\n+$/, ''));\n return r.exitCode !== 0 ? `[exit ${r.exitCode}] ${(r.error ?? '').trim()}\\n${out}`.trim() : out || '(command succeeded, no output)';\n },\n { kind: 'bash', label: command.slice(0, 60) },\n );\n return `Started background job ${id} — poll with JobOutput({id:\"${id}\"}) / JobStatus, stop with JobKill.`;\n}\n\n/** Image extensions the Read tool returns as a visual block (when the fs can read bytes). */\nconst IMG_MIME: Record<string, string> = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp' };\n\n/** Read a text file as 1-indexed numbered lines; arms the staleness guard for Edit. */\nexport const readTool: AgentTool = {\n name: 'Read',\n description:\n 'Read a file. Text files return 1-indexed numbered lines (with optional `offset`/`limit` and a re-Read pointer for partial reads). Image files (png/jpg/jpeg/gif/webp) return the picture itself so you can SEE it. Always Read a file before Editing it.',\n parameters: {\n type: 'object',\n required: ['path'],\n properties: {\n path: { type: 'string' },\n offset: { type: 'number' },\n limit: { type: 'number' },\n },\n },\n async run({ path, offset, limit }, ctx) {\n // Image file → return it as a visual block. The adapter turns a tool result whose JSON carries a\n // `dataUrl` into an image tool_result the model can see. Needs a binary-capable fs (disk default);\n // the utf8 VFS (sandbox/Mem) can't, so we say so instead of corrupting the bytes.\n const ext = String(path).toLowerCase().split('.').pop() ?? '';\n if (IMG_MIME[ext]) {\n const fs = ctx.fs as { readFileBytes?: (p: string) => Promise<Uint8Array> };\n if (typeof fs.readFileBytes !== 'function') {\n return `[${path} is an image, but this filesystem can't read binary — attach it as @${path} instead, or run on disk.]`;\n }\n const bytes = await fs.readFileBytes(path);\n const b64 = Buffer.from(bytes).toString('base64');\n return JSON.stringify({ dataUrl: `data:${IMG_MIME[ext]};base64,${b64}`, image: path });\n }\n const raw = await ctx.fs.readFile(path);\n ctx.readState.set(ctx.fs.resolvePath(path), raw); // staleness guard tracks the REAL content\n // Mask secret values in config files so keys can live there usably-but-hidden (line count is preserved).\n const content = CONFIG_FILE_RE.test(ctx.fs.resolvePath(path)) ? redactSecrets(raw) : raw;\n const total = content === '' ? 0 : content.split('\\n').length;\n const start = Math.max(0, offset ?? 0);\n const body = numberLines(content, start, limit);\n // snippet-with-pointer: when the slice doesn't cover the whole file, tell the\n // model what it's missing + how to pull it — so it expands precisely instead of re-reading blind.\n const shownEnd = limit != null ? Math.min(start + limit, total) : total;\n const shownCount = Math.max(0, shownEnd - start);\n if (shownCount >= total) return body; // whole file shown — no footer\n if (shownCount === 0) return `[no lines in range (offset ${start}${limit != null ? `, limit ${limit}` : ''}) — file has ${total} line(s)]`;\n return `${body}\\n\\n[lines ${start + 1}–${shownEnd} of ${total} · re-Read with offset/limit for the rest]`;\n },\n};\n\n/** Replace an exact, unique substring; requires a prior Read and guards against stale edits. */\nexport const editTool: AgentTool = {\n name: 'Edit',\n description:\n 'Replace an exact substring in a file. Requires a prior Read of the same file. `old_string` must occur exactly once — include surrounding context to disambiguate.',\n parameters: {\n type: 'object',\n required: ['path', 'old_string', 'new_string'],\n properties: {\n path: { type: 'string' },\n old_string: { type: 'string' },\n new_string: { type: 'string' },\n },\n },\n async run({ path, old_string, new_string }, ctx) {\n const key = ctx.fs.resolvePath(path);\n const snapshot = ctx.readState.get(key);\n if (snapshot == null) throw new Error(`File has not been read yet: ${path}. Read it before editing.`);\n const current = await ctx.fs.readFile(path);\n if (current !== snapshot) throw new Error(`File ${path} changed since it was read (stale). Re-read before editing.`);\n const count = old_string === '' ? 0 : current.split(old_string).length - 1;\n if (count > 1) throw new Error(`old_string is not unique in ${path} (${count} matches). Provide more surrounding context.`);\n let next: string, note = '';\n if (count === 1) {\n next = current.replace(old_string, () => new_string); // exact: function replacer, no $-pattern expansion\n } else {\n // exact match failed — try a whitespace-tolerant unique match before giving up (cuts re-read churn)\n const fuzzy = fuzzyLineReplace(current, old_string, new_string);\n if (fuzzy == null) throw new Error(`old_string not found in ${path}.`);\n next = fuzzy;\n note = ' (whitespace-tolerant match)';\n }\n if (ctx.lint) { const err = ctx.lint(path, next); if (err) throw new Error(err); }\n await ctx.fs.writeFile(path, next);\n ctx.readState.set(key, next);\n return `Edited ${path}${note}`;\n },\n};\n\nexport function defaultTools(): AgentTool[] {\n return [bashTool, readTool, editTool];\n}\n\n/**\n * The full catalog of selectable tools, keyed by name. The evolve loop's mutation\n * surface picks from this registry; embedders can build a custom tool set by name.\n */\nexport function toolRegistry(): Record<string, AgentTool> {\n const all = [bashTool, readTool, editTool, grepTool, globTool, writeTool, multiEditTool, applyEditsTool, repoMapTool, reviewTool(), todoWriteTool, webFetchTool, webSearchTool];\n return Object.fromEntries(all.map((t) => [t.name, t]));\n}\n\n/** Resolve a list of tool names against the registry (unknown names throw). */\nexport function toolsByName(names: string[]): AgentTool[] {\n const reg = toolRegistry();\n return names.map((n) => {\n const t = reg[n];\n if (!t) throw new Error(`unknown tool '${n}'. Known: ${Object.keys(reg).join(', ')}`);\n return t;\n });\n}\n","/**\n * Mask secret-looking values in arbitrary text before it reaches the model.\n *\n * Two complementary seams use this: real-shell output (`cat .env`, `printenv`) and the\n * `Read` tool (so provider keys stored in `.agent/settings.json` are usable-but-masked).\n * The FS jail hides whole secret FILES by name; this hides secret VALUES wherever they\n * surface in otherwise-legitimate content.\n *\n * Both regexes are linear (no nested quantifiers) — safe against catastrophic backtracking\n * and cheap enough to run on every tool output (see tests/redact.bench).\n */\n\nexport const REDACTED = '‹redacted›';\n\n/** Config/control files that may carry provider keys — readers (Read/Grep) mask secret VALUES in\n * these while keeping the rest readable. (Whole secret FILES like .env are hidden by the FS jail.) */\nexport const CONFIG_FILE_RE = /(^|\\/)\\.(agent|claude)\\/(settings(\\.[\\w-]+)?\\.json|config\\.(json|js|mjs|cjs|ts))$/i;\n\n// (A) `NAME=value` / `\"name\": \"value\"` pairs where NAME looks like a secret. Masks the value only,\n// so the agent still sees WHICH key exists (useful config context) without the secret itself.\nconst SECRET_PAIR =\n /((?:^|[\\s,{[])(?:export\\s+)?[\"']?[\\w.\\-]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|PRIVATE_KEY|ACCESS_?KEY|AUTH(?:_?TOKEN)?|BEARER)[\\w.\\-]*[\"']?\\s*[:=]\\s*)([\"']?)([^\\s\"',{}\\]]+)/gi;\n\n// (B) Bare tokens by well-known shape — catches secrets that appear without an obvious key\n// (Authorization headers, URLs, JSON dumps). Conservative prefixes to avoid false positives.\nconst SECRET_TOKEN =\n /\\b(sk-ant-[\\w-]{12,}|sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|github_pat_[\\w]{20,}|xox[baprs]-[\\w-]{10,}|AKIA[0-9A-Z]{12,}|AIza[\\w-]{20,}|eyJ[\\w-]{8,}\\.[\\w-]{8,}\\.[\\w-]{8,})\\b/g;\n\n/** Return `text` with secret values masked. Cheap no-op when nothing matches. */\nexport function redactSecrets(text: string): string {\n if (!text) return text;\n return text\n .replace(SECRET_PAIR, (_m, head, quote, _val) => `${head}${quote}${REDACTED}`)\n .replace(SECRET_TOKEN, REDACTED);\n}\n","// Import the log module directly from libx.js source: libx.js's main bundle\n// doesn't re-export `log` as a named ESM export, and source-importing keeps\n// libx.js patches live (no rebuild) — matching the `bun link` workflow.\nimport { log } from 'libx.js/src/modules/log';\n\n/** Component-scoped logger (libx.js). debug/verbose gated via DEBUG env/localStorage. */\nexport const forComponent = (name: string) => log.forComponent(name);\nexport { log };\n","import type { AgentTool } from './tools';\nimport { forComponent } from './logging';\n\n/**\n * Web tools — `WebFetch` (retrieve a URL as readable text) and `WebSearch` (ranked\n * results via a configured provider). Opt-in (NOT in the default tool set): network\n * access is a deliberate capability. Factory-built with an injectable `fetch` so they\n * stay edge-portable and unit-testable without real network. `fetch` is read at call\n * time, so a no-network runtime simply has the tool return an error.\n */\nconst log = forComponent('web');\n\n/** Strip HTML to readable text — dependency-free: drop script/style/comments, block tags → newlines, decode common entities. */\nexport function htmlToText(html: string): string {\n let s = html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<title[\\s\\S]*?<\\/title>/gi, ' ') // drop title text (don't leak it into context)\n .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, ' ') // …same for noscript / textarea content\n .replace(/<textarea[\\s\\S]*?<\\/textarea>/gi, ' ')\n .replace(/<!--[\\s\\S]*?-->/g, ' ')\n .replace(/<\\/(p|div|li|h[1-6]|tr|section|article|header|footer|nav)>/gi, '\\n')\n .replace(/<br\\s*\\/?>/gi, '\\n')\n .replace(/<[^>]+>/g, ' ');\n s = s\n .replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>').replace(/&quot;/g, '\"').replace(/&#0?39;/g, \"'\").replace(/&#x27;/gi, \"'\");\n return s\n .replace(/[ \\t\\f\\v]+/g, ' ')\n .split('\\n').map((l) => l.trim()).join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\nexport interface WebFetchOptions {\n /** Override the global fetch (tests inject a mock; edge runtimes can supply their own). */\n fetch?: typeof globalThis.fetch;\n maxBytes?: number; // cap the downloaded body (default 2 MB)\n maxChars?: number; // cap the returned text (default 100k)\n timeoutMs?: number; // request timeout (default 15s)\n /** Allow fetching private/loopback/link-local hosts (default false — blocks basic SSRF). */\n allowPrivateHosts?: boolean;\n}\n\n/**\n * Block obvious SSRF targets by hostname/IP literal (loopback, private ranges, link-local incl.\n * cloud metadata 169.254.169.254, `.internal`). Pure/edge-safe — no DNS, so DNS-rebinding and\n * redirect-to-internal are NOT covered (an embedder needing that should supply a vetting `fetch`).\n */\nexport function isPrivateHost(host: string): boolean {\n const h = host.toLowerCase().replace(/^\\[|\\]$/g, ''); // strip IPv6 brackets\n if (h === '' || h === 'localhost' || h.endsWith('.localhost') || h.endsWith('.internal')) return true;\n if (h === '::1' || h === '::' || h.startsWith('fe80:') || h.startsWith('fc') || h.startsWith('fd')) return true; // IPv6 loopback/link-local/ULA\n const m = h.match(/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/);\n if (m) {\n const a = +m[1], b = +m[2];\n return a === 0 || a === 127 || a === 10 || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168) || (a === 100 && b >= 64 && b <= 127);\n }\n return false;\n}\n\n/** Lazily-loaded node DNS resolver (absent on edge/browser) — closes DNS-rebinding (a public\n * hostname resolving to an internal IP) on the real-network path. Resolves null where unavailable. */\nlet _dnsLookup: ((h: string, opts?: any) => Promise<{ address: string }[]>) | null | undefined;\nasync function resolveIps(host: string): Promise<string[] | null> {\n if (_dnsLookup === undefined) {\n try { _dnsLookup = (await import('node:dns/promises')).lookup as any; }\n catch { _dnsLookup = null; } // edge/browser: no DNS — rely on the literal isPrivateHost check\n }\n if (!_dnsLookup) return null;\n try { return (await _dnsLookup(host, { all: true } as any)).map((a) => a.address); } catch { return null; }\n}\n\n/** Read a response body but stop at `maxBytes` of ACTUAL bytes (cancel the stream) — no unbounded download. */\nasync function readCapped(res: Response, maxBytes: number): Promise<string> {\n const reader = (res.body as any)?.getReader?.();\n if (!reader) { const t = await res.text(); return t.length > maxBytes ? t.slice(0, maxBytes) : t; }\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) { chunks.push(value); total += value.length; }\n if (total >= maxBytes) { try { await reader.cancel(); } catch { /* already closed */ } break; }\n }\n const out = new Uint8Array(Math.min(total, maxBytes));\n let off = 0;\n for (const c of chunks) { if (off >= out.length) break; const take = Math.min(c.length, out.length - off); out.set(c.subarray(0, take), off); off += take; }\n return new TextDecoder().decode(out);\n}\n\n/** Build a WebFetch tool. */\nexport function makeWebFetchTool(options: WebFetchOptions = {}): AgentTool {\n const maxBytes = options.maxBytes ?? 2_000_000;\n const maxChars = options.maxChars ?? 100_000;\n const timeoutMs = options.timeoutMs ?? 15_000;\n return {\n name: 'WebFetch',\n description:\n 'Fetch an http/https URL and return its readable text (HTML is stripped to text). Use to read docs or web pages. Returns the status line then up to ~100k chars of content.',\n parameters: { type: 'object', required: ['url'], properties: { url: { type: 'string', description: 'absolute http(s) URL' } } },\n async run({ url }) {\n const doFetch = options.fetch ?? globalThis.fetch;\n const customFetch = !!options.fetch; // injected fetch (tests/edge) owns its own vetting → skip DNS\n const u = String(url ?? '');\n try { new URL(u); } catch { return `Error: invalid URL: ${u}`; }\n if (!doFetch) return 'Error: no network (fetch) available in this runtime';\n // Reject a host that's a private/internal IP literal, or (on the real-network path) a name that\n // RESOLVES to one — re-checked on EVERY redirect hop so an external page can't bounce us internal.\n const hostBlock = async (hostname: string): Promise<string | null> => {\n if (options.allowPrivateHosts) return null;\n if (isPrivateHost(hostname)) return hostname;\n if (!customFetch) { const ips = await resolveIps(hostname); if (ips) for (const ip of ips) if (isPrivateHost(ip)) return `${hostname} → ${ip}`; }\n return null;\n };\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(), timeoutMs);\n try {\n let current = u;\n let res: Response;\n for (let hop = 0; ; hop++) {\n const pu = new URL(current);\n if (pu.protocol !== 'http:' && pu.protocol !== 'https:') return `Error: only http/https URLs are allowed (got \"${pu.protocol}\")`;\n const blocked = await hostBlock(pu.hostname);\n if (blocked) return `Error: refusing to fetch a private/internal address (${blocked}) — set allowPrivateHosts to override`;\n res = await doFetch(current, { signal: ctl.signal, redirect: 'manual', headers: { 'user-agent': 'agentx (+https://github.com/Livshitz/agent.libx.js)' } });\n if (res.status >= 300 && res.status < 400 && res.headers.get('location')) {\n if (hop >= 5) return `Error fetching ${u}: too many redirects`;\n current = new URL(res.headers.get('location')!, current).toString(); // re-validated at loop top\n continue;\n }\n break;\n }\n const type = res.headers.get('content-type') ?? '';\n const body = await readCapped(res, maxBytes);\n const text = /html/i.test(type) || /^\\s*<(?:!doctype|html)/i.test(body) ? htmlToText(body) : body.trim();\n const capped = text.length > maxChars ? text.slice(0, maxChars) + `\\n… [truncated at ${maxChars} chars]` : text;\n return `${res.status} ${res.statusText} · ${new URL(current).host}\\n\\n${capped}`;\n } catch (e: any) {\n log.debug(`WebFetch ${u} failed`, e);\n return `Error fetching ${u}: ${e?.name === 'AbortError' ? `timed out after ${timeoutMs}ms` : (e?.message ?? e)}`;\n } finally {\n clearTimeout(timer);\n }\n },\n };\n}\n\nexport interface WebSearchOptions {\n fetch?: typeof globalThis.fetch;\n /** API key for the search provider (default: process.env.TAVILY_API_KEY). */\n apiKey?: string;\n /** Provider endpoint (default: Tavily, an agent-oriented search API). */\n endpoint?: string;\n maxResults?: number; // default 5\n timeoutMs?: number; // default 15s\n}\n\n/** Build a WebSearch tool backed by a configured provider (Tavily by default). */\nexport function makeWebSearchTool(options: WebSearchOptions = {}): AgentTool {\n const endpoint = options.endpoint ?? 'https://api.tavily.com/search';\n const maxResults = options.maxResults ?? 5;\n const timeoutMs = options.timeoutMs ?? 15_000;\n return {\n name: 'WebSearch',\n description:\n 'Search the web; returns ranked results (title, URL, snippet). Requires a configured provider — set TAVILY_API_KEY to enable.',\n parameters: { type: 'object', required: ['query'], properties: { query: { type: 'string' } } },\n async run({ query }) {\n const doFetch = options.fetch ?? globalThis.fetch;\n const key = options.apiKey ?? process.env.TAVILY_API_KEY;\n if (!key) return 'Error: WebSearch is not configured. Set TAVILY_API_KEY (https://tavily.com) to enable web search.';\n if (!doFetch) return 'Error: no network (fetch) available in this runtime';\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(), timeoutMs);\n try {\n const res = await doFetch(endpoint, {\n method: 'POST',\n signal: ctl.signal,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ api_key: key, query: String(query ?? ''), max_results: maxResults }),\n });\n if (!res.ok) return `Error: search provider returned ${res.status} ${res.statusText}`;\n const data: any = await res.json();\n const results = Array.isArray(data?.results) ? data.results.slice(0, maxResults) : [];\n if (!results.length) return '(no results)';\n return results\n .map((r: any, i: number) => `${i + 1}. ${r.title ?? '(untitled)'}\\n ${r.url ?? ''}\\n ${String(r.content ?? '').replace(/\\s+/g, ' ').slice(0, 240)}`)\n .join('\\n\\n');\n } catch (e: any) {\n log.debug('WebSearch failed', e);\n return `Error searching: ${e?.name === 'AbortError' ? `timed out after ${timeoutMs}ms` : (e?.message ?? e)}`;\n } finally {\n clearTimeout(timer);\n }\n },\n };\n}\n\n/** Default instances (registered in the tool registry; opt-in by name). */\nexport const webFetchTool = makeWebFetchTool();\nexport const webSearchTool = makeWebSearchTool();\n","import type { AgentTool } from './tools';\nimport { truncateOutput } from './tools';\nimport { redactSecrets } from './redact';\nimport { forComponent } from './logging';\n\n/**\n * Real shell tool — node-only, OPT-IN, and deliberately NOT edge-portable.\n *\n * ⚠️ Unlike the default VFS `bash` (a sandboxed JS interpreter over the virtual filesystem),\n * this spawns a REAL `/bin/sh` process. It can run `bun`, `git`, `ssh`, scripts, deploys —\n * and, by the same token, it is NOT sandboxed: only cwd-binding constrains it. It is a\n * deliberate host escalation, kept out of `defaultTools()`/`toolRegistry()` and out of the\n * edge-safe `src/index.ts` (same policy as `mcp.client.ts`). A host opts in explicitly:\n *\n * tools: [...defaultTools(), makeRealShellTool({ cwd: nodeDiskRoot })]\n *\n * Mirrors `tools.web.ts`: a factory with an injectable `spawn` (tests + edge never import\n * node:child_process), an options bag, abort + timeout honored, output capped. Safety beyond\n * cwd-binding is the host's to add (e.g. a PermissionPolicy `decision:'ask'` per command, or\n * an OS sandbox wrapper) — see mind/03-roadmap.md \"OS-level access — capability tiers\".\n */\n\nconst log = forComponent('shell');\n\n/** Normalize shell output for return: trim trailing newlines, mask secret values, then size-truncate.\n * Redaction runs BEFORE truncation so a masked tail can't smuggle a secret past the line cap. */\nconst clean = (s: string): string => truncateOutput(redactSecrets(s.replace(/\\n+$/, '')));\n\n/** The slice of node's `child_process.spawn` we depend on — injectable so tests supply a fake. */\nexport type SpawnFn = (\n command: string,\n args: string[],\n options: { cwd?: string; env?: Record<string, string | undefined>; signal?: AbortSignal },\n) => SpawnedProcess;\n\n/** Minimal `ChildProcess` surface this tool uses. */\nexport interface SpawnedProcess {\n stdout?: { on(ev: 'data', cb: (chunk: any) => void): void } | null;\n stderr?: { on(ev: 'data', cb: (chunk: any) => void): void } | null;\n on(ev: 'close', cb: (code: number | null) => void): void;\n on(ev: 'error', cb: (err: Error) => void): void;\n kill(signal?: string): void;\n}\n\nexport interface RealShellOptions {\n /** Working directory the shell is bound to (typically a NodeDiskFilesystem `baseDir`). Required. */\n cwd: string;\n /** Override the spawner (tests inject a fake; default lazily imports node:child_process). */\n spawn?: SpawnFn;\n /** Per-command wall-clock cap (kill on overrun). Default 120s. */\n timeoutMs?: number;\n /** Extra env merged over the (optionally scrubbed) base env for the child. */\n env?: Record<string, string>;\n /** Strip likely-secret vars (API keys, tokens, cloud creds) from the child's env. Default ON.\n * The FS jail does NOT contain a real process, so this is the seam that keeps `echo $ANTHROPIC_API_KEY`\n * from leaking the host's secrets to a spawned command. `false` passes `process.env` through verbatim. */\n redactEnv?: boolean;\n /** Job registry enabling `Shell({background:true})` (long-running processes). Pair with `makeShellJobTools`. */\n registry?: ShellJobRegistry;\n}\n\n/** Env var names that look like secrets and are dropped before spawning (unless redactEnv:false). */\nconst SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;\n\n/** Build the child's env: `process.env` minus likely-secrets (when redacting), plus explicit `env`. */\nfunction childEnv(opts: { env?: Record<string, string>; redactEnv?: boolean }): Record<string, string | undefined> {\n const base: Record<string, string | undefined> = {};\n const redact = opts.redactEnv !== false; // default ON\n for (const [k, v] of Object.entries(process.env)) if (!(redact && SECRET_ENV_RE.test(k))) base[k] = v;\n return { ...base, ...opts.env };\n}\n\n/** Lazily resolve node's spawn (kept out of any eager edge import path). */\nlet _spawn: SpawnFn | undefined;\nasync function nodeSpawn(): Promise<SpawnFn> {\n if (!_spawn) _spawn = (await import('node:child_process')).spawn as unknown as SpawnFn;\n return _spawn;\n}\n\n// ---------------------------------------------------------------------------\n// Background jobs — long-running processes the agent starts, polls, and kills.\n// ---------------------------------------------------------------------------\nexport type JobStatus = 'running' | 'exited' | 'killed' | 'error';\n\nexport interface ShellJobConfig {\n cwd: string;\n spawn?: SpawnFn;\n env?: Record<string, string>;\n redactEnv?: boolean;\n /** Tail buffer cap per job (bytes); older output is dropped. Default 256 KB. */\n maxBuffer?: number;\n /** Kill all jobs on process exit (the CLI sets this; tests leave it off to avoid global handlers). */\n killOnExit?: boolean;\n}\n\ninterface Job { command: string; buf: string; status: JobStatus; exitCode?: number; proc?: SpawnedProcess; }\n\n/**\n * Per-session registry of background `/bin/sh` jobs. Backs `Shell({background:true})` and the\n * `ShellOutput`/`ShellStatus`/`ShellKill` tools. Output accumulates into a tail-capped ring so a\n * chatty process can't OOM. Bounded + killable; the CLI wires `killOnExit` so children are reaped.\n */\nexport class ShellJobRegistry {\n private jobs = new Map<string, Job>();\n private seq = 0;\n constructor(private cfg: ShellJobConfig) {\n if (cfg.killOnExit && typeof process !== 'undefined') process.once('exit', () => this.killAll());\n }\n\n async start(command: string): Promise<string> {\n const id = `job-${++this.seq}`;\n const max = this.cfg.maxBuffer ?? 256 * 1024;\n const job: Job = { command, buf: '', status: 'running' };\n const append = (chunk: any) => {\n const s = typeof chunk === 'string' ? chunk : chunk?.toString?.('utf8') ?? '';\n job.buf = (job.buf + s).slice(-max); // ring: keep the tail\n };\n try {\n const spawn = this.cfg.spawn ?? (await nodeSpawn());\n const proc = spawn('/bin/sh', ['-c', command], { cwd: this.cfg.cwd, env: childEnv(this.cfg) });\n job.proc = proc;\n proc.stdout?.on('data', append);\n proc.stderr?.on('data', append);\n proc.on('error', (err: any) => { if (job.status === 'running') { job.status = 'error'; append(`\\n[error] ${err?.message ?? err}`); } });\n proc.on('close', (code: number | null) => { if (job.status === 'running') { job.status = 'exited'; job.exitCode = code ?? undefined; } });\n } catch (e: any) {\n job.status = 'error';\n job.buf = `failed to spawn: ${e?.message ?? e}`;\n }\n this.jobs.set(id, job);\n return id;\n }\n\n /** Current tail output for a job (null = no such job). */\n output(id: string): string | null { return this.jobs.get(id)?.buf ?? (this.jobs.has(id) ? '' : null); }\n\n status(id: string): { status: JobStatus; exitCode?: number; bytes: number } | null {\n const j = this.jobs.get(id);\n return j ? { status: j.status, exitCode: j.exitCode, bytes: j.buf.length } : null;\n }\n\n list(): Array<{ id: string; command: string; status: JobStatus }> {\n return [...this.jobs].map(([id, j]) => ({ id, command: j.command, status: j.status }));\n }\n\n kill(id: string): boolean {\n const j = this.jobs.get(id);\n if (!j) return false;\n if (j.status === 'running') { try { j.proc?.kill('SIGTERM'); } catch { /* already gone */ } j.status = 'killed'; }\n return true;\n }\n\n killAll(): void { for (const id of this.jobs.keys()) this.kill(id); }\n}\n\n/** Build an opt-in real-shell tool bound to `options.cwd`. */\nexport function makeRealShellTool(options: RealShellOptions): AgentTool {\n const timeoutMs = options.timeoutMs ?? 120_000;\n return {\n name: 'Shell',\n description:\n 'Run a shell command via /bin/sh in the working directory. ' +\n 'Executes any installed binary — ls, cat, grep, git, bun, node, curl, scripts, etc. ' +\n 'Returns combined stdout+stderr; non-zero exits are prefixed `[exit N]`. ' +\n 'Set `background:true` for long-running processes (servers, watchers) — returns a job id immediately; poll with ShellOutput, stop with ShellKill.',\n parameters: {\n type: 'object',\n required: ['command'],\n properties: {\n command: { type: 'string', description: 'the shell command line to execute' },\n background: { type: 'boolean', description: 'run detached and return a job id immediately (for servers/watchers/long builds)' },\n },\n },\n async run({ command, background }, ctx) {\n const cmd = String(command ?? '');\n if (!cmd.trim()) return '[exit 1] empty command';\n if (background) {\n if (!options.registry) return 'Error: background execution is not enabled in this host (no job registry).';\n const id = await options.registry.start(cmd);\n return `Started background job ${id}. Poll output with ShellOutput({id:\"${id}\"}), check ShellStatus({id:\"${id}\"}), stop with ShellKill({id:\"${id}\"}).`;\n }\n const spawn = options.spawn ?? (await nodeSpawn());\n // Compose abort: the run's signal (ctx.signal) OR our per-command timeout both kill the child.\n const ctl = new AbortController();\n const onAbort = () => ctl.abort();\n if (ctx.signal) { if (ctx.signal.aborted) ctl.abort(); else ctx.signal.addEventListener('abort', onAbort, { once: true }); }\n let timedOut = false;\n const timer = setTimeout(() => { timedOut = true; ctl.abort(); }, timeoutMs);\n try {\n return await new Promise<string>((resolve) => {\n let out = '';\n let settled = false;\n const finish = (s: string) => { if (settled) return; settled = true; resolve(s); };\n let proc: SpawnedProcess;\n try {\n proc = spawn('/bin/sh', ['-c', cmd], { cwd: options.cwd, env: childEnv(options), signal: ctl.signal });\n } catch (e: any) {\n return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);\n }\n const collect = (chunk: any) => { out += typeof chunk === 'string' ? chunk : chunk?.toString?.('utf8') ?? ''; };\n proc.stdout?.on('data', collect);\n proc.stderr?.on('data', collect);\n proc.on('error', (err: any) => {\n // AbortError fires here when ctl.abort() kills the child — report timeout vs cancel.\n if (err?.name === 'AbortError' || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));\n log.debug('shell spawn error', err);\n finish(`[exit 1] ${err?.message ?? err}${out ? '\\n' + clean(out) : ''}`);\n });\n proc.on('close', (code: number | null) => {\n if (ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));\n const body = clean(out);\n if (code && code !== 0) return finish(`[exit ${code}]${body ? '\\n' + body : ''}`);\n finish(body || '(command succeeded, no output)');\n });\n });\n } finally {\n clearTimeout(timer);\n ctx.signal?.removeEventListener('abort', onAbort);\n }\n },\n };\n}\n\n/** Abort message: timeout vs external cancel, preserving any partial output. */\nfunction reasonFor(timedOut: boolean, timeoutMs: number, body: string): string {\n const head = timedOut ? `[exit 124] timed out after ${timeoutMs}ms (killed)` : '[exit 130] cancelled (killed)';\n return body ? `${head}\\n${body}` : head;\n}\n\nconst NO_JOB = (id: string) => `Error: no background job '${id}'. Use ShellStatus with no id to list jobs, or start one with Shell({background:true}).`;\n\n/** Build the background-job companion tools (ShellOutput / ShellStatus / ShellKill) over a registry. */\nexport function makeShellJobTools(registry: ShellJobRegistry): AgentTool[] {\n const idParam = { type: 'object', properties: { id: { type: 'string', description: 'the job id from Shell({background:true})' } } };\n return [\n {\n name: 'ShellOutput',\n description: 'Read the accumulated output (tail) of a background Shell job by id.',\n parameters: { type: 'object', required: ['id'], properties: { id: { type: 'string' } } },\n async run({ id }) {\n const out = registry.output(String(id));\n if (out == null) return NO_JOB(String(id));\n const st = registry.status(String(id))!;\n return `[${st.status}${st.exitCode != null ? ` exit ${st.exitCode}` : ''}]\\n${clean(out) || '(no output yet)'}`;\n },\n },\n {\n name: 'ShellStatus',\n description: 'Status of a background Shell job (running/exited/killed + exit code). Omit `id` to list all jobs.',\n parameters: idParam,\n async run({ id }) {\n if (!id) {\n const jobs = registry.list();\n return jobs.length ? jobs.map((j) => `${j.id} ${j.status} ${j.command}`).join('\\n') : '(no background jobs)';\n }\n const st = registry.status(String(id));\n return st ? `${st.status}${st.exitCode != null ? ` (exit ${st.exitCode})` : ''} · ${st.bytes} byte(s) buffered` : NO_JOB(String(id));\n },\n },\n {\n name: 'ShellKill',\n description: 'Stop a running background Shell job by id (SIGTERM).',\n parameters: { type: 'object', required: ['id'], properties: { id: { type: 'string' } } },\n async run({ id }) {\n return registry.kill(String(id)) ? `Killed job ${id}.` : NO_JOB(String(id));\n },\n },\n ];\n}\n"],"mappings":"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","names":["log","log"]}
1
+ {"version":3,"sources":["../src/tools.ts","../src/redact.ts","../src/logging.ts","../src/tools.web.ts","../src/tools.shell.ts"],"sourcesContent":["import type { IFilesystem } from '@livx.cc/wcli/core';\nimport { CommandExecutor, registerHeadlessCommands } from '@livx.cc/wcli/core';\nimport type { Tool, ChatLike } from './llm';\nimport { grepTool, globTool, writeTool, multiEditTool, applyEditsTool, repoMapTool, reviewTool, fuzzyLineReplace } from './tools.structured';\nimport { todoWriteTool, type TodoItem } from './todo';\nimport { webFetchTool, webSearchTool } from './tools.web';\nimport { OverlayFilesystem } from './OverlayFilesystem';\nimport { redactSecrets, CONFIG_FILE_RE } from './redact';\nimport type { SandboxJobRegistry } from './tools.jobs';\n\n/** A structured multiple-choice question the model can pose to a human. */\nexport interface UserQuestion {\n question: string;\n header?: string;\n options: { label: string; description?: string }[];\n multiSelect?: boolean;\n}\n\n/**\n * The host / human-in-the-loop seam (the \"third seam\" beyond LLM + filesystem).\n * Injected per host: a CLI reads stdin, a browser renders a dialog, edge/headless\n * omits it. Unifies user-questions, permission/plan approvals, and notifications.\n */\nexport type HostEvent =\n | { kind: 'text_delta'; message: string }\n | { kind: 'thinking_delta'; message: string }\n | { kind: 'tool_use'; id: string; name: string; input: unknown }\n | { kind: 'tool_result'; id: string; output: string; isError?: boolean }\n | { kind: 'tool_result_image'; id: string; dataUrl: string }\n | { kind: string; message: string; data?: unknown };\n\nexport interface HostBridge {\n /** Ask the user a structured question; resolve to the chosen label(s) / free text. */\n ask?(q: UserQuestion): Promise<string>;\n /** Request approval for a sensitive action (permission 'ask' / plan approval). */\n confirm?(prompt: string, meta?: { tool: string; input: unknown }): Promise<boolean>;\n /** Emit a progress / notification event to the host UI (non-blocking). */\n notify?(event: HostEvent): void;\n}\n\nexport interface ToolContext {\n fs: IFilesystem;\n exec: CommandExecutor;\n /** path -> content snapshot at last Read/Edit; powers the read-before-edit staleness guard. */\n readState: Map<string, string>;\n /** optional host interaction channel; absent => autonomous/headless. */\n host?: HostBridge;\n /** optional run-cancellation signal (mirrors AgentOptions.signal); lets abort-aware tools\n * (e.g. the real shell) kill in-flight work when the run is cancelled. */\n signal?: AbortSignal;\n /** the agent's working todo list (TodoWrite planning aid); replaced wholesale per call. */\n todos: TodoItem[];\n /** optional syntax guardrail: if set, write-class tools refuse to persist a broken result. */\n lint?: (path: string, content: string) => string | null;\n /** optional model handle for tools that run their own LLM pass (e.g. Review, a self-critique).\n * Populated by the Agent from its own ai/model; absent => such tools degrade to a no-op. */\n ai?: ChatLike;\n model?: string;\n /** optional sandbox background-job registry; enables `bash({background:true})`. Absent => no backgrounding. */\n jobs?: SandboxJobRegistry;\n}\n\nexport interface AgentTool {\n name: string;\n description: string;\n parameters: object; // JSON Schema for the function's arguments\n run(args: any, ctx: ToolContext): Promise<string | { text: string; images?: { mimeType: string; data: string }[] }>;\n}\n\n/** Build a tool context bound to a filesystem backend (Mem / Disk / …) and an optional host. */\nexport function makeContext(fs: IFilesystem, host?: HostBridge): ToolContext {\n const exec = new CommandExecutor(fs);\n registerHeadlessCommands(exec);\n return { fs, exec, readState: new Map(), host, todos: [] };\n}\n\n/** Convert AgentTools into the ai.libx.js `tools` array for chat(). */\nexport function toWireTools(tools: AgentTool[]): Tool[] {\n return tools.map((t) => ({\n type: 'function',\n function: { name: t.name, description: t.description, parameters: t.parameters },\n }));\n}\n\nconst numberLines = (content: string, offset = 0, limit?: number): string => {\n const lines = content.split('\\n');\n const start = Math.max(0, offset);\n const end = limit != null ? start + limit : lines.length;\n return lines\n .slice(start, end)\n .map((l, i) => `${start + i + 1}\\t${l}`)\n .join('\\n');\n};\n\n/** Keep huge tool output high-signal: head+tail with an omission marker (cuts re-runs to \"parse the wall\"). */\nexport function truncateOutput(s: string, headLines = 80, tailLines = 20): string {\n const lines = s.split('\\n');\n if (lines.length <= headLines + tailLines + 1) return s;\n const omitted = lines.length - headLines - tailLines;\n return [...lines.slice(0, headLines), `… (${omitted} lines omitted — narrow the command to see more) …`, ...lines.slice(-tailLines)].join('\\n');\n}\n\n/** Run any shell command line over the VFS (ls/cat/grep/find/head/tail/echo/mkdir/rm/mv/wc, pipes, redirects, &&/||/;). */\nexport const bashTool: AgentTool = {\n name: 'bash',\n description:\n 'Run a shell command. Supports ls, cat, grep, find, head, tail, echo, mkdir, rm, mv, cp, wc, pipes (|), redirects (>, >>), and chaining (&&, ||, ;). Best for: running tests/builds, file operations (mkdir/mv/rm), and piped workflows. For searching file contents, prefer `Grep` (structured results, no re-parse). For finding files by name, prefer `Glob`.',\n parameters: {\n type: 'object',\n required: ['command'],\n properties: {\n command: { type: 'string', description: 'the command line to execute' },\n background: { type: 'boolean', description: 'run detached over an isolated overlay (writes commit when it finishes); returns a job id to poll with JobOutput. Only worth it for slow work (remote VFS / long pipelines).' },\n },\n },\n async run({ command, background }, ctx) {\n if (background && ctx.jobs) return startBashJob(String(command ?? ''), ctx);\n const r = await ctx.exec.execute(String(command ?? ''));\n const out = truncateOutput((r.output ?? '').replace(/\\n+$/, ''));\n if (r.exitCode !== 0) {\n const err = (r.error ?? '').trim();\n return `[exit ${r.exitCode}]${err ? ' ' + err : ''}${out ? '\\n' + out : ''}`;\n }\n return out || '(command succeeded, no output)'; // explicit sentinel: don't re-run to \"check\"\n },\n};\n\n/** Kick a bash command into the background over an isolated overlay; its writes commit only on success.\n * A kill (abort) before completion skips the commit — the parent VFS is never touched mid-flight. */\nfunction startBashJob(command: string, ctx: ToolContext): string {\n const baseFs = ctx.fs;\n const id = ctx.jobs!.start(\n async ({ signal }) => {\n const overlay = new OverlayFilesystem(baseFs);\n const exec = new CommandExecutor(overlay);\n registerHeadlessCommands(exec);\n const r = await exec.execute(command); // wcli is sync-to-completion; abort can only gate the commit below\n if (signal.aborted) return '[killed before commit]';\n await overlay.commit(); // atomically flush this job's writes down into the parent VFS\n const out = truncateOutput((r.output ?? '').replace(/\\n+$/, ''));\n return r.exitCode !== 0 ? `[exit ${r.exitCode}] ${(r.error ?? '').trim()}\\n${out}`.trim() : out || '(command succeeded, no output)';\n },\n { kind: 'bash', label: command.slice(0, 60) },\n );\n return `Started background job ${id} — poll with JobOutput({id:\"${id}\"}) / JobStatus, stop with JobKill.`;\n}\n\n/** Image extensions the Read tool returns as a visual block (when the fs can read bytes). */\nconst IMG_MIME: Record<string, string> = { png: 'image/png', jpg: 'image/jpeg', jpeg: 'image/jpeg', gif: 'image/gif', webp: 'image/webp' };\n\n/** Read a text file as 1-indexed numbered lines; arms the staleness guard for Edit. */\nexport const readTool: AgentTool = {\n name: 'Read',\n description:\n 'Read a file. Text files return 1-indexed numbered lines (with optional `offset`/`limit` and a re-Read pointer for partial reads). Image files (png/jpg/jpeg/gif/webp) return the picture itself so you can SEE it. Always Read a file before Editing it.',\n parameters: {\n type: 'object',\n required: ['path'],\n properties: {\n path: { type: 'string' },\n offset: { type: 'number' },\n limit: { type: 'number' },\n },\n },\n async run({ path, offset, limit }, ctx) {\n // Image file → return it as a visual block. The adapter turns a tool result whose JSON carries a\n // `dataUrl` into an image tool_result the model can see. Needs a binary-capable fs (disk default);\n // the utf8 VFS (sandbox/Mem) can't, so we say so instead of corrupting the bytes.\n const ext = String(path).toLowerCase().split('.').pop() ?? '';\n if (IMG_MIME[ext]) {\n const fs = ctx.fs as { readFileBytes?: (p: string) => Promise<Uint8Array> };\n if (typeof fs.readFileBytes !== 'function') {\n return `[${path} is an image, but this filesystem can't read binary — attach it as @${path} instead, or run on disk.]`;\n }\n const bytes = await fs.readFileBytes(path);\n const b64 = Buffer.from(bytes).toString('base64');\n return JSON.stringify({ dataUrl: `data:${IMG_MIME[ext]};base64,${b64}`, image: path });\n }\n const raw = await ctx.fs.readFile(path);\n ctx.readState.set(ctx.fs.resolvePath(path), raw); // staleness guard tracks the REAL content\n // Mask secret values in config files so keys can live there usably-but-hidden (line count is preserved).\n const content = CONFIG_FILE_RE.test(ctx.fs.resolvePath(path)) ? redactSecrets(raw) : raw;\n const total = content === '' ? 0 : content.split('\\n').length;\n const start = Math.max(0, offset ?? 0);\n const body = numberLines(content, start, limit);\n // snippet-with-pointer: when the slice doesn't cover the whole file, tell the\n // model what it's missing + how to pull it — so it expands precisely instead of re-reading blind.\n const shownEnd = limit != null ? Math.min(start + limit, total) : total;\n const shownCount = Math.max(0, shownEnd - start);\n if (shownCount >= total) return body; // whole file shown — no footer\n if (shownCount === 0) return `[no lines in range (offset ${start}${limit != null ? `, limit ${limit}` : ''}) — file has ${total} line(s)]`;\n return `${body}\\n\\n[lines ${start + 1}–${shownEnd} of ${total} · re-Read with offset/limit for the rest]`;\n },\n};\n\n/** Replace an exact, unique substring; requires a prior Read and guards against stale edits. */\nexport const editTool: AgentTool = {\n name: 'Edit',\n description:\n 'Replace an exact substring in a file. Requires a prior Read of the same file. `old_string` must occur exactly once — include surrounding context to disambiguate.',\n parameters: {\n type: 'object',\n required: ['path', 'old_string', 'new_string'],\n properties: {\n path: { type: 'string' },\n old_string: { type: 'string' },\n new_string: { type: 'string' },\n },\n },\n async run({ path, old_string, new_string }, ctx) {\n const key = ctx.fs.resolvePath(path);\n const snapshot = ctx.readState.get(key);\n if (snapshot == null) throw new Error(`File has not been read yet: ${path}. Read it before editing.`);\n const current = await ctx.fs.readFile(path);\n if (current !== snapshot) throw new Error(`File ${path} changed since it was read (stale). Re-read before editing.`);\n const count = old_string === '' ? 0 : current.split(old_string).length - 1;\n if (count > 1) throw new Error(`old_string is not unique in ${path} (${count} matches). Provide more surrounding context.`);\n let next: string, note = '';\n if (count === 1) {\n next = current.replace(old_string, () => new_string); // exact: function replacer, no $-pattern expansion\n } else {\n // exact match failed — try a whitespace-tolerant unique match before giving up (cuts re-read churn)\n const fuzzy = fuzzyLineReplace(current, old_string, new_string);\n if (fuzzy == null) throw new Error(`old_string not found in ${path}.`);\n next = fuzzy;\n note = ' (whitespace-tolerant match)';\n }\n if (ctx.lint) { const err = ctx.lint(path, next); if (err) throw new Error(err); }\n await ctx.fs.writeFile(path, next);\n ctx.readState.set(key, next);\n return `Edited ${path}${note}`;\n },\n};\n\n/** Session-exit tool: the model calls this when the user wants to end the conversation.\n * The `onExit` callback is injected by the host (CLI sets it to flip a flag that breaks the REPL loop). */\nexport function exitSessionTool(onExit: () => void): AgentTool {\n return {\n name: 'ExitSession',\n description:\n 'End the current session and exit the CLI. Call this when the user says goodbye, asks to quit, ' +\n 'or clearly indicates they want to stop the conversation (e.g. \"ok bye\", \"that\\'s all\", \"exit\", \"goodnight\").',\n parameters: { type: 'object', properties: {} },\n async run() {\n onExit();\n return 'Session ending. Goodbye!';\n },\n };\n}\n\nexport function defaultTools(): AgentTool[] {\n return [bashTool, readTool, editTool];\n}\n\n/**\n * The full catalog of selectable tools, keyed by name. The evolve loop's mutation\n * surface picks from this registry; embedders can build a custom tool set by name.\n */\nexport function toolRegistry(): Record<string, AgentTool> {\n const all = [bashTool, readTool, editTool, grepTool, globTool, writeTool, multiEditTool, applyEditsTool, repoMapTool, reviewTool(), todoWriteTool, webFetchTool, webSearchTool];\n return Object.fromEntries(all.map((t) => [t.name, t]));\n}\n\n/** Resolve a list of tool names against the registry (unknown names throw). */\nexport function toolsByName(names: string[]): AgentTool[] {\n const reg = toolRegistry();\n return names.map((n) => {\n const t = reg[n];\n if (!t) throw new Error(`unknown tool '${n}'. Known: ${Object.keys(reg).join(', ')}`);\n return t;\n });\n}\n","/**\n * Mask secret-looking values in arbitrary text before it reaches the model.\n *\n * Two complementary seams use this: real-shell output (`cat .env`, `printenv`) and the\n * `Read` tool (so provider keys stored in `.agent/settings.json` are usable-but-masked).\n * The FS jail hides whole secret FILES by name; this hides secret VALUES wherever they\n * surface in otherwise-legitimate content.\n *\n * Both regexes are linear (no nested quantifiers) — safe against catastrophic backtracking\n * and cheap enough to run on every tool output (see tests/redact.bench).\n */\n\nexport const REDACTED = '‹redacted›';\n\n/** Config/control files that may carry provider keys — readers (Read/Grep) mask secret VALUES in\n * these while keeping the rest readable. (Whole secret FILES like .env are hidden by the FS jail.) */\nexport const CONFIG_FILE_RE = /(^|\\/)\\.(agent|claude)\\/(settings(\\.[\\w-]+)?\\.json|config\\.(json|js|mjs|cjs|ts))$/i;\n\n// (A) `NAME=value` / `\"name\": \"value\"` pairs where NAME looks like a secret. Masks the value only,\n// so the agent still sees WHICH key exists (useful config context) without the secret itself.\nconst SECRET_PAIR =\n /((?:^|[\\s,{[])(?:export\\s+)?[\"']?[\\w.\\-]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|CREDENTIAL|PRIVATE_KEY|ACCESS_?KEY|AUTH(?:_?TOKEN)?|BEARER)[\\w.\\-]*[\"']?\\s*[:=]\\s*)([\"']?)([^\\s\"',{}\\]]+)/gi;\n\n// (B) Bare tokens by well-known shape — catches secrets that appear without an obvious key\n// (Authorization headers, URLs, JSON dumps). Conservative prefixes to avoid false positives.\nconst SECRET_TOKEN =\n /\\b(sk-ant-[\\w-]{12,}|sk-[A-Za-z0-9]{20,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|github_pat_[\\w]{20,}|xox[baprs]-[\\w-]{10,}|AKIA[0-9A-Z]{12,}|AIza[\\w-]{20,}|eyJ[\\w-]{8,}\\.[\\w-]{8,}\\.[\\w-]{8,})\\b/g;\n\n/** Return `text` with secret values masked. Cheap no-op when nothing matches. */\nexport function redactSecrets(text: string): string {\n if (!text) return text;\n return text\n .replace(SECRET_PAIR, (_m, head, quote, _val) => `${head}${quote}${REDACTED}`)\n .replace(SECRET_TOKEN, REDACTED);\n}\n","// Import the log module directly from libx.js source: libx.js's main bundle\n// doesn't re-export `log` as a named ESM export, and source-importing keeps\n// libx.js patches live (no rebuild) — matching the `bun link` workflow.\nimport { log } from 'libx.js/src/modules/log';\n\n/** Component-scoped logger (libx.js). debug/verbose gated via DEBUG env/localStorage. */\nexport const forComponent = (name: string) => log.forComponent(name);\nexport { log };\n","import type { AgentTool } from './tools';\nimport { forComponent } from './logging';\n\n/**\n * Web tools — `WebFetch` (retrieve a URL as readable text) and `WebSearch` (ranked\n * results via a configured provider). Opt-in (NOT in the default tool set): network\n * access is a deliberate capability. Factory-built with an injectable `fetch` so they\n * stay edge-portable and unit-testable without real network. `fetch` is read at call\n * time, so a no-network runtime simply has the tool return an error.\n */\nconst log = forComponent('web');\n\n/** Strip HTML to readable text — dependency-free: drop script/style/comments, block tags → newlines, decode common entities. */\nexport function htmlToText(html: string): string {\n let s = html\n .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n .replace(/<title[\\s\\S]*?<\\/title>/gi, ' ') // drop title text (don't leak it into context)\n .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, ' ') // …same for noscript / textarea content\n .replace(/<textarea[\\s\\S]*?<\\/textarea>/gi, ' ')\n .replace(/<!--[\\s\\S]*?-->/g, ' ')\n .replace(/<\\/(p|div|li|h[1-6]|tr|section|article|header|footer|nav)>/gi, '\\n')\n .replace(/<br\\s*\\/?>/gi, '\\n')\n .replace(/<[^>]+>/g, ' ');\n s = s\n .replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&').replace(/&lt;/g, '<')\n .replace(/&gt;/g, '>').replace(/&quot;/g, '\"').replace(/&#0?39;/g, \"'\").replace(/&#x27;/gi, \"'\");\n return s\n .replace(/[ \\t\\f\\v]+/g, ' ')\n .split('\\n').map((l) => l.trim()).join('\\n')\n .replace(/\\n{3,}/g, '\\n\\n')\n .trim();\n}\n\nexport interface WebFetchOptions {\n /** Override the global fetch (tests inject a mock; edge runtimes can supply their own). */\n fetch?: typeof globalThis.fetch;\n maxBytes?: number; // cap the downloaded body (default 2 MB)\n maxChars?: number; // cap the returned text (default 100k)\n timeoutMs?: number; // request timeout (default 15s)\n /** Allow fetching private/loopback/link-local hosts (default false — blocks basic SSRF). */\n allowPrivateHosts?: boolean;\n}\n\n/**\n * Block obvious SSRF targets by hostname/IP literal (loopback, private ranges, link-local incl.\n * cloud metadata 169.254.169.254, `.internal`). Pure/edge-safe — no DNS, so DNS-rebinding and\n * redirect-to-internal are NOT covered (an embedder needing that should supply a vetting `fetch`).\n */\nexport function isPrivateHost(host: string): boolean {\n const h = host.toLowerCase().replace(/^\\[|\\]$/g, ''); // strip IPv6 brackets\n if (h === '' || h === 'localhost' || h.endsWith('.localhost') || h.endsWith('.internal')) return true;\n if (h === '::1' || h === '::' || h.startsWith('fe80:') || h.startsWith('fc') || h.startsWith('fd')) return true; // IPv6 loopback/link-local/ULA\n const m = h.match(/^(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})$/);\n if (m) {\n const a = +m[1], b = +m[2];\n return a === 0 || a === 127 || a === 10 || (a === 169 && b === 254) || (a === 172 && b >= 16 && b <= 31) || (a === 192 && b === 168) || (a === 100 && b >= 64 && b <= 127);\n }\n return false;\n}\n\n/** Lazily-loaded node DNS resolver (absent on edge/browser) — closes DNS-rebinding (a public\n * hostname resolving to an internal IP) on the real-network path. Resolves null where unavailable. */\nlet _dnsLookup: ((h: string, opts?: any) => Promise<{ address: string }[]>) | null | undefined;\nasync function resolveIps(host: string): Promise<string[] | null> {\n if (_dnsLookup === undefined) {\n try { _dnsLookup = (await import('node:dns/promises')).lookup as any; }\n catch { _dnsLookup = null; } // edge/browser: no DNS — rely on the literal isPrivateHost check\n }\n if (!_dnsLookup) return null;\n try { return (await _dnsLookup(host, { all: true } as any)).map((a) => a.address); } catch { return null; }\n}\n\n/** Read a response body but stop at `maxBytes` of ACTUAL bytes (cancel the stream) — no unbounded download. */\nasync function readCapped(res: Response, maxBytes: number): Promise<string> {\n const reader = (res.body as any)?.getReader?.();\n if (!reader) { const t = await res.text(); return t.length > maxBytes ? t.slice(0, maxBytes) : t; }\n const chunks: Uint8Array[] = [];\n let total = 0;\n for (;;) {\n const { done, value } = await reader.read();\n if (done) break;\n if (value) { chunks.push(value); total += value.length; }\n if (total >= maxBytes) { try { await reader.cancel(); } catch { /* already closed */ } break; }\n }\n const out = new Uint8Array(Math.min(total, maxBytes));\n let off = 0;\n for (const c of chunks) { if (off >= out.length) break; const take = Math.min(c.length, out.length - off); out.set(c.subarray(0, take), off); off += take; }\n return new TextDecoder().decode(out);\n}\n\n/** Build a WebFetch tool. */\nexport function makeWebFetchTool(options: WebFetchOptions = {}): AgentTool {\n const maxBytes = options.maxBytes ?? 2_000_000;\n const maxChars = options.maxChars ?? 100_000;\n const timeoutMs = options.timeoutMs ?? 15_000;\n return {\n name: 'WebFetch',\n description:\n 'Fetch an http/https URL and return its readable text (HTML is stripped to text). Use to read docs or web pages. Returns the status line then up to ~100k chars of content.',\n parameters: { type: 'object', required: ['url'], properties: { url: { type: 'string', description: 'absolute http(s) URL' } } },\n async run({ url }) {\n const doFetch = options.fetch ?? globalThis.fetch;\n const customFetch = !!options.fetch; // injected fetch (tests/edge) owns its own vetting → skip DNS\n const u = String(url ?? '');\n try { new URL(u); } catch { return `Error: invalid URL: ${u}`; }\n if (!doFetch) return 'Error: no network (fetch) available in this runtime';\n // Reject a host that's a private/internal IP literal, or (on the real-network path) a name that\n // RESOLVES to one — re-checked on EVERY redirect hop so an external page can't bounce us internal.\n const hostBlock = async (hostname: string): Promise<string | null> => {\n if (options.allowPrivateHosts) return null;\n if (isPrivateHost(hostname)) return hostname;\n if (!customFetch) { const ips = await resolveIps(hostname); if (ips) for (const ip of ips) if (isPrivateHost(ip)) return `${hostname} → ${ip}`; }\n return null;\n };\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(), timeoutMs);\n try {\n let current = u;\n let res: Response;\n for (let hop = 0; ; hop++) {\n const pu = new URL(current);\n if (pu.protocol !== 'http:' && pu.protocol !== 'https:') return `Error: only http/https URLs are allowed (got \"${pu.protocol}\")`;\n const blocked = await hostBlock(pu.hostname);\n if (blocked) return `Error: refusing to fetch a private/internal address (${blocked}) — set allowPrivateHosts to override`;\n res = await doFetch(current, { signal: ctl.signal, redirect: 'manual', headers: { 'user-agent': 'agentx (+https://github.com/Livshitz/agent.libx.js)' } });\n if (res.status >= 300 && res.status < 400 && res.headers.get('location')) {\n if (hop >= 5) return `Error fetching ${u}: too many redirects`;\n current = new URL(res.headers.get('location')!, current).toString(); // re-validated at loop top\n continue;\n }\n break;\n }\n const type = res.headers.get('content-type') ?? '';\n const body = await readCapped(res, maxBytes);\n const text = /html/i.test(type) || /^\\s*<(?:!doctype|html)/i.test(body) ? htmlToText(body) : body.trim();\n const capped = text.length > maxChars ? text.slice(0, maxChars) + `\\n… [truncated at ${maxChars} chars]` : text;\n return `${res.status} ${res.statusText} · ${new URL(current).host}\\n\\n${capped}`;\n } catch (e: any) {\n log.debug(`WebFetch ${u} failed`, e);\n return `Error fetching ${u}: ${e?.name === 'AbortError' ? `timed out after ${timeoutMs}ms` : (e?.message ?? e)}`;\n } finally {\n clearTimeout(timer);\n }\n },\n };\n}\n\nexport interface WebSearchOptions {\n fetch?: typeof globalThis.fetch;\n /** API key for the search provider (default: process.env.TAVILY_API_KEY). */\n apiKey?: string;\n /** Provider endpoint (default: Tavily, an agent-oriented search API). */\n endpoint?: string;\n maxResults?: number; // default 5\n timeoutMs?: number; // default 15s\n}\n\n/** Build a WebSearch tool backed by a configured provider (Tavily by default). */\nexport function makeWebSearchTool(options: WebSearchOptions = {}): AgentTool {\n const endpoint = options.endpoint ?? 'https://api.tavily.com/search';\n const maxResults = options.maxResults ?? 5;\n const timeoutMs = options.timeoutMs ?? 15_000;\n return {\n name: 'WebSearch',\n description:\n 'Search the web; returns ranked results (title, URL, snippet). Requires a configured provider — set TAVILY_API_KEY to enable.',\n parameters: { type: 'object', required: ['query'], properties: { query: { type: 'string' } } },\n async run({ query }) {\n const doFetch = options.fetch ?? globalThis.fetch;\n const key = options.apiKey ?? process.env.TAVILY_API_KEY;\n if (!key) return 'Error: WebSearch is not configured. Set TAVILY_API_KEY (https://tavily.com) to enable web search.';\n if (!doFetch) return 'Error: no network (fetch) available in this runtime';\n const ctl = new AbortController();\n const timer = setTimeout(() => ctl.abort(), timeoutMs);\n try {\n const res = await doFetch(endpoint, {\n method: 'POST',\n signal: ctl.signal,\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ api_key: key, query: String(query ?? ''), max_results: maxResults }),\n });\n if (!res.ok) return `Error: search provider returned ${res.status} ${res.statusText}`;\n const data: any = await res.json();\n const results = Array.isArray(data?.results) ? data.results.slice(0, maxResults) : [];\n if (!results.length) return '(no results)';\n return results\n .map((r: any, i: number) => `${i + 1}. ${r.title ?? '(untitled)'}\\n ${r.url ?? ''}\\n ${String(r.content ?? '').replace(/\\s+/g, ' ').slice(0, 240)}`)\n .join('\\n\\n');\n } catch (e: any) {\n log.debug('WebSearch failed', e);\n return `Error searching: ${e?.name === 'AbortError' ? `timed out after ${timeoutMs}ms` : (e?.message ?? e)}`;\n } finally {\n clearTimeout(timer);\n }\n },\n };\n}\n\n/** Default instances (registered in the tool registry; opt-in by name). */\nexport const webFetchTool = makeWebFetchTool();\nexport const webSearchTool = makeWebSearchTool();\n","import type { AgentTool } from './tools';\nimport { truncateOutput } from './tools';\nimport { redactSecrets } from './redact';\nimport { forComponent } from './logging';\n\n/**\n * Real shell tool — node-only, OPT-IN, and deliberately NOT edge-portable.\n *\n * ⚠️ Unlike the default VFS `bash` (a sandboxed JS interpreter over the virtual filesystem),\n * this spawns a REAL `/bin/sh` process. It can run `bun`, `git`, `ssh`, scripts, deploys —\n * and, by the same token, it is NOT sandboxed: only cwd-binding constrains it. It is a\n * deliberate host escalation, kept out of `defaultTools()`/`toolRegistry()` and out of the\n * edge-safe `src/index.ts` (same policy as `mcp.client.ts`). A host opts in explicitly:\n *\n * tools: [...defaultTools(), makeRealShellTool({ cwd: nodeDiskRoot })]\n *\n * Mirrors `tools.web.ts`: a factory with an injectable `spawn` (tests + edge never import\n * node:child_process), an options bag, abort + timeout honored, output capped. Safety beyond\n * cwd-binding is the host's to add (e.g. a PermissionPolicy `decision:'ask'` per command, or\n * an OS sandbox wrapper) — see mind/03-roadmap.md \"OS-level access — capability tiers\".\n */\n\nconst log = forComponent('shell');\n\n/** Normalize shell output for return: trim trailing newlines, mask secret values, then size-truncate.\n * Redaction runs BEFORE truncation so a masked tail can't smuggle a secret past the line cap. */\nconst clean = (s: string): string => truncateOutput(redactSecrets(s.replace(/\\n+$/, '')));\n\n/** The slice of node's `child_process.spawn` we depend on — injectable so tests supply a fake. */\nexport type SpawnFn = (\n command: string,\n args: string[],\n options: { cwd?: string; env?: Record<string, string | undefined>; signal?: AbortSignal },\n) => SpawnedProcess;\n\n/** Minimal `ChildProcess` surface this tool uses. */\nexport interface SpawnedProcess {\n stdout?: { on(ev: 'data', cb: (chunk: any) => void): void } | null;\n stderr?: { on(ev: 'data', cb: (chunk: any) => void): void } | null;\n on(ev: 'close', cb: (code: number | null) => void): void;\n on(ev: 'error', cb: (err: Error) => void): void;\n kill(signal?: string): void;\n}\n\nexport interface RealShellOptions {\n /** Working directory the shell is bound to (typically a NodeDiskFilesystem `baseDir`). Required. */\n cwd: string;\n /** Override the spawner (tests inject a fake; default lazily imports node:child_process). */\n spawn?: SpawnFn;\n /** Per-command wall-clock cap (kill on overrun). Default 120s. */\n timeoutMs?: number;\n /** Extra env merged over the (optionally scrubbed) base env for the child. */\n env?: Record<string, string>;\n /** Strip likely-secret vars (API keys, tokens, cloud creds) from the child's env. Default ON.\n * The FS jail does NOT contain a real process, so this is the seam that keeps `echo $ANTHROPIC_API_KEY`\n * from leaking the host's secrets to a spawned command. `false` passes `process.env` through verbatim. */\n redactEnv?: boolean;\n /** Job registry enabling `Shell({background:true})` (long-running processes). Pair with `makeShellJobTools`. */\n registry?: ShellJobRegistry;\n}\n\n/** Env var names that look like secrets and are dropped before spawning (unless redactEnv:false). */\nconst SECRET_ENV_RE = /(_API_KEY|_TOKEN|_SECRET|_PASSWORD|_PRIVATE_KEY|^AWS_|^GITHUB_TOKEN$|^OPENAI_|^ANTHROPIC_|^GOOGLE_|^GEMINI_|^GROQ_|^NPM_TOKEN$)/i;\n\n/** Build the child's env: `process.env` minus likely-secrets (when redacting), plus explicit `env`. */\nfunction childEnv(opts: { env?: Record<string, string>; redactEnv?: boolean }): Record<string, string | undefined> {\n const base: Record<string, string | undefined> = {};\n const redact = opts.redactEnv !== false; // default ON\n for (const [k, v] of Object.entries(process.env)) if (!(redact && SECRET_ENV_RE.test(k))) base[k] = v;\n return { ...base, ...opts.env };\n}\n\n/** Lazily resolve node's spawn (kept out of any eager edge import path). */\nlet _spawn: SpawnFn | undefined;\nasync function nodeSpawn(): Promise<SpawnFn> {\n if (!_spawn) _spawn = (await import('node:child_process')).spawn as unknown as SpawnFn;\n return _spawn;\n}\n\n// ---------------------------------------------------------------------------\n// Background jobs — long-running processes the agent starts, polls, and kills.\n// ---------------------------------------------------------------------------\nexport type JobStatus = 'running' | 'exited' | 'killed' | 'error';\n\nexport interface ShellJobConfig {\n cwd: string;\n spawn?: SpawnFn;\n env?: Record<string, string>;\n redactEnv?: boolean;\n /** Tail buffer cap per job (bytes); older output is dropped. Default 256 KB. */\n maxBuffer?: number;\n /** Kill all jobs on process exit (the CLI sets this; tests leave it off to avoid global handlers). */\n killOnExit?: boolean;\n}\n\ninterface Job { command: string; buf: string; status: JobStatus; exitCode?: number; proc?: SpawnedProcess; }\n\n/**\n * Per-session registry of background `/bin/sh` jobs. Backs `Shell({background:true})` and the\n * `ShellOutput`/`ShellStatus`/`ShellKill` tools. Output accumulates into a tail-capped ring so a\n * chatty process can't OOM. Bounded + killable; the CLI wires `killOnExit` so children are reaped.\n */\nexport class ShellJobRegistry {\n private jobs = new Map<string, Job>();\n private seq = 0;\n constructor(private cfg: ShellJobConfig) {\n if (cfg.killOnExit && typeof process !== 'undefined') process.once('exit', () => this.killAll());\n }\n\n async start(command: string): Promise<string> {\n const id = `job-${++this.seq}`;\n const max = this.cfg.maxBuffer ?? 256 * 1024;\n const job: Job = { command, buf: '', status: 'running' };\n const append = (chunk: any) => {\n const s = typeof chunk === 'string' ? chunk : chunk?.toString?.('utf8') ?? '';\n job.buf = (job.buf + s).slice(-max); // ring: keep the tail\n };\n try {\n const spawn = this.cfg.spawn ?? (await nodeSpawn());\n const proc = spawn('/bin/sh', ['-c', command], { cwd: this.cfg.cwd, env: childEnv(this.cfg) });\n job.proc = proc;\n proc.stdout?.on('data', append);\n proc.stderr?.on('data', append);\n proc.on('error', (err: any) => { if (job.status === 'running') { job.status = 'error'; append(`\\n[error] ${err?.message ?? err}`); } });\n proc.on('close', (code: number | null) => { if (job.status === 'running') { job.status = 'exited'; job.exitCode = code ?? undefined; } });\n } catch (e: any) {\n job.status = 'error';\n job.buf = `failed to spawn: ${e?.message ?? e}`;\n }\n this.jobs.set(id, job);\n return id;\n }\n\n /** Current tail output for a job (null = no such job). */\n output(id: string): string | null { return this.jobs.get(id)?.buf ?? (this.jobs.has(id) ? '' : null); }\n\n status(id: string): { status: JobStatus; exitCode?: number; bytes: number } | null {\n const j = this.jobs.get(id);\n return j ? { status: j.status, exitCode: j.exitCode, bytes: j.buf.length } : null;\n }\n\n list(): Array<{ id: string; command: string; status: JobStatus }> {\n return [...this.jobs].map(([id, j]) => ({ id, command: j.command, status: j.status }));\n }\n\n kill(id: string): boolean {\n const j = this.jobs.get(id);\n if (!j) return false;\n if (j.status === 'running') { try { j.proc?.kill('SIGTERM'); } catch { /* already gone */ } j.status = 'killed'; }\n return true;\n }\n\n killAll(): void { for (const id of this.jobs.keys()) this.kill(id); }\n}\n\n/** Build an opt-in real-shell tool bound to `options.cwd`. */\nexport function makeRealShellTool(options: RealShellOptions): AgentTool {\n const timeoutMs = options.timeoutMs ?? 120_000;\n return {\n name: 'Shell',\n description:\n 'Run a shell command via /bin/sh in the working directory. ' +\n 'Executes any installed binary — ls, cat, grep, git, bun, node, curl, scripts, etc. ' +\n 'Returns combined stdout+stderr; non-zero exits are prefixed `[exit N]`. ' +\n 'Set `background:true` for long-running processes (servers, watchers) — returns a job id immediately; poll with ShellOutput, stop with ShellKill.',\n parameters: {\n type: 'object',\n required: ['command'],\n properties: {\n command: { type: 'string', description: 'the shell command line to execute' },\n background: { type: 'boolean', description: 'run detached and return a job id immediately (for servers/watchers/long builds)' },\n },\n },\n async run({ command, background }, ctx) {\n const cmd = String(command ?? '');\n if (!cmd.trim()) return '[exit 1] empty command';\n if (background) {\n if (!options.registry) return 'Error: background execution is not enabled in this host (no job registry).';\n const id = await options.registry.start(cmd);\n return `Started background job ${id}. Poll output with ShellOutput({id:\"${id}\"}), check ShellStatus({id:\"${id}\"}), stop with ShellKill({id:\"${id}\"}).`;\n }\n const spawn = options.spawn ?? (await nodeSpawn());\n // Compose abort: the run's signal (ctx.signal) OR our per-command timeout both kill the child.\n const ctl = new AbortController();\n const onAbort = () => ctl.abort();\n if (ctx.signal) { if (ctx.signal.aborted) ctl.abort(); else ctx.signal.addEventListener('abort', onAbort, { once: true }); }\n let timedOut = false;\n const timer = setTimeout(() => { timedOut = true; ctl.abort(); }, timeoutMs);\n try {\n return await new Promise<string>((resolve) => {\n let out = '';\n let settled = false;\n const finish = (s: string) => { if (settled) return; settled = true; resolve(s); };\n let proc: SpawnedProcess;\n try {\n proc = spawn('/bin/sh', ['-c', cmd], { cwd: options.cwd, env: childEnv(options), signal: ctl.signal });\n } catch (e: any) {\n return finish(`[exit 1] failed to spawn shell: ${e?.message ?? e}`);\n }\n const collect = (chunk: any) => { out += typeof chunk === 'string' ? chunk : chunk?.toString?.('utf8') ?? ''; };\n proc.stdout?.on('data', collect);\n proc.stderr?.on('data', collect);\n proc.on('error', (err: any) => {\n // AbortError fires here when ctl.abort() kills the child — report timeout vs cancel.\n if (err?.name === 'AbortError' || ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));\n log.debug('shell spawn error', err);\n finish(`[exit 1] ${err?.message ?? err}${out ? '\\n' + clean(out) : ''}`);\n });\n proc.on('close', (code: number | null) => {\n if (ctl.signal.aborted) return finish(reasonFor(timedOut, timeoutMs, clean(out)));\n const body = clean(out);\n if (code && code !== 0) return finish(`[exit ${code}]${body ? '\\n' + body : ''}`);\n finish(body || '(command succeeded, no output)');\n });\n });\n } finally {\n clearTimeout(timer);\n ctx.signal?.removeEventListener('abort', onAbort);\n }\n },\n };\n}\n\n/** Abort message: timeout vs external cancel, preserving any partial output. */\nfunction reasonFor(timedOut: boolean, timeoutMs: number, body: string): string {\n const head = timedOut ? `[exit 124] timed out after ${timeoutMs}ms (killed)` : '[exit 130] cancelled (killed)';\n return body ? `${head}\\n${body}` : head;\n}\n\nconst NO_JOB = (id: string) => `Error: no background job '${id}'. Use ShellStatus with no id to list jobs, or start one with Shell({background:true}).`;\n\n/** Build the background-job companion tools (ShellOutput / ShellStatus / ShellKill) over a registry. */\nexport function makeShellJobTools(registry: ShellJobRegistry): AgentTool[] {\n const idParam = { type: 'object', properties: { id: { type: 'string', description: 'the job id from Shell({background:true})' } } };\n return [\n {\n name: 'ShellOutput',\n description: 'Read the accumulated output (tail) of a background Shell job by id.',\n parameters: { type: 'object', required: ['id'], properties: { id: { type: 'string' } } },\n async run({ id }) {\n const out = registry.output(String(id));\n if (out == null) return NO_JOB(String(id));\n const st = registry.status(String(id))!;\n return `[${st.status}${st.exitCode != null ? ` exit ${st.exitCode}` : ''}]\\n${clean(out) || '(no output yet)'}`;\n },\n },\n {\n name: 'ShellStatus',\n description: 'Status of a background Shell job (running/exited/killed + exit code). Omit `id` to list all jobs.',\n parameters: idParam,\n async run({ id }) {\n if (!id) {\n const jobs = registry.list();\n return jobs.length ? jobs.map((j) => `${j.id} ${j.status} ${j.command}`).join('\\n') : '(no background jobs)';\n }\n const st = registry.status(String(id));\n return st ? `${st.status}${st.exitCode != null ? ` (exit ${st.exitCode})` : ''} · ${st.bytes} byte(s) buffered` : NO_JOB(String(id));\n },\n },\n {\n name: 'ShellKill',\n description: 'Stop a running background Shell job by id (SIGTERM).',\n parameters: { type: 'object', required: ['id'], properties: { id: { type: 'string' } } },\n async run({ id }) {\n return registry.kill(String(id)) ? `Killed job ${id}.` : NO_JOB(String(id));\n },\n },\n ];\n}\n"],"mappings":"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","names":["log","log"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent.libx.js",
3
- "version": "0.89.9",
3
+ "version": "0.92.1",
4
4
  "description": "Edge-native AI agent runtime — drives a virtual filesystem via any LLM (ai.libx.js). Same bytes run in node, browser, or edge.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -46,7 +46,7 @@
46
46
  "node": ">=18"
47
47
  },
48
48
  "bin": {
49
- "agentx": "dist/cli.js"
49
+ "agentx": "cli/cli.ts"
50
50
  },
51
51
  "files": [
52
52
  "dist",