@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 +37 -1
- package/package.json +7 -7
- package/src/ipy/gateway-coordinator.ts +1 -1
- package/src/mcp/tool-bridge.ts +3 -42
- package/src/modes/interactive-mode.ts +4 -1
- package/src/modes/theme/theme.ts +11 -10
- package/src/modes/types.ts +1 -1
- package/src/prompts/system/system-prompt.md +15 -1
- package/src/prompts/tools/ast-find.md +16 -0
- package/src/prompts/tools/ast-replace.md +16 -0
- package/src/session/agent-session.ts +3 -3
- package/src/slash-commands/builtin-registry.ts +4 -2
- package/src/tools/ast-find.ts +13 -3
- package/src/tools/ast-replace.ts +7 -1
- package/src/tools/grep.ts +26 -8
- package/src/tools/submit-result.ts +1 -1
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.
|
|
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.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.3.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.3.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.3.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.3.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.3.
|
|
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,
|
|
113
|
+
fd = await fs.promises.open(lockPath, "wx");
|
|
114
114
|
let heartbeatRunning = true;
|
|
115
115
|
const heartbeat = (async () => {
|
|
116
116
|
while (heartbeatRunning) {
|
package/src/mcp/tool-bridge.ts
CHANGED
|
@@ -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 =
|
|
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 =
|
|
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> {
|
package/src/modes/theme/theme.ts
CHANGED
|
@@ -1629,25 +1629,26 @@ function detectTerminalBackground(): "dark" | "light" {
|
|
|
1629
1629
|
if (terminalReportedAppearance) {
|
|
1630
1630
|
return terminalReportedAppearance;
|
|
1631
1631
|
}
|
|
1632
|
-
//
|
|
1633
|
-
//
|
|
1634
|
-
//
|
|
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
|
-
|
|
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
|
|
package/src/modes/types.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
-
|
|
79
|
-
|
|
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
|
},
|
package/src/tools/ast-find.ts
CHANGED
|
@@ -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
|
|
140
|
-
|
|
141
|
-
|
|
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))
|
package/src/tools/ast-replace.ts
CHANGED
|
@@ -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}:${
|
|
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
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
|
|
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(
|
|
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:
|
|
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/
|
|
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";
|