diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 66eccd892..000735fc1 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -23,6 +23,7 @@ - Changed `/debug` handling in interactive mode so `/debug` with arguments now executes the requested debug subcommand instead of always opening the debug selector - Changed `/debug dump-next-request` path handling to expand `~` and resolve relative paths against the current working directory - Changed the task tool's TUI block: the header now shows the task dispatch glyph (`tool.task`) while agents are in flight instead of a spinner (async spawns return immediately, so a spinner misread the call as blocking), and per-agent rows use one static dot for every state — completed rows keep the same dot and settle from accent to the plain foreground color instead of switching to a different status glyph +- Compressed the `eval`, `browser`, `read`, and `irc` tool prompts (6156 → 4932 tokens o200k, −20%): deduplicated claims across sections, tightened helper reference descriptions, trimmed redundant examples; input grammar, examples, and template conditionals (`py`/`js`/`spawns`, read display modes) unchanged ### Fixed diff --git a/packages/coding-agent/src/prompts/tools/browser.md b/packages/coding-agent/src/prompts/tools/browser.md index 7c3a3fa7d..a2c936eaa 100644 --- a/packages/coding-agent/src/prompts/tools/browser.md +++ b/packages/coding-agent/src/prompts/tools/browser.md @@ -1,40 +1,39 @@ Drives real Chromium tab; full puppeteer access via JS execution. -- For static web content (articles, docs, issues/PRs, JSON, PDFs, feeds), prefer `read` tool with URL — reader-mode text without spinning up browser. Use this tool when you need JS execution, authentication, or interactive actions. -- Three actions only: - - `open` — acquire or reuse named tab. `name` defaults `"main"`. Optional `url` navigates after tab ready. Optional `viewport` sets dimensions. Optional `dialogs: "accept" | "dismiss"` auto-handles `alert`/`confirm`/`beforeunload` so navigation/clicks don't hang; by default dialogs are unhandled and the page hangs until you wire `page.on('dialog', …)`. - - `close` — release tab by `name`, or every tab with `all: true`. For spawned-app browsers, set `kill: true` to terminate process tree (default leaves running). - - `run` — execute JS against existing tab. `code` is body of async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. Function's return value JSON-stringified into tool result; multiple `display(value)` calls accumulate text/images. -- Tabs survive across `run` calls and across in-process subagents. Open once, reuse many times. -- Browser kinds, selected by `app` field on `open`: +- Static content (articles, docs, issues/PRs, JSON, PDFs, feeds)? Use `read` with the URL. Reach for browser only for JS execution, authentication, or interactive actions. +- Three actions: + - `open` — acquire or reuse named tab (`name` defaults `"main"`). Optional `url` (navigate once ready), `viewport`, `dialogs: "accept" | "dismiss"` (auto-handle `alert`/`confirm`/`beforeunload`; unhandled dialogs hang the page until you wire `page.on('dialog', …)`). + - `close` — release tab by `name`, or every tab with `all: true`. `kill: true` also terminates spawned-app process trees (default leaves them running). + - `run` — execute JS in an existing tab. `code` is the body of an async function with `page`, `browser`, `tab`, `display`, `assert`, `wait` in scope. Return value is JSON-stringified into the result; `display(value)` calls accumulate text/images. +- Tabs survive across `run` calls and in-process subagents — open once, reuse. +- Browser kinds (`app` field on `open`): - default (no `app`) → headless Chromium with stealth patches. - - `app.path` → spawn absolute binary (Electron/CDP); a running instance with an open CDP port is reused. No stealth patches — NEVER tamper with real desktop app. + - `app.path` → spawn absolute binary (Electron/CDP); a running instance with an open CDP port is reused. No stealth patches — NEVER tamper with a real desktop app. - `app.cdp_url` → connect to existing CDP endpoint (e.g. `http://127.0.0.1:9222`). - - `app.target` (with `path`/`cdp_url`) — substring matched against url+title to pick BrowserWindow when app exposes several. -- Inside `run`, `tab` exposes high-level helpers; reach for `page` (raw puppeteer Page) when you need anything they don't cover. - - `tab.goto(url, { waitUntil? })` — clears element cache and navigates. - - `tab.observe({ includeAll?, viewportOnly? })` — accessibility snapshot. Returns `{ url, title, viewport, scroll, elements: [{ id, role, name, value, states, … }] }`. Element ids stable until next observe/goto. - - `tab.id(n)` — resolves element id from most recent observe to real `ElementHandle` you can `.click()`, `.type()`, etc. - - `tab.click(selector)` / `tab.type(selector, text)` / `tab.fill(selector, value)` / `tab.press(key, { selector? })` / `tab.scroll(dx, dy)` — selector-based actions. - - `tab.waitFor(selector)` — waits until selector attached, returns resolved `ElementHandle` for chaining (e.g. `const btn = await tab.waitFor('text/Submit'); await btn.click();`). - - `tab.drag(from, to)` — drag from one point to another. Each endpoint either selector string (drag center-to-center) or `{ x, y }` viewport-coordinate point (for canvases, sliders). - - `tab.scrollIntoView(selector)` — scroll matching element to center of viewport (use before clicking off-screen elements). - - `tab.select(selector, …values)` — set selected option(s) on ``. Paths resolve relative to cwd. - - `tab.waitForUrl(pattern, { timeout? })` — pattern substring or `RegExp`. Polls `location.href` so works for SPA pushState navigations, not just real navigations. Returns matched URL. - - `tab.waitForResponse(pattern, { timeout? })` — pattern substring, `RegExp`, or `(response) => boolean`. Returns raw puppeteer `HTTPResponse` (call `.text()` / `.json()` / `.status()` / `.headers()` on it). - - `tab.evaluate(fn, …args)` — sugar for `page.evaluate` with abort signal already wired. Use this instead of dropping to `page.evaluate` for ad-hoc DOM reads. - - `tab.screenshot({ selector?, fullPage?, save?, silent? })` — captures a screenshot and attaches it for you to view (`silent: true` skips attaching). Pass `save` (a path) only when a later step needs the file; never just to look. - - `tab.extract(format = "markdown")` — returns Readability-extracted page content as a string (`"markdown"` or `"text"`). Throws if the page yields no readable content. -- Selectors accept CSS plus puppeteer query handlers: `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`. Playwright-style `p-aria/[name="…"]`, `p-text/…` normalized. -- Default `tab.observe()` over `tab.screenshot()` for page state. Screenshot only when visual appearance matters. + - `app.target` (with `path`/`cdp_url`) — substring matched against url+title to pick a BrowserWindow. +- `tab` helpers; drop to raw puppeteer `page` for anything they don't cover: + - `tab.goto(url, { waitUntil? })` — navigate; clears element cache. + - `tab.observe({ includeAll?, viewportOnly? })` — accessibility snapshot: `{ url, title, viewport, scroll, elements: [{ id, role, name, value, states, … }] }`. Ids stable until next observe/goto. + - `tab.id(n)` — element id from last observe → `ElementHandle` (`.click()`, `.type()`, …). + - `tab.click(selector)` / `tab.type(selector, text)` / `tab.fill(selector, value)` / `tab.press(key, { selector? })` / `tab.scroll(dx, dy)`. + - `tab.waitFor(selector)` — wait until attached; returns the `ElementHandle`. + - `tab.drag(from, to)` — endpoints: selector (center-to-center) or `{ x, y }` viewport point (canvases, sliders). + - `tab.scrollIntoView(selector)` — center element in viewport; use before clicking off-screen elements. + - `tab.select(selector, …values)` — set ``; paths relative to cwd. + - `tab.waitForUrl(pattern, { timeout? })` — substring or `RegExp`; polls `location.href` (catches SPA pushState). Returns matched URL. + - `tab.waitForResponse(pattern, { timeout? })` — substring, `RegExp`, or `(response) => boolean`; returns puppeteer `HTTPResponse` (`.text()`/`.json()`/`.status()`/`.headers()`). + - `tab.evaluate(fn, …args)` — `page.evaluate` with abort signal wired; use for ad-hoc DOM reads. + - `tab.screenshot({ selector?, fullPage?, save?, silent? })` — capture and attach for viewing (`silent: true` skips). Pass `save` (a path) only when a later step needs the file. + - `tab.extract(format = "markdown")` — Readability-extracted content (`"markdown"` | `"text"`); throws when nothing readable. +- Selectors: CSS plus puppeteer handlers `aria/Sign in`, `text/Continue`, `xpath/…`, `pierce/…`; Playwright-style `p-aria/…`, `p-text/…` normalized. -- MUST call `open` before `run`. `run` does not implicitly create tab. -- NEVER screenshot just to "see what's on page" — `tab.observe()` returns structured data with element ids you can act on immediately. -- After `tab.goto()` or any navigation, prior element ids from `tab.observe()` invalidated. Re-observe before referencing them. +- MUST `open` before `run` — `run` never creates a tab. +- Default to `tab.observe()` for page state — structured data with actionable element ids. Screenshot ONLY when visual appearance matters. +- Navigation invalidates element ids — re-observe before using them. - `code` runs with full Node access. Treat as your code, not sandboxed code. @@ -46,28 +45,19 @@ Drives real Chromium tab; full puppeteer access via JS execution. # Click an observed element by id `{"action":"run","name":"docs","code":"const obs = await tab.observe(); const link = obs.elements.find(e => e.role === 'link' && e.name === 'Sign in'); assert(link, 'Sign in link missing'); await (await tab.id(link.id)).click();"}` -# Screenshot to look at the page — no save path -`{"action":"run","name":"docs","code":"await tab.screenshot();"}` - -# Keep a full-page screenshot on disk for a later step -`{"action":"run","name":"docs","code":"await tab.screenshot({ fullPage: true, save: 'screenshot.png' });"}` - # Fill and submit a form via selectors `{"action":"run","name":"docs","code":"await tab.fill('input[name=email]', 'me@example.com'); await tab.click('text/Continue');"}` +# Screenshot to look at the page — no save path +`{"action":"run","name":"docs","code":"await tab.screenshot();"}` + # Attach to an existing Electron app `{"action":"open","name":"cursor","app":{"path":"/Applications/Cursor.app/Contents/MacOS/Cursor"}}` -# Close one tab (browser stays alive if other tabs reference it) -`{"action":"close","name":"docs"}` - -# Close every tab; leave spawned apps running -`{"action":"close","all":true}` - -# Close every tab and kill spawned-app processes too +# Close every tab and kill spawned-app processes `{"action":"close","all":true,"kill":true}` -- Per call: any `display(value)` outputs (text/images) followed by JSON-stringified return value of `code` function. `run` always produces at least status line. +Per call: `display(value)` outputs (text/images), then the JSON-stringified return value of `code`. `run` always produces at least a status line. diff --git a/packages/coding-agent/src/prompts/tools/eval.md b/packages/coding-agent/src/prompts/tools/eval.md index 52f25f101..e4b959576 100644 --- a/packages/coding-agent/src/prompts/tools/eval.md +++ b/packages/coding-agent/src/prompts/tools/eval.md @@ -1,92 +1,69 @@ Run code in a persistent kernel using a list of cells. -Each call submits one or more cells. Cells run in array order. State persists within each language — across cells, tool calls, and subagents spawned with `task`: variables a parent or subagent declares are visible to the other. Lean on this: stage helpers, loaded datasets, or live clients once, then fan out `task` subagents that use them directly. No re-importing, re-fetching, or serializing across the boundary. +Cells run in array order. State persists per language — across cells, tool calls, and `task` subagents: variables either side defines are visible to the other. Stage helpers, datasets, or live clients once; subagents use them directly — no re-importing or serializing across the boundary. Cell fields: - `language` — {{#if py}}`"py"` for the IPython kernel{{/if}}{{#ifAll py js}}, {{/ifAll}}{{#if js}}`"js"` for the persistent JavaScript VM{{/if}}. -- `code` — cell body, verbatim. Newlines, quotes, and indentation are JSON-encoded; no fences, no headers. -- `title` (optional) — short label shown in the transcript (e.g. `"imports"`, `"load config"`). -- `timeout` (optional) — per-cell wall-clock budget in seconds (1-3600). Default 30. It bounds the cell's **own** work: compute, `print`/stdout, `log()`/`phase()`, and ordinary tool calls all count. The clock pauses while an `agent()`/`parallel()`/`completion()` call is in flight, so long fanouts and slow completions never need a raised `timeout`. Raise it only for heavy local work or long non-agent tool calls. -- `reset` (optional) — wipe this cell's language kernel before running.{{#ifAll py js}} Reset is per-language: a `py` cell's reset does not touch the JavaScript VM and vice versa.{{/ifAll}} +- `code` — cell body, verbatim. Newlines and quotes JSON-encoded; no fences, no headers. +- `title` (optional) — short transcript label (e.g. `"imports"`). +- `timeout` (optional) — per-cell seconds (1-3600, default 30). Bounds the cell's own work only; the clock pauses while `agent()`/`parallel()`/`completion()` calls are in flight, so fanouts never need a raise. Raise only for heavy local compute or long non-agent tool calls. +- `reset` (optional) — wipe this cell's language kernel first.{{#ifAll py js}} Per-language: a `py` reset never touches the JS VM.{{/ifAll}} -**Work incrementally:** - -- One logical step per cell (imports, define, test, use). -- Pass multiple small cells in one call. -- Define small reusable functions for individual debugging. -- Put workflow explanations in the assistant message or `title` — never inside cell code. -{{#if py}}- Python cells run inside an IPython kernel with a live event loop. Use top-level `await` directly (e.g. `await main()`); `asyncio.run(…)` raises "cannot be called from a running event loop".{{/if}} -**On failure:** errors identify the failing cell (e.g., "Cell 3 failed"). Resubmit only the fixed cell (or fixed cell + remaining cells). +Work incrementally: one logical step per cell (imports, define, test, use); pass multiple small cells per call; define small reusable functions for individual debugging. Workflow explanations go in the assistant message or `title`, never inside cell code. +{{#if py}}Python runs in IPython with a live event loop: use top-level `await` directly; `asyncio.run(…)` raises "cannot be called from a running event loop".{{/if}} +On failure, errors name the failing cell ("Cell 3 failed") — resubmit only the fixed cell (plus any remaining). -{{#ifAll py js}}Same helpers in both runtimes with the same positional argument order. Python: trailing options as keyword args. JavaScript: trailing options are a single trailing object literal, never positional — passing options positionally (or any extra positional arg) throws. JavaScript helpers are async and `await`able; Python helpers run synchronously.{{else}}{{#if py}}Helpers run synchronously. Trailing options are keyword arguments.{{/if}}{{#if js}}Helpers are async and `await`able. Trailing options are a single trailing object literal, never positional — passing options positionally (or any extra positional arg) throws.{{/if}}{{/ifAll}} +{{#ifAll py js}}Same helpers in both runtimes, same positional order. Python: helpers run synchronously; trailing options are keyword args. JavaScript: helpers are async and `await`able; trailing options are ONE trailing object literal, never positional (extra positional args throw).{{else}}{{#if py}}Helpers run synchronously. Trailing options are keyword arguments.{{/if}}{{#if js}}Helpers are async and `await`able. Trailing options are ONE trailing object literal, never positional (extra positional args throw).{{/if}}{{/ifAll}} ``` display(value) → None - Render a value in the current cell output. + Render value in cell output, shows presentable values natively (figures, images, dataframes) print(value, ...) → None - Print to the cell's text output. + Print to text output. read(path, offset?=1, limit?=None) → str - Read file contents as text. offset/limit are 1-indexed line bounds. Accepts `local://…` (resolved to the session-local root, same place `read local://…` reads). + Read file as text; offset/limit are 1-indexed lines. Accepts `local://…`. write(path, content) → str - Write content to a file (creates parent directories). Returns the resolved path. Accepts `local://…` to persist artifacts across turns / share with subagents. + Write file (creates parents); returns resolved path. `local://…` persists across turns / subagents. append(path, content) → str - Append content to a file. Returns the resolved path. Accepts `local://…`. + Append to file; returns resolved path. Accepts `local://…`. tree(path?=".", max_depth?=3, show_hidden?=False) → str - Render a directory tree. + Directory tree. diff(a, b) → str - Unified diff between two files. + Unified diff of two files. env(key?=None, value?=None) → str | None | dict - No args → full environment as dict. One arg → value of `key`. Two args → set `key=value` and return value. + No args → full env dict; one → value of `key`; two → set `key=value`, return value. output(*ids, format?="raw", query?=None, offset?=None, limit?=None) → str | dict | list[dict] - Read task/agent output by ID. Single id returns text/dict; multiple ids return a list. + Read task/agent output by id; one id → text/dict, multiple → list. tool.(args) → unknown - Invoke any session tool by name. `args` is the tool's parameter object. + Invoke any session tool; `args` is its parameter object. completion(prompt, model?="default", system?=None, schema?=None) → str | dict - Oneshot, stateless completion (no history, no tools). `model` picks a tier: "smol" (fast), "default" (this session's model), "slow" (most capable). Pass `system` for a system prompt. Pass a JSON-Schema `schema` to force structured output and get the parsed object back; otherwise returns the completion text. + Oneshot stateless completion (no history, no tools). `model` tier: "smol" (fast) | "default" (session model) | "slow" (most capable). JSON-Schema `schema` forces structured output, returns parsed object. {{#if spawns}}agent(prompt, agent_type?="task", model?=None, label?=None, schema?=None) → str | dict - Run a subagent and return its final output. Defaults to the bundled "task" agent; pass `agent_type`/`agentType` for another discovered agent. Pass a JSON-Schema `schema` to force structured output and get the parsed object back. Share background by writing a `local://` file and referencing it in the prompt. -{{#if js}} In JS, pass options as one trailing object — never positional: agent(prompt, { agentType, schema }). + Run a subagent, return its final output. `agent_type`/`agentType` picks another discovered agent; `schema` as in completion(). Share background via `local://` files referenced in the prompt. +{{#if js}} JS: options are ONE trailing object — agent(prompt, { agentType, schema }). {{/if}} {{/if}} parallel(thunks) → list - Run thunks (callables) through a bounded pool, preserving input order. The pool is as wide as a `task` tool batch, so fan out as wide as the work divides — don't pre-shrink it. Barrier: returns once all finish; a thunk that throws propagates. + Run thunks through a bounded pool (as wide as a `task` batch — don't pre-shrink), preserving input order. Barrier: returns when all finish; a throwing thunk propagates. pipeline(items, ...stages) → list - Map each item through stages left-to-right; a barrier runs between stages (every item clears stage N before stage N+1). Each stage is a one-arg callable: stage 1 gets the original item, later stages get the previous result. Same pool width as parallel(). + Map items through one-arg stages left-to-right, barrier between stages; stage 1 gets the item, later stages the previous result. Same pool width as parallel(). log(message) → None - Emit a progress line above the status tree. + Progress line above the status tree. phase(title) → None - Start a phase; the status lines that follow group under it. + Start a phase grouping subsequent status lines. budget → per-turn token budget - {{#if py}}`budget.total` (ceiling or None), `budget.spent()` (output tokens this turn), `budget.remaining()` (math.inf when no ceiling), `budget.hard` (bool).{{/if}}{{#if js}}`await budget.total()` (ceiling or null), `await budget.spent()`, `await budget.remaining()` (Infinity when no ceiling), `await budget.hard()`.{{/if}} A ceiling is set by a `+Nk` message directive (advisory) or `+Nk!`/Goal Mode (hard — `agent()` refuses to spawn past it); otherwise total is None/null and spend is still tracked across the turn (main loop + eval subagents). + {{#if py}}`budget.total` (ceiling or None), `budget.spent()`, `budget.remaining()` (math.inf when no ceiling), `budget.hard` (bool).{{/if}}{{#if js}}`await budget.total()` (ceiling or null), `await budget.spent()`, `await budget.remaining()` (Infinity when no ceiling), `await budget.hard()`.{{/if}} Ceiling comes from a `+Nk` directive (advisory) or `+Nk!`/Goal Mode (hard — `agent()` refuses to spawn past it); otherwise None/null, spend still tracked across the turn. ``` - -Cells render like a Jupyter notebook. `display(value)` renders non-presentable data as an interactive JSON tree. Presentable values (figures, images, dataframes, etc.) use their native representation. - - - -{{#if js}}- **js**: the VM exposes a selective `process` subset, Web APIs, `Buffer`, `fs/promises`, and the `Bun` global. -{{/if}} - -{{#if py}}```json { "cells": [ { "language": "py", "title": "imports", "timeout": 10, "code": "import json\nfrom pathlib import Path" }, { "language": "py", "title": "load config", "code": "data = json.loads(read('package.json'))\ndisplay(data)" } ] } -```{{/if}}{{#ifAll py js}} - -{{/ifAll}}{{#if js}}```json -{ - "cells": [ - { "language": "js", "title": "summary", "reset": true, "code": "const data = JSON.parse(await read('package.json'));\ndisplay(data);\nreturn data.name;" } - ] -} -```{{/if}} diff --git a/packages/coding-agent/src/prompts/tools/irc.md b/packages/coding-agent/src/prompts/tools/irc.md index 02afc4434..b202daed9 100644 --- a/packages/coding-agent/src/prompts/tools/irc.md +++ b/packages/coding-agent/src/prompts/tools/irc.md @@ -1,55 +1,53 @@ Sends short text messages to other agents in this process and receives theirs. -- The main agent is addressable as `Main`. Subagents reuse their task id (e.g. `AuthLoader`, or `AuthLoader-2` when the name repeats). -- `op: "list"` — every addressable peer with status (`running` | `idle` | `parked`), unread count, parent, and last activity. Use it before sending if you are not sure who exists. -- `op: "send"` — fire-and-forget delivery of `message` to `to` (a peer id, or `"all"` to broadcast to live peers). Returns per-recipient receipts immediately; it NEVER waits for the recipient to act. Receipt outcomes: `injected` (recipient was mid-turn; message folded in at their next step boundary), `woken` (idle recipient started a turn), `revived` (parked recipient was brought back and woken), `failed`. -- Messaging an `idle` or `parked` peer is how you wake it — there is no separate revive call. -- `send` with `await: true` — convenience round-trip: send, then block until the next message from that peer arrives (or the timeout passes). Invalid with `to: "all"`. -- `op: "wait"` — block until a message arrives (optionally only `from` a specific peer); consumes and returns it. A timeout is a clean "no message" result, not an error. -- `op: "inbox"` — drain pending messages without blocking (`peek: true` to leave them unread). -- `replyTo` — set it to the id of the message you are answering so the sender can correlate. -- Nobody answers on a peer's behalf — a reply normally arrives only when the recipient sends one — with one exception: `send` with `await: true` to a peer that is mid-turn and cannot reach a step boundary (async execution disabled, e.g. blocked in a synchronous task spawn) gets a side-channel auto-reply generated from that peer's context. For background on what a peer has been doing, `read` `history://` instead of interrogating them. +- Main agent is `Main`; subagents reuse their task id (`AuthLoader`, or `AuthLoader-2` when the name repeats). +- `op: "list"` — peers with status (`running` | `idle` | `parked`), unread count, parent, last activity. Use when unsure who exists. +- `op: "send"` — fire-and-forget `message` to `to` (peer id, or `"all"` to broadcast to live peers). Returns per-recipient receipts immediately; NEVER waits for the recipient to act. Outcomes: `injected` (mid-turn; folded in at next step boundary), `woken` (idle peer started a turn), `revived` (parked peer brought back and woken), `failed`. +- Messaging an `idle`/`parked` peer is how you wake it — there is no separate revive call. +- `send` + `await: true` — round-trip: send, then block until that peer's next message (or timeout). Invalid with `to: "all"`. +- `op: "wait"` — block until a message arrives (optionally only `from` one peer); consumes and returns it. Timeout = clean "no message", not an error. +- `op: "inbox"` — drain pending messages without blocking (`peek: true` leaves them unread). +- `replyTo` — id of the message you are answering, so the sender can correlate. +- Replies arrive only when the recipient sends one. Exception: `await: true` to a peer stuck mid-turn (async execution disabled, e.g. blocked in a synchronous task spawn) gets a side-channel auto-reply from its context. For background on a peer, `read` `history://` instead of interrogating it. -You SHOULD reach for `irc` proactively when continuing alone is wasteful or wrong. When in doubt, prefer messaging. -- **Unexpected state.** The task did not describe what you found — missing file, config contradicting the assignment, API or tool behaving differently than told. DM `Main` (or the spawning agent) instead of guessing. -- **Blocked by another agent.** A peer holds the file/branch/resource you need, started the change you are about to make, or owns a decision you depend on. DM that peer (or broadcast to discover who) before duplicating work. -- **Decision points outside your scope.** A genuine fork the assignment did not pre-decide (e.g. which of two viable APIs, whether to refactor adjacent code). Ask the requester rather than picking unilaterally. -- **Coordination opportunities.** A peer's in-flight work would benefit from yours, or vice-versa. +Reach for `irc` proactively when continuing alone is wasteful or wrong; when in doubt, message. +- **Unexpected state** — missing file, config contradicting the assignment, API/tool behaving differently than told. DM `Main` (or your spawner) instead of guessing. +- **Blocked by another agent** — a peer holds the file/branch/resource or decision you need, or started the change you're about to make. DM them (or broadcast to discover who) before duplicating work. +- **Decision outside your scope** — a genuine fork the assignment didn't pre-decide. Ask the requester rather than picking unilaterally. +- **Coordination** — a peer's in-flight work would benefit from yours, or vice-versa. -NEVER use `irc` for: routine progress updates, things a tool call can verify, or questions already answered by your assignment / repo / docs. +NEVER for: routine progress updates, things a tool call can verify, questions your assignment/repo/docs already answer. -These rules apply to both sending and replying. -- **Plain prose only.** NEVER send structured JSON status payloads (e.g. `{"type":"task_completed",…}`). Write a normal sentence: "Done with the auth refactor — left a TODO in `src/server/auth.ts` for the rate limiter." -- **NEVER quote the message you are replying to.** Lead with the answer; set `replyTo` instead. -- **Use IRC, not terminal tools, to learn about peers.** NEVER `grep` artifacts, read other sessions' JSONL files, or shell-poke to figure out what another agent is doing. DM them, or `read` `history://`. -- **Send, then keep working.** `send` returns immediately — only `wait` (or `await: true`) when you genuinely cannot proceed without the answer. NEVER follow up with "did you get my message?"; a `failed` receipt means the peer is unreachable — move on or report the blocker; NEVER retry in a loop. -- **Answer when a response is expected.** When an incoming message asks something, reply with `irc send` to the sender (you may finish your current step first). -- **Stay terse.** A DM is a chat message, not a memo. One question per send. Share file paths and artifacts via `local://` / `memory://` / `artifact://` URLs instead of pasting blobs. -- **Address peers by id.** Use the exact id from `op: "list"` (e.g. `AuthLoader`, `Main`). NEVER invent friendly names. -- **NEVER IRC for things a tool would answer.** If a `read`, `grep`, or build command resolves the question, do that first. +Applies to sending and replying. +- **Plain prose only.** NEVER JSON status payloads like `{"type":"task_completed",…}` — write a normal sentence. +- **NEVER quote the message you answer.** Lead with the answer; set `replyTo`. +- **Learn about peers via IRC** — NEVER grep artifacts, read other sessions' JSONL, or shell-poke. DM them, or `read` `history://`. +- **Send, then keep working.** `wait`/`await: true` only when you genuinely cannot proceed. NEVER "did you get my message?". A `failed` receipt = peer unreachable — move on; NEVER retry in a loop. +- **Answer expected questions** via `irc send` to the sender (finishing your current step first is fine). +- **Stay terse.** One question per send; share files via `local://`/`memory://`/`artifact://` URLs, never pasted blobs. +- **Address peers by exact id** from `op: "list"` (e.g. `AuthLoader`, `Main`). NEVER invent friendly names. +- **NEVER IRC what a tool answers.** A `read`, grep, or build resolves it? Do that first. -- `send`: per-recipient delivery receipts (`injected` / `woken` / `revived` / `failed`); with `await: true`, also the reply (or a timeout notice). +- `send`: per-recipient receipts; with `await: true`, also the reply (or timeout notice). - `wait`: the consumed message, or a clean timeout notice. - `inbox`: pending messages, oldest first. -- `list`: peers with status, unread count, parent, and last activity. +- `list`: peers with status, unread count, parent, last activity. # List peers `{"op": "list"}` -# Fire-and-forget DM — keep working, check inbox later -`{"op": "send", "to": "AuthLoader", "message": "Are you still touching src/server/auth.ts? I need to add a 401 path."}` +# Fire-and-forget DM — same send wakes idle/parked peers +`{"op": "send", "to": "AuthLoader", "message": "Still touching src/server/auth.ts? I need to add a 401 path."}` # Round-trip when you cannot proceed without the answer -`{"op": "send", "to": "Main", "message": "Should I prefer JWT or session cookies for the auth flow?", "await": true}` -# Wake a parked agent (same send — the bus revives it) -`{"op": "send", "to": "SchemaMigrator", "message": "The users table changed again; please re-check your migration."}` +`{"op": "send", "to": "Main", "message": "JWT or session cookies for the auth flow?", "await": true}` # Block until a specific peer answers `{"op": "wait", "from": "AuthLoader", "timeoutMs": 60000}` # Drain pending messages diff --git a/packages/coding-agent/src/prompts/tools/read.md b/packages/coding-agent/src/prompts/tools/read.md index b95a1a36c..b452f59a4 100644 --- a/packages/coding-agent/src/prompts/tools/read.md +++ b/packages/coding-agent/src/prompts/tools/read.md @@ -1,84 +1,78 @@ Read files, directories, archives, SQLite databases, images, documents, internal resources, and web URLs through a single `path` string. -- One tool for filesystem, archives, SQLite, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), internal URIs, and web URLs (reader-mode by default). - You SHOULD parallelize independent reads when exploring related files. -- You SHOULD reach for `read` — not a browser/puppeteer tool — for fetching web content. +- You SHOULD reach for `read` — not a browser/puppeteer tool — for web content; browser only when `read` cannot deliver it. ## Parameters -- `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `history://`, `memory://`, `rule://`, `local://`, `vault://`, `mcp://`, `omp://`, `issue://`, `pr://`), or URL. Append `:` for line ranges, raw mode, or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`). +- `path` — required. Local path, internal URI (`skill://`, `agent://`, `artifact://`, `history://`, `memory://`, `rule://`, `local://`, `vault://`, `mcp://`, `omp://`, `issue://`, `pr://`), or URL. Append `:` for line ranges or special modes (e.g. `src/foo.ts:50-200`, `src/foo.ts:raw`, `db.sqlite:users:42`). ## Selectors -Append `:` to `path`. The bare path falls back to the default mode. +Append `:` to `path`; bare path = default mode. -- _(none)_ — parseable code → structural summary (signatures kept, bodies elided); other files → read from the start (up to {{DEFAULT_LIMIT}} lines). -- `:50` / `:50-` — read from line 50 onward. +- _(none)_ — parseable code → structural summary; other files → from start (up to {{DEFAULT_LIMIT}} lines). +- `:50` / `:50-` — from line 50 onward. - `:50-200` — lines 50–200 inclusive. -- `:50+150` — 150 lines starting at line 50. -- `:20+1` — anchor on line 20 (single-range reads expand by ≤1 leading and ≤3 trailing context lines). -- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged). Multi-range mode returns exact bounds with no context padding. -- `:raw` — verbatim text; no anchors, no summary, no line prefixes. -- `:2-4:raw` or `:raw:2-4` — range AND verbatim; the two compose in either order. -- `:conflicts` — one-line-per-block index of every unresolved git merge conflict. +- `:50+150` — 150 lines starting at 50. +- `:20+1` — anchor line 20 (single-range reads pad ≤1 leading / ≤3 trailing context lines). +- `:5-16,960-973` — multiple ranges in one call (sorted, overlaps merged); exact bounds, no padding. +- `:raw` — verbatim; no anchors, no summary, no line prefixes. +- `:2-4:raw` / `:raw:2-4` — range AND verbatim; compose in either order. +- `:conflicts` — one line per unresolved git merge conflict block. # Files -- Reading a directory path returns a depth-limited dirent listing. +- Directory path → depth-limited dirent listing. {{#if IS_HL_MODE}} -- Reading a file with an explicit selector emits a file snapshot tag header and numbered lines: `[src/foo.ts#1A2B]` then `41:def alpha():`. Copy the `[PATH#TAG]` header for anchored edits; ops use bare line numbers. NEVER fabricate the tag. +- File with explicit selector → snapshot tag header + numbered lines: `[src/foo.ts#1A2B]` then `41:def alpha():`. Copy the `[PATH#TAG]` header for anchored edits; ops use bare line numbers. NEVER fabricate the tag. {{else}} {{#if IS_LINE_NUMBER_MODE}} -- Reading a file with an explicit selector returns lines prefixed with line numbers: `41|def alpha():`. +- File with explicit selector → lines prefixed with numbers: `41|def alpha():`. {{/if}} {{/if}} -- Parseable code without a selector returns a **structural summary**: declarations kept, large bodies collapsed to `..` (merged brace pair) or `…` (standalone). Summarized output ends with a footer demonstrating the multi-range selector you can use to recover the elided bodies, e.g.: - - `[NN lines elided; re-read needed ranges, e.g. :5-16,40-80]` - - Re-issue **only the relevant range(s)** using the multi-range selector (e.g. `:5-16,120-200`). NEVER guess what's inside `..` / `…` — those markers carry no content. NEVER re-read the whole file or use `:raw` when targeted ranges suffice. +- Parseable code without selector → **structural summary**: declarations kept, bodies collapsed to `..` (merged brace pair) or `…` (standalone). The footer shows the recovery selector: `[NN lines elided; re-read needed ranges, e.g. :5-16,40-80]`. Re-issue ONLY the ranges you need via the multi-range selector. `..`/`…` carry no content — NEVER guess what's inside; NEVER re-read the whole file or `:raw` when ranges suffice. # Documents & Notebooks -Extracts text from PDF, Word, PowerPoint, Excel, RTF, and EPUB. Notebooks (`.ipynb`) are shown as editable `# %% [type] cell:N` text; edits round-trip back to the underlying JSON preserving notebook metadata. Add `:raw` to a notebook to bypass the converter and read the JSON directly. +PDF, Word, PowerPoint, Excel, RTF, EPUB → extracted text. Notebooks (`.ipynb`) → editable `# %% [type] cell:N` text; edits round-trip to the underlying JSON preserving metadata. `:raw` bypasses the converter. # Images {{#if INSPECT_IMAGE_ENABLED}} -Reading an image path returns metadata (mime, bytes, dimensions, channels, alpha). For actual visual analysis, call `inspect_image` with the path and a question describing what to inspect. +Image path → metadata (mime, bytes, dimensions, channels, alpha). For visual analysis, call `inspect_image` with the path and a question. {{else}} -Reading an image path returns the decoded image inline (PNG, JPEG, GIF, WEBP) for direct visual analysis. +Image path → decoded image inline (PNG, JPEG, GIF, WEBP) for direct visual analysis. {{/if}} # Archives -Supports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read a member, and append a normal selector to the inner path: `archive.zip:dir/file.ts:50-60`. +`.tar`, `.tar.gz`, `.tgz`, `.zip`. `archive.ext:path/inside/archive` reads a member; inner paths take normal selectors: `archive.zip:dir/file.ts:50-60`. # SQLite For `.sqlite`, `.sqlite3`, `.db`, `.db3`: -- `file.db` — list tables with row counts +- `file.db` — tables with row counts - `file.db:table` — schema + sample rows -- `file.db:table:key` — single row by primary key -- `file.db:table?limit=50&offset=100` — paginated rows -- `file.db:table?where=status='active'&order=created:desc` — filtered rows -- `file.db?q=SELECT …` — read-only SELECT query +- `file.db:table:key` — row by primary key +- `file.db:table?limit=50&offset=100` — pagination +- `file.db:table?where=status='active'&order=created:desc` — filter/order +- `file.db?q=SELECT …` — read-only SELECT # URLs -- Default reader-mode: HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs → clean text/markdown. -- `:raw` returns untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetched output. -- Bare `host:port` URLs collide with the selector grammar — add a trailing slash before the selector: `https://example.com/:80`. +- Reader-mode by default: HTML, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs → clean text/markdown. +- `:raw` → untouched HTML; line selectors (`:50`, `:50-100`, `:50+150`) paginate the cached fetch. +- Bare `host:port` collides with the selector grammar — add a trailing slash: `https://example.com/:80`. # Internal URIs -`skill://`, `agent://`, `artifact://`, `history://`, `memory://root`, `rule://`, `local://.md`, `vault:///`, `mcp://`, `omp://.md`, `issue://`, and `pr://` resolve transparently and accept the same line selectors as filesystem paths. Use `artifact://` to recover full output that a previous bash/eval/tool result spilled or truncated. `history://` is an agent's transcript as concise markdown; bare `history://` lists agents. +All `path` URI schemes resolve transparently and take the same line selectors. `artifact://` recovers full output a previous bash/eval/tool result spilled or truncated. `history://` is an agent's transcript as concise markdown; bare `history://` lists agents. -- You MUST use `read` for every file, directory, archive, and URL inspection. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, `wget` are FORBIDDEN — any such bash call is a bug, regardless of how short or convenient it looks. -- You MUST prefer `read` over a browser/puppeteer tool for URL content; only reach for a browser when `read` cannot deliver reasonable content. -- For line ranges, append the selector to `path` (`path="src/foo.ts:50-200"`, `path="src/foo.ts:50+150"`). NEVER substitute `sed -n`, `awk NR`, or `head`/`tail` pipelines. -- Summary footer names ranges to re-read? Re-issue ONLY the ranges you need via the multi-range selector. NEVER guess what's inside `..` / `…` markers — they carry no content. +- You MUST use `read` for every file, directory, archive, and URL inspection. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, `wget` are FORBIDDEN bash calls, however short or convenient. +- Line ranges go in the selector (`path="src/foo.ts:50-200"`) — NEVER `sed -n`, `awk NR`, or `head`/`tail` pipelines. +- Summary footer names elided ranges? Re-issue ONLY those ranges. NEVER guess `..`/`…` content.