@oh-my-pi/pi-coding-agent 14.2.1 → 14.3.0

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +59 -0
  2. package/package.json +19 -19
  3. package/src/cli/args.ts +10 -1
  4. package/src/cli/shell-cli.ts +15 -3
  5. package/src/config/settings-schema.ts +60 -1
  6. package/src/debug/system-info.ts +6 -2
  7. package/src/discovery/claude.ts +58 -36
  8. package/src/discovery/opencode.ts +20 -2
  9. package/src/edit/index.ts +2 -1
  10. package/src/edit/modes/chunk.ts +132 -56
  11. package/src/edit/modes/hashline.ts +36 -11
  12. package/src/edit/renderer.ts +98 -133
  13. package/src/edit/streaming.ts +351 -0
  14. package/src/exec/bash-executor.ts +60 -5
  15. package/src/internal-urls/docs-index.generated.ts +5 -5
  16. package/src/internal-urls/pi-protocol.ts +0 -2
  17. package/src/lsp/client.ts +8 -1
  18. package/src/lsp/defaults.json +2 -1
  19. package/src/modes/acp/acp-agent.ts +76 -2
  20. package/src/modes/components/assistant-message.ts +1 -34
  21. package/src/modes/components/hook-editor.ts +1 -1
  22. package/src/modes/components/tool-execution.ts +111 -101
  23. package/src/modes/controllers/input-controller.ts +1 -1
  24. package/src/modes/interactive-mode.ts +0 -2
  25. package/src/modes/theme/mermaid-cache.ts +13 -52
  26. package/src/modes/theme/theme.ts +2 -2
  27. package/src/prompts/system/system-prompt.md +1 -1
  28. package/src/prompts/tools/browser.md +1 -0
  29. package/src/prompts/tools/chunk-edit.md +25 -22
  30. package/src/prompts/tools/gh-pr-push.md +2 -1
  31. package/src/prompts/tools/grep.md +4 -3
  32. package/src/prompts/tools/lsp.md +6 -0
  33. package/src/prompts/tools/read-chunk.md +46 -7
  34. package/src/prompts/tools/read.md +7 -4
  35. package/src/sdk.ts +8 -5
  36. package/src/session/agent-session.ts +36 -20
  37. package/src/session/session-manager.ts +228 -57
  38. package/src/session/streaming-output.ts +11 -0
  39. package/src/system-prompt.ts +7 -2
  40. package/src/task/executor.ts +1 -0
  41. package/src/tools/bash.ts +13 -0
  42. package/src/tools/gh.ts +6 -16
  43. package/src/tools/sqlite-reader.ts +116 -3
  44. package/src/web/search/providers/codex.ts +129 -6
@@ -1,13 +1,14 @@
1
- Edits files via syntax-aware chunks. Run `read(path="file.ts")` first.
1
+ Edits files via syntax-aware chunks. Use `read(path="file.ts")` to read and discover chunks before editing.
2
+ - `read` is the canonical read path for chunk source and `sel="?"` tree listings.
2
3
  - `write` rewrites the entire targeted region — best for most edits.
3
- - `replace` does surgical find-and-replace within a chunk — use when making small changes to a large chunk, or batching multiple substitutions.
4
4
  - `insert` adds content before/after a chunk.
5
+ - `delete` deletes a targeted chunk and must be explicit.
5
6
 
6
7
  Call format: `{"edits": [{"path": "file:chunk#ID~", "write": "new body"}, …]}`
7
8
 
8
9
  <rules>
9
- - **MUST** `read` first. Never invent chunk paths or IDs. Copy them from the latest `read` output or edit response.
10
- - `path` format: `file:selector` — e.g. `src/app.ts:fn_foo#ABCD~`. Append `~` for body, `^` for head, or nothing for the whole chunk. Include `#ID` for `put`/`find`+`replace`/`delete`.
10
+ - **MUST** inspect first with `read`. Never invent chunk paths or IDs. Copy them from the latest `read` output or edit response.
11
+ - `path` format: `file:selector` — e.g. `src/app.ts:fn_foo#ABCD~`. Append `~` for body, `^` for head, or nothing for the whole chunk. Include `#ID` for `write`/`delete`.
11
12
  - If the exact chunk path is unclear, run `read(path="file", sel="?")` and copy a selector from that listing.
12
13
  {{#if chunkAutoIndent}}
13
14
  - Use `\t` for indentation in `content`. Write content at indent-level 0 — the tool re-indents it to match the chunk's position in the file. For example, to replace `~` of a method, write the body starting at column 0:
@@ -16,6 +17,8 @@ Call format: `{"edits": [{"path": "file:chunk#ID~", "write": "new body"}, …]}`
16
17
  ```
17
18
  The tool adds the correct base indent automatically. Never manually pad with the chunk's own indentation.
18
19
  Multiple sibling body lines at the same level all start at column 0: `"print(a)\nprint(b)\nprint(c)\n"`. Only use `\t` when nesting deeper (e.g. `"if cond:\n\tinner\nouter\n"`).
20
+ Before applying the target's base indent, the tool strips any common leading whitespace shared by all non-empty `write` lines as a safety net. Do not rely on that cleanup for mixed indentation; write `~` bodies at column 0 and use one `\t` per relative nesting level.
21
+ Multi-line replacements use the same relative-indentation model: the replacement text is dedented, then re-indented to the matched source line. Do not include the chunk's base indentation in replacement text.
19
22
  **Common mistake** when replacing `~` of a function body: do NOT include the function's own indentation.
20
23
  Wrong: `"if b == 0:\n\t\treturn None\n\treturn a / b\n"` — adds the function's base `\t` to every line.
21
24
  Correct: `"if b == 0:\n\treturn None\nreturn a / b\n"` — `if` and `return a / b` at column 0, only `return None` gets `\t` for nesting.
@@ -26,10 +29,15 @@ Call format: `{"edits": [{"path": "file:chunk#ID~", "write": "new body"}, …]}`
26
29
  content: "if (x) {\n return true;\n}"
27
30
  ```
28
31
  The tool adds the correct base indent automatically, then preserves the tabs/spaces you used inside the snippet. Never manually pad with the chunk's own indentation.
32
+ Before applying the target's base indent, the tool strips any common leading whitespace shared by all non-empty `write` lines as a safety net. Do not rely on that cleanup for mixed indentation; write `~` bodies at column 0.
33
+ Multi-line replacements use the same relative-indentation model: the replacement text is dedented, then re-indented to the matched source line. Do not include the chunk's base indentation in replacement text.
29
34
  {{/if}}
30
- - Region suffixes only apply to container chunks (classes, functions, impl blocks, sections). On leaf chunks (enum variants, fields, single statements, and compound statements like `if`/`for`/`while`/`match`/`try`), `~` and `^` silently fall back to whole-chunk replacement — prefer the unsuffixed form and always supply the complete replacement (condition + body, not just the body) to avoid dropping structural parts.
31
- - `put`, `find`+`replace`, and `delete` require the current ID. `prepend`/`append` do not.
35
+ - Region suffixes only apply to chunks with a real head/body boundary (classes, functions, impl blocks, and similar containers). On code leaf chunks (enum variants, fields, single statements, and compound statements like `if`/`for`/`while`/`match`/`try`), `~` and `^` are rejected. Use the unsuffixed selector and supply the complete replacement content, or edit the parent container's `~` body.
36
+ - Unsuffixed `write` on a leaf chunk uses your content verbatim after normal replacement; it is not a body-region rewrite. Include the exact indentation and punctuation the leaf needs in the file.
37
+ - `^` head writes and `~` body writes use the same base-indent model: write content at column 0 relative to the target region, and the tool applies the chunk's file indentation.
38
+ - `write` and `delete` require the current ID. `prepend`/`append` do not.
32
39
  - **IDs change after every edit.** The edit response always carries the new IDs — use those for the next call or run `read(path="file", sel="?")` to refresh. Never reuse an ID from before the latest edit.
40
+ - Same-file edit batches are transactional: if any operation in that file fails, no changes from that file's batch are saved. Multi-file edit calls run per file, so a later file error does not roll back earlier files that already succeeded.
33
41
  </rules>
34
42
 
35
43
  <critical>
@@ -42,24 +50,25 @@ You **MUST** use the narrowest region that covers your change. Putting without a
42
50
 
43
51
  <regions>
44
52
  In `read` output, lines marked `^` between the line number and `|` are **head** lines (doc comments, attributes/decorators, signature). Lines without `^` are **body** lines. Use this to decide which region to target:
45
- - `fn_foo#ID~` — **body only (the default choice for most edits).** Head lines (`^`) are preserved automatically — doc comments, attributes, and signature stay untouched. On leaf chunks, falls back to whole chunk.
53
+ - `fn_foo#ID~` — **body only (the default choice for most edits).** Head lines (`^`) are preserved automatically — doc comments, attributes, and signature stay untouched. On code leaf chunks, this is rejected because there is no safe body boundary.
46
54
  - `fn_foo#ID^` — head only (decorators, attributes, doc comments, signature, opening delimiter). Body stays untouched.
47
55
  - `fn_foo#ID` — entire chunk including leading trivia. **You must include doc comments and attributes in `content`; omitting them deletes them.**
48
- - `chunk~` + `append`/`prepend` inserts *inside* the container. `chunk` + `append`/`prepend` inserts *outside*.
56
+ - `chunk~` + `append`/`prepend` inserts *inside* the container. `chunk` + `append`/`prepend` inserts *outside*. Appending to a container without `~` emits a warning because it lands after the closing delimiter, not before it.
49
57
 
50
- **Note on leading trivia:** whether a decorator/doc comment belongs to `^` depends on the parser. In Rust and Python, attributes and decorators are attached to the function chunk, so `^` covers them. In TypeScript/JavaScript, a `@decorator` + `/** jsdoc */` block immediately above a method often surfaces as a **separate sibling chunk** (shown as `chunk#ID` in the `?` listing) rather than as part of the function's `^`. If you need to rewrite a decorator, check the `?` listing for a sibling `chunk#ID` directly above your target.
58
+ **Note on leading trivia:** whether a decorator/doc comment belongs to `^` depends on the parser. In Rust and Python, attributes and decorators are attached to the function chunk, so `^` covers them. In TypeScript/JavaScript, a `@decorator` + `/** jsdoc */` block immediately above a method often surfaces as a **separate sibling chunk** (shown as `chunk#ID` in the `?` listing) rather than as part of the function's `^`. JSDoc directly above a plain function is more likely to be absorbed into that function's `^`. If you need to rewrite a decorated member, run `read(path="file", sel="?")` and check for a sibling `chunk#ID` directly above your target.
51
59
 
52
- **Note on non-code formats:** for prose and data formats (markdown, YAML, JSON, fenced code blocks, frontmatter), `^` and `~` fall back to the whole chunk. Always replace the entire chunk and include any delimiter syntax (fence backticks, `---` frontmatter markers, list markers) in your `content` omitting them deletes them. For markdown sections (`sect_*`), always use unsuffixed whole-chunk replace — `^` and `~` on section containers also fall back to whole-chunk replace. When editing fenced code blocks in markdown, use the exact whitespace from the file (read with `raw` first) — the tool preserves literal indentation inside fenced blocks, but any content you supply is written verbatim. To insert content after a markdown section heading, use `after` on the heading chunk (`sect_*.chunk` or `sect_*.chunk_1`) not `before`/`prepend` on the section itself, which lands physically before the heading and gets absorbed by the preceding section on reparse.
60
+ **Python notes:** Python docstrings are body lines, not head lines. A `~` body write on a function that has a docstring deletes the docstring unless you include the docstring in `content`. Python enum members and nested functions/closures are often opaque inside their parent chunk and may not appear as addressable child chunks; rewrite the parent container body. Python decorated class/function `^` writes and Python `^` deletes are rejected because indentation-sensitive bodies can become attached to the wrong block while still parsing.
61
+
62
+ **Note on non-code formats:** for prose and data formats (markdown, YAML, JSON, frontmatter), unsupported `^` and `~` suffixes warn and fall back to whole-chunk editing. Always replace the entire chunk and include any delimiter syntax (fence backticks, `---` frontmatter markers, list markers, table rows, headings) in your `content` — omitting them deletes them. For markdown sections (`sect_*`), prefer unsuffixed whole-chunk replace because `^`/`~` on prose sections can replace the heading and child content too; if you only need the heading, target the heading child chunk shown in `sel="?"`. Fenced code blocks with a declared language are parsed again and can expose inner chunks such as `code_py#ID.fn_gre#ID`; target those inner chunks when available. Markdown root writes preserve fenced code indentation verbatim. Recognized pipe tables expose `row_N` children for row-level edits; table cells and list items are not independently addressable, so rewrite the whole list/table chunk for those structural changes. Appending a table-row-shaped string (`| value |`) to a table chunk inserts it before the trailing blank-line separator so it remains part of the table. Otherwise read with `raw` first and preserve the exact whitespace inside fences. To insert content after a markdown section heading, use `after` on the heading chunk (`sect_*.chunk` or `sect_*.chunk_1`) — not `before`/`prepend` on the section itself, which lands physically before the heading and gets absorbed by the preceding section on reparse.
53
63
  </regions>
54
64
 
55
65
  <ops>
56
- Each edit entry has `path` (`file:selector`) plus **exactly one** operation field — `write`, `replace`, or `insert`. Never set more than one on the same entry.
66
+ Each edit entry has `path` (`file:selector`) plus **exactly one** operation field — `write`, `insert`, or `delete`. Never set more than one on the same entry. `write:null`, `write:""`, and bare `{path}` entries are rejected; they do not delete.
57
67
 
58
68
  |fields|path (selector part)|effect|
59
69
  |---|---|---|
60
70
  |`write: "content"`|`file:chunk#ID`, `file:chunk#ID~`, or `file:chunk#ID^`|write complete new content to the region|
61
- |`write: null`|`file:chunk#ID`|delete the chunk|
62
- |`replace: {old, new}`|`file:chunk#ID`|find a literal substring in the chunk and replace it|
71
+ |`delete: true`|`file:chunk#ID`|delete the chunk explicitly|
63
72
  |`insert: {loc, body}`|`file:chunk` or `file:chunk~`|insert before/after the chunk (`loc`: `"prepend"` or `"append"`)|
64
73
  </ops>
65
74
 
@@ -185,12 +194,6 @@ Result — the head (all `^` lines + opening brace) changes, body untouched:
185
194
  }
186
195
  ```
187
196
 
188
- **Find and replace** (surgical edit within a chunk):
189
- ```
190
- { "path": "counter.rs:impl_Counte.fn_increm#MNHV", "replace": { "old": "self.value += 1;", "new": "self.value = (self.value + 1).min(self.max);" } }
191
- ```
192
- Result — only the matched substring changes, everything else is preserved.
193
-
194
197
  **Insert before a chunk** (`prepend`):
195
198
  ```
196
199
  { "path": "counter.rs:impl_Counte.fn_get", "insert": { "loc": "prepend", "body": "/// Resets the counter to zero.\npub fn reset(&mut self) {\n\tself.value = 0;\n}\n\n" } }
@@ -258,7 +261,7 @@ Result — a new method is added at the end of the impl body, before the closing
258
261
 
259
262
  **Delete a chunk**:
260
263
  ```
261
- { "path": "counter.rs:impl_Counte.fn_decrem#TTWB", "write": null }
264
+ { "path": "counter.rs:impl_Counte.fn_decrem#TTWB", "delete": true }
262
265
  ```
263
266
  Result — the method (including its doc comment and signature) is removed.
264
267
  - Indentation rules (important):
@@ -268,12 +271,12 @@ Result — the method (including its doc comment and signature) is removed.
268
271
  - Match the file's real indentation characters in your snippet. The tool preserves your literal tabs/spaces after adding the target region's base indent.
269
272
  {{/if}}
270
273
  - Do NOT include the chunk's base indentation — only indent relative to the region's opening level.
274
+ - For `write`, the tool strips common leading whitespace shared by all non-empty lines, then adds the target region's base indent. If lines have mixed relative indentation, write them at column 0 so the common-margin cleanup cannot change the structure.
271
275
  - For `~` of a function: write at column 0, and use `\t` for *relative* nesting. Flat body: `"return x;\n"`. Multiple sibling lines: `"print(a)\nprint(b)\nprint(c)\n"` — all at column 0, the tool adds the function's base indent. Nested body: `"if (cond) {\n\treturn x;\n}\n"` — the `if` is at column 0, the `return` is one tab in. Python example — to replace `~` of `def divide(a, b):`, write: `"if b == 0:\n\treturn None\nreturn a / b\n"` — the `if` and `return a / b` are at column 0, `return None` is one `\t` in.
272
- - For `^`: write at the chunk's own depth. A class member's head uses `"/// doc\n#[attr]\npub fn start() {"`.
276
+ - For `^`: write at column 0 relative to the head region, just like `~`. A class member's head uses `"/// doc\n#[attr]\npub fn start() {"` — do not include the class/member base indentation.
273
277
  {{#if chunkAutoIndent}}
274
278
  - For a top-level item: start at zero indent. Write `"fn foo() {\n\treturn 1;\n}\n"`.
275
279
  {{else}}
276
280
  - For a top-level item: start at zero indent. Write `"fn foo() {\n return 1;\n}\n"`.
277
281
  {{/if}}
278
- - The tool strips common leading indentation from your content as a safety net, so accidental over-indentation is corrected.
279
282
  </examples>
@@ -2,7 +2,8 @@ Pushes a checked-out pull request branch back to its source branch through local
2
2
 
3
3
  <instruction>
4
4
  - Defaults to the current checked-out git branch
5
- - Uses branch metadata recorded by `gh_pr_checkout` to push back to the contributor fork and PR head branch
5
+ - Requires branch metadata recorded by `gh_pr_checkout`; fail instead of pushing if the branch was not checked out with `gh_pr_checkout`
6
+ - Pushes back to the contributor fork and PR head branch recorded in that metadata
6
7
  - Use `forceWithLease` only when rewriting the branch intentionally
7
8
  </instruction>
8
9
 
@@ -21,7 +21,8 @@ Searches files using powerful regex matching.
21
21
  </output>
22
22
 
23
23
  <critical>
24
- - You **MUST** use Grep when searching for content.
25
- - You **MUST NOT** invoke `grep` or `rg` via Bash.
26
- - If the search is open-ended, requiring multiple rounds, you **MUST** use Task tool with explore subagent instead.
24
+ - You **MUST** use the built-in Grep tool for any content search. Do **NOT** shell out to `grep`, `rg`, `ripgrep`, `ag`, `ack`, `git grep`, `awk`, `sed`-for-search, or any other CLI search via Bash — even for a single match, even "just to check quickly", even piped through other commands.
25
+ - Bash `grep`/`rg` returns raw text without chunk paths, loses `.gitignore` semantics, bypasses result limits, and wastes tokens. The Grep tool is faster, structured, and already wired into the workspace — there is no scenario where Bash search is preferable.
26
+ - If you catch yourself typing `grep`, `rg`, or `| grep` in a Bash command, stop and re-issue the search through the Grep tool instead.
27
+ - If the search is open-ended, requiring multiple rounds, you **MUST** use the Task tool with the explore subagent instead of chaining Grep calls yourself.
27
28
  </critical>
@@ -31,3 +31,9 @@ Interacts with Language Server Protocol servers for code intelligence.
31
31
  - Glob expansion samples up to 20 files per request; use `file: "*"` for broader coverage
32
32
  - When `symbol` is provided for position-based actions, missing symbols or out-of-bounds `occurrence` values return an explicit error instead of silently falling back
33
33
  </caution>
34
+
35
+ <critical>
36
+ - You **MUST** use `lsp` for symbol-aware operations (rename, find references, go to definition/implementation, code actions) whenever a language server is available — it is safer and more accurate than text-based alternatives.
37
+ - You **MUST NOT** perform cross-file renames with `ast_edit`, `sed`, `rsed`, or manual edits when `lsp` `rename` can do it. Text-based renames miss shadowing, re-exports, and usages in other files.
38
+ - Prefer `lsp` `code_actions` for imports, quick-fixes, and refactors the language server already knows how to apply.
39
+ </critical>
@@ -1,23 +1,54 @@
1
- Reads files using syntax-aware chunks.
1
+ Reads files using syntax-aware chunks. Also inspects directories, archives, SQLite databases, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), **and URLs**.
2
2
 
3
3
  <instruction>
4
- - `path` file path or URL; may include `:selector` suffix
5
- - `sel` — optional selector: `class_Foo`, `class_Foo.fn_bar#ABCD~`, `?`, `L50`, `L50-L120`, or `raw`
4
+ The chunk-aware `read` variant returns AST-scoped chunks with current checksum IDs for structural editing, and otherwise behaves like `open` for non-code content.
5
+
6
+ - You **MUST** parallelize calls when exploring related files
7
+ - For URLs, `read` fetches the page and returns clean extracted text/markdown by default (reader-mode). It handles HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs, etc. You **SHOULD** reach for `read` — not a browser/puppeteer tool — for fetching and inspecting web content.
8
+
9
+ ## Parameters
10
+ - `path` — file path or URL; may include `:selector` suffix (required)
11
+ - `sel` — optional selector for chunks, line ranges, listing, or raw mode
6
12
  - `timeout` — seconds, for URLs only
7
13
 
14
+ ## Selectors
15
+
16
+ |`sel` value|Behavior|
17
+ |---|---|
18
+ |*(omitted)*|Read full file as chunks (up to {{DEFAULT_LIMIT}} lines)|
19
+ |`class_Foo`|Read a specific chunk|
20
+ |`class_Foo.fn_bar#ABCD~`|Read a chunk region (body `~` / head `^`) by ID|
21
+ |`?`|List all chunk paths with IDs|
22
+ |`L50`|Read from line 50 onward (shorthand for L50 to EOF)|
23
+ |`L50-L120`|Read lines 50 through 120|
24
+ |`L20-L20`|Read exactly one line|
25
+ |`raw`|Raw content without transformations (for URLs: untouched HTML)|
26
+
27
+ Max {{DEFAULT_MAX_LINES}} lines per call.
28
+
29
+ # Chunks
8
30
  Each anchor `@full.chunk.path#CCCC` (with `-` prefixes for nesting depth) in the output identifies a chunk. Use `full.chunk.path#CCCC` as-is to read truncated chunks.
9
- If you need a canonical target list, run `read(path="file", sel="?")`. That listing shows chunk paths with IDs.
31
+ If you need a canonical target list, run `read(path="file", sel="?")`. That listing shows chunk paths with IDs and is the safest structural discovery mode. Summary lines in this listing are orientation hints; follow a selector with `read(path="file", sel="chunk#ID")` or use `raw` when you need exact source.
10
32
  Line numbers in the gutter are absolute file line numbers.
11
33
 
12
- `L20` (single line, no explicit end) is shorthand for `L20` to end-of-file. Use `L20-L20` for a one-line window.
13
-
14
34
  {{#if chunkAutoIndent}}
15
35
  Chunk reads normalize leading indentation so copied content round-trips cleanly into chunk edits.
16
36
  {{else}}
17
37
  Chunk reads preserve literal leading tabs/spaces from the file. When editing, keep the same whitespace characters you see here.
18
38
  {{/if}}
39
+ `raw` shows the file's literal whitespace. Structured chunk views may normalize or display indentation for edit round-tripping, so use `raw` when exact tabs/spaces matter, especially inside markdown fenced code blocks.
40
+
41
+ IDs change after every edit. Use the new IDs from the edit response or refresh with `sel="?"` before the next `write`/`delete`. `insert` selectors may omit IDs, but still prefer fresh paths after structural edits.
42
+
43
+ Parser boundaries vary by language: TypeScript/JavaScript decorators and JSDoc above decorated methods may appear as sibling `chunk#ID` entries, Python decorators are part of the function/class head, Python docstrings are body lines, and Python enum members or nested closures may remain opaque inside their parent chunk. Decorated Python `^` writes and Python `^` deletes are rejected for safety.
44
+ Markdown sections, lists, and tables are structural chunks. Recognized pipe tables expose `row_N` children for row-level edits; list items and table cells are not independently addressable. Fenced code blocks with a declared language are parsed again when possible, so functions inside a markdown fence can appear as addressable nested chunks.
19
45
 
20
46
  Chunk trees: JS, TS, TSX, Python, Rust, Go. Others use blank-line fallback.
47
+ # Inspection
48
+ Extracts text from PDF, Word, PowerPoint, Excel, RTF, EPUB, and Jupyter notebook files. Can inspect images.
49
+
50
+ # Directories & Archives
51
+ Directories and archive roots return a list of entries. Supports `.tar`, `.tar.gz`, `.tgz`, `.zip`. Use `archive.ext:path/inside/archive` to read contents.
21
52
 
22
53
  # SQLite Databases
23
54
  When used against a SQLite database (`.sqlite`, `.sqlite3`, `.db`, `.db3`), returns structured database content.
@@ -27,9 +58,17 @@ When used against a SQLite database (`.sqlite`, `.sqlite3`, `.db`, `.db3`), retu
27
58
  - `file.db:table?limit=50&offset=100` — paginated rows
28
59
  - `file.db:table?where=status='active'&order=created:desc` — filtered rows
29
60
  - `file.db?q=SELECT …` — read-only SELECT query
61
+
62
+ # URLs
63
+ Extracts content from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom feeds, JSON endpoints, PDFs at URLs, and similar text-based resources. Returns clean reader-mode text/markdown — no browser required. Use `sel="raw"` for untouched HTML; `timeout` to override the default request timeout. You **SHOULD** prefer `read` over a browser/puppeteer tool for fetching URL content; only use a browser when the page requires JS execution, authentication, or interactive actions (clicks, forms, scrolling).
30
64
  </instruction>
31
65
 
32
66
  <critical>
33
- - **MUST** `read` before editing — never invent chunk names or IDs.
67
+ - You **MUST** `read` before editing — never invent chunk names or IDs.
34
68
  - Chunk names are truncated (e.g., `handleRequest` becomes `fn_handleRequ`). Always copy chunk paths from `read` or `?` output — never construct them from source identifiers.
69
+ - You **MUST** use `read` (never bash `cat`/`head`/`tail`/`less`/`more`/`ls`/`tar`/`unzip`/`curl`/`wget`) for all file, directory, archive, and URL reads.
70
+ - You **MUST NOT** reach for a browser/puppeteer tool to fetch static web content — `read` handles HTML, PDFs, JSON, feeds, and docs directly. Reserve browser tools for JS-heavy pages or interactive flows.
71
+ - You **MUST** always include the `path` parameter; never call with `{}`.
72
+ - For specific line ranges, use `sel`: `read(path="file", sel="L50-L150")` — not `cat -n file | sed`.
73
+ - You **MAY** use `sel` with URL reads; the tool paginates cached fetched output.
35
74
  </critical>
@@ -1,8 +1,9 @@
1
1
  Reads the content at the specified path or URL.
2
2
 
3
3
  <instruction>
4
- The `read` tool is multi-purpose — inspects files, directories, archives, SQLite databases, and URLs.
4
+ The `read` tool is multi-purpose and more capable than it looks — inspects files, directories, archives, SQLite databases, images, documents (PDF/DOCX/PPTX/XLSX/RTF/EPUB/ipynb), **and URLs**.
5
5
  - You **MUST** parallelize reads when exploring related files
6
+ - For URLs, `read` fetches the page and returns clean extracted text/markdown by default (reader-mode). It handles HTML pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom, JSON endpoints, PDFs, etc. You **SHOULD** reach for `read` — not a browser/puppeteer tool — for fetching and inspecting web content.
6
7
 
7
8
  ## Parameters
8
9
  - `path` — file path or URL (required)
@@ -14,8 +15,9 @@ The `read` tool is multi-purpose — inspects files, directories, archives, SQLi
14
15
  |`sel` value|Behavior|
15
16
  |---|---|
16
17
  |*(omitted)*|Read full file (up to {{DEFAULT_LIMIT}} lines)|
17
- |`L50`|Read from line 50 onward|
18
+ |`L50`|Read from line 50 onward (shorthand for L50 to EOF)|
18
19
  |`L50-L120`|Read lines 50 through 120|
20
+ |`L20-L20`|Read exactly one line|
19
21
  |`raw`|Raw content without transformations (for URLs: untouched HTML)|
20
22
 
21
23
  Max {{DEFAULT_MAX_LINES}} lines per call.
@@ -45,11 +47,12 @@ For `.sqlite`, `.sqlite3`, `.db`, `.db3`:
45
47
  - `file.db?q=SELECT …` — read-only SELECT query
46
48
 
47
49
  # URLs
48
- Extracts content from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom feeds, JSON endpoints, and similar text-based resources. Use `sel="raw"` for untouched HTML; `timeout` to override the default request timeout.
50
+ Extracts content from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, Reddit, NPM, arXiv, RSS/Atom feeds, JSON endpoints, PDFs at URLs, and similar text-based resources. Returns clean reader-mode text/markdown — no browser required. Use `sel="raw"` for untouched HTML; `timeout` to override the default request timeout. You **SHOULD** prefer `read` over a browser/puppeteer tool for fetching URL content; only use a browser when the page requires JS execution, authentication, or interactive actions (clicks, forms, scrolling).
49
51
  </instruction>
50
52
 
51
53
  <critical>
52
- - You **MUST** use `read` (never bash `cat`/`head`/`tail`/`less`/`more`/`ls`/`tar`/`unzip`) for all file, directory, and archive reads.
54
+ - You **MUST** use `read` (never bash `cat`/`head`/`tail`/`less`/`more`/`ls`/`tar`/`unzip`/`curl`/`wget`) for all file, directory, archive, and URL reads.
55
+ - You **MUST NOT** reach for a browser/puppeteer tool to fetch static web content — `read` handles HTML, PDFs, JSON, feeds, and docs directly. Reserve browser tools for JS-heavy pages or interactive flows.
53
56
  - You **MUST** always include the `path` parameter; never call with `{}`.
54
57
  - For specific line ranges, use `sel`: `read(path="file", sel="L50-L150")` — not `cat -n file | sed`.
55
58
  - You **MAY** use `sel` with URL reads; the tool paginates cached fetched output.
package/src/sdk.ts CHANGED
@@ -194,6 +194,8 @@ export interface CreateAgentSessionOptions {
194
194
 
195
195
  /** Enable MCP server discovery from .mcp.json files. Default: true */
196
196
  enableMCP?: boolean;
197
+ /** Existing MCP manager to reuse (skips discovery, propagates to toolSession). */
198
+ mcpManager?: MCPManager;
197
199
 
198
200
  /** Enable LSP integration (tool, formatting, diagnostics, warmup). Default: true */
199
201
  enableLsp?: boolean;
@@ -1005,10 +1007,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1005
1007
  const builtinTools = await logger.time("createAllTools", createTools, toolSession, options.toolNames);
1006
1008
 
1007
1009
  // Discover MCP tools from .mcp.json files
1008
- let mcpManager: MCPManager | undefined;
1010
+ let mcpManager: MCPManager | undefined = options.mcpManager;
1009
1011
  const enableMCP = options.enableMCP ?? true;
1010
1012
  const customTools: CustomTool[] = [];
1011
- if (enableMCP) {
1013
+ if (enableMCP && !mcpManager) {
1012
1014
  const mcpResult = await logger.time("discoverAndLoadMCPTools", discoverAndLoadMCPTools, cwd, {
1013
1015
  onConnecting: serverNames => {
1014
1016
  if (options.hasUI && serverNames.length > 0) {
@@ -1024,7 +1026,6 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1024
1026
  authStorage,
1025
1027
  });
1026
1028
  mcpManager = mcpResult.manager;
1027
- toolSession.mcpManager = mcpManager;
1028
1029
 
1029
1030
  if (settings.get("mcp.notifications")) {
1030
1031
  mcpManager.setNotificationsEnabled(true);
@@ -1044,6 +1045,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1044
1045
  customTools.push(...mcpResult.tools.map(loaded => loaded.tool));
1045
1046
  }
1046
1047
  }
1048
+ toolSession.mcpManager = mcpManager;
1047
1049
 
1048
1050
  // Add Gemini image tools if GEMINI_API_KEY (or GOOGLE_API_KEY) is available
1049
1051
  const geminiImageTools = await logger.time("getGeminiImageTools", getGeminiImageTools);
@@ -1663,8 +1665,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1663
1665
  }),
1664
1666
  );
1665
1667
 
1666
- // Wire MCP manager callbacks to session for reactive tool updates
1667
- if (mcpManager) {
1668
+ // Wire MCP manager callbacks to session for reactive tool updates.
1669
+ // Skip when reusing a parent's manager — the parent owns the callbacks.
1670
+ if (mcpManager && !options.mcpManager) {
1668
1671
  mcpManager.setOnToolsChanged(tools => {
1669
1672
  void session.refreshMCPTools(tools);
1670
1673
  });
@@ -1179,6 +1179,29 @@ export class AgentSession {
1179
1179
  );
1180
1180
  }
1181
1181
 
1182
+ #scheduleAutoContinuePrompt(generation: number): void {
1183
+ const continuePrompt = async () => {
1184
+ await this.#promptWithMessage(
1185
+ {
1186
+ role: "developer",
1187
+ content: [{ type: "text", text: "Continue if you have next steps." }],
1188
+ attribution: "agent",
1189
+ timestamp: Date.now(),
1190
+ },
1191
+ "Continue if you have next steps.",
1192
+ { skipPostPromptRecoveryWait: true },
1193
+ );
1194
+ };
1195
+ this.#schedulePostPromptTask(
1196
+ async signal => {
1197
+ await Promise.resolve();
1198
+ if (signal.aborted) return;
1199
+ await continuePrompt();
1200
+ },
1201
+ { generation },
1202
+ );
1203
+ }
1204
+
1182
1205
  #cancelPostPromptTasks(): void {
1183
1206
  this.#postPromptTasksAbortController.abort();
1184
1207
  this.#postPromptTasksAbortController = new AbortController();
@@ -4813,6 +4836,9 @@ export class AgentSession {
4813
4836
  aborted: false,
4814
4837
  willRetry: false,
4815
4838
  });
4839
+ if (!autoCompactionSignal.aborted && reason !== "idle" && compactionSettings.autoContinue !== false) {
4840
+ this.#scheduleAutoContinuePrompt(generation);
4841
+ }
4816
4842
  return;
4817
4843
  }
4818
4844
  }
@@ -5064,26 +5090,7 @@ export class AgentSession {
5064
5090
  await this.#emitSessionEvent({ type: "auto_compaction_end", action, result, aborted: false, willRetry });
5065
5091
 
5066
5092
  if (!willRetry && reason !== "idle" && compactionSettings.autoContinue !== false) {
5067
- const continuePrompt = async () => {
5068
- await this.#promptWithMessage(
5069
- {
5070
- role: "developer",
5071
- content: [{ type: "text", text: "Continue if you have next steps." }],
5072
- attribution: "agent",
5073
- timestamp: Date.now(),
5074
- },
5075
- "Continue if you have next steps.",
5076
- { skipPostPromptRecoveryWait: true },
5077
- );
5078
- };
5079
- this.#schedulePostPromptTask(
5080
- async signal => {
5081
- await Promise.resolve();
5082
- if (signal.aborted) return;
5083
- await continuePrompt();
5084
- },
5085
- { generation },
5086
- );
5093
+ this.#scheduleAutoContinuePrompt(generation);
5087
5094
  }
5088
5095
 
5089
5096
  if (willRetry) {
@@ -5604,6 +5611,14 @@ export class AgentSession {
5604
5611
  // Bash Execution
5605
5612
  // =========================================================================
5606
5613
 
5614
+ async #saveBashOriginalArtifact(originalText: string): Promise<string | undefined> {
5615
+ try {
5616
+ return await this.sessionManager.saveArtifact(originalText, "bash-original");
5617
+ } catch {
5618
+ return undefined;
5619
+ }
5620
+ }
5621
+
5607
5622
  /**
5608
5623
  * Execute a bash command.
5609
5624
  * Adds result to agent context and session.
@@ -5640,6 +5655,7 @@ export class AgentSession {
5640
5655
  signal: this.#bashAbortController.signal,
5641
5656
  sessionKey: this.sessionId,
5642
5657
  timeout: clampTimeout("bash") * 1000,
5658
+ onMinimizedSave: originalText => this.#saveBashOriginalArtifact(originalText),
5643
5659
  });
5644
5660
 
5645
5661
  this.recordBashResult(command, result, options);