@oh-my-pi/pi-coding-agent 13.0.1 → 13.0.2

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 (39) hide show
  1. package/package.json +7 -7
  2. package/scripts/format-prompts.ts +33 -3
  3. package/src/commit/prompts/analysis-system.md +3 -3
  4. package/src/commit/prompts/changelog-system.md +3 -3
  5. package/src/commit/prompts/summary-system.md +5 -5
  6. package/src/extensibility/custom-tools/wrapper.ts +1 -0
  7. package/src/extensibility/extensions/wrapper.ts +2 -0
  8. package/src/extensibility/hooks/tool-wrapper.ts +1 -0
  9. package/src/lsp/index.ts +1 -0
  10. package/src/patch/diff.ts +2 -2
  11. package/src/patch/hashline.ts +88 -119
  12. package/src/patch/index.ts +92 -128
  13. package/src/patch/shared.ts +17 -23
  14. package/src/prompts/system/agent-creation-architect.md +2 -2
  15. package/src/prompts/system/system-prompt.md +1 -1
  16. package/src/prompts/tools/bash.md +1 -1
  17. package/src/prompts/tools/find.md +9 -0
  18. package/src/prompts/tools/hashline.md +130 -155
  19. package/src/prompts/tools/task.md +3 -3
  20. package/src/task/index.ts +1 -0
  21. package/src/tools/ask.ts +1 -0
  22. package/src/tools/bash.ts +1 -0
  23. package/src/tools/browser.ts +1 -0
  24. package/src/tools/calculator.ts +1 -0
  25. package/src/tools/cancel-job.ts +1 -0
  26. package/src/tools/exit-plan-mode.ts +1 -0
  27. package/src/tools/fetch.ts +1 -0
  28. package/src/tools/find.ts +1 -0
  29. package/src/tools/grep.ts +1 -0
  30. package/src/tools/notebook.ts +1 -0
  31. package/src/tools/plan-mode-guard.ts +2 -2
  32. package/src/tools/poll-jobs.ts +1 -0
  33. package/src/tools/python.ts +1 -0
  34. package/src/tools/read.ts +1 -0
  35. package/src/tools/ssh.ts +1 -0
  36. package/src/tools/submit-result.ts +1 -0
  37. package/src/tools/todo-write.ts +1 -0
  38. package/src/tools/write.ts +1 -0
  39. package/src/web/search/index.ts +1 -0
@@ -1,97 +1,115 @@
1
1
  # Edit
2
2
 
3
- Apply precise file edits using `LINE#ID` tags, anchoring to the file content.
3
+ Apply precise file edits using `LINE#ID` tags from `read` output.
4
4
 
5
5
  <workflow>
6
- 1. You MUST `read` the target range to capture current `LINE#ID` tags.
7
- 2. You MUST pick the smallest operation per change site (line/range/insert/content-replace).
8
- 3. You MUST direction-lock every edit: exact current text intended text.
9
- 4. You MUST submit one `edit` call per file containing all operations.
10
- 5. If another edit is needed in that file, you MUST re-read first (hashes changed).
11
- 6. You MUST output tool calls only; no prose.
6
+ 1. You SHOULD issue a `read` call before editing if you have no tagged context for a file.
7
+ 2. You MUST pick the smallest operation per change site.
8
+ 3. You MUST submit one `edit` call per file with all operations, think your changes through before submitting.
12
9
  </workflow>
13
10
 
14
11
  <operations>
15
- Every edit has `op`, `first` (start anchor), `last` (end anchor), and `content`. All anchors use `"LINE#ID"` format.
16
- - **Line or range replace/delete**
17
- - `{ op: "replace", first: "N#ID", content: […] }` (single line)
18
- - `{ op: "replace", first: "N#ID", last: "N#ID", content: […] }` (range)
19
- - Use for swaps, block rewrites, or deleting a full span (`content: null`).
20
- - **Insert** (new content)
21
- - `{ op: "prepend", last: "N#ID", content: […] }` or `{ op: "prepend", content: […] }` (no anchor = insert at beginning of file)
22
- - `{ op: "append", first: "N#ID", content: […] }` or `{ op: "append", content: […] }` (no anchor = insert at end of file)
23
- - `{ op: "insert", first: "N#ID", last: "N#ID", content: […] }` (between adjacent anchors; safest for blocks)
24
- - **File-level controls**
25
- - `{ delete: true, edits: [] }` deletes the file (cannot be combined with `rename`).
26
- - `{ rename: "new/path.ts", edits: […] }` writes result to new path and removes old path.
27
- **Atomicity:** all ops validate against the same pre-edit file snapshot; refs are interpreted against last `read`; applicator applies bottom-up.
12
+ Every edit has `op`, `pos`, and `lines`. Range replaces also have `end`. Both `pos` and `end` use `"N#ID"` format (e.g. `"23#XY"`).
13
+ **`pos`** the anchor line. Meaning depends on `op`:
14
+ - `replace`: start of range (or the single line to replace)
15
+ - `prepend`: insert new lines **before** this line; omit for beginning of file
16
+ - `append`: insert new lines **after** this line; omit for end of file
17
+ **`end`** range replace only. The last line of the range (inclusive). Omit for single-line replace.
18
+ **`lines`** the replacement content:
19
+ - `["line1", "line2"]` replace with these lines (array of strings)
20
+ - `"line1"` shorthand for `["line1"]` (single-line replace)
21
+ - `[""]` — replace content with a blank line (line preserved, content cleared)
22
+ - `null` or `[]` **delete** the line(s) entirely
23
+
24
+ ### Line or range replace/delete
25
+ - `{ file: "…", edits: [{ op: "replace", pos: "N#ID", lines: null }] }` — delete one line
26
+ - `{ file: "…", edits: [{ op: "replace", pos: "N#ID", end: "M#ID", lines: null }] }` — delete a range
27
+ - `{ file: "…", edits: [{ op: "replace", pos: "N#ID", lines: […] }] }` — replace one line
28
+ - `{ file: "…", edits: [{ op: "replace", pos: "N#ID", end: "M#ID", lines: […] }] }` — replace a range
29
+
30
+ ### Insert new lines
31
+ - `{ file: "…", edits: [{ op: "prepend", pos: "N#ID", lines: […] }] }` — insert before tagged line
32
+ - `{ file: "…", edits: [{ op: "prepend", lines: […] }] }` — insert at beginning of file (no tag)
33
+ - `{ file: "…", edits: [{ op: "append", pos: "N#ID", lines: […] }] }` — insert after tagged line
34
+ - `{ file: "…", edits: [{ op: "append", lines: […] }] }` — insert at end of file (no tag)
35
+
36
+ ### File-level controls
37
+ - `{ file: "…", delete: true, edits: [] }` — delete the file
38
+ - `{ file: "…", move: "new/path.ts", edits: […] }` — move file to new path (edits applied first)
39
+ **Atomicity:** all ops in one call validate against the same pre-edit snapshot; tags reference the last `read`. Edits are applied bottom-up, so earlier tags stay valid even when later ops add or remove lines.
28
40
  </operations>
29
41
 
30
42
  <rules>
31
- 1. **Minimize scope:** You MUST use one logical mutation site per operation.
32
- 2. **Preserve formatting:** You MUST keep indentation, punctuation, line breaks, trailing commas, brace style.
33
- 3. **Prefer insertion over neighbor rewrites:** You SHOULD anchor on structural boundaries (`}`, `]`, `},`) not interior property lines.
34
- 4. **No no-ops:** replacement content MUST differ from current content.
35
- 5. **Touch only requested code:** You MUST NOT make incidental edits.
36
- 6. **Use exact current tokens:** You MUST NOT rewrite approximately; mutate the token that exists now.
37
- 7. **For swaps/moves:** You SHOULD prefer one range operation over multiple single-line operations.
43
+ 1. **Minimize scope:** You MUST use one logical mutation per operation.
44
+ 2. **No no-ops:** replacement MUST differ from current.
45
+ 3. **Prefer insertion over neighbor rewrites:** You SHOULD anchor on structural boundaries (`}`, `]`, `},`), not interior lines.
46
+ 4. **For swaps/moves:** You SHOULD prefer one range op over multiple single-line ops.
38
47
  </rules>
39
48
 
40
- <op-choice>
41
- - One wrong line → MUST use `set`
42
- - Adjacent block changed → MUST use `insert`
43
- - Missing line/block → MUST use `append`/`prepend`
44
- </op-choice>
45
-
46
- <tag-choice>
47
- - You MUST copy tags exactly from the prefix of the `read` or error output.
48
- - You MUST NOT guess tags.
49
- - For inserts, you SHOULD prefer `insert` > `append`/`prepend` when both boundaries are known.
50
- - You MUST re-read after each successful edit call before issuing another on same file.
51
- </tag-choice>
52
-
53
49
  <recovery>
54
- **Tag mismatch (`>>>`)**
55
- - You MUST retry with the updated tags shown in error output.
56
- - You MUST re-read only if required tags are missing from error snippet.
57
- - If mismatch repeats, you MUST stop and re-read the exact block.
50
+ **Tag mismatch (`>>>`):** You MUST retry using fresh tags from the error snippet. Re-read only if snippet lacks context.
51
+ **No-op (`identical`):** You MUST NOT resubmit. Re-read target lines and adjust the edit.
58
52
  </recovery>
59
53
 
60
- <example name="fix a value or type">
54
+ <example name="single-line replace">
61
55
  ```ts
62
56
  {{hlinefull 23 " const timeout: number = 5000;"}}
63
57
  ```
64
58
  ```
65
- op: "replace"
66
- first: "{{hlineref 23 " const timeout: number = 5000;"}}"
67
- content: [" const timeout: number = 30_000;"]
59
+ {
60
+ file: "",
61
+ edits: [{
62
+ op: "replace",
63
+ pos: "{{hlineref 23 " const timeout: number = 5000;"}}",
64
+ lines: [" const timeout: number = 30_000;"]
65
+ }]
66
+ }
68
67
  ```
69
68
  </example>
70
69
 
71
- <example name="remove a line entirely">
72
- ```ts
73
- {{hlinefull 7 "// @ts-ignore"}}
74
- {{hlinefull 8 "const data = fetchSync(url);"}}
75
- ```
76
- ```
77
- op: "replace"
78
- first: "{{hlineref 7 "// @ts-ignore"}}"
79
- content: null
70
+ <example name="delete lines">
71
+ Single line — `lines: null` deletes entirely:
72
+ ```
73
+ {
74
+ file: "…",
75
+ edits: [{
76
+ op: "replace",
77
+ pos: "{{hlineref 7 "// @ts-ignore"}}",
78
+ lines: null
79
+ }]
80
+ }
81
+ ```
82
+ Range — add `end`:
83
+ ```
84
+ {
85
+ file: "…",
86
+ edits: [{
87
+ op: "replace",
88
+ pos: "{{hlineref 80 " // TODO: remove after migration"}}",
89
+ end: "{{hlineref 83 " }"}}",
90
+ lines: null
91
+ }]
92
+ }
80
93
  ```
81
94
  </example>
82
95
 
83
- <example name="clear content but keep the line break">
96
+ <example name="clear text but keep the line break">
84
97
  ```ts
85
98
  {{hlinefull 14 " placeholder: \"DO NOT SHIP\","}}
86
99
  ```
87
100
  ```
88
- op: "replace"
89
- first: "{{hlineref 14 " placeholder: \"DO NOT SHIP\","}}"
90
- content: [""]
101
+ {
102
+ file: "",
103
+ edits: [{
104
+ op: "replace",
105
+ pos: "{{hlineref 14 " placeholder: \"DO NOT SHIP\","}}",
106
+ lines: [""]
107
+ }]
108
+ }
91
109
  ```
92
110
  </example>
93
111
 
94
- <example name="rewrite a block of logic">
112
+ <example name="rewrite a block">
95
113
  ```ts
96
114
  {{hlinefull 60 " } catch (err) {"}}
97
115
  {{hlinefull 61 " console.error(err);"}}
@@ -99,117 +117,74 @@ content: [""]
99
117
  {{hlinefull 63 " }"}}
100
118
  ```
101
119
  ```
102
- op: "replace"
103
- first: "{{hlineref 60 " } catch (err) {"}}"
104
- last: "{{hlineref 63 " }"}}"
105
- content: [" } catch (err) {", " if (isEnoent(err)) return null;", " throw err;", " }"]
106
- ```
107
- </example>
108
-
109
- <example name="remove a full block">
110
- ```ts
111
- {{hlinefull 80 " // TODO: remove after migration"}}
112
- {{hlinefull 81 " if (legacy) {"}}
113
- {{hlinefull 82 " legacyHandler(req);"}}
114
- {{hlinefull 83 " }"}}
115
- ```
116
- ```
117
- op: "replace"
118
- first: "{{hlineref 80 " // TODO: remove after migration"}}"
119
- last: "{{hlineref 83 " }"}}"
120
- content: null
120
+ {
121
+ file: "",
122
+ edits: [{
123
+ op: "replace",
124
+ pos: "{{hlineref 60 " } catch (err) {"}}",
125
+ end: "{{hlineref 63 " }"}}",
126
+ lines: [
127
+ " } catch (err) {",
128
+ " if (isEnoent(err)) return null;",
129
+ " throw err;",
130
+ " }"
131
+ ]
132
+ }]
133
+ }
121
134
  ```
122
135
  </example>
123
136
 
124
- <example name="add an import above the first import">
137
+ <example name="insert between siblings">
125
138
  ```ts
126
- {{hlinefull 1 "import * as fs from \"node:fs/promises\";"}}
127
- {{hlinefull 2 "import * as path from \"node:path\";"}}
128
- ```
129
- ```
130
- op: "prepend"
131
- last: "{{hlineref 1 "import * as fs from \"node:fs/promises\";"}}"
132
- content: ["import * as os from \"node:os\";"]
133
- ```
134
- Use `last` for anchored insertion before a specific line. Omit anchor to prepend at BOF.
135
- </example>
136
-
137
- <example name="append at end of file">
138
- ```ts
139
- {{hlinefull 260 "export { serialize, deserialize };"}}
139
+ {{hlinefull 44 " \"build\": \"bun run compile\","}}
140
+ {{hlinefull 45 " \"test\": \"bun test\""}}
140
141
  ```
141
142
  ```
142
- op: "append"
143
- first: "{{hlineref 260 "export { serialize, deserialize };"}}"
144
- content: ["export { validate };"]
143
+ {
144
+ file: "",
145
+ edits: [{
146
+ op: "prepend",
147
+ pos: "{{hlineref 45 " \"test\": \"bun test\""}}",
148
+ lines: [" \"lint\": \"biome check\","]
149
+ }]
150
+ }
145
151
  ```
146
- Use `first` for anchored insertion after a specific line. Omit anchor to append at EOF.
147
- </example>
148
-
149
- <example name="add an entry between known siblings">
152
+ Result:
150
153
  ```ts
151
154
  {{hlinefull 44 " \"build\": \"bun run compile\","}}
152
- {{hlinefull 45 " \"test\": \"bun test\""}}
153
- ```
154
- ```
155
- op: "insert"
156
- first: "{{hlineref 44 " \"build\": \"bun run compile\","}}"
157
- last: "{{hlineref 45 " \"test\": \"bun test\""}}"
158
- content: [" \"lint\": \"biome check\","]
155
+ {{hlinefull 45 " \"lint\": \"biome check\","}}
156
+ {{hlinefull 46 " \"test\": \"bun test\""}}
159
157
  ```
160
- Dual anchors pin the insert to exactly one gap, preventing drift from edits elsewhere in the file. **Always prefer dual anchors when both boundaries are content lines.**
161
158
  </example>
162
159
 
163
- <example name="insert a function before another function">
160
+ <example name="anchor to structure, not whitespace">
161
+ Trailing `""` in `lines` preserves blank-line separators. Anchor to the structural line, not the blank line above — blank lines are ambiguous and shift.
164
162
  ```ts
165
- {{hlinefull 100 " return buf.toString(\"hex\");"}}
166
163
  {{hlinefull 101 "}"}}
167
164
  {{hlinefull 102 ""}}
168
165
  {{hlinefull 103 "export function serialize(data: unknown): string {"}}
169
166
  ```
170
- ```
171
- op: "insert"
172
- last: "{{hlineref 103 "export function serialize(data: unknown): string {"}}"
173
- content: ["function validate(data: unknown): boolean {", " return data != null && typeof data === \"object\";", "}", ""]
174
- ```
175
- The trailing `""` in `content` preserves the blank-line separator. **Anchor to the structural line (`export function …`), not the blank line above it** — blank lines are ambiguous and may be added or removed by other edits.
176
- </example>
177
-
178
- <example name="file delete">
179
- ```
180
- path: "src/deprecated/legacy.ts"
181
- delete: true
182
- ```
183
- </example>
184
-
185
- <example name="file rename with edits">
186
- ```
187
- path: "src/utils.ts"
188
- rename: "src/helpers/utils.ts"
189
- edits: […]
190
- ```
191
- </example>
192
-
193
- <example name="anti-pattern: anchoring to whitespace">
194
- Bad — tags to a blank line; fragile if blank lines shift:
195
- ```
196
- first: "{{hlineref 102 ""}}"
197
- content: ["function validate() {", …, "}"]
198
- ```
199
-
200
- Good — anchors to the structural target:
201
-
202
- ```
203
- last: "{{hlineref 103 "export function serialize(data: unknown): string {"}}"
204
- content: ["function validate() {", …, "}"]
167
+ Bad — append after "}"
168
+ Good — anchors to structural line:
169
+ ```
170
+ {
171
+ file: "…",
172
+ edits: [{
173
+ op: "prepend",
174
+ pos: "{{hlineref 103 "export function serialize(data: unknown): string {"}}",
175
+ lines: [
176
+ "function validate(data: unknown): boolean {",
177
+ " return data != null && typeof data === \"object\";",
178
+ "}",
179
+ ""
180
+ ]
181
+ }]
182
+ }
205
183
  ```
206
184
  </example>
207
185
 
208
186
  <critical>
209
- You MUST ensure:
210
- - Payload shape is `{ "path": string, "edits": [operation, …], "delete"?: boolean, "rename"?: string }`
211
- - Each edit has `op`, optional `first`/`last` anchors, and `content`
212
- - Every tag MUST be copied EXACTLY from a tool result as `N#ID`
213
- - Scope MUST be minimal and formatting MUST be preserved except targeted token changes
214
- </critical>
215
- **Final reminder:** tags are immutable references to the last read snapshot. You MUST re-read when state changes, then edit.
187
+ - Edit payload: `{ file, edits[] }`. Each entry: `op`, `lines`, optional `pos`/`end`. No extra keys.
188
+ - Every tag MUST be copied exactly from fresh tool result as `N#ID`.
189
+ - You MUST re-read after each edit call before issuing another on same file.
190
+ </critical>
@@ -172,7 +172,7 @@ Micromanaging (you think, agent types):
172
172
  ```
173
173
  assignment: "In src/api/handler.ts, line 47, change `throw err` to `throw new ApiError(err.message, 500)`.
174
174
  On line 63, wrap fetch call try/catch return 502 on failure.
175
- On line 89, add null check before accessing resp.body..."
175
+ On line 89, add null check before accessing resp.body"
176
176
  ```
177
177
 
178
178
  Delegating (agent thinks within constraints):
@@ -226,8 +226,8 @@ Do not touch TS bindings or downstream consumers — separate phase.
226
226
 
227
227
  ## Change
228
228
  - Implement three N-API exports in grep.rs:
229
- - `search(pattern: JsString, path: JsString, env: Env) -> napi::Result<Vec<Match>>`
230
- ...
229
+ - `search(pattern: JsString, path: JsString, env: Env) napi::Result<Vec<Match>>`
230
+
231
231
 
232
232
  ## Acceptance (task-local)
233
233
  - Three functions exported with correct signatures (caller verifies build after all tasks)
package/src/task/index.ts CHANGED
@@ -134,6 +134,7 @@ function renderDescription(
134
134
  export class TaskTool implements AgentTool<TaskSchema, TaskToolDetails, Theme> {
135
135
  readonly name = "task";
136
136
  readonly label = "Task";
137
+ readonly strict = true;
137
138
  readonly parameters: TaskSchema;
138
139
  readonly renderCall = renderCall;
139
140
  readonly renderResult = renderResult;
package/src/tools/ask.ts CHANGED
@@ -246,6 +246,7 @@ export class AskTool implements AgentTool<typeof askSchema, AskToolDetails> {
246
246
  readonly label = "Ask";
247
247
  readonly description: string;
248
248
  readonly parameters = askSchema;
249
+ readonly strict = true;
249
250
 
250
251
  constructor(private readonly session: ToolSession) {
251
252
  this.description = renderPromptTemplate(askDescription);
package/src/tools/bash.ts CHANGED
@@ -91,6 +91,7 @@ export class BashTool implements AgentTool<BashToolSchema, BashToolDetails> {
91
91
  readonly description: string;
92
92
  readonly parameters: BashToolSchema;
93
93
  readonly concurrency = "exclusive";
94
+ readonly strict = true;
94
95
  readonly #asyncEnabled: boolean;
95
96
 
96
97
  constructor(private readonly session: ToolSession) {
@@ -484,6 +484,7 @@ export class BrowserTool implements AgentTool<typeof browserSchema, BrowserToolD
484
484
  readonly label = "Puppeteer";
485
485
  readonly description: string;
486
486
  readonly parameters = browserSchema;
487
+ readonly strict = true;
487
488
  #browser: Browser | null = null;
488
489
  #page: Page | null = null;
489
490
  #currentHeadless: boolean | null = null;
@@ -399,6 +399,7 @@ export class CalculatorTool implements AgentTool<typeof calculatorSchema, Calcul
399
399
  readonly label = "Calc";
400
400
  readonly description: string;
401
401
  readonly parameters = calculatorSchema;
402
+ readonly strict = true;
402
403
 
403
404
  constructor(_session: ToolSession) {
404
405
  this.description = renderPromptTemplate(calculatorDescription);
@@ -20,6 +20,7 @@ export class CancelJobTool implements AgentTool<typeof cancelJobSchema, CancelJo
20
20
  readonly label = "CancelJob";
21
21
  readonly description: string;
22
22
  readonly parameters = cancelJobSchema;
23
+ readonly strict = true;
23
24
 
24
25
  constructor(private readonly session: ToolSession) {
25
26
  this.description = renderPromptTemplate(cancelJobDescription);
@@ -45,6 +45,7 @@ export class ExitPlanModeTool implements AgentTool<typeof exitPlanModeSchema, Ex
45
45
  readonly label = "ExitPlanMode";
46
46
  readonly description: string;
47
47
  readonly parameters = exitPlanModeSchema;
48
+ readonly strict = true;
48
49
 
49
50
  constructor(private readonly session: ToolSession) {
50
51
  this.description = renderPromptTemplate(exitPlanModeDescription);
@@ -856,6 +856,7 @@ export class FetchTool implements AgentTool<typeof fetchSchema, FetchToolDetails
856
856
  readonly label = "Fetch";
857
857
  readonly description: string;
858
858
  readonly parameters = fetchSchema;
859
+ readonly strict = true;
859
860
 
860
861
  constructor(private readonly session: ToolSession) {
861
862
  this.description = renderPromptTemplate(fetchDescription);
package/src/tools/find.ts CHANGED
@@ -127,6 +127,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
127
127
  readonly label = "Find";
128
128
  readonly description: string;
129
129
  readonly parameters = findSchema;
130
+ readonly strict = true;
130
131
 
131
132
  readonly #customOps?: FindOperations;
132
133
 
package/src/tools/grep.ts CHANGED
@@ -60,6 +60,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
60
60
  readonly label = "Grep";
61
61
  readonly description: string;
62
62
  readonly parameters = grepSchema;
63
+ readonly strict = true;
63
64
 
64
65
  constructor(private readonly session: ToolSession) {
65
66
  const displayMode = resolveFileDisplayMode(session);
@@ -65,6 +65,7 @@ export class NotebookTool implements AgentTool<typeof notebookSchema, NotebookTo
65
65
  readonly description =
66
66
  "Completely replaces the contents of a specific cell in a Jupyter notebook (.ipynb file) with new source. Jupyter notebooks are interactive documents that combine code, text, and visualizations, commonly used for data analysis and scientific computing. The notebook_path parameter must be an absolute path, not a relative path. The cell_number is 0-indexed. Use edit_mode=insert to add a new cell at the index specified by cell_number. Use edit_mode=delete to delete the cell at the index specified by cell_number.";
67
67
  readonly parameters = notebookSchema;
68
+ readonly strict = true;
68
69
  readonly concurrency = "exclusive";
69
70
 
70
71
  constructor(private readonly session: ToolSession) {}
@@ -19,7 +19,7 @@ export function resolvePlanPath(session: ToolSession, targetPath: string): strin
19
19
  export function enforcePlanModeWrite(
20
20
  session: ToolSession,
21
21
  targetPath: string,
22
- options?: { rename?: string; op?: "create" | "update" | "delete" },
22
+ options?: { move?: string; op?: "create" | "update" | "delete" },
23
23
  ): void {
24
24
  const state = session.getPlanModeState?.();
25
25
  if (!state?.enabled) return;
@@ -27,7 +27,7 @@ export function enforcePlanModeWrite(
27
27
  const resolvedTarget = resolvePlanPath(session, targetPath);
28
28
  const resolvedPlan = resolvePlanPath(session, state.planFilePath);
29
29
 
30
- if (options?.rename) {
30
+ if (options?.move) {
31
31
  throw new ToolError("Plan mode: renaming files is not allowed.");
32
32
  }
33
33
 
@@ -39,6 +39,7 @@ export class PollJobsTool implements AgentTool<typeof pollJobsSchema, PollJobsTo
39
39
  readonly label = "PollJobs";
40
40
  readonly description: string;
41
41
  readonly parameters = pollJobsSchema;
42
+ readonly strict = true;
42
43
 
43
44
  constructor(private readonly session: ToolSession) {
44
45
  this.description = renderPromptTemplate(pollJobsDescription);
@@ -148,6 +148,7 @@ export class PythonTool implements AgentTool<typeof pythonSchema> {
148
148
  readonly description: string;
149
149
  readonly parameters = pythonSchema;
150
150
  readonly concurrency = "exclusive";
151
+ readonly strict = true;
151
152
 
152
153
  readonly #proxyExecutor?: PythonProxyExecutor;
153
154
 
package/src/tools/read.ts CHANGED
@@ -558,6 +558,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
558
558
  readonly description: string;
559
559
  readonly parameters = readSchema;
560
560
  readonly nonAbortable = true;
561
+ readonly strict = true;
561
562
 
562
563
  readonly #autoResizeImages: boolean;
563
564
 
package/src/tools/ssh.ts CHANGED
@@ -122,6 +122,7 @@ export class SshTool implements AgentTool<typeof sshSchema, SSHToolDetails> {
122
122
  readonly label = "SSH";
123
123
  readonly parameters = sshSchema;
124
124
  readonly concurrency = "exclusive";
125
+ readonly strict = true;
125
126
 
126
127
  readonly #allowedHosts: Set<string>;
127
128
 
@@ -59,6 +59,7 @@ export class SubmitResultTool implements AgentTool<TObject, SubmitResultDetails>
59
59
  "Finish the task with structured JSON output. Call exactly once at the end of the task.\n\n" +
60
60
  "If you cannot complete the task, call with status='aborted' and an error message.";
61
61
  readonly parameters: TObject;
62
+ readonly strict = true;
62
63
 
63
64
  readonly #validate?: ValidateFunction;
64
65
  readonly #schemaError?: string;
@@ -289,6 +289,7 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
289
289
  readonly description: string;
290
290
  readonly parameters = todoWriteSchema;
291
291
  readonly concurrency = "exclusive";
292
+ readonly strict = true;
292
293
 
293
294
  constructor(private readonly session: ToolSession) {
294
295
  this.description = renderPromptTemplate(todoWriteDescription);
@@ -75,6 +75,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
75
75
  readonly description: string;
76
76
  readonly parameters = writeSchema;
77
77
  readonly nonAbortable = true;
78
+ readonly strict = true;
78
79
  readonly concurrency = "exclusive";
79
80
 
80
81
  readonly #writethrough: WritethroughCallback;
@@ -258,6 +258,7 @@ export class SearchTool implements AgentTool<typeof webSearchSchema, SearchRende
258
258
  readonly label = "Web Search";
259
259
  readonly description: string;
260
260
  readonly parameters = webSearchSchema;
261
+ readonly strict = true;
261
262
 
262
263
  constructor(_session: ToolSession) {
263
264
  this.description = renderPromptTemplate(webSearchDescription);