diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 60c34866a..6c05cb830 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,15 +1,23 @@ # Changelog ## [Unreleased] - ### Added +- Added fetch tool for URL content retrieval with enhanced processing capabilities - Added `isolated` option to run tasks in isolated git worktrees - Added automatic patch generation and application for isolated task execution - Added worktree management for isolated task execution with baseline capture and delta patching ### Changed +- Renamed web_fetch tool to fetch for consistency and brevity +- Updated output tool to automatically convert JSON to YAML for better readability +- Removed format parameter from output tool to simplify usage +- Improved bash tool output truncation to 50KB or 2000 lines +- Updated python tool output truncation limit to 100KB +- Enhanced grep tool documentation for different output modes +- Updated read tool documentation to clarify supported file types +- Improved write tool documentation to mention LSP auto-formatting - Simplified web-search tool parameters by removing advanced configuration options - Standardized web-search tool to use built-in system prompt for consistent response style - Updated web-search tool to use `recency` parameter instead of `search_recency_filter` @@ -45,6 +53,10 @@ - Removed model parameter from gemini-image tool to use provider defaults - Improved variable display in task output with humanized keys for single variables +### Removed + +- Removed web_fetch tool (replaced by fetch tool) + ### Fixed - Added timeout clamping to reasonable ranges across all tools (1s to 3600s for bash/ssh, 1s to 600s for python/gemini-image) diff --git a/packages/coding-agent/DEVELOPMENT.md b/packages/coding-agent/DEVELOPMENT.md index b47499fc3..0829af2de 100644 --- a/packages/coding-agent/DEVELOPMENT.md +++ b/packages/coding-agent/DEVELOPMENT.md @@ -163,7 +163,7 @@ src/ │ ├── review.ts # Code review tools │ ├── rulebook.ts # Rulebook tool │ ├── write.ts # File writing -│ ├── web-fetch.ts # Web content fetching +│ ├── fetch.ts # URL content fetching │ ├── exa/ # Exa MCP tools (22 tools) │ ├── lsp/ # LSP integration tools │ ├── task/ # Task/subagent spawning diff --git a/packages/coding-agent/README.md b/packages/coding-agent/README.md index d7fbaaea5..61ab95a58 100644 --- a/packages/coding-agent/README.md +++ b/packages/coding-agent/README.md @@ -938,7 +938,7 @@ All tools are enabled by default. Use `--tools ` to restrict to a subset. | `lsp` | Language Server Protocol queries (go-to-definition, references, hover) | | `todo_write` | Track task progress during sessions | | `web_search` | Search the web | -| `web_fetch` | Fetch and render URLs | +| `fetch` | Fetch and process URLs | | `python` | Execute Python code in IPython kernel | | `notebook` | Edit Jupyter notebook cells | diff --git a/packages/coding-agent/docs/sdk.md b/packages/coding-agent/docs/sdk.md index 2588c25eb..e04dbce9a 100644 --- a/packages/coding-agent/docs/sdk.md +++ b/packages/coding-agent/docs/sdk.md @@ -375,7 +375,7 @@ All tools are defined in `BUILTIN_TOOLS`: - `output` - Task output retrieval - `read` - File reading (text and images) - `task` - Subagent spawning -- `web_fetch` - Web page fetching +- `fetch` - URL fetching - `web_search` - Web search - `write` - File writing diff --git a/packages/coding-agent/src/cli/args.ts b/packages/coding-agent/src/cli/args.ts index 8bb419eb9..685b561e3 100644 --- a/packages/coding-agent/src/cli/args.ts +++ b/packages/coding-agent/src/cli/args.ts @@ -296,7 +296,7 @@ ${chalk.bold("Available Tools (all enabled by default):")} python - Execute Python code (requires: ${APP_NAME} setup python) notebook - Edit Jupyter notebooks task - Launch sub-agents for parallel tasks - web_fetch - Fetch and process web pages + fetch - Fetch and process URLs web_search - Search the web ask - Ask user questions (interactive mode only) `); diff --git a/packages/coding-agent/src/core/tools/web-fetch.ts b/packages/coding-agent/src/core/tools/fetch.ts similarity index 96% rename from packages/coding-agent/src/core/tools/web-fetch.ts rename to packages/coding-agent/src/core/tools/fetch.ts index fd37955b2..19a81ede3 100644 --- a/packages/coding-agent/src/core/tools/web-fetch.ts +++ b/packages/coding-agent/src/core/tools/fetch.ts @@ -9,7 +9,7 @@ import { type Static, Type } from "@sinclair/typebox"; import { nanoid } from "nanoid"; import { parse as parseHtml } from "node-html-parser"; import { type Theme, theme } from "../../modes/interactive/theme/theme"; -import webFetchDescription from "../../prompts/tools/web-fetch.md" with { type: "text" }; +import fetchDescription from "../../prompts/tools/fetch.md" with { type: "text" }; import { ensureTool } from "../../utils/tools-manager"; import type { RenderResultOptions } from "../custom-tools/types"; import { renderPromptTemplate } from "../prompt-templates"; @@ -867,13 +867,13 @@ async function renderUrl( // Tool Definition // ============================================================================= -const webFetchSchema = Type.Object({ +const fetchSchema = Type.Object({ url: Type.String({ description: "URL to fetch" }), timeout: Type.Optional(Type.Number({ description: "Timeout in seconds (default: 20)" })), raw: Type.Optional(Type.Boolean({ description: "Return raw HTML without transforms" })), }); -export interface WebFetchToolDetails { +export interface FetchToolDetails { url: string; finalUrl: string; contentType: string; @@ -882,23 +882,23 @@ export interface WebFetchToolDetails { notes: string[]; } -export class WebFetchTool implements AgentTool { - public readonly name = "web_fetch"; - public readonly label = "Web Fetch"; +export class FetchTool implements AgentTool { + public readonly name = "fetch"; + public readonly label = "Fetch"; public readonly description: string; - public readonly parameters = webFetchSchema; + public readonly parameters = fetchSchema; constructor(_session: ToolSession) { - this.description = renderPromptTemplate(webFetchDescription); + this.description = renderPromptTemplate(fetchDescription); } public async execute( _toolCallId: string, - params: Static, + params: Static, signal?: AbortSignal, - _onUpdate?: AgentToolUpdateCallback, + _onUpdate?: AgentToolUpdateCallback, _context?: AgentToolContext, - ): Promise> { + ): Promise> { const { url, timeout: rawTimeout = 20, raw = false } = params; // Auto-convert milliseconds to seconds if value > 1000 (16+ min is unreasonable) @@ -927,7 +927,7 @@ export class WebFetchTool implements AgentTool l.trim()).length; } -/** Render web fetch call (URL preview) */ -export function renderWebFetchCall( +/** Render fetch call (URL preview) */ +export function renderFetchCall( args: { url: string; timeout?: number; raw?: boolean }, uiTheme: Theme = theme, ): Component { const domain = getDomain(args.url); const path = truncate(args.url.replace(/^https?:\/\/[^/]+/, ""), 50, uiTheme.format.ellipsis); const icon = uiTheme.styledSymbol("status.pending", "muted"); - const text = `${icon} ${uiTheme.fg("toolTitle", "Web Fetch")} ${uiTheme.fg("accent", domain)}${uiTheme.fg("dim", path)}`; + const text = `${icon} ${uiTheme.fg("toolTitle", "Fetch")} ${uiTheme.fg("accent", domain)}${uiTheme.fg("dim", path)}`; return new Text(text, 0, 0); } -/** Render web fetch result with tree-based layout */ -export function renderWebFetchResult( - result: { content: Array<{ type: string; text?: string }>; details?: WebFetchToolDetails }, +/** Render fetch result with tree-based layout */ +export function renderFetchResult( + result: { content: Array<{ type: string; text?: string }>; details?: FetchToolDetails }, options: RenderResultOptions, uiTheme: Theme = theme, ): Component { @@ -1106,7 +1106,7 @@ export function renderWebFetchResult( return new Text(text, 0, 0); } -export const webFetchToolRenderer = { - renderCall: renderWebFetchCall, - renderResult: renderWebFetchResult, +export const fetchToolRenderer = { + renderCall: renderFetchCall, + renderResult: renderFetchResult, }; diff --git a/packages/coding-agent/src/core/tools/index.ts b/packages/coding-agent/src/core/tools/index.ts index 1d3af6c11..56a702087 100644 --- a/packages/coding-agent/src/core/tools/index.ts +++ b/packages/coding-agent/src/core/tools/index.ts @@ -5,6 +5,7 @@ export { CompleteTool } from "./complete"; // Exa MCP tools (22 tools) export { exaTools } from "./exa/index"; export type { ExaRenderDetails, ExaSearchResponse, ExaSearchResult } from "./exa/types"; +export { FetchTool, type FetchToolDetails } from "./fetch"; export { type FindOperations, FindTool, type FindToolDetails, type FindToolOptions } from "./find"; export { setPreferredImageProvider } from "./gemini-image"; export { type GrepOperations, GrepTool, type GrepToolDetails, type GrepToolOptions } from "./grep"; @@ -39,7 +40,6 @@ export { truncateLine, truncateTail, } from "./truncate"; -export { WebFetchTool, type WebFetchToolDetails } from "./web-fetch"; export { companyWebSearchTools, exaWebSearchTools, @@ -70,6 +70,7 @@ import { AskTool } from "./ask"; import { BashTool } from "./bash"; import { CalculatorTool } from "./calculator"; import { CompleteTool } from "./complete"; +import { FetchTool } from "./fetch"; import { FindTool } from "./find"; import { GrepTool } from "./grep"; import { LsTool } from "./ls"; @@ -83,7 +84,6 @@ import { reportFindingTool } from "./review"; import { loadSshTool } from "./ssh"; import { TaskTool } from "./task/index"; import { TodoWriteTool } from "./todo-write"; -import { WebFetchTool } from "./web-fetch"; import { WebSearchTool } from "./web-search/index"; import { WriteTool } from "./write"; @@ -157,7 +157,7 @@ export const BUILTIN_TOOLS: Record = { read: (s) => new ReadTool(s), task: TaskTool.create, todo_write: (s) => new TodoWriteTool(s), - web_fetch: (s) => new WebFetchTool(s), + fetch: (s) => new FetchTool(s), web_search: (s) => new WebSearchTool(s), write: (s) => new WriteTool(s), }; diff --git a/packages/coding-agent/src/core/tools/output.ts b/packages/coding-agent/src/core/tools/output.ts index 3d853826d..1fa46c379 100644 --- a/packages/coding-agent/src/core/tools/output.ts +++ b/packages/coding-agent/src/core/tools/output.ts @@ -7,10 +7,10 @@ import * as fs from "node:fs"; import * as path from "node:path"; import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core"; -import { StringEnum } from "@oh-my-pi/pi-ai"; import type { Component } from "@oh-my-pi/pi-tui"; import { Text } from "@oh-my-pi/pi-tui"; import { Type } from "@sinclair/typebox"; +import { YAML } from "bun"; import type { Theme } from "../../modes/interactive/theme/theme"; import outputDescription from "../../prompts/tools/output.md" with { type: "text" }; import type { RenderResultOptions } from "../custom-tools/types"; @@ -30,14 +30,9 @@ const outputSchema = Type.Object({ ids: Type.Array(Type.String(), { description: "Agent output IDs to read (e.g., ['reviewer_0', 'explore_1'])", }), - format: Type.Optional( - StringEnum(["raw", "json", "stripped"], { - description: "Output format: raw (default), json (structured), stripped (no ANSI)", - }), - ), query: Type.Optional( Type.String({ - description: "jq-like query for JSON outputs (e.g., .result.items[0].name). Requires JSON output.", + description: "jq-like query for JSON outputs (e.g., .result.items[0].name)", }), ), offset: Type.Optional( @@ -83,11 +78,6 @@ export interface OutputToolDetails { availableIds?: string[]; } -/** Strip ANSI escape codes from text */ -function stripAnsi(text: string): string { - return text.replace(/\x1b\[[0-9;]*m/g, ""); -} - function parseQuery(query: string): Array { let input = query.trim(); if (!input) return []; @@ -233,7 +223,6 @@ function extractPreviewLines(content: string, maxLines: number): string[] { type OutputParams = { ids: string[]; - format?: "raw" | "json" | "stripped"; query?: string; offset?: number; limit?: number; @@ -286,7 +275,6 @@ export class OutputTool implements AgentTool(); const query = params.query?.trim(); const wantsQuery = query !== undefined && query.length > 0; - const format = params.format ?? (wantsQuery ? "json" : "raw"); if (wantsQuery && (params.offset !== undefined || params.limit !== undefined)) { throw new Error("query cannot be combined with offset/limit"); @@ -366,41 +354,29 @@ export class OutputTool implements AgentTool { + let content = outputContentById.get(o.id) ?? ""; - if (format === "json") { - const jsonData = wantsQuery - ? queryResults - : outputs.map((o) => ({ - id: o.id, - lineCount: o.lineCount, - charCount: o.charCount, - provenance: o.provenance, - previewLines: o.previewLines, - range: o.range, - content: outputContentById.get(o.id) ?? "", - })); - contentText = JSON.stringify(jsonData, null, 2); - } else { - // raw or stripped - const parts = outputs.map((o) => { - let content = outputContentById.get(o.id) ?? ""; - if (format === "stripped") { - content = stripAnsi(content); - } - if (o.range && o.range.endLine < o.range.totalLines) { - const nextOffset = o.range.endLine + 1; - content += `\n\n[Showing lines ${o.range.startLine}-${o.range.endLine} of ${o.range.totalLines}. Use offset=${nextOffset} to continue]`; - } - // Add header for multiple outputs - if (outputs.length > 1) { - return `=== ${o.id} (${o.lineCount} lines, ${formatBytes(o.charCount)}) ===\n${content}`; - } - return content; - }); - contentText = parts.join("\n\n"); - } + // Try to convert JSON to YAML for readability + try { + const parsed = JSON.parse(content); + content = YAML.stringify(parsed); + } catch { + // Not JSON, keep as-is + } + + if (o.range && o.range.endLine < o.range.totalLines) { + const nextOffset = o.range.endLine + 1; + content += `\n[Showing lines ${o.range.startLine}-${o.range.endLine} of ${o.range.totalLines}. Use offset=${nextOffset} to continue]`; + } + // Add header for multiple outputs + if (outputs.length > 1) { + return `=== ${o.id} (${o.lineCount} lines, ${formatBytes(o.charCount)}) ===\n${content}`; + } + return content; + }); + const contentText = parts.join("\n\n"); return { content: [{ type: "text", text: contentText }], @@ -415,7 +391,6 @@ export class OutputTool implements AgentTool = { ssh: sshToolRenderer as ToolRenderer, task: taskToolRenderer as ToolRenderer, todo_write: todoWriteToolRenderer as ToolRenderer, - web_fetch: webFetchToolRenderer as ToolRenderer, + fetch: fetchToolRenderer as ToolRenderer, web_search: webSearchToolRenderer as ToolRenderer, write: writeToolRenderer as ToolRenderer, }; diff --git a/packages/coding-agent/src/prompts/tools/bash.md b/packages/coding-agent/src/prompts/tools/bash.md index 419b0657b..403395443 100644 --- a/packages/coding-agent/src/prompts/tools/bash.md +++ b/packages/coding-agent/src/prompts/tools/bash.md @@ -12,8 +12,8 @@ Executes a bash command in a shell session for terminal operations like git, bun Returns stdout, stderr, and exit code from command execution. -- Output truncated after 50KB; use `| head -n 50` or similar filters for large output -- Exit codes and stderr are always captured +- Output truncated after 50KB or 2000 lines (whichever comes first); use `| head -n 50` for large output +- Exit codes shown on non-zero exit; stderr captured diff --git a/packages/coding-agent/src/prompts/tools/web-fetch.md b/packages/coding-agent/src/prompts/tools/fetch.md similarity index 88% rename from packages/coding-agent/src/prompts/tools/web-fetch.md rename to packages/coding-agent/src/prompts/tools/fetch.md index 979d026a7..837aceaad 100644 --- a/packages/coding-agent/src/prompts/tools/web-fetch.md +++ b/packages/coding-agent/src/prompts/tools/fetch.md @@ -1,6 +1,6 @@ -# Web Fetch +# Fetch -Fetches and analyzes web content by retrieving a URL. +Retrieves content from a URL and returns it in a clean, readable format. - Extract information from web pages (documentation, articles, API references) diff --git a/packages/coding-agent/src/prompts/tools/grep.md b/packages/coding-agent/src/prompts/tools/grep.md index 0360933a3..97ea617b8 100644 --- a/packages/coding-agent/src/prompts/tools/grep.md +++ b/packages/coding-agent/src/prompts/tools/grep.md @@ -15,7 +15,8 @@ Results depend on `output_mode`: - `files_with_matches`: File paths only (one per line) - `count`: Match counts per file -Truncated at 100 matches by default (configurable via `limit`). +In `content` mode, truncated at 100 matches by default (configurable via `limit`). +For `files_with_matches` and `count` modes, use `head_limit` to truncate results. diff --git a/packages/coding-agent/src/prompts/tools/output.md b/packages/coding-agent/src/prompts/tools/output.md index 0d037ef5c..2f6e32c27 100644 --- a/packages/coding-agent/src/prompts/tools/output.md +++ b/packages/coding-agent/src/prompts/tools/output.md @@ -11,24 +11,15 @@ Retrieves complete output from background tasks spawned with the Task tool. - `ids`: Array of output IDs from Task results (e.g., `["ApiAudit", "DbAudit"]`) -- `format` (optional): - - `"raw"` (default): Full output with ANSI codes preserved - - `"json"`: Structured object with metadata - - `"stripped"`: Plain text with ANSI codes removed for parsing -- `query` (optional): jq-like query for JSON outputs (e.g., `.endpoints[0].file`) +- `query` (optional): jq-like query for structured outputs (e.g., `.endpoints[0].file`). Cannot combine with `offset`/`limit`. - `offset` (optional): Line number to start reading from (1-indexed) - `limit` (optional): Maximum number of lines to read -Use offset/limit for line ranges to reduce context usage on large outputs. Use `query` for structured agent outputs (agents that call `complete` with `output`). +Use `offset`/`limit` for pagination. Use `query` for structured agent outputs. -Returns the requested task output content: -- `raw`: Full output text with ANSI escape codes intact -- `json`: Structured object with metadata (status, timing, structured data from `complete`) -- `stripped`: Plain text with ANSI codes removed for easier parsing - -Output is truncated after 50KB. +Returns task output content. diff --git a/packages/coding-agent/src/prompts/tools/python.md b/packages/coding-agent/src/prompts/tools/python.md index 8ac67a5d6..c6e5a4d8b 100644 --- a/packages/coding-agent/src/prompts/tools/python.md +++ b/packages/coding-agent/src/prompts/tools/python.md @@ -58,7 +58,7 @@ All helpers auto-print results and return values for chaining. -Output streams in real time, truncated after 50KB. +Output streams in real time, truncated after 100KB. The user sees output like a Jupyter notebook—rich displays are fully rendered: - `display(JSON(data))` → interactive JSON tree diff --git a/packages/coding-agent/src/prompts/tools/read.md b/packages/coding-agent/src/prompts/tools/read.md index 69d3ec7bb..febc9ac8c 100644 --- a/packages/coding-agent/src/prompts/tools/read.md +++ b/packages/coding-agent/src/prompts/tools/read.md @@ -6,7 +6,7 @@ Reads a file from the local filesystem. - Reads up to {{DEFAULT_MAX_LINES}} lines by default - Use `offset` and `limit` for large files - Use `lines: true` to include line numbers -- Supports images (PNG, JPG), PDFs, and Jupyter notebooks +- Supports images (PNG, JPG) and PDFs - For directories, use the ls tool instead - Parallelize reads when exploring related files @@ -14,7 +14,6 @@ Reads a file from the local filesystem. - Returns file content as text - Images: returns visual content for analysis -- PDFs/notebooks: returns extracted text/cells -- Empty files: triggers a warning +- PDFs: returns extracted text - Missing files: returns closest filename matches for correction diff --git a/packages/coding-agent/src/prompts/tools/write.md b/packages/coding-agent/src/prompts/tools/write.md index 73d34017a..8cf4738f7 100644 --- a/packages/coding-agent/src/prompts/tools/write.md +++ b/packages/coding-agent/src/prompts/tools/write.md @@ -8,7 +8,7 @@ Creates or overwrites a file at the specified path. -Confirmation of file creation/write with path. Returns error if write fails (permissions, invalid path, disk full). +Confirmation of file creation/write with path. When LSP is available, content may be auto-formatted before writing and diagnostics are returned. Returns error if write fails (permissions, invalid path, disk full). diff --git a/packages/coding-agent/test/tools/index.test.ts b/packages/coding-agent/test/tools/index.test.ts index 7c68d3322..37bd2342f 100644 --- a/packages/coding-agent/test/tools/index.test.ts +++ b/packages/coding-agent/test/tools/index.test.ts @@ -48,7 +48,7 @@ describe("createTools", () => { expect(names).toContain("task"); expect(names).toContain("todo_write"); expect(names).toContain("output"); - expect(names).toContain("web_fetch"); + expect(names).toContain("fetch"); expect(names).toContain("web_search"); }); @@ -163,7 +163,7 @@ describe("createTools", () => { "read", "task", "todo_write", - "web_fetch", + "fetch", "web_search", "write", ]; diff --git a/packages/coding-agent/test/tools/schema-validation.test.ts b/packages/coding-agent/test/tools/schema-validation.test.ts index ec2de555e..160200eff 100644 --- a/packages/coding-agent/test/tools/schema-validation.test.ts +++ b/packages/coding-agent/test/tools/schema-validation.test.ts @@ -381,7 +381,7 @@ describe("tool schema validation (post-sanitization)", () => { "read", "task", "todo_write", - "web_fetch", + "fetch", "web_search", "write", ];