@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.
- package/CHANGELOG.md +59 -0
- package/package.json +19 -19
- package/src/cli/args.ts +10 -1
- package/src/cli/shell-cli.ts +15 -3
- package/src/config/settings-schema.ts +60 -1
- package/src/debug/system-info.ts +6 -2
- package/src/discovery/claude.ts +58 -36
- package/src/discovery/opencode.ts +20 -2
- package/src/edit/index.ts +2 -1
- package/src/edit/modes/chunk.ts +132 -56
- package/src/edit/modes/hashline.ts +36 -11
- package/src/edit/renderer.ts +98 -133
- package/src/edit/streaming.ts +351 -0
- package/src/exec/bash-executor.ts +60 -5
- package/src/internal-urls/docs-index.generated.ts +5 -5
- package/src/internal-urls/pi-protocol.ts +0 -2
- package/src/lsp/client.ts +8 -1
- package/src/lsp/defaults.json +2 -1
- package/src/modes/acp/acp-agent.ts +76 -2
- package/src/modes/components/assistant-message.ts +1 -34
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/tool-execution.ts +111 -101
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +0 -2
- package/src/modes/theme/mermaid-cache.ts +13 -52
- package/src/modes/theme/theme.ts +2 -2
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/chunk-edit.md +25 -22
- package/src/prompts/tools/gh-pr-push.md +2 -1
- package/src/prompts/tools/grep.md +4 -3
- package/src/prompts/tools/lsp.md +6 -0
- package/src/prompts/tools/read-chunk.md +46 -7
- package/src/prompts/tools/read.md +7 -4
- package/src/sdk.ts +8 -5
- package/src/session/agent-session.ts +36 -20
- package/src/session/session-manager.ts +228 -57
- package/src/session/streaming-output.ts +11 -0
- package/src/system-prompt.ts +7 -2
- package/src/task/executor.ts +1 -0
- package/src/tools/bash.ts +13 -0
- package/src/tools/gh.ts +6 -16
- package/src/tools/sqlite-reader.ts +116 -3
- package/src/web/search/providers/codex.ts +129 -6
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
Edits files via syntax-aware chunks.
|
|
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
|
|
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 `
|
|
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
|
|
31
|
-
-
|
|
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,
|
|
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
|
|
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
|
-
**
|
|
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`, `
|
|
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
|
-
|`
|
|
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", "
|
|
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
|
|
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
|
-
-
|
|
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
|
|
25
|
-
-
|
|
26
|
-
- If
|
|
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>
|
package/src/prompts/tools/lsp.md
CHANGED
|
@@ -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
|
-
- `
|
|
5
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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);
|