@oh-my-pi/pi-coding-agent 14.3.0 → 14.4.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 (117) hide show
  1. package/CHANGELOG.md +84 -1
  2. package/package.json +7 -7
  3. package/src/autoresearch/prompt.md +1 -1
  4. package/src/commit/agentic/prompts/analyze-file.md +1 -1
  5. package/src/config/model-registry.ts +67 -15
  6. package/src/config/prompt-templates.ts +5 -5
  7. package/src/config/settings-schema.ts +4 -4
  8. package/src/cursor.ts +3 -8
  9. package/src/discovery/helpers.ts +3 -3
  10. package/src/edit/diff.ts +50 -47
  11. package/src/edit/index.ts +86 -57
  12. package/src/edit/line-hash.ts +735 -19
  13. package/src/edit/modes/apply-patch.ts +0 -9
  14. package/src/edit/modes/atom.ts +658 -0
  15. package/src/edit/modes/chunk.ts +14 -24
  16. package/src/edit/modes/hashline.ts +188 -136
  17. package/src/edit/modes/patch.ts +5 -9
  18. package/src/edit/modes/replace.ts +6 -11
  19. package/src/edit/renderer.ts +14 -10
  20. package/src/edit/streaming.ts +50 -16
  21. package/src/exec/bash-executor.ts +2 -4
  22. package/src/export/html/template.generated.ts +1 -1
  23. package/src/export/html/template.js +4 -12
  24. package/src/extensibility/custom-tools/types.ts +2 -0
  25. package/src/extensibility/custom-tools/wrapper.ts +2 -1
  26. package/src/internal-urls/docs-index.generated.ts +2 -2
  27. package/src/lsp/index.ts +1 -1
  28. package/src/mcp/render.ts +1 -8
  29. package/src/modes/components/assistant-message.ts +4 -0
  30. package/src/modes/components/diff.ts +23 -14
  31. package/src/modes/components/footer.ts +21 -16
  32. package/src/modes/components/settings-defs.ts +6 -1
  33. package/src/modes/components/todo-reminder.ts +1 -8
  34. package/src/modes/components/tool-execution.ts +1 -4
  35. package/src/modes/controllers/selector-controller.ts +1 -1
  36. package/src/modes/print-mode.ts +8 -0
  37. package/src/prompts/agents/librarian.md +1 -1
  38. package/src/prompts/agents/reviewer.md +4 -4
  39. package/src/prompts/ci-green-request.md +1 -1
  40. package/src/prompts/review-request.md +1 -1
  41. package/src/prompts/system/subagent-system-prompt.md +3 -3
  42. package/src/prompts/system/subagent-yield-reminder.md +11 -0
  43. package/src/prompts/system/system-prompt.md +3 -0
  44. package/src/prompts/tools/ask.md +3 -2
  45. package/src/prompts/tools/ast-edit.md +15 -19
  46. package/src/prompts/tools/ast-grep.md +18 -24
  47. package/src/prompts/tools/atom.md +96 -0
  48. package/src/prompts/tools/chunk-edit.md +37 -161
  49. package/src/prompts/tools/debug.md +4 -5
  50. package/src/prompts/tools/exit-plan-mode.md +4 -5
  51. package/src/prompts/tools/find.md +4 -8
  52. package/src/prompts/tools/github.md +18 -0
  53. package/src/prompts/tools/grep.md +4 -5
  54. package/src/prompts/tools/hashline.md +22 -89
  55. package/src/prompts/tools/{gemini-image.md → image-gen.md} +1 -1
  56. package/src/prompts/tools/inspect-image.md +6 -6
  57. package/src/prompts/tools/lsp.md +1 -1
  58. package/src/prompts/tools/patch.md +12 -19
  59. package/src/prompts/tools/python.md +3 -2
  60. package/src/prompts/tools/read-chunk.md +2 -3
  61. package/src/prompts/tools/read.md +2 -2
  62. package/src/prompts/tools/ssh.md +8 -17
  63. package/src/prompts/tools/todo-write.md +54 -41
  64. package/src/sdk.ts +14 -9
  65. package/src/session/agent-session.ts +25 -2
  66. package/src/task/executor.ts +43 -48
  67. package/src/task/render.ts +11 -13
  68. package/src/tools/ask.ts +7 -7
  69. package/src/tools/ast-edit.ts +45 -41
  70. package/src/tools/ast-grep.ts +77 -85
  71. package/src/tools/bash.ts +8 -9
  72. package/src/tools/browser.ts +32 -30
  73. package/src/tools/calculator.ts +4 -4
  74. package/src/tools/cancel-job.ts +1 -1
  75. package/src/tools/checkpoint.ts +2 -2
  76. package/src/tools/debug.ts +41 -37
  77. package/src/tools/exit-plan-mode.ts +1 -1
  78. package/src/tools/find.ts +4 -4
  79. package/src/tools/gh-renderer.ts +12 -4
  80. package/src/tools/gh.ts +509 -697
  81. package/src/tools/grep.ts +115 -130
  82. package/src/tools/{gemini-image.ts → image-gen.ts} +459 -60
  83. package/src/tools/index.ts +14 -32
  84. package/src/tools/inspect-image.ts +3 -3
  85. package/src/tools/json-tree.ts +114 -114
  86. package/src/tools/match-line-format.ts +9 -8
  87. package/src/tools/notebook.ts +8 -7
  88. package/src/tools/poll-tool.ts +2 -1
  89. package/src/tools/python.ts +9 -23
  90. package/src/tools/read.ts +32 -21
  91. package/src/tools/render-mermaid.ts +1 -1
  92. package/src/tools/render-utils.ts +18 -0
  93. package/src/tools/renderers.ts +2 -2
  94. package/src/tools/report-tool-issue.ts +3 -2
  95. package/src/tools/resolve.ts +1 -1
  96. package/src/tools/review.ts +12 -10
  97. package/src/tools/search-tool-bm25.ts +2 -4
  98. package/src/tools/ssh.ts +4 -4
  99. package/src/tools/todo-write.ts +172 -147
  100. package/src/tools/vim.ts +14 -15
  101. package/src/tools/write.ts +4 -4
  102. package/src/tools/{submit-result.ts → yield.ts} +11 -13
  103. package/src/utils/edit-mode.ts +2 -1
  104. package/src/utils/file-display-mode.ts +10 -5
  105. package/src/utils/git.ts +9 -5
  106. package/src/utils/shell-snapshot.ts +2 -3
  107. package/src/vim/render.ts +4 -4
  108. package/src/prompts/system/subagent-submit-reminder.md +0 -11
  109. package/src/prompts/tools/gh-issue-view.md +0 -11
  110. package/src/prompts/tools/gh-pr-checkout.md +0 -12
  111. package/src/prompts/tools/gh-pr-diff.md +0 -12
  112. package/src/prompts/tools/gh-pr-push.md +0 -12
  113. package/src/prompts/tools/gh-pr-view.md +0 -11
  114. package/src/prompts/tools/gh-repo-view.md +0 -11
  115. package/src/prompts/tools/gh-run-watch.md +0 -12
  116. package/src/prompts/tools/gh-search-issues.md +0 -11
  117. package/src/prompts/tools/gh-search-prs.md +0 -11
@@ -19,15 +19,14 @@ Use when:
19
19
  Presents plan to user for approval. If approved, plan mode exits with full tool access restored and the plan is renamed to `local://<title>.md`.
20
20
  </output>
21
21
 
22
- <example name="ready">
22
+ <examples>
23
+ # Ready
23
24
  Plan complete at local://PLAN.md, no open questions.
24
25
  → Call `exit_plan_mode` with `{ "title": "WP_MIGRATION_PLAN" }`
25
- </example>
26
-
27
- <example name="unclear">
26
+ # Unclear
28
27
  Unsure about auth method (OAuth vs JWT).
29
28
  → Use `ask` first to clarify, then call `exit_plan_mode`
30
- </example>
29
+ </examples>
31
30
 
32
31
  <avoid>
33
32
  - **MUST NOT** call before plan is written to file
@@ -8,14 +8,10 @@ Finds files using fast pattern matching that works with any codebase size.
8
8
  Matching file paths sorted by modification time (most recent first). Truncated at 1000 entries or 50KB (configurable via `limit`).
9
9
  </output>
10
10
 
11
- <example name="find files">
12
- ```
13
- {
14
- "pattern": "src/**/*.ts",
15
- "limit": 1000
16
- }
17
- ```
18
- </example>
11
+ <examples>
12
+ # Find files
13
+ `{"pattern": "src/**/*.ts", "limit": 1000}`
14
+ </examples>
19
15
 
20
16
  <avoid>
21
17
  For open-ended searches requiring multiple rounds of globbing and grepping, you **MUST** use Task tool instead.
@@ -0,0 +1,18 @@
1
+ GitHub CLI tool with a single op-based dispatch. Wraps `gh` for repository, issue, pull request, search, checkout, push, and Actions watch workflows.
2
+
3
+ <instruction>
4
+ Pick the operation via `op`. Each op uses a subset of the parameters:
5
+ - `repo_view` — Read repository metadata. Optional `repo` (owner/repo) and `branch`. Falls back to the current checkout or default `gh` repo.
6
+ - `issue_view` — Read an issue. Required `issue` (number or URL). Optional `repo`. Set `comments: false` to skip discussion.
7
+ - `pr_view` — Read a pull request, including reviews and inline review comments. Optional `pr` (number, URL, or branch); omitting it targets the current branch's PR. Optional `repo`. Set `comments: false` for a lighter summary.
8
+ - `pr_diff` — Read a pull request diff. Optional `pr`, `repo`. Set `nameOnly: true` for changed file names. Use `exclude` to drop generated paths from the diff.
9
+ - `pr_checkout` — Check a pull request out into a dedicated git worktree. Optional `pr`, `repo`, `branch` (local), `worktree` (path), `force` (reset existing local branch).
10
+ - `pr_push` — Push a checked-out PR branch back to its source branch. Requires the branch to have been checked out via `op: pr_checkout` (carries push metadata). Optional `branch`; defaults to the current checked-out git branch. Optional `forceWithLease`.
11
+ - `search_issues` — Search issues using normal GitHub issue search syntax. Required `query`. Optional `repo`, `limit`.
12
+ - `search_prs` — Search pull requests using normal GitHub PR search syntax. Required `query`. Optional `repo`, `limit`.
13
+ - `run_watch` — Watch a GitHub Actions workflow run. Optional `run` (id or URL). Omitting `run` watches all workflow runs for the current HEAD commit; `branch` falls back to the current branch. Optional `tail` (log lines per failed job). Streams snapshots, fast-fails on the first detected job failure (with a brief grace period to capture concurrent failures), then fetches tailed logs for the failed jobs. The full failed-job logs are saved as a session artifact for on-demand reads.
14
+ </instruction>
15
+
16
+ <output>
17
+ Returns a concise readable summary tailored to the chosen op (repo/issue/PR metadata, diff text, search results, checkout info, push target, or workflow run snapshot). For `run_watch`, the full failed-job logs are saved as a session artifact when failures occur.
18
+ </output>
@@ -2,21 +2,20 @@ Searches files using powerful regex matching.
2
2
 
3
3
  <instruction>
4
4
  - Supports full regex syntax (e.g., `log.*Error`, `function\\s+\\w+`); literal braces need escaping (`interface\\{\\}` for `interface{}` in Go)
5
- - `path` also accepts comma-separated path lists; pair with `glob` when you need a relative file filter in addition to `type`
6
- - For cross-line patterns like `struct \\{[\\s\\S]*?field`, set `multiline: true`
7
- - If the pattern contains a literal `\n`, `multiline` defaults to true automatically
5
+ - `path` is required and accepts a file, directory, glob, comma-separated path list, or internal URL
6
+ - Cross-line patterns are detected from literal `\n` or escaped `\\n` in `pattern`
8
7
  </instruction>
9
8
 
10
9
  <output>
11
10
  {{#if IS_HASHLINE_MODE}}
12
- - Text output is CID prefixed: `LINE#ID:content`
11
+ - Text output is anchor-prefixed: `123th:content` (match) or `123th-content` (context). The 2-letter ID is a content fingerprint.
13
12
  {{else}}
14
13
  {{#if IS_LINE_NUMBER_MODE}}
15
14
  - Text output is line-number-prefixed
16
15
  {{/if}}
17
16
  {{/if}}
18
17
  {{#if IS_CHUNK_MODE}}
19
- - Text output is chunk-path-prefixed: `path:selector>LINE|content`
18
+ - Text output is chunk-path-prefixed: `path:sel>123th:content`
20
19
  {{/if}}
21
20
  </output>
22
21
 
@@ -1,22 +1,21 @@
1
- Applies precise file edits using `LINE#ID` anchors from `read` output.
1
+ Applies precise file edits using full anchors from `read` output (for example `160sr`).
2
2
 
3
- Read the file first. Copy anchors exactly from the latest `read` output. After any successful edit, re-read before editing that file again.
3
+ Read the file first. Copy the full anchors exactly as shown by `read`.
4
4
 
5
5
  <operations>
6
6
  **Top level**
7
7
  - `edits` — array of edit entries
8
+ - `path` (optional) — default file path used when an entry omits its own `path`. Lets you share the path across many edits in one request.
8
9
 
9
- **Edit entry**: `{ path, loc, content }` or `{ path, delete: true }` or `{ path, move: "new/path" }`
10
- - `path` — file path
10
+ **Edit entry**: `{ path?, loc, content }`
11
+ - `path` — file path (omit to fall back to the request-level `path`)
11
12
  - `loc` — where to apply the edit (see below)
12
- - `content` — replacement/inserted lines (array of strings preferred, `null` to delete)
13
- - `delete` — delete the file
14
- - `move` — move/rename the file
13
+ - `content` — replacement/inserted lines (`string[]`, one element per line; `null` to delete)
15
14
 
16
15
  **`loc` values**
17
16
  - `"append"` / `"prepend"` — insert at end/start of file
18
- - `{ append: "N#ID" }` / `{ prepend: "N#ID" }` — insert after/before anchored line
19
- - `{ range: { pos: "N#ID", end: "N#ID" } }` — replace inclusive range `pos..end` with new content (set `pos == end` for single-line replace)
17
+ - `{ append: "123th" }` / `{ prepend: "123th" }` — insert after/before anchored line
18
+ - `{ range: { pos: "123th", end: "123th" } }` — replace inclusive range `pos..end` with new content (set `pos == end` for single-line replace)
20
19
  </operations>
21
20
 
22
21
  <examples>
@@ -43,92 +42,26 @@ All examples below reference the same file:
43
42
  {{hline 18 "}"}}
44
43
  ```
45
44
 
46
- <example name="replace a block body">
45
+ # Replace a block body
47
46
  Replace only the catch body. Do not target the shared boundary line `} catch (err) {`.
48
-
49
- ```
50
- {
51
- edits: [{
52
- path: "a.ts",
53
- loc: { range: { pos: {{href 15 "\t\tconsole.error(err);"}}, end: {{href 16 "\t\treturn null;"}} } },
54
- content: [
55
- "\t\tif (isEnoent(err)) return null;",
56
- "\t\tthrow err;"
57
- ]
58
- }]
59
- }
60
- ```
61
- </example>
62
-
63
- <example name="replace whole block including closing brace">
64
- Replace the entire body of `alpha`, including its closing `}`. `end` **MUST** be {{href 7 "}"}} because `content` includes `}`.
65
-
66
- ```
67
- {
68
- edits: [{
69
- path: "a.ts",
70
- loc: { range: { pos: {{href 6 "\tlog();"}}, end: {{href 7 "}"}} } },
71
- content: [
72
- "\tvalidate();",
73
- "\tlog();",
74
- "}"
75
- ]
76
- }]
77
- }
78
- ```
79
-
80
- **Wrong**: `end: {{href 6 "\tlog();"}}` with the same content — line 7 (`}`) survives AND content emits `}`, producing two closing braces.
81
- </example>
82
-
83
- <example name="replace one line">
47
+ `{edits:[{path:"a.ts",loc:{range:{pos:{{href 15 "\t\tconsole.error(err);"}},end:{{href 16 "\t\treturn null;"}}}},content:["\t\tif (isEnoent(err)) return null;","\t\tthrow err;"]}]}`
48
+ # Replace whole block including closing brace
49
+ Replace `alpha`'s entire body including the closing `}`. `end` **MUST** be {{href 7 "}"}} because `content` includes `}`.
50
+ `{edits:[{path:"a.ts",loc:{range:{pos:{{href 6 "\tlog();"}},end:{{href 7 "}"}}}},content:["\tvalidate();","\tlog();","}"]}]}`
51
+ **Wrong**: `end: {{href 6 "\tlog();"}}` — line 7 (`}`) survives AND content emits `}`, producing two closing braces.
52
+ # Replace one line
84
53
  Single-line replace uses `pos == end`.
85
-
86
- ```
87
- {
88
- edits: [{
89
- path: "a.ts",
90
- loc: { range: { pos: {{href 2 "const timeout = 5000;"}}, end: {{href 2 "const timeout = 5000;"}} } },
91
- content: ["const timeout = 30_000;"]
92
- }]
93
- }
94
- ```
95
- </example>
96
-
97
- <example name="delete a range">
98
- ```
99
- {
100
- edits: [{
101
- path: "a.ts",
102
- loc: { range: { pos: {{href 10 "\t// TODO: remove after migration"}}, end: {{href 11 "\tlegacy();"}} } },
103
- content: null
104
- }]
105
- }
106
- ```
107
- </example>
108
-
109
- <example name="insert before sibling">
54
+ `{edits:[{path:"a.ts",loc:{range:{pos:{{href 2 "const timeout = 5000;"}},end:{{href 2 "const timeout = 5000;"}}}},content:["const timeout = 30_000;"]}]}`
55
+ # Delete a range
56
+ `{edits:[{path:"a.ts",loc:{range:{pos:{{href 10 "\t// TODO: remove after migration"}},end:{{href 11 "\tlegacy();"}}}},content:null}]}`
57
+ # Insert before a sibling
110
58
  When adding a sibling declaration, prefer `prepend` on the next declaration.
111
-
112
- ```
113
- {
114
- edits: [{
115
- path: "a.ts",
116
- loc: { prepend: {{href 9 "function beta() {"}} },
117
- content: [
118
- "function gamma() {",
119
- "\tvalidate();",
120
- "}",
121
- ""
122
- ]
123
- }]
124
- }
125
- ```
126
- </example>
59
+ `{edits:[{path:"a.ts",loc:{prepend:{{href 9 "function beta() {"}}},content:["function gamma() {","\tvalidate();","}",""]}]}`
127
60
  </examples>
128
61
 
129
62
  <critical>
130
- - Make the minimum exact edit. Do not rewrite nearby code unless the range requires it.
131
- - Copy anchors exactly as `N#ID` from the latest `read` output.
63
+ - Make the minimum exact edit.
64
+ - Copy the full anchors exactly as shown by `read/grep` (for example `160sr`, not just `sr`).
132
65
  - `range` requires both `pos` and `end`.
133
66
  - **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.
134
67
  - For a range, replace only the body or the whole range — don't split range boundaries.
@@ -1,4 +1,4 @@
1
- Generates or edits images using Gemini image models.
1
+ Generates or edits images using the configured image provider.
2
2
 
3
3
  <instructions>
4
4
  - You **MUST** provide a single detailed `subject` prompt for image generation or editing.
@@ -12,12 +12,12 @@ Inspects an image file with a vision-capable model and returns compact text anal
12
12
  </instruction>
13
13
 
14
14
  <examples>
15
- - OCR with strict formatting:
16
- - `{"path":"screenshots/error.png","question":"Extract all visible text verbatim. Return as bullet list in reading order."}`
17
- - Screenshot debugging:
18
- - `{"path":"screenshots/settings.png","question":"Identify the likely cause of the disabled Save button. Return: (1) observations, (2) likely cause, (3) confidence."}`
19
- - Scene/object question:
20
- - `{"path":"photos/shelf.jpg","question":"List all clearly visible product labels and their shelf positions (top/middle/bottom). If unreadable, say unreadable."}`
15
+ # OCR with strict formatting
16
+ `{"path":"screenshots/error.png","question":"Extract all visible text verbatim. Return as bullet list in reading order."}`
17
+ # Screenshot debugging
18
+ `{"path":"screenshots/settings.png","question":"Identify the likely cause of the disabled Save button. Return: (1) observations, (2) likely cause, (3) confidence."}`
19
+ # Scene/object question
20
+ `{"path":"photos/shelf.jpg","question":"List all clearly visible product labels and their shelf positions (top/middle/bottom). If unreadable, say unreadable."}`
21
21
  </examples>
22
22
 
23
23
  <output>
@@ -36,4 +36,4 @@ Interacts with Language Server Protocol servers for code intelligence.
36
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
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
38
  - Prefer `lsp` `code_actions` for imports, quick-fixes, and refactors the language server already knows how to apply.
39
- </critical>
39
+ </critical>
@@ -50,25 +50,18 @@ Returns success/failure; on failure, error message indicates:
50
50
  - **NEVER** use edit to fix indentation, whitespace, or reformat code. Formatting is a single command run once at the end (`bun fmt`, `cargo fmt`, `prettier —write`, etc.)—not N individual edits. If you see inconsistent indentation after an edit, leave it; the formatter will fix all of it in one pass.
51
51
  </critical>
52
52
 
53
- <example name="create">
54
- edit {"edits":[{"path":"hello.txt","op":"create","diff":"Hello\n"}]}
55
- </example>
56
-
57
- <example name="update">
58
- edit {"edits":[{"path":"src/app.py","op":"update","diff":"@@ def greet():\n def greet():\n-print('Hi')\n+print('Hello')\n"}]}
59
- </example>
60
-
61
- <example name="rename">
62
- edit {"edits":[{"path":"src/app.py","op":"update","rename":"src/main.py","diff":"@@\n …\n"}]}
63
- </example>
64
-
65
- <example name="delete">
66
- edit {"edits":[{"path":"obsolete.txt","op":"delete"}]}
67
- </example>
68
-
69
- <example name="multi-file">
70
- edit {"edits":[{"path":"src/types.ts","op":"update","diff":"@@\n-old\n+new\n"},{"path":"src/index.ts","op":"update","diff":"@@\n-old\n+new\n"}]}
71
- </example>
53
+ <examples>
54
+ # Create
55
+ `edit {"edits":[{"path":"hello.txt","op":"create","diff":"Hello\n"}]}`
56
+ # Update
57
+ `edit {"edits":[{"path":"src/app.py","op":"update","diff":"@@ def greet():\n def greet():\n-print('Hi')\n+print('Hello')\n"}]}`
58
+ # Rename
59
+ `edit {"edits":[{"path":"src/app.py","op":"update","rename":"src/main.py","diff":"@@\n …\n"}]}`
60
+ # Delete
61
+ `edit {"edits":[{"path":"obsolete.txt","op":"delete"}]}`
62
+ # Multi-file
63
+ `edit {"edits":[{"path":"src/types.ts","op":"update","diff":"@@\n-old\n+new\n"},{"path":"src/index.ts","op":"update","diff":"@@\n-old\n+new\n"}]}`
64
+ </examples>
72
65
 
73
66
  <avoid>
74
67
  - Generic anchors: `import`, `export`, `describe`, `function`, `const`
@@ -44,7 +44,8 @@ User sees output like Jupyter notebook; rich displays render fully:
44
44
  - You **MUST** use `run()` for shell commands; you **MUST NOT** use raw `subprocess`
45
45
  </critical>
46
46
 
47
- <example name="multiple small cells">
47
+ <examples>
48
+ # Multiple small cells
48
49
  ```python
49
50
  cells: [
50
51
  {"title": "imports", "code": "import json\nfrom pathlib import Path"},
@@ -53,4 +54,4 @@ cells: [
53
54
  {"title": "use helper", "code": "configs = [parse_config(p) for p in Path('.').glob('*.json')]"}
54
55
  ]
55
56
  ```
56
- </example>
57
+ </examples>
@@ -2,7 +2,6 @@ Reads files using syntax-aware chunks. Also inspects directories, archives, SQLi
2
2
 
3
3
  <instruction>
4
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
5
  - You **MUST** parallelize calls when exploring related files
7
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.
8
7
 
@@ -17,7 +16,7 @@ The chunk-aware `read` variant returns AST-scoped chunks with current checksum I
17
16
  |---|---|
18
17
  |*(omitted)*|Read full file as chunks (up to {{DEFAULT_LIMIT}} lines)|
19
18
  |`class_Foo`|Read a specific chunk|
20
- |`class_Foo.fn_bar#ABCD~`|Read a chunk region (body `~` / head `^`) by ID|
19
+ |`class_Foo.fn_bar#thth~`|Read a chunk region (body `~` / head `^`) by ID|
21
20
  |`?`|List all chunk paths with IDs|
22
21
  |`L50`|Read from line 50 onward (shorthand for L50 to EOF)|
23
22
  |`L50-L120`|Read lines 50 through 120|
@@ -27,7 +26,7 @@ The chunk-aware `read` variant returns AST-scoped chunks with current checksum I
27
26
  Max {{DEFAULT_MAX_LINES}} lines per call.
28
27
 
29
28
  # Chunks
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.
29
+ Each anchor `@full.chunk.path#thth` (with `-` prefixes for nesting depth) in the output identifies a chunk. Use `full.chunk.path#thth` as-is to read truncated chunks.
31
30
  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.
32
31
  Line numbers in the gutter are absolute file line numbers.
33
32
 
@@ -18,13 +18,13 @@ The `read` tool is multi-purpose and more capable than it looks — inspects fil
18
18
  |`L50`|Read from line 50 onward (shorthand for L50 to EOF)|
19
19
  |`L50-L120`|Read lines 50 through 120|
20
20
  |`L20-L20`|Read exactly one line|
21
- |`raw`|Raw content without transformations (for URLs: untouched HTML)|
21
+ |`raw`|Skip line-numbering / hashline / chunking; return file content as plain text. For URLs: untouched HTML.|
22
22
 
23
23
  Max {{DEFAULT_MAX_LINES}} lines per call.
24
24
 
25
25
  # Filesystem
26
26
  {{#if IS_HASHLINE_MODE}}
27
- - Reading from FS returns lines prefixed with anchors: `41#ZZ:def alpha():`
27
+ - Reading from FS returns lines prefixed with anchors: `41th:def alpha():` (line number, 2-letter ID, colon, then content)
28
28
  {{else}}
29
29
  {{#if IS_LINE_NUMBER_MODE}}
30
30
  - Reading from FS returns lines prefixed with line numbers: `41:def alpha():`
@@ -25,20 +25,11 @@ You **MUST** build commands from the reference below
25
25
  You **MUST** verify the shell type from "Available hosts" and use matching commands.
26
26
  </critical>
27
27
 
28
- <example name="linux">
29
- Task: List /home/user files on "server1"
30
- Host: server1 (10.0.0.1) | linux/bash
31
- Command: `ls -la /home/user`
32
- </example>
33
-
34
- <example name="windows-cmd">
35
- Task: Show running processes on "winbox"
36
- Host: winbox (192.168.1.5) | windows/cmd
37
- Command: `tasklist /v`
38
- </example>
39
-
40
- <example name="macos">
41
- Task: Get system info on "macbook"
42
- Host: macbook (10.0.0.20) | macos/zsh
43
- Command: `uname -a && sw_vers`
44
- </example>
28
+ <examples>
29
+ # List files: Linux
30
+ Host: server1 (10.0.0.1) | linux/bash. Command: `ls -la /home/user`
31
+ # Show running processes: Windows cmd
32
+ Host: winbox (192.168.1.5) | windows/cmd. Command: `tasklist /v`
33
+ # Get system info: macOS
34
+ Host: macbook (10.0.0.20) | macos/zsh. Command: `uname -a && sw_vers`
35
+ </examples>
@@ -1,28 +1,53 @@
1
- Manages a phased task list. Each field is a verb set the ones you need in a single call.
1
+ Manages a phased task list through an `ops` array of flat operations.
2
2
  The next pending task is auto-promoted to `in_progress` after completing the current one.
3
3
 
4
4
  <protocol>
5
- ## Fields
5
+ ## Shape
6
+
7
+ Pass an object with an `ops` array:
8
+
9
+ ```ts
10
+ {
11
+ ops: [
12
+ { op: "replace", phases: [...] },
13
+ { op: "start", task: "task-3" },
14
+ { op: "done", phase: "Implementation" },
15
+ { op: "rm" },
16
+ { op: "drop", task: "task-9" },
17
+ { op: "append", phase: "Implementation", items: [{ id: "task-10", label: "Run tests" }] },
18
+ ],
19
+ }
20
+ ```
21
+
22
+ ## Operation fields
6
23
 
7
24
  |Field|Type|When to use|
8
25
  |---|---|---|
9
- |`phases`|Phase[]|Initial setup, or full restructure when the plan changes significantly|
10
- |`complete`|string[]|Mark tasks done|
11
- |`start`|string|Jump to a specific task out of order|
12
- |`abandon`|string[]|Drop tasks intentionally|
13
- |`remove`|string[]|Remove tasks that are no longer relevant|
14
- |`add_notes`|{id, notes}[]|Append runtime observations to tasks|
15
- |`add_tasks`|{phase, content, details?}[]|Add tasks to a phase (by name or ID)|
16
- |`add_phase`|{name, tasks?}|Add a new phase of work discovered mid-task|
26
+ |`op`|string|Required. One of `replace`, `start`, `done`, `rm`, `drop`, `append`|
27
+ |`task`|string|Task id for `start`, or a task target for `done` / `rm` / `drop`|
28
+ |`phase`|string|Phase target for `done` / `rm` / `drop`, or append destination for `append`|
29
+ |`items`|{id, label}[]|Required for `append`. If the phase does not exist, it is created at the end|
30
+ |`phases`|Phase[]|Only for `replace`. Keeps initial phased setup available for harness bootstrap and full restructures|
31
+
32
+ ## Semantics
33
+ - `start`: requires `task`; sets that task to `in_progress`
34
+ - `done`: marks one task, one phase, or all tasks completed
35
+ - `rm`: removes one task, one phase's tasks, or all tasks
36
+ - `drop`: marks one task, one phase, or all tasks abandoned
37
+ - `append`: appends `items` to `phase`; creates the phase if missing
38
+ - `replace`: replaces the full todo list
39
+
40
+ If `done`, `rm`, or `drop` omits both `task` and `phase`, it applies to all tasks.
17
41
 
18
42
  ## Task Anatomy
19
- - `content`: Short label (5-10 words). What is being done, not how.
20
- - `details`: File paths, implementation steps, edge cases. Shown only when the task is active.
43
+ - `label`: Short label (5-10 words). What is being done, not how.
44
+ - `replace` task `content` should stay short and specific.
21
45
 
22
46
  ## Rules
23
- - Mark tasks completed immediately after finishing — never defer
24
- - Complete phases in order — do not skip ahead while earlier ones are pending
25
- - On blockers: add a new task describing the blocker
47
+ - Mark tasks done immediately after finishing — never defer.
48
+ - Complete phases in order — do not skip ahead while earlier ones are pending.
49
+ - On blockers, append a new task to the active phase.
50
+ - Keep ids stable once introduced.
26
51
  </protocol>
27
52
 
28
53
  <conditions>
@@ -33,32 +58,20 @@ Create a todo list when:
33
58
  4. New instructions arrive mid-task — capture before proceeding
34
59
  </conditions>
35
60
 
36
- <example name="initial-setup">
37
- {phases: [
38
- {name: "Investigation", tasks: [{content: "Read source"}, {content: "Map callsites"}]},
39
- {name: "Implementation", tasks: [{content: "Apply fix", details: "Update parser.ts to handle edge case in line 42"}, {content: "Run tests"}]}
40
- ]}
41
- </example>
42
-
43
- <example name="complete">
44
- {complete: ["task-2", "task-3"]}
45
- </example>
46
-
47
- <example name="add-notes">
48
- {add_notes: [{id: "task-3", notes: "Found edge case in parser — needs null check"}]}
49
- </example>
50
-
51
- <example name="add-task">
52
- {add_tasks: [{phase: "Implementation", content: "Handle retries", details: "Cap exponential backoff in retry.ts"}]}
53
- </example>
54
-
55
- <example name="add-phase">
56
- {add_phase: {name: "Cleanup", tasks: [{content: "Remove dead code"}]}}
57
- </example>
58
-
59
- <example name="combined">
60
- {complete: ["task-2"], add_notes: [{id: "task-3", notes: "Needs extra validation"}]}
61
- </example>
61
+ <examples>
62
+ # Initial setup
63
+ `{"ops":[{"op":"replace","phases":[{"name":"Investigation","tasks":[{"content":"Read source"},{"content":"Map callsites"}]},{"name":"Implementation","tasks":[{"content":"Apply fix"},{"content":"Run tests"}]}]}]}`
64
+ # Complete one task
65
+ `{"ops":[{"op":"done","task":"task-2"}]}`
66
+ # Complete a whole phase
67
+ `{"ops":[{"op":"done","phase":"Implementation"}]}`
68
+ # Remove all tasks
69
+ `{"ops":[{"op":"rm"}]}`
70
+ # Drop one task
71
+ `{"ops":[{"op":"drop","task":"task-7"}]}`
72
+ # Append tasks to a phase
73
+ `{"ops":[{"op":"append","phase":"Implementation","items":[{"id":"task-8","label":"Handle retries"},{"id":"task-9","label":"Run tests"}]}]}`
74
+ </examples>
62
75
 
63
76
  <avoid>
64
77
  - Single-step tasks — act directly
package/src/sdk.ts CHANGED
@@ -128,7 +128,7 @@ import {
128
128
  warmupLspServers,
129
129
  } from "./tools";
130
130
  import { ToolContextStore } from "./tools/context";
131
- import { getGeminiImageTools } from "./tools/gemini-image";
131
+ import { getImageGenTools } from "./tools/image-gen";
132
132
  import { wrapToolWithMetaNotice } from "./tools/output-meta";
133
133
  import { queueResolveHandler } from "./tools/resolve";
134
134
  import { EventBus } from "./utils/event-bus";
@@ -209,8 +209,8 @@ export interface CreateAgentSessionOptions {
209
209
 
210
210
  /** Output schema for structured completion (subagents) */
211
211
  outputSchema?: unknown;
212
- /** Whether to include the submit_result tool by default */
213
- requireSubmitResultTool?: boolean;
212
+ /** Whether to include the yield tool by default */
213
+ requireYieldTool?: boolean;
214
214
  /** Task recursion depth (for subagent sessions). Default: 0 */
215
215
  taskDepth?: number;
216
216
  /** Parent task ID prefix for nested artifact naming (e.g., "6-Extensions") */
@@ -673,7 +673,12 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
673
673
  }
674
674
 
675
675
  const imageProvider = settings.get("providers.image");
676
- if (imageProvider === "auto" || imageProvider === "gemini" || imageProvider === "openrouter") {
676
+ if (
677
+ imageProvider === "auto" ||
678
+ imageProvider === "openai" ||
679
+ imageProvider === "gemini" ||
680
+ imageProvider === "openrouter"
681
+ ) {
677
682
  setPreferredImageProvider(imageProvider);
678
683
  }
679
684
 
@@ -916,7 +921,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
916
921
  skills,
917
922
  eventBus,
918
923
  outputSchema: options.outputSchema,
919
- requireSubmitResultTool: options.requireSubmitResultTool,
924
+ requireYieldTool: options.requireYieldTool,
920
925
  taskDepth: options.taskDepth ?? 0,
921
926
  getSessionFile: () => sessionManager.getSessionFile() ?? null,
922
927
  getPythonKernelOwnerId: () => pythonKernelOwnerId,
@@ -1047,10 +1052,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
1047
1052
  }
1048
1053
  toolSession.mcpManager = mcpManager;
1049
1054
 
1050
- // Add Gemini image tools if GEMINI_API_KEY (or GOOGLE_API_KEY) is available
1051
- const geminiImageTools = await logger.time("getGeminiImageTools", getGeminiImageTools);
1052
- if (geminiImageTools.length > 0) {
1053
- customTools.push(...(geminiImageTools as unknown as CustomTool[]));
1055
+ // Add image tools when the active model or configured image providers can generate images.
1056
+ const imageGenTools = await logger.time("getImageGenTools", () => getImageGenTools(modelRegistry, model));
1057
+ if (imageGenTools.length > 0) {
1058
+ customTools.push(...(imageGenTools as unknown as CustomTool[]));
1054
1059
  }
1055
1060
 
1056
1061
  // Add web search tools
@@ -525,6 +525,7 @@ export class AgentSession {
525
525
  #obfuscator: SecretObfuscator | undefined;
526
526
  #checkpointState: CheckpointState | undefined = undefined;
527
527
  #pendingRewindReport: string | undefined = undefined;
528
+ #lastSuccessfulYieldToolCallId: string | undefined = undefined;
528
529
  #promptGeneration = 0;
529
530
  #providerSessionState = new Map<string, ProviderSessionState>();
530
531
 
@@ -789,6 +790,9 @@ export class AgentSession {
789
790
  this.#toolChoiceQueue.resolve();
790
791
  }
791
792
  }
793
+ if (event.type === "tool_execution_end" && event.toolName === "yield" && !event.isError) {
794
+ this.#lastSuccessfulYieldToolCallId = event.toolCallId;
795
+ }
792
796
  if (event.type === "turn_end" && this.#pendingRewindReport) {
793
797
  const report = this.#pendingRewindReport;
794
798
  this.#pendingRewindReport = undefined;
@@ -1026,7 +1030,10 @@ export class AgentSession {
1026
1030
  .find((message): message is AssistantMessage => message.role === "assistant");
1027
1031
  const msg = this.#lastAssistantMessage ?? fallbackAssistant;
1028
1032
  this.#lastAssistantMessage = undefined;
1029
- if (!msg) return;
1033
+ if (!msg) {
1034
+ this.#lastSuccessfulYieldToolCallId = undefined;
1035
+ return;
1036
+ }
1030
1037
 
1031
1038
  // Invalidate GitHub Copilot credentials on auth failure so stale tokens
1032
1039
  // aren't reused on the next request
@@ -1040,8 +1047,15 @@ export class AgentSession {
1040
1047
 
1041
1048
  if (this.#skipPostTurnMaintenanceAssistantTimestamp === msg.timestamp) {
1042
1049
  this.#skipPostTurnMaintenanceAssistantTimestamp = undefined;
1050
+ this.#lastSuccessfulYieldToolCallId = undefined;
1051
+ return;
1052
+ }
1053
+
1054
+ if (this.#assistantEndedWithSuccessfulYield(msg)) {
1055
+ this.#lastSuccessfulYieldToolCallId = undefined;
1043
1056
  return;
1044
1057
  }
1058
+ this.#lastSuccessfulYieldToolCallId = undefined;
1045
1059
 
1046
1060
  // Check for retryable errors first (overloaded, rate limit, server errors)
1047
1061
  if (this.#isRetryableError(msg)) {
@@ -3227,7 +3241,6 @@ export class AgentSession {
3227
3241
  id: task.id,
3228
3242
  content: task.content,
3229
3243
  status: task.status,
3230
- notes: task.notes,
3231
3244
  })),
3232
3245
  }));
3233
3246
  }
@@ -4230,6 +4243,16 @@ export class AgentSession {
4230
4243
  }
4231
4244
  }
4232
4245
  }
4246
+ #assistantEndedWithSuccessfulYield(assistantMessage: AssistantMessage): boolean {
4247
+ const toolCallId = this.#lastSuccessfulYieldToolCallId;
4248
+ if (!toolCallId) return false;
4249
+ const lastToolCall = assistantMessage.content
4250
+ .slice()
4251
+ .reverse()
4252
+ .find((content): content is ToolCall => content.type === "toolCall");
4253
+ return lastToolCall?.name === "yield" && lastToolCall.id === toolCallId;
4254
+ }
4255
+
4233
4256
  #enforceRewindBeforeYield(): boolean {
4234
4257
  if (!this.#checkpointState || this.#pendingRewindReport) {
4235
4258
  return false;