@oh-my-pi/pi-coding-agent 14.5.3 → 14.5.5
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 +44 -0
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/package.json +7 -7
- package/src/config/prompt-templates.ts +103 -8
- package/src/config/settings-schema.ts +14 -13
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +4 -4
- package/src/edit/index.ts +111 -109
- package/src/edit/line-hash.ts +33 -3
- package/src/edit/modes/apply-patch.ts +6 -4
- package/src/edit/modes/atom.lark +27 -0
- package/src/edit/modes/atom.ts +1057 -841
- package/src/edit/modes/hashline.ts +9 -10
- package/src/edit/modes/patch.ts +23 -19
- package/src/edit/modes/replace.ts +19 -15
- package/src/edit/renderer.ts +65 -8
- package/src/edit/streaming.ts +47 -77
- package/src/extensibility/extensions/types.ts +11 -11
- package/src/extensibility/hooks/types.ts +6 -6
- package/src/lsp/edits.ts +8 -5
- package/src/lsp/index.ts +4 -4
- package/src/lsp/utils.ts +7 -7
- package/src/mcp/discoverable-tool-metadata.ts +1 -1
- package/src/mcp/manager.ts +3 -3
- package/src/mcp/tool-bridge.ts +4 -4
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-defs.ts +3 -3
- package/src/modes/components/tree-selector.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +31 -7
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +2 -2
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/system-prompt.md +34 -31
- package/src/prompts/tools/apply-patch.md +0 -2
- package/src/prompts/tools/atom.md +81 -63
- package/src/prompts/tools/bash.md +7 -4
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/find.md +6 -1
- package/src/prompts/tools/hashline.md +10 -11
- package/src/prompts/tools/patch.md +13 -13
- package/src/prompts/tools/read.md +4 -4
- package/src/prompts/tools/replace.md +3 -3
- package/src/prompts/tools/{grep.md → search.md} +4 -4
- package/src/sdk.ts +19 -9
- package/src/session/agent-session.ts +65 -0
- package/src/system-prompt.ts +15 -5
- package/src/task/executor.ts +5 -0
- package/src/task/index.ts +10 -1
- package/src/tools/ast-edit.ts +4 -6
- package/src/tools/ast-grep.ts +4 -6
- package/src/tools/bash.ts +1 -1
- package/src/tools/file-recorder.ts +6 -6
- package/src/tools/find.ts +11 -13
- package/src/tools/index.ts +7 -7
- package/src/tools/path-utils.ts +31 -4
- package/src/tools/read.ts +12 -6
- package/src/tools/renderers.ts +2 -2
- package/src/tools/{grep.ts → search.ts} +32 -40
- package/src/tools/write.ts +8 -4
- package/src/web/search/index.ts +1 -1
- package/src/edit/block.ts +0 -308
- package/src/edit/indent.ts +0 -150
|
@@ -1,76 +1,94 @@
|
|
|
1
|
-
|
|
1
|
+
Your patch language is a compact, file-oriented edit format.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Each edit **MUST** have exactly one `loc` and **MUST** include one or more verbs.
|
|
6
|
-
|
|
7
|
-
# Locators
|
|
8
|
-
- `"A"` targets one anchored line. `"$"` targets the whole file: `pre` = BOF, `post` = EOF, `sed` = every line.
|
|
9
|
-
- Bracketed locators are **`splice` only** and select a balanced region around anchor `A`.
|
|
10
|
-
- `"(A)"` = block body. `"[A]"` = whole block/node.
|
|
11
|
-
- `"[A"` / `"(A"` = tail after/including anchor, closer excluded.
|
|
12
|
-
- `"A]"` / `"A)"` = head through/before anchor, opener excluded.
|
|
13
|
-
- Anchor bracketed forms on a body line of the intended block, not the opener line.
|
|
14
|
-
- Do not use bracketed locators on files that do not currently parse.
|
|
3
|
+
When emitting a patch, the first non-blank line **MUST** be `---PATH`.
|
|
4
|
+
A Lid is the anchor emitted in read/grep etc. (line number + id, e.g. `5th`).
|
|
15
5
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
6
|
+
<ops>
|
|
7
|
+
---PATH start editing PATH with cursor at EOF
|
|
8
|
+
!rm delete PATH
|
|
9
|
+
!mv X move file to X
|
|
10
|
+
$ move cursor to BOF
|
|
11
|
+
^ move cursor to EOF
|
|
12
|
+
@Lid move cursor after Lid
|
|
13
|
+
+X insert X at the cursor; `+` alone inserts a blank line
|
|
14
|
+
Lid=X replace whole line with X; `Lid=` blanks it out
|
|
15
|
+
-Lid delete line (repeat for multi)
|
|
20
16
|
</ops>
|
|
21
17
|
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
<sed>
|
|
30
|
-
Use for tiny inline edits: names, operators, literals.
|
|
31
|
-
- Keep `pat` as short as possible, it does not have to be unique.
|
|
32
|
-
- `g:false` by default; set to replace all instead of first.
|
|
33
|
-
</sed>
|
|
18
|
+
<rules>
|
|
19
|
+
- You may have multiple `---PATH` sections to edit multiple files at once.
|
|
20
|
+
- Ops starting with `$` / `^` / `@Lid` do not alter lines; you must still issue an op like `+` afterwards.
|
|
21
|
+
- Consecutive `+X` ops insert consecutive lines.
|
|
22
|
+
- `Lid=X` replaces the whole line. X must be the complete new line, not a fragment.
|
|
23
|
+
- To rewrite multiple adjacent lines, delete each with `-Lid` then emit the new content as `+TEXT` lines. Do not stack `Lid=X` over a contiguous range — it requires the new block to match the old line count and silently corrupts the file when they differ.
|
|
24
|
+
</rules>
|
|
34
25
|
|
|
35
|
-
<
|
|
36
|
-
|
|
37
|
-
{{hline 1 "const FALLBACK = \"guest\";"}}
|
|
26
|
+
<case file="a.ts">
|
|
27
|
+
{{hline 1 "const DEF = \"guest\";"}}
|
|
38
28
|
{{hline 2 ""}}
|
|
39
29
|
{{hline 3 "export function label(name) {"}}
|
|
40
|
-
{{hline 4 "\tconst clean = name ||
|
|
41
|
-
{{hline 5 "\treturn clean.trim()
|
|
30
|
+
{{hline 4 "\tconst clean = name || DEF;"}}
|
|
31
|
+
{{hline 5 "\treturn clean.trim();"}}
|
|
42
32
|
{{hline 6 "}"}}
|
|
43
|
-
|
|
33
|
+
</case>
|
|
34
|
+
|
|
35
|
+
<examples>
|
|
36
|
+
# Replace line
|
|
37
|
+
---a.ts
|
|
38
|
+
{{hrefr 5}}= return clean.trim().toUpperCase();
|
|
39
|
+
|
|
40
|
+
# Rewrite multiple adjacent lines (delete the old, insert the new)
|
|
41
|
+
---a.ts
|
|
42
|
+
-{{hrefr 3}}
|
|
43
|
+
-{{hrefr 4}}
|
|
44
|
+
-{{hrefr 5}}
|
|
45
|
+
-{{hrefr 6}}
|
|
46
|
+
+export function label(name: string): string {
|
|
47
|
+
+ return (name || DEF).trim().toUpperCase();
|
|
48
|
+
+}
|
|
49
|
+
|
|
50
|
+
# Append after
|
|
51
|
+
---a.ts
|
|
52
|
+
@{{hrefr 4}}
|
|
53
|
+
+ const suffix = "";
|
|
54
|
+
|
|
55
|
+
# Delete a line
|
|
56
|
+
---a.ts
|
|
57
|
+
-{{hrefr 2}}
|
|
58
|
+
|
|
59
|
+
# Prepend and append
|
|
60
|
+
---a.ts
|
|
61
|
+
$
|
|
62
|
+
+// Copyright (c) 2026
|
|
63
|
+
+
|
|
64
|
+
^
|
|
65
|
+
+export { DEF };
|
|
66
|
+
|
|
67
|
+
# File ops
|
|
68
|
+
---a.ts
|
|
69
|
+
!rm
|
|
70
|
+
---b.ts
|
|
71
|
+
!mv a.ts
|
|
72
|
+
|
|
73
|
+
# Wrong: `@Lid=TEXT` is not replacement syntax
|
|
74
|
+
---a.ts
|
|
75
|
+
@{{hrefr 5}}= return clean.trim().toUpperCase();
|
|
76
|
+
|
|
77
|
+
# Wrong: do not split `Lid=TEXT` across lines
|
|
78
|
+
---a.ts
|
|
79
|
+
{{hrefr 5}}=
|
|
80
|
+
return clean.trim().toUpperCase();
|
|
44
81
|
|
|
45
|
-
#
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
# Insert before / after an anchor:
|
|
50
|
-
`{path:"a.ts",edits:[{loc:{{href 5 "\treturn clean.trim().toLowerCase();"}},pre:["\tif (!clean) return FALLBACK;"],post:["\t// normalized label"]}]}`
|
|
51
|
-
# Delete a line vs make it blank:
|
|
52
|
-
`{path:"a.ts",edits:[{loc:{{href 2 ""}},splice:[]}]}`
|
|
53
|
-
`{path:"a.ts",edits:[{loc:{{href 2 ""}},splice:[""]}]}`
|
|
54
|
-
# File edges:
|
|
55
|
-
`{path:"a.ts",edits:[{loc:"$",pre:["// Copyright (c) 2026",""]}]}`
|
|
56
|
-
`{path:"a.ts",edits:[{loc:"$",post:["","export { FALLBACK };"]}]}`
|
|
57
|
-
# Cross-file override:
|
|
58
|
-
`{path:"a.ts",edits:[{loc:{{href 1 "const FALLBACK = \"guest\";" "config.ts:" ""}},splice:["const FALLBACK = \"anonymous\";"]}]}`
|
|
59
|
-
# Body replacement: use bracketed `splice`, write body at column 0:
|
|
60
|
-
`{path:"a.ts",edits:[{loc:{{href 4 "\tconst clean = name || FALLBACK;" "(" ")"}},splice:["if (name == null) return FALLBACK;","const clean = String(name).trim();","return clean || FALLBACK;"]}]}`
|
|
61
|
-
# Whole function replacement: anchor on a body line:
|
|
62
|
-
`{path:"a.ts",edits:[{loc:{{href 5 "\treturn clean.trim().toLowerCase();" "[" "]"}},splice:["export function label(name) {","\treturn String(name ?? FALLBACK).trim().toLowerCase();","}"]}]}`
|
|
63
|
-
# WRONG: bare-anchor `splice` does not own neighboring lines:
|
|
64
|
-
`{path:"a.ts",edits:[{loc:{{href 4 "\tconst clean = name || FALLBACK;"}},splice:["\tconst clean = String(name ?? FALLBACK).trim();","\treturn clean.toLowerCase();"]}]}`
|
|
65
|
-
This replaces only line 4. Original line 5 still shifts down, so the function now has two returns.
|
|
66
|
-
# RIGHT: use a body edit for that rewrite:
|
|
67
|
-
`{path:"a.ts",edits:[{loc:{{href 4 "\tconst clean = name || FALLBACK;" "(" ")"}},splice:["const clean = String(name ?? FALLBACK).trim();","return clean.toLowerCase();"]}]}`
|
|
82
|
+
# Wrong: do not replace by deleting then adding
|
|
83
|
+
---a.ts
|
|
84
|
+
-{{hrefr 5}}
|
|
85
|
+
+{{hrefr 5}}= return clean.trim().toUpperCase();
|
|
68
86
|
</examples>
|
|
69
87
|
|
|
70
88
|
<critical>
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
89
|
+
- Copy Lids **EXACTLY** from prior tool output. Never guess, shorten, or omit the letters.
|
|
90
|
+
- Only emit lines that change. Never repeat unchanged context — anchors imply it.
|
|
91
|
+
- This is **NOT** unified diff. Never send `@@`, `-OLD` / `+NEW` pairs, or unchanged context.
|
|
92
|
+
- Never split `Lid=TEXT` across two physical lines.
|
|
93
|
+
- Never stack `Lid=X` over a contiguous range. Use `-Lid`+`+TEXT` for block rewrites.
|
|
76
94
|
</critical>
|
|
@@ -29,22 +29,25 @@ Returns output and exit code.
|
|
|
29
29
|
</output>
|
|
30
30
|
|
|
31
31
|
<critical>
|
|
32
|
-
You **MUST
|
|
32
|
+
You **MUST** use specialized tools instead of bash for any file, directory, or text-search operation. Do **NOT** use Bash to run commands when a relevant dedicated tool is provided — dedicated tools are faster, render diffs, respect `.gitignore`, and let the user review your work. Bash commands matching the patterns below are intercepted and blocked at runtime.
|
|
33
33
|
|
|
34
34
|
|Instead of (WRONG)|Use (CORRECT)|
|
|
35
35
|
|---|---|
|
|
36
36
|
|`cat file`, `head -n N file`|`read(path="file", limit=N)`|
|
|
37
37
|
|`cat -n file \|sed -n '50,150p'`|`read(path="file", offset=50, limit=100)`|
|
|
38
|
-
{{#if
|
|
39
|
-
|`grep -rn 'pat' dir/`|`
|
|
40
|
-
|`rg 'pattern' dir/`|`
|
|
38
|
+
{{#if hasSearch}}|`grep -A 20 'pat' file`|`search(pattern="pat", path="file", post=20)`|
|
|
39
|
+
|`grep -rn 'pat' dir/`|`search(pattern="pat", path="dir/")`|
|
|
40
|
+
|`rg 'pattern' dir/`|`search(pattern="pattern", path="dir/")`|{{/if}}
|
|
41
41
|
{{#if hasFind}}|`find dir -name '*.ts'`|`find(pattern="dir/**/*.ts")`|{{/if}}
|
|
42
42
|
|`ls dir/`|`read(path="dir/")`|
|
|
43
43
|
|`cat <<'EOF' > file`|`write(path="file", content="…")`|
|
|
44
44
|
|`sed -i 's/old/new/' file`|`edit(path="file", edits=[…])`|
|
|
45
45
|
{{#if hasAstEdit}}|`sed -i 's/oldFn(/newFn(/' src/*.ts`|`ast_edit({ops:[{pat:"oldFn($$$A)", out:"newFn($$$A)"}], path:"src/"})`|{{/if}}
|
|
46
|
+
- You **MUST NOT** create files with `cat <<EOF`, `echo > file`, or `printf > file`. Use `write` — heredoc content cannot be cached for permission reuse, every revision triggers a fresh review, and there is no diff. This is the most-violated rule.
|
|
47
|
+
- You **MUST NOT** read line ranges with `sed -n 'A,Bp'`, `awk 'NR≥A && NR≤B'`, or `head | tail` pipelines. Use `read` with `offset`/`limit` (or `sel` if available).
|
|
46
48
|
{{#if hasAstGrep}}- You **MUST** use `ast_grep` for structural code search instead of bash `grep`/`awk`/`perl` pipelines{{/if}}
|
|
47
49
|
{{#if hasAstEdit}}- You **MUST** use `ast_edit` for structural rewrites instead of bash `sed`/`awk`/`perl` pipelines{{/if}}
|
|
48
50
|
- You **MUST NOT** use `2>&1` or `2>/dev/null` — stdout and stderr are already merged
|
|
49
51
|
- You **MUST NOT** use `| head -n 50` or `| tail -n 100` — use `head`/`tail` parameters instead
|
|
52
|
+
- If you catch yourself typing `cat`, `head`, `tail`, `less`, `more`, `ls`, `grep`, `rg`, `find`, `fd`, `sed -i`, `awk -i`, or a heredoc redirect inside a Bash call, stop and switch to the dedicated tool. There is no scenario where bash is preferable for these operations.
|
|
50
53
|
</critical>
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Creates a context checkpoint before exploratory work so you can later rewind and keep only a concise report.
|
|
2
2
|
|
|
3
|
-
Use this when you need to investigate with many intermediate tool calls (read/
|
|
3
|
+
Use this when you need to investigate with many intermediate tool calls (read/search/find/lsp/etc.) and want to minimize context cost afterward.
|
|
4
4
|
|
|
5
5
|
Rules:
|
|
6
6
|
- You **MUST** call `rewind` before yielding after starting a checkpoint.
|
|
@@ -14,5 +14,10 @@ Matching file paths sorted by modification time (most recent first). Truncated a
|
|
|
14
14
|
</examples>
|
|
15
15
|
|
|
16
16
|
<avoid>
|
|
17
|
-
For open-ended searches requiring multiple rounds of globbing and
|
|
17
|
+
For open-ended searches requiring multiple rounds of globbing and searching, you **MUST** use Task tool instead.
|
|
18
18
|
</avoid>
|
|
19
|
+
|
|
20
|
+
<critical>
|
|
21
|
+
- You **MUST** use the built-in Find tool for every file-name lookup. Do **NOT** shell out to `find`, `fd`, `locate`, `ls`, or `git ls-files` via Bash — they ignore `.gitignore`, blow past result limits, and waste tokens.
|
|
22
|
+
- If you catch yourself typing `find -name`, `fd`, or `ls **/*.ext` in a Bash command, stop and re-issue the lookup through the Find tool with a glob pattern instead.
|
|
23
|
+
</critical>
|
|
@@ -5,10 +5,9 @@ Read the file first. Copy the full anchors exactly as shown by `read`.
|
|
|
5
5
|
<operations>
|
|
6
6
|
**Top level**
|
|
7
7
|
- `edits` — array of edit entries
|
|
8
|
-
- `path` (
|
|
8
|
+
- `path` (required) — file path for all edits in this request
|
|
9
9
|
|
|
10
|
-
**Edit entry**: `{
|
|
11
|
-
- `path` — file path (omit to fall back to the request-level `path`)
|
|
10
|
+
**Edit entry**: `{ loc, content }`
|
|
12
11
|
- `loc` — where to apply the edit (see below)
|
|
13
12
|
- `content` — replacement/inserted lines (`string[]`, one element per line; `null` to delete)
|
|
14
13
|
|
|
@@ -44,24 +43,24 @@ All examples below reference the same file:
|
|
|
44
43
|
|
|
45
44
|
# Replace a block body
|
|
46
45
|
Replace only the catch body. Do not target the shared boundary line `} catch (err) {`.
|
|
47
|
-
`{
|
|
46
|
+
`{path:"a.ts",edits:[{loc:{range:{pos:{{href 15}},end:{{href 16}}}},content:["\t\tif (isEnoent(err)) return null;","\t\tthrow err;"]}]}`
|
|
48
47
|
# Replace whole block including closing brace
|
|
49
|
-
Replace `alpha`'s entire body including the closing `}`. `end` **MUST** be {{href 7
|
|
50
|
-
`{
|
|
51
|
-
**Wrong**: `end: {{href 6
|
|
48
|
+
Replace `alpha`'s entire body including the closing `}`. `end` **MUST** be {{href 7}} because `content` includes `}`.
|
|
49
|
+
`{path:"a.ts",edits:[{loc:{range:{pos:{{href 6}},end:{{href 7}}}},content:["\tvalidate();","\tlog();","}"]}]}`
|
|
50
|
+
**Wrong**: `end: {{href 6}}` — line 7 (`}`) survives AND content emits `}`, producing two closing braces.
|
|
52
51
|
# Replace one line
|
|
53
52
|
Single-line replace uses `pos == end`.
|
|
54
|
-
`{
|
|
53
|
+
`{path:"a.ts",edits:[{loc:{range:{pos:{{href 2}},end:{{href 2}}}},content:["const timeout = 30_000;"]}]}`
|
|
55
54
|
# Delete a range
|
|
56
|
-
`{
|
|
55
|
+
`{path:"a.ts",edits:[{loc:{range:{pos:{{href 10}},end:{{href 11}}}},content:null}]}`
|
|
57
56
|
# Insert before a sibling
|
|
58
57
|
When adding a sibling declaration, prefer `prepend` on the next declaration.
|
|
59
|
-
`{
|
|
58
|
+
`{path:"a.ts",edits:[{loc:{prepend:{{href 9}}},content:["function gamma() {","\tvalidate();","}",""]}]}`
|
|
60
59
|
</examples>
|
|
61
60
|
|
|
62
61
|
<critical>
|
|
63
62
|
- Make the minimum exact edit.
|
|
64
|
-
- Copy the full anchors exactly as shown by `read/
|
|
63
|
+
- Copy the full anchors exactly as shown by `read/search` (for example `160sr`, not just `sr`).
|
|
65
64
|
- `range` requires both `pos` and `end`.
|
|
66
65
|
- **Closing-delimiter check**: when your replacement `content` ends with a closing delimiter (`}`, `*/`, `)`, `]`), compare it against the line immediately after `end` in the file. If they match, extend `end` to include that line — otherwise the original delimiter survives and `content` adds a second copy.
|
|
67
66
|
- For a range, replace only the body or the whole range — don't split range boundaries.
|
|
@@ -18,19 +18,19 @@ When editing structured blocks (nested braces, tags, indented regions), include
|
|
|
18
18
|
|
|
19
19
|
<parameters>
|
|
20
20
|
```ts
|
|
21
|
-
// Input is { edits: Entry[] }
|
|
21
|
+
// Input is { path: string, edits: Entry[] }. `path` is required and applies to every entry.
|
|
22
22
|
type Entry =
|
|
23
|
-
// Diff is one or more hunks
|
|
23
|
+
// Diff is one or more hunks for the top-level path.
|
|
24
24
|
// - Each hunk begins with "@@" (anchor optional).
|
|
25
25
|
// - Each hunk body only has lines starting with ' ' | '+' | '-'.
|
|
26
26
|
// - Each hunk includes at least one change (+ or -).
|
|
27
|
-
| {
|
|
27
|
+
| { op: "update", diff: string }
|
|
28
28
|
// Diff is full file content, no prefixes.
|
|
29
|
-
| {
|
|
29
|
+
| { op: "create", diff: string }
|
|
30
30
|
// No diff for delete.
|
|
31
|
-
| {
|
|
32
|
-
// New path for update+move.
|
|
33
|
-
| {
|
|
31
|
+
| { op: "delete" }
|
|
32
|
+
// New path for update+move from the top-level path.
|
|
33
|
+
| { op: "update", rename: string, diff: string }
|
|
34
34
|
```
|
|
35
35
|
</parameters>
|
|
36
36
|
|
|
@@ -52,15 +52,15 @@ Returns success/failure; on failure, error message indicates:
|
|
|
52
52
|
|
|
53
53
|
<examples>
|
|
54
54
|
# Create
|
|
55
|
-
`edit {"
|
|
55
|
+
`edit {"path":"hello.txt","edits":[{"op":"create","diff":"Hello\n"}]}`
|
|
56
56
|
# Update
|
|
57
|
-
`edit {"
|
|
57
|
+
`edit {"path":"src/app.py","edits":[{"op":"update","diff":"@@ def greet():\n def greet():\n-print('Hi')\n+print('Hello')\n"}]}`
|
|
58
58
|
# Rename
|
|
59
|
-
`edit {"
|
|
59
|
+
`edit {"path":"src/app.py","edits":[{"op":"update","rename":"src/main.py","diff":"@@\n …\n"}]}`
|
|
60
60
|
# Delete
|
|
61
|
-
`edit {"
|
|
62
|
-
#
|
|
63
|
-
|
|
61
|
+
`edit {"path":"obsolete.txt","edits":[{"op":"delete"}]}`
|
|
62
|
+
# Multiple entries
|
|
63
|
+
All entries in one call apply to the top-level `path`; use separate calls for different files.
|
|
64
64
|
</examples>
|
|
65
65
|
|
|
66
66
|
<avoid>
|
|
@@ -50,9 +50,9 @@ Extracts content from web pages, GitHub issues/PRs, Stack Overflow, Wikipedia, R
|
|
|
50
50
|
</instruction>
|
|
51
51
|
|
|
52
52
|
<critical>
|
|
53
|
-
- You **MUST** use `read` for
|
|
54
|
-
You **MUST** prefer `read` over a browser/puppeteer tool for fetching URL content; only use a browser if
|
|
55
|
-
- You **MUST** always include the `path` parameter
|
|
56
|
-
- For specific line ranges, use `sel
|
|
53
|
+
- You **MUST** use `read` for every file, directory, archive, and URL read. `cat`, `head`, `tail`, `less`, `more`, `ls`, `tar`, `unzip`, `curl`, and `wget` are **FORBIDDEN** for inspection — any such Bash call is a bug, regardless of how short or convenient it looks.
|
|
54
|
+
- You **MUST** prefer `read` over a browser/puppeteer tool for fetching URL content; only use a browser if `read` fails to deliver reasonable content.
|
|
55
|
+
- You **MUST** always include the `path` parameter — never call `read` with an empty argument object `{}`.
|
|
56
|
+
- For specific line ranges, use `sel` (e.g. `sel="50-200"`, `sel="50+150"`) — do **NOT** reach for `sed -n`, `awk NR`, or `head`/`tail` pipelines.
|
|
57
57
|
- You **MAY** use `sel` with URL reads; the tool paginates cached fetched output.
|
|
58
58
|
</critical>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
Performs string replacements in files with fuzzy whitespace matching.
|
|
2
2
|
|
|
3
3
|
<instruction>
|
|
4
|
-
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
4
|
+
- Params **MUST** be `{ path, edits }`; `path` is required at the top level and applies to every replacement
|
|
5
|
+
- You **MUST** use the smallest `old_text` that uniquely identifies the change
|
|
6
|
+
- If `old_text` is not unique, you **MUST** expand it with more context or use `all: true` to replace all occurrences
|
|
7
7
|
- You **SHOULD** prefer editing existing files over creating new ones
|
|
8
8
|
</instruction>
|
|
9
9
|
|
|
@@ -17,8 +17,8 @@ Searches files using powerful regex matching.
|
|
|
17
17
|
</output>
|
|
18
18
|
|
|
19
19
|
<critical>
|
|
20
|
-
- You **MUST** use the built-in
|
|
21
|
-
- Bash `grep`/`rg` loses `.gitignore` semantics, bypasses result limits, and wastes tokens. The
|
|
22
|
-
- If you catch yourself typing `grep`, `rg`, or `| grep` in a Bash command, stop and re-issue the
|
|
23
|
-
- If the search is open-ended, requiring multiple rounds, you **MUST** use the Task tool with the explore subagent instead of chaining
|
|
20
|
+
- You **MUST** use the built-in `search` 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.
|
|
21
|
+
- Bash `grep`/`rg` loses `.gitignore` semantics, bypasses result limits, and wastes tokens. The `search` tool is faster, structured, and already wired into the workspace — there is no scenario where Bash search is preferable.
|
|
22
|
+
- If you catch yourself typing `grep`, `rg`, or `| grep` in a Bash command, stop and re-issue the lookup through the `search` tool instead.
|
|
23
|
+
- If the search is open-ended, requiring multiple rounds, you **MUST** use the Task tool with the explore subagent instead of chaining `search` calls yourself.
|
|
24
24
|
</critical>
|
package/src/sdk.ts
CHANGED
|
@@ -66,6 +66,7 @@ import {
|
|
|
66
66
|
InternalUrlRouter,
|
|
67
67
|
JobsProtocolHandler,
|
|
68
68
|
LocalProtocolHandler,
|
|
69
|
+
type LocalProtocolOptions,
|
|
69
70
|
McpProtocolHandler,
|
|
70
71
|
MemoryProtocolHandler,
|
|
71
72
|
PiProtocolHandler,
|
|
@@ -111,7 +112,6 @@ import {
|
|
|
111
112
|
discoverStartupLspServers,
|
|
112
113
|
EditTool,
|
|
113
114
|
FindTool,
|
|
114
|
-
GrepTool,
|
|
115
115
|
getSearchTools,
|
|
116
116
|
HIDDEN_TOOLS,
|
|
117
117
|
isSearchProviderPreference,
|
|
@@ -121,10 +121,12 @@ import {
|
|
|
121
121
|
ReadTool,
|
|
122
122
|
ResolveTool,
|
|
123
123
|
renderSearchToolBm25Description,
|
|
124
|
+
SearchTool,
|
|
124
125
|
setPreferredImageProvider,
|
|
125
126
|
setPreferredSearchProvider,
|
|
126
127
|
type Tool,
|
|
127
128
|
type ToolSession,
|
|
129
|
+
WebSearchTool,
|
|
128
130
|
WriteTool,
|
|
129
131
|
warmupLspServers,
|
|
130
132
|
} from "./tools";
|
|
@@ -226,6 +228,9 @@ export interface CreateAgentSessionOptions {
|
|
|
226
228
|
/** Session manager. Default: session stored under the configured agentDir sessions root */
|
|
227
229
|
sessionManager?: SessionManager;
|
|
228
230
|
|
|
231
|
+
/** Override local:// protocol options for subagent local:// sharing. Default: uses the session's own artifacts dir and session ID. */
|
|
232
|
+
localProtocolOptions?: LocalProtocolOptions;
|
|
233
|
+
|
|
229
234
|
/** Settings instance. Default: Settings.init({ cwd, agentDir }) */
|
|
230
235
|
settings?: Settings;
|
|
231
236
|
|
|
@@ -271,13 +276,14 @@ export {
|
|
|
271
276
|
createTools,
|
|
272
277
|
EditTool,
|
|
273
278
|
FindTool,
|
|
274
|
-
GrepTool,
|
|
275
279
|
HIDDEN_TOOLS,
|
|
276
280
|
loadSshTool,
|
|
277
281
|
PythonTool,
|
|
278
282
|
ReadTool,
|
|
279
283
|
ResolveTool,
|
|
284
|
+
SearchTool,
|
|
280
285
|
type ToolSession,
|
|
286
|
+
WebSearchTool,
|
|
281
287
|
WriteTool,
|
|
282
288
|
};
|
|
283
289
|
|
|
@@ -996,10 +1002,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
996
1002
|
}),
|
|
997
1003
|
);
|
|
998
1004
|
internalRouter.register(
|
|
999
|
-
new LocalProtocolHandler(
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1005
|
+
new LocalProtocolHandler(
|
|
1006
|
+
options.localProtocolOptions ?? {
|
|
1007
|
+
getArtifactsDir,
|
|
1008
|
+
getSessionId: () => sessionManager.getSessionId(),
|
|
1009
|
+
},
|
|
1010
|
+
),
|
|
1003
1011
|
);
|
|
1004
1012
|
internalRouter.register(
|
|
1005
1013
|
new SkillProtocolHandler({
|
|
@@ -1386,7 +1394,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1386
1394
|
? requestedActiveToolNames
|
|
1387
1395
|
: requestedActiveToolNames.filter(name => !defaultInactiveToolNames.has(name));
|
|
1388
1396
|
const explicitlyRequestedMCPToolNames = options.toolNames
|
|
1389
|
-
? requestedActiveToolNames.filter(name => name.startsWith("
|
|
1397
|
+
? requestedActiveToolNames.filter(name => name.startsWith("mcp__"))
|
|
1390
1398
|
: [];
|
|
1391
1399
|
const discoveryDefaultServers = new Set(
|
|
1392
1400
|
(settings.get("mcp.discoveryDefaultServers") ?? []).map(serverName => serverName.trim()).filter(Boolean),
|
|
@@ -1412,7 +1420,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1412
1420
|
: [...new Set([...restoredSelectedMCPToolNames, ...defaultSelectedMCPToolNames])];
|
|
1413
1421
|
initialToolNames = [
|
|
1414
1422
|
...new Set([
|
|
1415
|
-
...initialRequestedActiveToolNames.filter(name => !name.startsWith("
|
|
1423
|
+
...initialRequestedActiveToolNames.filter(name => !name.startsWith("mcp__")),
|
|
1416
1424
|
...initialSelectedMCPToolNames,
|
|
1417
1425
|
]),
|
|
1418
1426
|
];
|
|
@@ -1424,7 +1432,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1424
1432
|
...registeredTools.filter(t => !t.definition.defaultInactive).map(t => t.definition.name),
|
|
1425
1433
|
];
|
|
1426
1434
|
for (const name of alwaysInclude) {
|
|
1427
|
-
if (mcpDiscoveryEnabled && name.startsWith("
|
|
1435
|
+
if (mcpDiscoveryEnabled && name.startsWith("mcp__")) {
|
|
1428
1436
|
continue;
|
|
1429
1437
|
}
|
|
1430
1438
|
if (toolRegistry.has(name) && !initialToolNames.includes(name)) {
|
|
@@ -1601,6 +1609,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1601
1609
|
ttsrManager,
|
|
1602
1610
|
obfuscator,
|
|
1603
1611
|
asyncJobManager,
|
|
1612
|
+
agentId: resolvedAgentId,
|
|
1613
|
+
agentRegistry,
|
|
1604
1614
|
});
|
|
1605
1615
|
hasSession = true;
|
|
1606
1616
|
|
|
@@ -130,6 +130,7 @@ import planModeToolDecisionReminderPrompt from "../prompts/system/plan-mode-tool
|
|
|
130
130
|
type: "text",
|
|
131
131
|
};
|
|
132
132
|
import ttsrInterruptTemplate from "../prompts/system/ttsr-interrupt.md" with { type: "text" };
|
|
133
|
+
import { type AgentRegistry, MAIN_AGENT_ID } from "../registry/agent-registry";
|
|
133
134
|
import { deobfuscateSessionContext, type SecretObfuscator } from "../secrets/obfuscator";
|
|
134
135
|
import { resolveThinkingLevelForModel, toReasoningEffort } from "../thinking";
|
|
135
136
|
import { assertEditableFile } from "../tools/auto-generated-guard";
|
|
@@ -263,6 +264,10 @@ export interface AgentSessionConfig {
|
|
|
263
264
|
obfuscator?: SecretObfuscator;
|
|
264
265
|
/** Logical owner for retained Python kernels created by this session. */
|
|
265
266
|
pythonKernelOwnerId?: string;
|
|
267
|
+
/** Agent identity (registry id like "0-Main" or "3-Alice") used for IRC routing. */
|
|
268
|
+
agentId?: string;
|
|
269
|
+
/** Shared agent registry (for forwarding IRC observations to the main session UI). */
|
|
270
|
+
agentRegistry?: AgentRegistry;
|
|
266
271
|
}
|
|
267
272
|
|
|
268
273
|
/** Options for AgentSession.prompt() */
|
|
@@ -478,6 +483,9 @@ export class AgentSession {
|
|
|
478
483
|
// Drained into history (via emitExternalEvent) once the recipient becomes idle.
|
|
479
484
|
#pendingBackgroundExchanges: CustomMessage[][] = [];
|
|
480
485
|
#scheduledBackgroundExchangeFlush = false;
|
|
486
|
+
// Agent identity + registry for IRC relay forwarding to the main session UI.
|
|
487
|
+
#agentId: string | undefined;
|
|
488
|
+
#agentRegistry: AgentRegistry | undefined;
|
|
481
489
|
// Extension system
|
|
482
490
|
#extensionRunner: ExtensionRunner | undefined = undefined;
|
|
483
491
|
#turnIndex = 0;
|
|
@@ -611,6 +619,8 @@ export class AgentSession {
|
|
|
611
619
|
);
|
|
612
620
|
this.#ttsrManager = config.ttsrManager;
|
|
613
621
|
this.#obfuscator = config.obfuscator;
|
|
622
|
+
this.#agentId = config.agentId;
|
|
623
|
+
this.#agentRegistry = config.agentRegistry;
|
|
614
624
|
this.agent.setAssistantMessageEventInterceptor((message, assistantMessageEvent) => {
|
|
615
625
|
const event: AgentEvent = {
|
|
616
626
|
type: "message_update",
|
|
@@ -5999,6 +6009,13 @@ export class AgentSession {
|
|
|
5999
6009
|
timestamp: incomingTimestamp,
|
|
6000
6010
|
};
|
|
6001
6011
|
void this.#emitSessionEvent({ type: "irc_message", message: incomingRecord });
|
|
6012
|
+
this.#forwardIrcRelayToMain({
|
|
6013
|
+
from: args.from,
|
|
6014
|
+
to: this.#agentId ?? "?",
|
|
6015
|
+
body: args.message,
|
|
6016
|
+
kind: "message",
|
|
6017
|
+
timestamp: incomingTimestamp,
|
|
6018
|
+
});
|
|
6002
6019
|
|
|
6003
6020
|
if (!awaitReply) {
|
|
6004
6021
|
this.#queueBackgroundExchangeInjection([incomingRecord]);
|
|
@@ -6024,11 +6041,59 @@ export class AgentSession {
|
|
|
6024
6041
|
timestamp: Date.now(),
|
|
6025
6042
|
};
|
|
6026
6043
|
void this.#emitSessionEvent({ type: "irc_message", message: replyRecord });
|
|
6044
|
+
this.#forwardIrcRelayToMain({
|
|
6045
|
+
from: this.#agentId ?? "?",
|
|
6046
|
+
to: args.from,
|
|
6047
|
+
body: replyText,
|
|
6048
|
+
kind: "reply",
|
|
6049
|
+
timestamp: replyRecord.timestamp,
|
|
6050
|
+
});
|
|
6027
6051
|
this.#queueBackgroundExchangeInjection([incomingRecord, replyRecord]);
|
|
6028
6052
|
|
|
6029
6053
|
return { replyText };
|
|
6030
6054
|
}
|
|
6031
6055
|
|
|
6056
|
+
/**
|
|
6057
|
+
* Forward an IRC exchange observation to the main agent's session UI so the
|
|
6058
|
+
* user can see every IRC conversation in the main transcript, even when the
|
|
6059
|
+
* main agent is not a direct participant. The relay record is display-only:
|
|
6060
|
+
* it is NOT injected into the main agent's persisted history.
|
|
6061
|
+
*/
|
|
6062
|
+
#forwardIrcRelayToMain(args: {
|
|
6063
|
+
from: string;
|
|
6064
|
+
to: string;
|
|
6065
|
+
body: string;
|
|
6066
|
+
kind: "message" | "reply";
|
|
6067
|
+
timestamp: number;
|
|
6068
|
+
}): void {
|
|
6069
|
+
const registry = this.#agentRegistry;
|
|
6070
|
+
if (!registry) return;
|
|
6071
|
+
// If this session is the main agent, the local emit already reached the main UI.
|
|
6072
|
+
if (this.#agentId === MAIN_AGENT_ID) return;
|
|
6073
|
+
const mainRef = registry.get(MAIN_AGENT_ID);
|
|
6074
|
+
const mainSession = mainRef?.session;
|
|
6075
|
+
if (!mainSession || mainSession === this) return;
|
|
6076
|
+
const arrow = args.kind === "reply" ? "\u2192 (auto)" : "\u2192";
|
|
6077
|
+
const relayRecord: CustomMessage = {
|
|
6078
|
+
role: "custom",
|
|
6079
|
+
customType: "irc:relay",
|
|
6080
|
+
content: `[IRC \`${args.from}\` ${arrow} \`${args.to}\`]\n\n${args.body}`,
|
|
6081
|
+
display: true,
|
|
6082
|
+
details: { from: args.from, to: args.to, body: args.body, kind: args.kind },
|
|
6083
|
+
attribution: "agent",
|
|
6084
|
+
timestamp: args.timestamp,
|
|
6085
|
+
};
|
|
6086
|
+
mainSession.emitIrcRelayObservation(relayRecord);
|
|
6087
|
+
}
|
|
6088
|
+
|
|
6089
|
+
/**
|
|
6090
|
+
* Emit an IRC relay observation event on this session for UI rendering only.
|
|
6091
|
+
* Does not persist the record to history. Public so other sessions can forward.
|
|
6092
|
+
*/
|
|
6093
|
+
emitIrcRelayObservation(record: CustomMessage): void {
|
|
6094
|
+
void this.#emitSessionEvent({ type: "irc_message", message: record });
|
|
6095
|
+
}
|
|
6096
|
+
|
|
6032
6097
|
/**
|
|
6033
6098
|
* Run a single ephemeral side-channel turn against this session's current
|
|
6034
6099
|
* model + system prompt + history. No tools are used; the side request
|
package/src/system-prompt.ts
CHANGED
|
@@ -384,6 +384,8 @@ export async function loadSystemPromptFiles(options: LoadContextFilesOptions = {
|
|
|
384
384
|
export interface SystemPromptToolMetadata {
|
|
385
385
|
label: string;
|
|
386
386
|
description: string;
|
|
387
|
+
/** Tool name the model sees on the provider wire. Defaults to the internal tool name. */
|
|
388
|
+
wireName?: string;
|
|
387
389
|
}
|
|
388
390
|
|
|
389
391
|
export function buildSystemPromptToolMetadata(
|
|
@@ -394,12 +396,16 @@ export function buildSystemPromptToolMetadata(
|
|
|
394
396
|
Array.from(tools.entries(), ([name, tool]) => {
|
|
395
397
|
const toolRecord = tool as AgentTool & { label?: string; description?: string };
|
|
396
398
|
const override = overrides[name];
|
|
399
|
+
const wireName =
|
|
400
|
+
override?.wireName ??
|
|
401
|
+
(typeof toolRecord.customWireName === "string" ? toolRecord.customWireName : undefined);
|
|
397
402
|
return [
|
|
398
403
|
name,
|
|
399
404
|
{
|
|
400
405
|
label: override?.label ?? (typeof toolRecord.label === "string" ? toolRecord.label : ""),
|
|
401
406
|
description:
|
|
402
407
|
override?.description ?? (typeof toolRecord.description === "string" ? toolRecord.description : ""),
|
|
408
|
+
wireName,
|
|
403
409
|
},
|
|
404
410
|
] as const;
|
|
405
411
|
}),
|
|
@@ -570,14 +576,17 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
570
576
|
}
|
|
571
577
|
}
|
|
572
578
|
|
|
573
|
-
// Build tool descriptions for system prompt rendering
|
|
579
|
+
// Build tool descriptions for system prompt rendering.
|
|
580
|
+
const toolPromptNames = new Map<string, string>(toolNames.map(name => [name, tools?.get(name)?.wireName ?? name]));
|
|
581
|
+
const toolRefs = Object.fromEntries(toolPromptNames.entries());
|
|
574
582
|
const toolInfo = toolNames.map(name => ({
|
|
575
|
-
name,
|
|
583
|
+
name: toolPromptNames.get(name) ?? name,
|
|
584
|
+
internalName: name,
|
|
576
585
|
label: tools?.get(name)?.label ?? "",
|
|
577
586
|
description: tools?.get(name)?.description ?? "",
|
|
578
587
|
}));
|
|
579
588
|
|
|
580
|
-
// Filter skills to only include those with read tool
|
|
589
|
+
// Filter skills to only include those with read tool.
|
|
581
590
|
const hasRead = tools?.has("read");
|
|
582
591
|
const filteredSkills = hasRead ? skills : [];
|
|
583
592
|
|
|
@@ -589,6 +598,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
589
598
|
const injectedAlwaysApplyRules = dedupeAlwaysApplyRules(alwaysApplyRules, promptSources);
|
|
590
599
|
|
|
591
600
|
const environment = await logger.time("getEnvironmentInfo", getEnvironmentInfo);
|
|
601
|
+
const reportToolIssueToolName = toolPromptNames.get("report_tool_issue") ?? "report_tool_issue";
|
|
592
602
|
const data = {
|
|
593
603
|
systemPromptCustomization: effectiveSystemPromptCustomization,
|
|
594
604
|
customPrompt: resolvedCustomPrompt,
|
|
@@ -596,6 +606,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
596
606
|
tools: toolNames,
|
|
597
607
|
toolInfo,
|
|
598
608
|
repeatToolDescriptions,
|
|
609
|
+
toolRefs,
|
|
599
610
|
environment,
|
|
600
611
|
contextFiles,
|
|
601
612
|
agentsMdSearch,
|
|
@@ -617,8 +628,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
617
628
|
|
|
618
629
|
// When autoqa is active the report_tool_issue tool is in the tool set — nudge the agent.
|
|
619
630
|
if (toolNames.includes("report_tool_issue")) {
|
|
620
|
-
rendered +=
|
|
621
|
-
"\n\n<critical>\nThe `report_tool_issue` tool is available for automated QA. If ANY tool you call returns output that is unexpected, incorrect, malformed, or otherwise inconsistent with what you anticipated given the tool's described behavior and your parameters, call `report_tool_issue` with the tool name and a concise description of the discrepancy. Do not hesitate to report — false positives are acceptable.\n</critical>";
|
|
631
|
+
rendered += `\n\n<critical>\nThe \`${reportToolIssueToolName}\` tool is available for automated QA. If ANY tool you call returns output that is unexpected, incorrect, malformed, or otherwise inconsistent with what you anticipated given the tool's described behavior and your parameters, call \`${reportToolIssueToolName}\` with the tool name and a concise description of the discrepancy. Do not hesitate to report — false positives are acceptable.\n</critical>`;
|
|
622
632
|
}
|
|
623
633
|
|
|
624
634
|
return rendered;
|