@oh-my-pi/pi-coding-agent 13.3.12 → 13.3.14

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 CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [13.3.14] - 2026-02-28
6
+
7
+ ### Added
8
+
9
+ - Expanded AST tool language support from 7 to all 25 ast-grep tree-sitter languages (Bash, C, C++, C#, CSS, Elixir, Go, Haskell, HCL, HTML, Java, JavaScript, JSON, Kotlin, Lua, Nix, PHP, Python, Ruby, Rust, Scala, Solidity, Swift, TSX, TypeScript, YAML)
10
+ - AST find now emits all lines of multiline matches with hashline tags (LINE#HASH:content) consistent with read/grep output
11
+ - Added AST pattern syntax reference (metavariables, wildcards, variadics) to system prompt
12
+ - Added examples and scoping guidance to ast-find and ast-replace tool prompts
13
+ - Added `provider-schema-compatibility.test.ts`: integration test that instantiates every builtin and hidden tool, runs their parameter schemas through `adaptSchemaForStrict`, `sanitizeSchemaForGoogle`, and `prepareSchemaForCCA`, and asserts zero violations against each provider's compatibility rules
14
+
15
+ ### Fixed
16
+
17
+ - Non-code files (.md, .zip, .bin, .gitignore, etc.) are now silently skipped by AST tools instead of producing misleading parse errors
18
+ - Fixed `grep` path wildcard handling so file patterns passed via `path` (for example `schema-review-*.test.ts`) are resolved as glob filters instead of failing path existence checks
19
+
5
20
  ## [13.3.11] - 2026-02-28
6
21
 
7
22
  ### Fixed
@@ -92,6 +107,7 @@
92
107
  - Fixed `todo_write` task normalization to auto-activate the first remaining task and include explicit remaining-items output in tool results, removing the need for an immediate follow-up start update
93
108
 
94
109
  ## [13.3.7] - 2026-02-27
110
+
95
111
  ### Breaking Changes
96
112
 
97
113
  - Removed `preloadedSkills` option from `CreateAgentSessionOptions`; skills are no longer inlined into system prompts
@@ -125,11 +141,13 @@
125
141
  - Fixed handling of union type schemas (e.g., object|null) to normalize them into strict-mode compatible variants
126
142
 
127
143
  ## [13.3.6] - 2026-02-26
144
+
128
145
  ### Breaking Changes
129
146
 
130
147
  - Changed `submit_result` tool parameter structure from top-level `data` or `error` fields to nested `result` object containing either `result.data` or `result.error`
131
148
 
132
149
  ## [13.3.5] - 2026-02-26
150
+
133
151
  ### Added
134
152
 
135
153
  - Added support for setting array and record configuration values using JSON syntax
@@ -144,6 +162,7 @@
144
162
  - Enhanced type display in config list output to show correct type indicators for number, array, and record settings
145
163
 
146
164
  ## [13.3.3] - 2026-02-26
165
+
147
166
  ### Added
148
167
 
149
168
  - Support for `move` parameter in `computeHashlineDiff` to enable file move operations alongside content edits
@@ -195,13 +214,16 @@
195
214
  ## [13.2.1] - 2026-02-24
196
215
 
197
216
  ### Fixed
217
+
198
218
  - Fixed changelog tools to enforce category-specific arrays and reuse the shared category list for generation
199
219
  - Non-interactive environment variables (pager, editor, prompt suppression) were not applied to non-PTY bash execution, causing commands to potentially block on pagers or prompts
200
220
 
201
221
  ### Changed
202
222
 
203
223
  - Extracted non-interactive environment config from `bash-interactive.ts` into shared `non-interactive-env.ts` module, applied consistently to all bash execution paths
224
+
204
225
  ## [13.2.0] - 2026-02-23
226
+
205
227
  ### Breaking Changes
206
228
 
207
229
  - Made `description` field required in CustomTool interface
@@ -225,19 +247,24 @@
225
247
  ## [13.1.2] - 2026-02-23
226
248
 
227
249
  ### Breaking Changes
250
+
228
251
  - Removed `timeout` parameter from await tool—tool now waits indefinitely until jobs complete or the call is aborted
229
252
  - Renamed `job_ids` parameter to `jobs` in await tool schema
230
253
  - Removed `timedOut` field from await tool result details
231
254
 
232
255
  ### Changed
256
+
233
257
  - Resolved docs index generation paths using path.resolve relative to the script directory
258
+
234
259
  ## [13.1.1] - 2026-02-23
235
260
 
236
261
  ### Fixed
237
262
 
238
263
  - Fixed bash internal URL expansion to resolve `local://` targets to concrete filesystem paths, including newly created destination files for commands like `mv src.json local://dest.json`
239
264
  - Fixed bash local URL resolution to create missing parent directories under the session local root before command execution, preventing `mv` destination failures for new paths
265
+
240
266
  ## [13.1.0] - 2026-02-23
267
+
241
268
  ### Breaking Changes
242
269
 
243
270
  - Renamed `file` parameter to `path` in replace, patch, and hashline edit operations
@@ -254,6 +281,7 @@
254
281
  - Moved intent field documentation from per-tool JSON schema descriptions into a single system prompt block, reducing token overhead proportional to tool count
255
282
 
256
283
  ## [13.0.1] - 2026-02-22
284
+
257
285
  ### Changed
258
286
 
259
287
  - Simplified hashline edit schema to use unified `first`/`last` anchor fields instead of operation-specific field names (`tag`, `before`, `after`)
@@ -261,6 +289,7 @@
261
289
  - Updated hashline tool documentation to reflect new unified anchor syntax across all operations (replace, append, prepend, insert)
262
290
 
263
291
  ## [13.0.0] - 2026-02-22
292
+
264
293
  ### Added
265
294
 
266
295
  - Added `getTodoPhases()` and `setTodoPhases()` methods to ToolSession API for managing todo state programmatically
@@ -301,6 +330,7 @@
301
330
  - Fixed todo reminder XML tags from underscore to kebab-case format (`system-reminder`)
302
331
 
303
332
  ## [12.19.3] - 2026-02-22
333
+
304
334
  ### Added
305
335
 
306
336
  - Added `pty` parameter to bash tool to enable PTY mode for commands requiring a real terminal (e.g., sudo, ssh, top, less)
@@ -314,12 +344,14 @@
314
344
  - Removed `bash.virtualTerminal` setting; use the `pty` parameter on individual bash commands instead
315
345
 
316
346
  ## [12.19.1] - 2026-02-22
347
+
317
348
  ### Removed
318
349
 
319
350
  - Removed `replaceText` edit operation from hashline mode (substring-based text replacement)
320
351
  - Removed autocorrect heuristics that attempted to detect and fix line merges and formatting rewrites in hashline edits
321
352
 
322
353
  ## [12.19.0] - 2026-02-22
354
+
323
355
  ### Added
324
356
 
325
357
  - Added `poll_jobs` tool to block until background jobs complete, providing an alternative to polling `read jobs://` in loops
@@ -367,6 +399,7 @@
367
399
  - Fixed `submit_result` schema generation to use valid JSON Schema when no explicit output schema is provided
368
400
 
369
401
  ## [12.18.1] - 2026-02-21
402
+
370
403
  ### Added
371
404
 
372
405
  - Added Buffer.toBase64() polyfill for Bun compatibility to enable base64 encoding of buffers
@@ -395,6 +428,7 @@
395
428
  - Fixed potential race condition in bash interactive component where output could be appended after the component was closed
396
429
 
397
430
  ## [12.17.2] - 2026-02-21
431
+
398
432
  ### Changed
399
433
 
400
434
  - Modified bash command normalization to only apply explicit head/tail parameters from tool input, removing automatic extraction from command pipes
@@ -406,6 +440,7 @@
406
440
  - Fixed hard timeout handling to properly interrupt long-running commands that exceed the grace period beyond the configured timeout
407
441
 
408
442
  ## [12.17.1] - 2026-02-21
443
+
409
444
  ### Added
410
445
 
411
446
  - Added `filterBrowser` option to filter out browser automation MCP servers when builtin browser tool is enabled
@@ -414,6 +449,7 @@
414
449
  - Added `BrowserFilterResult` type for browser MCP server filtering results
415
450
 
416
451
  ## [12.17.0] - 2026-02-21
452
+
417
453
  ### Added
418
454
 
419
455
  - Added timeout protection (5 seconds) for system prompt preparation with graceful fallback to minimal context on timeout
@@ -5326,4 +5362,4 @@ Initial public release.
5326
5362
  - Git branch display in footer
5327
5363
  - Message queueing during streaming responses
5328
5364
  - OAuth integration for Gmail and Google Calendar access
5329
- - HTML export with syntax highlighting and collapsible sections
5365
+ - HTML export with syntax highlighting and collapsible sections
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "13.3.12",
4
+ "version": "13.3.14",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://github.com/can1357/oh-my-pi",
7
7
  "author": "Can Boluk",
@@ -41,12 +41,12 @@
41
41
  },
42
42
  "dependencies": {
43
43
  "@mozilla/readability": "^0.6",
44
- "@oh-my-pi/omp-stats": "13.3.12",
45
- "@oh-my-pi/pi-agent-core": "13.3.12",
46
- "@oh-my-pi/pi-ai": "13.3.12",
47
- "@oh-my-pi/pi-natives": "13.3.12",
48
- "@oh-my-pi/pi-tui": "13.3.12",
49
- "@oh-my-pi/pi-utils": "13.3.12",
44
+ "@oh-my-pi/omp-stats": "13.3.14",
45
+ "@oh-my-pi/pi-agent-core": "13.3.14",
46
+ "@oh-my-pi/pi-ai": "13.3.14",
47
+ "@oh-my-pi/pi-natives": "13.3.14",
48
+ "@oh-my-pi/pi-tui": "13.3.14",
49
+ "@oh-my-pi/pi-utils": "13.3.14",
50
50
  "@sinclair/typebox": "^0.34",
51
51
  "@xterm/headless": "^6.0",
52
52
  "ajv": "^8.18",
@@ -110,7 +110,7 @@ async function withGatewayLock<T>(handler: () => Promise<T>): Promise<T> {
110
110
  while (true) {
111
111
  let fd: fs.promises.FileHandle | undefined;
112
112
  try {
113
- fd = await fs.promises.open(lockPath, fs.constants.O_WRONLY | fs.constants.O_CREAT | fs.constants.O_EXCL);
113
+ fd = await fs.promises.open(lockPath, "wx");
114
114
  let heartbeatRunning = true;
115
115
  const heartbeat = (async () => {
116
116
  while (heartbeatRunning) {
@@ -4,6 +4,7 @@
4
4
  * Converts MCP tool definitions to CustomTool format for the agent.
5
5
  */
6
6
  import type { AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
7
+ import { sanitizeSchemaForMCP } from "@oh-my-pi/pi-ai/utils/schema";
7
8
  import type { TSchema } from "@sinclair/typebox";
8
9
  import type { SourceMeta } from "../capability/types";
9
10
  import type {
@@ -49,46 +50,6 @@ export interface MCPToolDetails {
49
50
  /** Provider display name (e.g., "Claude Code", "MCP Config") */
50
51
  providerName?: string;
51
52
  }
52
-
53
- /**
54
- * Recursively strip JSON Schema fields that cause AJV validation errors.
55
- *
56
- * - `$schema`: AJV throws on unknown meta-schema URIs (e.g. draft 2020-12 from schemars 1.x / rmcp 0.15+)
57
- * - `nullable`: OpenAPI 3.0 extension, not standard JSON Schema — AJV rejects it as an unknown keyword.
58
- */
59
- function sanitizeSchema(schema: unknown): unknown {
60
- if (Array.isArray(schema)) {
61
- return schema.map(sanitizeSchema);
62
- }
63
- if (schema !== null && typeof schema === "object") {
64
- const { $schema: _, nullable: __, ...rest } = schema as Record<string, unknown>;
65
- const out: Record<string, unknown> = {};
66
- for (const [k, v] of Object.entries(rest)) {
67
- out[k] = sanitizeSchema(v);
68
- }
69
- return out;
70
- }
71
- return schema;
72
- }
73
-
74
- /**
75
- * Convert JSON Schema from MCP to TypeBox-compatible schema.
76
- * MCP uses standard JSON Schema, TypeBox uses a compatible subset.
77
- *
78
- * Also normalizes schemas to work around common issues:
79
- * - Adds `properties: {}` to object schemas missing it (some LLM providers require this)
80
- * - Strips `$schema` and `nullable` fields (see sanitizeSchema)
81
- */
82
- function convertSchema(mcpSchema: MCPToolDefinition["inputSchema"]): TSchema {
83
- const schema = sanitizeSchema(mcpSchema) as Record<string, unknown>;
84
-
85
- // Normalize: object schemas must have properties field for some providers
86
- if (schema.type === "object" && !("properties" in schema)) {
87
- return { ...schema, properties: {} } as unknown as TSchema;
88
- }
89
- return schema as unknown as TSchema;
90
- }
91
-
92
53
  /**
93
54
  * Format MCP content for LLM consumption.
94
55
  */
@@ -193,7 +154,7 @@ export class MCPTool implements CustomTool<TSchema, MCPToolDetails> {
193
154
  this.name = createMCPToolName(connection.name, tool.name);
194
155
  this.label = `${connection.name}/${tool.name}`;
195
156
  this.description = tool.description ?? `MCP tool from ${connection.name}`;
196
- this.parameters = convertSchema(tool.inputSchema);
157
+ this.parameters = sanitizeSchemaForMCP(tool.inputSchema) as TSchema;
197
158
  this.mcpToolName = tool.name;
198
159
  this.mcpServerName = connection.name;
199
160
  }
@@ -297,7 +258,7 @@ export class DeferredMCPTool implements CustomTool<TSchema, MCPToolDetails> {
297
258
  this.name = createMCPToolName(serverName, tool.name);
298
259
  this.label = `${serverName}/${tool.name}`;
299
260
  this.description = tool.description ?? `MCP tool from ${serverName}`;
300
- this.parameters = convertSchema(tool.inputSchema);
261
+ this.parameters = sanitizeSchemaForMCP(tool.inputSchema) as TSchema;
301
262
  this.mcpToolName = tool.name;
302
263
  this.mcpServerName = serverName;
303
264
  this.#fallbackProvider = source?.provider;
@@ -702,7 +702,7 @@ export class InteractiveMode implements InteractiveModeContext {
702
702
  await this.session.prompt(prompt, { synthetic: true });
703
703
  }
704
704
 
705
- async handlePlanModeCommand(): Promise<void> {
705
+ async handlePlanModeCommand(initialPrompt?: string): Promise<void> {
706
706
  if (this.planModeEnabled) {
707
707
  const confirmed = await this.showHookConfirm(
708
708
  "Exit plan mode?",
@@ -713,6 +713,9 @@ export class InteractiveMode implements InteractiveModeContext {
713
713
  return;
714
714
  }
715
715
  await this.#enterPlanMode();
716
+ if (initialPrompt) {
717
+ this.onInputCallback?.({ text: initialPrompt });
718
+ }
716
719
  }
717
720
 
718
721
  async handleExitPlanModeTool(details: ExitPlanModeDetails): Promise<void> {
@@ -1629,25 +1629,26 @@ function detectTerminalBackground(): "dark" | "light" {
1629
1629
  if (terminalReportedAppearance) {
1630
1630
  return terminalReportedAppearance;
1631
1631
  }
1632
- // macOS: query system appearance via CoreFoundation (native, no shell).
1633
- // Uses cached observer value, or falls back to CFPreferencesCopyAppValue.
1634
- // Works on all terminals including Warp which lacks Mode 2031 / OSC 11.
1635
- const macAppearance = macOSReportedAppearance ?? detectMacOSAppearance();
1636
- if (macAppearance) {
1637
- return macAppearance;
1638
- }
1639
- // Fallback: COLORFGBG environment variable (static, set once at terminal launch)
1632
+ // COLORFGBG is set by the terminal emulator to reflect the actual profile colors.
1633
+ // Check it before macOS system appearance because the terminal profile may differ
1634
+ // from the OS-level dark/light setting (e.g. dark terminal on macOS light mode).
1640
1635
  const colorfgbg = Bun.env.COLORFGBG || "";
1641
1636
  if (colorfgbg) {
1642
1637
  const parts = colorfgbg.split(";");
1643
1638
  if (parts.length >= 2) {
1644
1639
  const bg = parseInt(parts[1], 10);
1645
1640
  if (!Number.isNaN(bg)) {
1646
- const result = bg < 8 ? "dark" : "light";
1647
- return result;
1641
+ return bg < 8 ? "dark" : "light";
1648
1642
  }
1649
1643
  }
1650
1644
  }
1645
+ // macOS: query system appearance via CoreFoundation (native, no shell).
1646
+ // Uses cached observer value, or falls back to CFPreferencesCopyAppValue.
1647
+ // Works on all terminals including Warp which lacks Mode 2031 / OSC 11.
1648
+ const macAppearance = macOSReportedAppearance ?? detectMacOSAppearance();
1649
+ if (macAppearance) {
1650
+ return macAppearance;
1651
+ }
1651
1652
  return "dark";
1652
1653
  }
1653
1654
 
@@ -197,7 +197,7 @@ export interface InteractiveModeContext {
197
197
  toggleThinkingBlockVisibility(): void;
198
198
  openExternalEditor(): void;
199
199
  registerExtensionShortcuts(): void;
200
- handlePlanModeCommand(): Promise<void>;
200
+ handlePlanModeCommand(initialPrompt?: string): Promise<void>;
201
201
  handleExitPlanModeTool(details: ExitPlanModeDetails): Promise<void>;
202
202
 
203
203
  // Hook UI methods
@@ -161,6 +161,21 @@ When AST tools are available, syntax-aware operations take priority over text ha
161
161
  {{#has tools "ast_find"}}- Use `ast_find` for structural discovery (call shapes, declarations, syntax patterns) before text grep when code structure matters{{/has}}
162
162
  {{#has tools "ast_replace"}}- Use `ast_replace` for structural codemods/replacements; do not use bash `sed`/`perl`/`awk` for syntax-level rewrites{{/has}}
163
163
  - Use `grep` for plain text/regex lookup only when AST shape is irrelevant
164
+
165
+ #### Pattern syntax
166
+
167
+ Patterns match **AST structure, not text** — whitespace and formatting are irrelevant. `foo( x, y )` and `foo(x,y)` are the same pattern.
168
+
169
+ |Syntax|Name|Matches|
170
+ |---|---|---|
171
+ |`$VAR`|Capture|One AST node, bound as `$VAR`|
172
+ |`$_`|Wildcard|One AST node, not captured|
173
+ |`$$$VAR`|Variadic capture|Zero or more nodes, bound as `$VAR`|
174
+ |`$$$`|Variadic wildcard|Zero or more nodes, not captured|
175
+
176
+ Metavariable names **MUST** be UPPERCASE (`$A`, `$FUNC`, `$MY_VAR`). Lowercase `$var` is invalid.
177
+
178
+ When a metavariable appears multiple times in one pattern, all occurrences must match **identical** code: `$A == $A` matches `x == x` but not `x == y`.
164
179
  {{/ifAny}}
165
180
  {{#if eagerTasks}}
166
181
  <eager-tasks>
@@ -244,7 +259,6 @@ Justify sequential work; default parallel. Cannot articulate why B depends on A
244
259
  - You **MUST NOT** yield without proof when non-trivial work, self-assessment is deceptive: tests, linters, type checks, repro steps… exhaust all external verification.
245
260
  ## 8. Handoff
246
261
  Before finishing, you **MUST**:
247
- - List all commands run and confirm they passed.
248
262
  - Summarize changes with file and line references.
249
263
  - Call out TODOs, follow-up work, or uncertainties — no surprises are **PERMITTED**.
250
264
 
@@ -3,9 +3,13 @@ Performs structural code search using AST matching via native ast-grep.
3
3
  <instruction>
4
4
  - Use this when syntax shape matters more than raw text (calls, declarations, specific language constructs)
5
5
  - Prefer a precise `path` scope to keep results targeted and deterministic (`path` accepts files, directories, or glob patterns)
6
+ - Default to language-scoped search in mixed repositories: pair `path` glob + explicit `lang` to avoid parse-noise from non-source files
6
7
  - `pattern` is required; `lang` is optional (`lang` is inferred per file extension when omitted)
7
8
  - Use `selector` only for contextual pattern mode; otherwise provide a direct pattern
8
9
  - Enable `include_meta` when metavariable captures are needed in output
10
+ - For variadic arguments/fields, use `$$$NAME` (not `$$NAME`)
11
+ - Patterns match AST structure, not text — whitespace/formatting differences are ignored
12
+ - When the same metavariable appears multiple times, all occurrences must match identical code
9
13
  </instruction>
10
14
 
11
15
  <output>
@@ -13,8 +17,20 @@ Performs structural code search using AST matching via native ast-grep.
13
17
  - Includes summary counts (`totalMatches`, `filesWithMatches`, `filesSearched`) and parse issues when present
14
18
  </output>
15
19
 
20
+ <examples>
21
+ - Find prompt-template call sites in tool code (scoped + typed):
22
+ `{"pattern":"renderPromptTemplate($A)","lang":"typescript","path":"packages/coding-agent/src/tools/**/*.ts","include_meta":true}`
23
+ - Match variadic call arguments correctly:
24
+ `{"pattern":"renderStatusLine($$$ARGS)","lang":"typescript","path":"packages/coding-agent/src/tools/**/*.ts","include_meta":true}`
25
+ - Exact call-shape match in one file:
26
+ `{"pattern":"renderStatusLine({ icon: \"pending\", title: \"AST Find\", description, meta }, uiTheme)","lang":"typescript","path":"packages/coding-agent/src/tools/ast-find.ts"}`
27
+ - Contextual pattern with selector — match only the identifier `foo`, not the whole call:
28
+ `{"pattern":"foo()","selector":"identifier","lang":"typescript","path":"src/utils.ts"}`
29
+ </examples>
30
+
16
31
  <critical>
17
32
  - `pattern` is required
18
33
  - Set `lang` explicitly to constrain matching when path pattern spans mixed-language trees
34
+ - Avoid repo-root AST scans when the target is language-specific; narrow `path` first
19
35
  - If exploration is broad/open-ended across subsystems, use Task tool with explore subagent first
20
36
  </critical>
@@ -3,9 +3,13 @@ Performs structural AST-aware rewrites via native ast-grep.
3
3
  <instruction>
4
4
  - Use for codemods and structural rewrites where plain text replace is unsafe
5
5
  - Narrow scope with `path` before replacing (`path` accepts files, directories, or glob patterns)
6
+ - Default to language-scoped rewrites in mixed repositories: set `lang` and keep `path` narrow
6
7
  - `pattern` + `rewrite` are required; `lang` is optional only when all matched files resolve to a single language
7
8
  - Keep `dry_run` enabled unless explicit apply intent is clear
8
9
  - Use `max_files` and `max_replacements` as safety caps on broad rewrites
10
+ - Treat parse issues as a scoping signal: tighten `path`/`lang` before retrying
11
+ - Metavariables captured in `pattern` (`$A`, `$$$ARGS`) are substituted into `rewrite` — this is the core mechanism
12
+ - Each rewrite is a 1:1 structural substitution; you cannot split one capture into multiple nodes or merge multiple captures into one node
9
13
  </instruction>
10
14
 
11
15
  <output>
@@ -14,8 +18,20 @@ Performs structural AST-aware rewrites via native ast-grep.
14
18
  - Includes parse issues when files cannot be processed
15
19
  </output>
16
20
 
21
+ <examples>
22
+ - Preview a single exact-shape rewrite in one file:
23
+ `{"pattern":"renderStatusLine({ icon: \"pending\", title: \"AST Find\", description, meta }, uiTheme)","rewrite":"renderStatusLine({ icon: \"success\", title: \"AST Find\", description, meta }, uiTheme)","lang":"typescript","path":"packages/coding-agent/src/tools/ast-find.ts","dry_run":true}`
24
+ - Preview with safety caps across multiple files (demonstrates cap behavior):
25
+ `{"pattern":"renderPromptTemplate($A)","rewrite":"String(renderPromptTemplate($A))","lang":"typescript","path":"packages/coding-agent/src/tools/**/*.ts","dry_run":true,"max_files":2,"max_replacements":3}`
26
+ - Swap two arguments using captures:
27
+ `{"pattern":"assertEqual($A, $B)","rewrite":"assertEqual($B, $A)","lang":"typescript","path":"tests/**/*.ts","dry_run":true}`
28
+ - Preserve variadic arguments through a rewrite:
29
+ `{"pattern":"oldApi($$$ARGS)","rewrite":"newApi($$$ARGS)","lang":"typescript","path":"src/**/*.ts","dry_run":true}`
30
+ </examples>
31
+
17
32
  <critical>
18
33
  - `pattern` + `rewrite` are required
19
34
  - If the path pattern spans multiple languages, set `lang` explicitly for deterministic rewrites
35
+ - Run `dry_run: true` first, review preview, then rerun with `dry_run: false` only when intent is explicit
20
36
  - For one-off local text edits, prefer the Edit tool instead of AST replace
21
37
  </critical>
@@ -3200,7 +3200,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
3200
3200
  this.model && assistantMessage.provider === this.model.provider && assistantMessage.model === this.model.id;
3201
3201
  // This handles the case where an error was kept after compaction (in the "kept" region).
3202
3202
  // The error shouldn't trigger another compaction since we already compacted.
3203
- // Example: opus fails \u2192 switch to codex \u2192 compact \u2192 switch back to opus \u2192 opus error
3203
+ // Example: opus fails -> switch to codex -> compact -> switch back to opus -> opus error
3204
3204
  // is still in context but shouldn't trigger compaction again.
3205
3205
  const compactionEntry = getLatestCompactionEntry(this.sessionManager.getBranch());
3206
3206
  const errorIsFromBeforeCompaction =
@@ -3213,7 +3213,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
3213
3213
  this.agent.replaceMessages(messages.slice(0, -1));
3214
3214
  }
3215
3215
 
3216
- // Try context promotion first \u2014 switch to a larger model and retry without compacting
3216
+ // Try context promotion first - switch to a larger model and retry without compacting
3217
3217
  const promoted = await this.#tryContextPromotion(assistantMessage);
3218
3218
  if (promoted) {
3219
3219
  // Retry on the promoted (larger) model without compacting
@@ -3221,7 +3221,7 @@ Be thorough - include exact file paths, function names, error messages, and tech
3221
3221
  return;
3222
3222
  }
3223
3223
 
3224
- // No promotion target available \u2014 fall through to compaction
3224
+ // No promotion target available fall through to compaction
3225
3225
  const compactionSettings = this.settings.getGroup("compaction");
3226
3226
  if (compactionSettings.enabled) {
3227
3227
  await this.#runAutoCompaction("overflow", true);
@@ -75,8 +75,10 @@ const BUILTIN_SLASH_COMMAND_REGISTRY: ReadonlyArray<BuiltinSlashCommandSpec> = [
75
75
  {
76
76
  name: "plan",
77
77
  description: "Toggle plan mode (agent plans before executing)",
78
- handle: async (_command, runtime) => {
79
- await runtime.ctx.handlePlanModeCommand();
78
+ inlineHint: "[prompt]",
79
+ allowArgs: true,
80
+ handle: async (command, runtime) => {
81
+ await runtime.ctx.handlePlanModeCommand(command.args || undefined);
80
82
  runtime.ctx.editor.setText("");
81
83
  },
82
84
  },
@@ -7,8 +7,10 @@ import { type Static, Type } from "@sinclair/typebox";
7
7
  import { renderPromptTemplate } from "../config/prompt-templates";
8
8
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
9
9
  import type { Theme } from "../modes/theme/theme";
10
+ import { computeLineHash } from "../patch/hashline";
10
11
  import astFindDescription from "../prompts/tools/ast-find.md" with { type: "text" };
11
12
  import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
13
+ import { resolveFileDisplayMode } from "../utils/file-display-mode";
12
14
  import type { ToolSession } from ".";
13
15
  import type { OutputMeta } from "./output-meta";
14
16
  import { hasGlobPathChars, parseSearchPath, resolveToCwd } from "./path-utils";
@@ -133,12 +135,20 @@ export class AstFindTool implements AgentTool<typeof astFindSchema, AstFindToolD
133
135
  grouped.set(match.path, [match]);
134
136
  }
135
137
  }
138
+ const useHashLines = resolveFileDisplayMode(this.session).hashLines;
136
139
  for (const [filePath, matches] of grouped) {
137
140
  lines.push("", `# ${filePath}`);
138
141
  for (const match of matches) {
139
- const firstLine = match.text.split("\n", 1)[0] ?? "";
140
- const preview = firstLine.length > 140 ? `${firstLine.slice(0, 137)}...` : firstLine;
141
- lines.push(`${match.startLine}:${match.startColumn}-${match.endLine}:${match.endColumn}: ${preview}`);
142
+ const matchLines = match.text.split("\n");
143
+ for (let i = 0; i < matchLines.length; i++) {
144
+ const lineNum = match.startLine + i;
145
+ const line = matchLines[i];
146
+ if (useHashLines) {
147
+ lines.push(`${lineNum}#${computeLineHash(lineNum, line)}:${line}`);
148
+ } else {
149
+ lines.push(`${lineNum}:${line}`);
150
+ }
151
+ }
142
152
  if (match.metaVariables && Object.keys(match.metaVariables).length > 0) {
143
153
  const serializedMeta = Object.entries(match.metaVariables)
144
154
  .sort(([left], [right]) => left.localeCompare(right))
@@ -7,8 +7,10 @@ import { type Static, Type } from "@sinclair/typebox";
7
7
  import { renderPromptTemplate } from "../config/prompt-templates";
8
8
  import type { RenderResultOptions } from "../extensibility/custom-tools/types";
9
9
  import type { Theme } from "../modes/theme/theme";
10
+ import { computeLineHash } from "../patch/hashline";
10
11
  import astReplaceDescription from "../prompts/tools/ast-replace.md" with { type: "text" };
11
12
  import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, truncateToWidth } from "../tui";
13
+ import { resolveFileDisplayMode } from "../utils/file-display-mode";
12
14
  import type { ToolSession } from ".";
13
15
  import type { OutputMeta } from "./output-meta";
14
16
  import { hasGlobPathChars, parseSearchPath, resolveToCwd } from "./path-utils";
@@ -129,11 +131,15 @@ export class AstReplaceTool implements AgentTool<typeof astReplaceSchema, AstRep
129
131
  }
130
132
  }
131
133
  if (result.changes.length > 0) {
134
+ const useHashLines = resolveFileDisplayMode(this.session).hashLines;
132
135
  lines.push("", "Preview:");
133
136
  for (const change of result.changes.slice(0, 30)) {
137
+ const tag = useHashLines
138
+ ? `${change.startLine}#${computeLineHash(change.startLine, change.before.split("\n", 1)[0] ?? "")}`
139
+ : `${change.startLine}:${change.startColumn}`;
134
140
  const before = (change.before.split("\n", 1)[0] ?? "").slice(0, 80);
135
141
  const after = (change.after.split("\n", 1)[0] ?? "").slice(0, 80);
136
- lines.push(`${change.path}:${change.startLine}:${change.startColumn} ${before} -> ${after}`);
142
+ lines.push(`${change.path}:${tag} ${before} -> ${after}`);
137
143
  }
138
144
  if (result.changes.length > 30) {
139
145
  lines.push(`... ${result.changes.length - 30} more changes`);
package/src/tools/grep.ts CHANGED
@@ -16,7 +16,7 @@ import { Ellipsis, Hasher, type RenderCache, renderStatusLine, renderTreeList, t
16
16
  import { resolveFileDisplayMode } from "../utils/file-display-mode";
17
17
  import type { ToolSession } from ".";
18
18
  import { formatFullOutputReference, type OutputMeta } from "./output-meta";
19
- import { resolveToCwd } from "./path-utils";
19
+ import { hasGlobPathChars, parseSearchPath, resolveToCwd } from "./path-utils";
20
20
  import { formatCount, formatEmptyMessage, formatErrorMessage, PREVIEW_LIMITS } from "./render-utils";
21
21
  import { ToolError } from "./tool-errors";
22
22
  import { toolResult } from "./tool-result";
@@ -106,15 +106,33 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
106
106
 
107
107
  const useHashLines = resolveFileDisplayMode(this.session).hashLines;
108
108
  let searchPath: string;
109
+ let globFilter = glob?.trim() || undefined;
109
110
  const internalRouter = this.session.internalRouter;
110
- if (searchDir && internalRouter?.canHandle(searchDir)) {
111
- const resource = await internalRouter.resolve(searchDir);
112
- if (!resource.sourcePath) {
113
- throw new ToolError(`Cannot grep internal URL without a backing file: ${searchDir}`);
111
+ if (searchDir?.trim()) {
112
+ const rawPath = searchDir.trim();
113
+ if (internalRouter?.canHandle(rawPath)) {
114
+ if (hasGlobPathChars(rawPath)) {
115
+ throw new ToolError(`Glob patterns are not supported for internal URLs: ${rawPath}`);
116
+ }
117
+ const resource = await internalRouter.resolve(rawPath);
118
+ if (!resource.sourcePath) {
119
+ throw new ToolError(`Cannot grep internal URL without a backing file: ${rawPath}`);
120
+ }
121
+ searchPath = resource.sourcePath;
122
+ } else {
123
+ const parsedPath = parseSearchPath(rawPath);
124
+ searchPath = resolveToCwd(parsedPath.basePath, this.session.cwd);
125
+ if (parsedPath.glob) {
126
+ if (globFilter) {
127
+ throw new ToolError(
128
+ "When path already includes glob characters, omit the separate glob parameter",
129
+ );
130
+ }
131
+ globFilter = parsedPath.glob;
132
+ }
114
133
  }
115
- searchPath = resource.sourcePath;
116
134
  } else {
117
- searchPath = resolveToCwd(searchDir || ".", this.session.cwd);
135
+ searchPath = resolveToCwd(".", this.session.cwd);
118
136
  }
119
137
  const scopePath = (() => {
120
138
  const relative = path.relative(this.session.cwd, searchPath).replace(/\\/g, "/");
@@ -139,7 +157,7 @@ export class GrepTool implements AgentTool<typeof grepSchema, GrepToolDetails> {
139
157
  result = await grep({
140
158
  pattern: normalizedPattern,
141
159
  path: searchPath,
142
- glob: glob?.trim() || undefined,
160
+ glob: globFilter,
143
161
  type: type?.trim() || undefined,
144
162
  ignoreCase,
145
163
  multiline: effectiveMultiline,
@@ -4,7 +4,7 @@
4
4
  * Subagents must call this tool to finish and return structured JSON output.
5
5
  */
6
6
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
7
- import { sanitizeSchemaForStrictMode } from "@oh-my-pi/pi-ai/utils/typebox-helpers";
7
+ import { sanitizeSchemaForStrictMode } from "@oh-my-pi/pi-ai/utils/schema";
8
8
  import type { Static, TSchema } from "@sinclair/typebox";
9
9
  import { Type } from "@sinclair/typebox";
10
10
  import Ajv, { type ErrorObject, type ValidateFunction } from "ajv";