@oh-my-pi/pi-coding-agent 14.5.3 → 14.5.6
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 +49 -0
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/sdk/README.md +1 -1
- package/package.json +7 -7
- package/src/config/prompt-templates.ts +103 -8
- package/src/config/settings-schema.ts +14 -13
- package/src/config/settings.ts +1 -1
- package/src/cursor.ts +4 -4
- package/src/edit/index.ts +111 -109
- package/src/edit/line-hash.ts +33 -3
- package/src/edit/modes/apply-patch.ts +6 -4
- package/src/edit/modes/atom.lark +27 -0
- package/src/edit/modes/atom.ts +1039 -841
- package/src/edit/modes/hashline.ts +9 -10
- package/src/edit/modes/patch.ts +23 -19
- package/src/edit/modes/replace.ts +19 -15
- package/src/edit/renderer.ts +65 -8
- package/src/edit/streaming.ts +47 -77
- package/src/extensibility/extensions/types.ts +11 -11
- package/src/extensibility/hooks/types.ts +6 -6
- package/src/lsp/edits.ts +8 -5
- package/src/lsp/index.ts +4 -4
- package/src/lsp/utils.ts +7 -7
- package/src/mcp/discoverable-tool-metadata.ts +1 -1
- package/src/mcp/manager.ts +3 -3
- package/src/mcp/tool-bridge.ts +4 -4
- package/src/memories/index.ts +1 -1
- package/src/modes/acp/acp-event-mapper.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +1 -1
- package/src/modes/components/settings-defs.ts +3 -3
- package/src/modes/components/tree-selector.ts +2 -2
- package/src/modes/utils/ui-helpers.ts +31 -7
- package/src/prompts/agents/explore.md +1 -1
- package/src/prompts/agents/librarian.md +2 -2
- package/src/prompts/agents/plan.md +2 -2
- package/src/prompts/agents/reviewer.md +1 -1
- package/src/prompts/agents/task.md +2 -2
- package/src/prompts/system/plan-mode-active.md +1 -1
- package/src/prompts/system/system-prompt.md +116 -60
- package/src/prompts/tools/apply-patch.md +0 -2
- package/src/prompts/tools/atom.md +81 -63
- package/src/prompts/tools/bash.md +7 -4
- package/src/prompts/tools/checkpoint.md +1 -1
- package/src/prompts/tools/find.md +6 -1
- package/src/prompts/tools/hashline.md +10 -11
- package/src/prompts/tools/patch.md +13 -13
- package/src/prompts/tools/read.md +4 -4
- package/src/prompts/tools/replace.md +3 -3
- package/src/prompts/tools/{grep.md → search.md} +4 -4
- package/src/sdk.ts +19 -9
- package/src/session/agent-session.ts +65 -0
- package/src/system-prompt.ts +15 -5
- package/src/task/executor.ts +5 -0
- package/src/task/index.ts +10 -1
- package/src/tools/ast-edit.ts +4 -6
- package/src/tools/ast-grep.ts +4 -6
- package/src/tools/bash.ts +1 -1
- package/src/tools/file-recorder.ts +6 -6
- package/src/tools/find.ts +11 -13
- package/src/tools/index.ts +7 -7
- package/src/tools/path-utils.ts +31 -4
- package/src/tools/read.ts +12 -6
- package/src/tools/renderers.ts +2 -2
- package/src/tools/{grep.ts → search.ts} +32 -40
- package/src/tools/write.ts +8 -4
- package/src/web/search/index.ts +1 -1
- package/src/edit/block.ts +0 -308
- package/src/edit/indent.ts +0 -150
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,55 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [14.5.6] - 2026-04-29
|
|
6
|
+
### Changed
|
|
7
|
+
|
|
8
|
+
- Removed the atom edit mode's multi-anchor auto-rebase rejection so stale-but-uniquely-rebasable block edits apply with warnings instead of failing.
|
|
9
|
+
|
|
10
|
+
## [14.5.5] - 2026-04-29
|
|
11
|
+
### Breaking Changes
|
|
12
|
+
|
|
13
|
+
- Rejected atom diffs with unrecognized operations (including lone '-' lines) by throwing parse errors instead of treating them as inserts
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
|
|
17
|
+
- Added duplicate-line post-edit detection that warns on newly introduced adjacent identical lines and auto-removes one duplicate when bracket-balance is restored
|
|
18
|
+
- Added a warning when suspicious adjacent duplicates are introduced after edits so users can review potential stale-line issues
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Changed anchor rebase handling to fail when multiple mutating anchors would need auto-rebase, preventing silent misapplied contiguous block rewrites
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Fixed bracket-corruption caused by botched block rewrites by automatically removing a newly introduced duplicate adjacent line when removing it restores the original `{}`, `()`, and `[]` balance and by warning when automatic removal is unsafe
|
|
27
|
+
|
|
28
|
+
## [14.5.4] - 2026-04-28
|
|
29
|
+
### Breaking Changes
|
|
30
|
+
|
|
31
|
+
- Changed the `atom` edit mode from JSON `{ path, edits }` calls to the compact file-oriented `input` patch language that was previously exposed as `atomd`; `atomd` is no longer a separate edit variant
|
|
32
|
+
- Renamed MCP tool identifiers from the `mcp_<server>_<tool>` format to `mcp__<server>_<tool>` so custom tool names, active tool lists, and persisted MCP selections must be updated to the new prefix
|
|
33
|
+
- Renamed the built-in content-search tool from `grep` to `search`, including SDK/tool event names and settings keys (`search.enabled`, `search.contextBefore`, `search.contextAfter`), so integrations using `grep` and `grep.*` references must be updated
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
|
|
37
|
+
- Added internal URL support to the `search` tool, allowing `artifact://`-style paths that resolve to local files to be searched directly
|
|
38
|
+
- Added IRC relay observation in the main agent UI so every IRC exchange between agents is rendered in the main transcript, even when the main agent is not a direct participant
|
|
39
|
+
- Added stateful `href`/`hrefr` prompt helpers that can reuse anchors remembered from prior `hline` helper calls
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- Changed file-path rendering across search, find, AST, LSP, and related edit outputs to display targets as cwd-relative paths when they resolve inside the working directory and keep absolute paths for files outside the cwd
|
|
44
|
+
- Changed system prompt guidance so in-cwd tool paths must be passed as cwd-relative paths and absolute paths only for out-of-cwd targets or `~` expansion
|
|
45
|
+
- Updated `edit` streaming diff previews for `patch`, `replace`, and `hashline` to produce a single request-level preview for the new single-file `path` mode
|
|
46
|
+
- Bumped default `read.defaultLimit` from 300 to 500 lines, and scaled the read tool's byte budget with the line limit (`max(50KB, lines * 512)`) so the configured line count is no longer truncated by the shared 50KB cap
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- Fixed atom edit streaming previews to use atom headers for file names instead of apply_patch parsing errors.
|
|
51
|
+
- Fixed collapsed search result rendering so summary and truncation rows stay within the collapsed output budget
|
|
52
|
+
- Updated search path handling to support path lists and internal file paths while preserving previous search behavior
|
|
53
|
+
|
|
5
54
|
## [14.5.3] - 2026-04-27
|
|
6
55
|
|
|
7
56
|
### Added
|
|
@@ -22,7 +22,7 @@ import type { ExtensionAPI, ExtensionContext } from "@oh-my-pi/pi-coding-agent";
|
|
|
22
22
|
import { Key } from "@oh-my-pi/pi-tui";
|
|
23
23
|
|
|
24
24
|
// Read-only tools for plan mode
|
|
25
|
-
const PLAN_MODE_TOOLS = ["read", "bash", "
|
|
25
|
+
const PLAN_MODE_TOOLS = ["read", "bash", "search", "find"];
|
|
26
26
|
|
|
27
27
|
// Full set of tools for normal mode
|
|
28
28
|
const NORMAL_MODE_TOOLS = ["read", "bash", "edit", "write"];
|
package/examples/sdk/README.md
CHANGED
|
@@ -69,7 +69,7 @@ const { session } = await createAgentSession({
|
|
|
69
69
|
});
|
|
70
70
|
|
|
71
71
|
// Read-only tools
|
|
72
|
-
const { session } = await createAgentSession({ toolNames: ["read", "
|
|
72
|
+
const { session } = await createAgentSession({ toolNames: ["read", "search", "find"], authStorage, modelRegistry });
|
|
73
73
|
|
|
74
74
|
// In-memory
|
|
75
75
|
const { session } = await createAgentSession({
|
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": "14.5.
|
|
4
|
+
"version": "14.5.6",
|
|
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",
|
|
@@ -46,12 +46,12 @@
|
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@agentclientprotocol/sdk": "0.20.0",
|
|
48
48
|
"@mozilla/readability": "^0.6.0",
|
|
49
|
-
"@oh-my-pi/omp-stats": "14.5.
|
|
50
|
-
"@oh-my-pi/pi-agent-core": "14.5.
|
|
51
|
-
"@oh-my-pi/pi-ai": "14.5.
|
|
52
|
-
"@oh-my-pi/pi-natives": "14.5.
|
|
53
|
-
"@oh-my-pi/pi-tui": "14.5.
|
|
54
|
-
"@oh-my-pi/pi-utils": "14.5.
|
|
49
|
+
"@oh-my-pi/omp-stats": "14.5.6",
|
|
50
|
+
"@oh-my-pi/pi-agent-core": "14.5.6",
|
|
51
|
+
"@oh-my-pi/pi-ai": "14.5.6",
|
|
52
|
+
"@oh-my-pi/pi-natives": "14.5.6",
|
|
53
|
+
"@oh-my-pi/pi-tui": "14.5.6",
|
|
54
|
+
"@oh-my-pi/pi-utils": "14.5.6",
|
|
55
55
|
"@puppeteer/browsers": "^2.13.0",
|
|
56
56
|
"@sinclair/typebox": "^0.34.49",
|
|
57
57
|
"@xterm/headless": "^6.0.0",
|
|
@@ -43,24 +43,119 @@ function formatHashlineRef(lineNum: unknown, content: unknown): { num: number; t
|
|
|
43
43
|
return { num, text, ref };
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
interface HashlineHelperRef {
|
|
47
|
+
line: number;
|
|
48
|
+
ref: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface HashlineHelperState {
|
|
52
|
+
last?: HashlineHelperRef;
|
|
53
|
+
byLine: Map<number, HashlineHelperRef>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const HASHLINE_HELPER_STATE = Symbol("hashlineHelperState");
|
|
57
|
+
|
|
58
|
+
interface HashlineHelperStateHolder {
|
|
59
|
+
[HASHLINE_HELPER_STATE]?: HashlineHelperState;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function isHelperOptions(value: unknown): value is prompt.HelperOptions {
|
|
63
|
+
return typeof value === "object" && value !== null && "hash" in value;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function splitHelperArgs(args: unknown[]): { positional: unknown[]; options?: prompt.HelperOptions } {
|
|
67
|
+
const maybeOptions = args.at(-1);
|
|
68
|
+
if (!isHelperOptions(maybeOptions)) return { positional: args };
|
|
69
|
+
return { positional: args.slice(0, -1), options: maybeOptions };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function getHashlineHelperState(context: unknown, options: prompt.HelperOptions | undefined): HashlineHelperState {
|
|
73
|
+
const data = options?.data;
|
|
74
|
+
const root = data?.root;
|
|
75
|
+
const holderTarget = data && typeof data === "object" ? data : root && typeof root === "object" ? root : context;
|
|
76
|
+
if (!holderTarget || typeof holderTarget !== "object") {
|
|
77
|
+
throw new Error("hashline prompt helpers require an object render context");
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const holder = holderTarget as HashlineHelperStateHolder;
|
|
81
|
+
if (!holder[HASHLINE_HELPER_STATE]) {
|
|
82
|
+
holder[HASHLINE_HELPER_STATE] = { byLine: new Map() };
|
|
83
|
+
}
|
|
84
|
+
return holder[HASHLINE_HELPER_STATE];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isLineNumberArg(value: unknown): boolean {
|
|
88
|
+
const num = typeof value === "number" ? value : Number.parseInt(String(value), 10);
|
|
89
|
+
return Number.isFinite(num);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function rememberHashlineRef(state: HashlineHelperState, line: number, ref: string): void {
|
|
93
|
+
const entry = { line, ref };
|
|
94
|
+
state.last = entry;
|
|
95
|
+
state.byLine.set(line, entry);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function requireStoredHashlineRef(state: HashlineHelperState, lineArg?: unknown): string {
|
|
99
|
+
if (lineArg === undefined) {
|
|
100
|
+
if (!state.last) {
|
|
101
|
+
throw new Error("{{href}} requires a previous {{hline}} call in the same prompt render");
|
|
102
|
+
}
|
|
103
|
+
return state.last.ref;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const line = typeof lineArg === "number" ? lineArg : Number.parseInt(String(lineArg), 10);
|
|
107
|
+
const entry = state.byLine.get(line);
|
|
108
|
+
if (!entry) {
|
|
109
|
+
throw new Error(`{{href ${line}}} requires a previous {{hline ${line} ...}} call in the same prompt render`);
|
|
110
|
+
}
|
|
111
|
+
return entry.ref;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function wrapHashlineRef(ref: string, args: unknown[]): string {
|
|
115
|
+
const preStr = typeof args[0] === "string" ? args[0] : "";
|
|
116
|
+
const postStr = typeof args[1] === "string" ? args[1] : "";
|
|
117
|
+
return `${preStr}${ref}${postStr}`;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function resolveHashlineRef(state: HashlineHelperState, args: unknown[]): string {
|
|
121
|
+
if (args.length === 0) return requireStoredHashlineRef(state);
|
|
122
|
+
const [first, second, ...rest] = args;
|
|
123
|
+
if (isLineNumberArg(first)) {
|
|
124
|
+
if (second === undefined) return requireStoredHashlineRef(state, first);
|
|
125
|
+
const { ref } = formatHashlineRef(first, second);
|
|
126
|
+
return wrapHashlineRef(ref, rest);
|
|
127
|
+
}
|
|
128
|
+
return wrapHashlineRef(requireStoredHashlineRef(state), args);
|
|
129
|
+
}
|
|
130
|
+
|
|
46
131
|
/**
|
|
47
132
|
* {{href lineNum "content"}} — compute a real hashline ref for prompt examples.
|
|
48
|
-
* {{href lineNum
|
|
133
|
+
* {{href lineNum}} — quote the ref remembered by the earlier {{hline lineNum "..."}}
|
|
134
|
+
* {{href}} — quote the ref from the previous {{hline}} call.
|
|
135
|
+
* {{href "[" "]"}} — wrap the previous {{hline}} ref with pre/post chars.
|
|
49
136
|
* Returns `"lineNumBIGRAM"` (e.g., `"42nd"`), or `"[42nd]"` when pre/post are supplied.
|
|
50
137
|
*/
|
|
51
|
-
prompt.registerHelper("href", (
|
|
52
|
-
const {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
138
|
+
prompt.registerHelper("href", function (this: unknown, ...args: unknown[]): string {
|
|
139
|
+
const { positional, options } = splitHelperArgs(args);
|
|
140
|
+
const state = getHashlineHelperState(this, options);
|
|
141
|
+
return JSON.stringify(resolveHashlineRef(state, positional));
|
|
142
|
+
});
|
|
143
|
+
prompt.registerHelper("hrefr", function (this: unknown, ...args: unknown[]): string {
|
|
144
|
+
const { positional, options } = splitHelperArgs(args);
|
|
145
|
+
const state = getHashlineHelperState(this, options);
|
|
146
|
+
return resolveHashlineRef(state, positional);
|
|
56
147
|
});
|
|
57
148
|
|
|
58
149
|
/**
|
|
59
150
|
* {{hline lineNum "content"}} — format a full read-style line with prefix.
|
|
60
151
|
* Returns `"lineNumBIGRAM|content"` (pipe between anchor and content).
|
|
61
152
|
*/
|
|
62
|
-
prompt.registerHelper("hline", (
|
|
63
|
-
const {
|
|
153
|
+
prompt.registerHelper("hline", function (this: unknown, ...args: unknown[]): string {
|
|
154
|
+
const { positional, options } = splitHelperArgs(args);
|
|
155
|
+
const [lineNum, content] = positional;
|
|
156
|
+
const { num, ref, text } = formatHashlineRef(lineNum, content);
|
|
157
|
+
const state = getHashlineHelperState(this, options);
|
|
158
|
+
rememberHashlineRef(state, num, ref);
|
|
64
159
|
return `${ref}${HASHLINE_CONTENT_SEPARATOR}${text}`;
|
|
65
160
|
});
|
|
66
161
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { THINKING_EFFORTS } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { TASK_SIMPLE_MODES } from "../task/simple-mode";
|
|
3
|
+
import { EDIT_MODES } from "../utils/edit-mode";
|
|
3
4
|
|
|
4
5
|
/** Unified settings schema - single source of truth for all settings.
|
|
5
6
|
* Unified settings schema - single source of truth for all settings.
|
|
@@ -160,8 +161,8 @@ export const DEFAULT_BASH_INTERCEPTOR_RULES: BashInterceptorRule[] = [
|
|
|
160
161
|
},
|
|
161
162
|
{
|
|
162
163
|
pattern: "^\\s*(grep|rg|ripgrep|ag|ack)\\s+",
|
|
163
|
-
tool: "
|
|
164
|
-
message: "Use the `
|
|
164
|
+
tool: "search",
|
|
165
|
+
message: "Use the `search` tool instead of grep/rg. It respects .gitignore and provides structured output.",
|
|
165
166
|
},
|
|
166
167
|
{
|
|
167
168
|
pattern: "^\\s*(find|fd|locate)\\s+.*(-name|-iname|-type|--type|-glob)",
|
|
@@ -955,12 +956,12 @@ export const SETTINGS_SCHEMA = {
|
|
|
955
956
|
// Edit tool
|
|
956
957
|
"edit.mode": {
|
|
957
958
|
type: "enum",
|
|
958
|
-
values:
|
|
959
|
+
values: EDIT_MODES,
|
|
959
960
|
default: "hashline",
|
|
960
961
|
ui: {
|
|
961
962
|
tab: "editing",
|
|
962
963
|
label: "Edit Mode",
|
|
963
|
-
description: "Select the edit tool variant (replace, patch, hashline, vim, or apply_patch)",
|
|
964
|
+
description: "Select the edit tool variant (replace, patch, hashline, atom, vim, or apply_patch)",
|
|
964
965
|
},
|
|
965
966
|
},
|
|
966
967
|
|
|
@@ -1027,7 +1028,7 @@ export const SETTINGS_SCHEMA = {
|
|
|
1027
1028
|
|
|
1028
1029
|
"read.defaultLimit": {
|
|
1029
1030
|
type: "number",
|
|
1030
|
-
default:
|
|
1031
|
+
default: 500,
|
|
1031
1032
|
ui: {
|
|
1032
1033
|
tab: "editing",
|
|
1033
1034
|
label: "Default Read Limit",
|
|
@@ -1198,30 +1199,30 @@ export const SETTINGS_SCHEMA = {
|
|
|
1198
1199
|
ui: { tab: "tools", label: "Find", description: "Enable the find tool for file searching" },
|
|
1199
1200
|
},
|
|
1200
1201
|
|
|
1201
|
-
"
|
|
1202
|
+
"search.enabled": {
|
|
1202
1203
|
type: "boolean",
|
|
1203
1204
|
default: true,
|
|
1204
|
-
ui: { tab: "tools", label: "
|
|
1205
|
+
ui: { tab: "tools", label: "Search", description: "Enable the search tool for content searching" },
|
|
1205
1206
|
},
|
|
1206
1207
|
|
|
1207
|
-
"
|
|
1208
|
+
"search.contextBefore": {
|
|
1208
1209
|
type: "number",
|
|
1209
1210
|
default: 1,
|
|
1210
1211
|
ui: {
|
|
1211
1212
|
tab: "tools",
|
|
1212
|
-
label: "
|
|
1213
|
-
description: "Lines of context before each
|
|
1213
|
+
label: "Search Context Before",
|
|
1214
|
+
description: "Lines of context before each search match",
|
|
1214
1215
|
submenu: true,
|
|
1215
1216
|
},
|
|
1216
1217
|
},
|
|
1217
1218
|
|
|
1218
|
-
"
|
|
1219
|
+
"search.contextAfter": {
|
|
1219
1220
|
type: "number",
|
|
1220
1221
|
default: 3,
|
|
1221
1222
|
ui: {
|
|
1222
1223
|
tab: "tools",
|
|
1223
|
-
label: "
|
|
1224
|
-
description: "Lines of context after each
|
|
1224
|
+
label: "Search Context After",
|
|
1225
|
+
description: "Lines of context after each search match",
|
|
1225
1226
|
submenu: true,
|
|
1226
1227
|
},
|
|
1227
1228
|
},
|
package/src/config/settings.ts
CHANGED
|
@@ -326,7 +326,7 @@ export class Settings {
|
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
328
|
* Get the edit variant for a specific model.
|
|
329
|
-
* Returns "patch", "replace", "hashline", "vim", "apply_patch", or null (use global default).
|
|
329
|
+
* Returns "patch", "replace", "hashline", "atom", "vim", "apply_patch", or null (use global default).
|
|
330
330
|
*/
|
|
331
331
|
getEditVariantForModel(model: string | undefined): EditMode | null {
|
|
332
332
|
if (!model) return null;
|
package/src/cursor.ts
CHANGED
|
@@ -177,10 +177,10 @@ export class CursorExecHandlers implements ICursorExecHandlers {
|
|
|
177
177
|
|
|
178
178
|
async grep(args: Parameters<NonNullable<ICursorExecHandlers["grep"]>>[0]) {
|
|
179
179
|
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
180
|
-
const
|
|
181
|
-
const toolResultMessage = await executeTool(this.options, "
|
|
180
|
+
const searchPath = args.glob ? `${args.path || "."}/${args.glob}` : args.path || ".";
|
|
181
|
+
const toolResultMessage = await executeTool(this.options, "search", toolCallId, {
|
|
182
182
|
pattern: args.pattern,
|
|
183
|
-
path:
|
|
183
|
+
path: searchPath,
|
|
184
184
|
i: args.caseInsensitive || undefined,
|
|
185
185
|
});
|
|
186
186
|
return toolResultMessage;
|
|
@@ -327,7 +327,7 @@ export class CursorExecHandlers implements ICursorExecHandlers {
|
|
|
327
327
|
const toolCallId = decodeToolCallId(call.toolCallId);
|
|
328
328
|
const tool = this.options.tools.get(toolName);
|
|
329
329
|
if (!tool) {
|
|
330
|
-
const availableTools = Array.from(this.options.tools.keys()).filter(name => name.startsWith("
|
|
330
|
+
const availableTools = Array.from(this.options.tools.keys()).filter(name => name.startsWith("mcp__"));
|
|
331
331
|
const message = formatMcpToolErrorMessage(toolName, availableTools);
|
|
332
332
|
const result = buildToolErrorResult(message);
|
|
333
333
|
return createToolResultMessage(toolCallId, toolName, result, true);
|
package/src/edit/index.ts
CHANGED
|
@@ -19,13 +19,8 @@ import { type EditMode, normalizeEditMode, resolveEditMode } from "../utils/edit
|
|
|
19
19
|
import type { VimToolDetails } from "../vim/types";
|
|
20
20
|
import { type ApplyPatchParams, applyPatchSchema, expandApplyPatchToEntries } from "./modes/apply-patch";
|
|
21
21
|
import applyPatchGrammar from "./modes/apply-patch.lark" with { type: "text" };
|
|
22
|
-
import {
|
|
23
|
-
|
|
24
|
-
type AtomToolEdit,
|
|
25
|
-
atomEditParamsSchema,
|
|
26
|
-
executeAtomSingle,
|
|
27
|
-
resolveAtomEntryPaths,
|
|
28
|
-
} from "./modes/atom";
|
|
22
|
+
import { type AtomParams, atomEditParamsSchema, executeAtomSingle } from "./modes/atom";
|
|
23
|
+
import atomGrammar from "./modes/atom.lark" with { type: "text" };
|
|
29
24
|
import {
|
|
30
25
|
executeHashlineSingle,
|
|
31
26
|
HashlineMismatchError,
|
|
@@ -122,45 +117,11 @@ function createEditWritethrough(session: ToolSession): WritethroughCallback {
|
|
|
122
117
|
return enableLsp ? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics }) : writethroughNoop;
|
|
123
118
|
}
|
|
124
119
|
|
|
125
|
-
/**
|
|
126
|
-
|
|
127
|
-
* If both are absent on an entry, throws a descriptive error.
|
|
128
|
-
*/
|
|
129
|
-
function resolveEntryPaths<T extends { path?: string }>(
|
|
130
|
-
edits: readonly T[],
|
|
131
|
-
topLevelPath: string | undefined,
|
|
132
|
-
): (T & { path: string })[] {
|
|
133
|
-
return edits.map((edit, i) => {
|
|
134
|
-
const path = (edit && typeof edit.path === "string" && edit.path) || topLevelPath;
|
|
135
|
-
if (!path) {
|
|
136
|
-
throw new Error(
|
|
137
|
-
`Edit ${i}: missing \`path\`. Provide \`path\` on this edit or supply a top-level \`path\` for the request.`,
|
|
138
|
-
);
|
|
139
|
-
}
|
|
140
|
-
return { ...edit, path };
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** Group items by a key, preserving insertion order. */
|
|
145
|
-
function groupBy<T, K>(items: T[], key: (item: T) => K): Map<K, T[]> {
|
|
146
|
-
const map = new Map<K, T[]>();
|
|
147
|
-
for (const item of items) {
|
|
148
|
-
const k = key(item);
|
|
149
|
-
let arr = map.get(k);
|
|
150
|
-
if (!arr) {
|
|
151
|
-
arr = [];
|
|
152
|
-
map.set(k, arr);
|
|
153
|
-
}
|
|
154
|
-
arr.push(item);
|
|
155
|
-
}
|
|
156
|
-
return map;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** Run single-file executors for each file group and aggregate results. */
|
|
160
|
-
async function executePerFile(
|
|
120
|
+
/** Run apply_patch file operations and aggregate their multi-file result. */
|
|
121
|
+
async function executeApplyPatchPerFile(
|
|
161
122
|
fileEntries: {
|
|
162
123
|
path: string;
|
|
163
|
-
run: (batchRequest: LspBatchRequest | undefined) => Promise<AgentToolResult<EditToolDetails
|
|
124
|
+
run: (batchRequest: LspBatchRequest | undefined) => Promise<AgentToolResult<EditToolDetails>>;
|
|
164
125
|
}[],
|
|
165
126
|
outerBatchRequest: LspBatchRequest | undefined,
|
|
166
127
|
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
@@ -230,6 +191,58 @@ async function executePerFile(
|
|
|
230
191
|
};
|
|
231
192
|
}
|
|
232
193
|
|
|
194
|
+
async function executeSinglePathEntries(
|
|
195
|
+
path: string,
|
|
196
|
+
runs: ((batchRequest: LspBatchRequest | undefined) => Promise<AgentToolResult<EditToolDetails>>)[],
|
|
197
|
+
outerBatchRequest: LspBatchRequest | undefined,
|
|
198
|
+
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
199
|
+
): Promise<AgentToolResult<EditToolDetails, TInput>> {
|
|
200
|
+
if (runs.length === 1) {
|
|
201
|
+
return runs[0](outerBatchRequest);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const contentTexts: string[] = [];
|
|
205
|
+
const diffTexts: string[] = [];
|
|
206
|
+
let firstChangedLine: number | undefined;
|
|
207
|
+
|
|
208
|
+
for (let i = 0; i < runs.length; i++) {
|
|
209
|
+
const isLast = i === runs.length - 1;
|
|
210
|
+
const batchRequest: LspBatchRequest | undefined = outerBatchRequest
|
|
211
|
+
? { id: outerBatchRequest.id, flush: isLast && outerBatchRequest.flush }
|
|
212
|
+
: undefined;
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = await runs[i](batchRequest);
|
|
216
|
+
const details = result.details;
|
|
217
|
+
if (details?.diff) diffTexts.push(details.diff);
|
|
218
|
+
firstChangedLine ??= details?.firstChangedLine;
|
|
219
|
+
const text = result.content?.find(c => c.type === "text")?.text ?? "";
|
|
220
|
+
if (text) contentTexts.push(text);
|
|
221
|
+
} catch (err) {
|
|
222
|
+
const errorText = err instanceof Error ? err.message : String(err);
|
|
223
|
+
contentTexts.push(`Error editing ${path}: ${errorText}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!isLast && onUpdate) {
|
|
227
|
+
onUpdate({
|
|
228
|
+
content: [{ type: "text", text: contentTexts.join("\n") }],
|
|
229
|
+
details: {
|
|
230
|
+
diff: diffTexts.join("\n"),
|
|
231
|
+
firstChangedLine,
|
|
232
|
+
},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return {
|
|
238
|
+
content: [{ type: "text", text: contentTexts.join("\n") }],
|
|
239
|
+
details: {
|
|
240
|
+
diff: diffTexts.join("\n"),
|
|
241
|
+
firstChangedLine,
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
233
246
|
export class EditTool implements AgentTool<TInput> {
|
|
234
247
|
readonly name = "edit";
|
|
235
248
|
readonly label = "Edit";
|
|
@@ -278,8 +291,9 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
278
291
|
* and fall back to emitting a JSON function tool from `parameters`.
|
|
279
292
|
*/
|
|
280
293
|
get customFormat(): { syntax: "lark"; definition: string } | undefined {
|
|
281
|
-
if (this.mode
|
|
282
|
-
return { syntax: "lark", definition:
|
|
294
|
+
if (this.mode === "apply_patch") return { syntax: "lark", definition: applyPatchGrammar };
|
|
295
|
+
if (this.mode === "atom") return { syntax: "lark", definition: atomGrammar };
|
|
296
|
+
return undefined;
|
|
283
297
|
}
|
|
284
298
|
|
|
285
299
|
/**
|
|
@@ -316,13 +330,12 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
316
330
|
batchRequest: LspBatchRequest | undefined,
|
|
317
331
|
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
318
332
|
) => {
|
|
319
|
-
const { edits, path
|
|
320
|
-
const
|
|
321
|
-
|
|
322
|
-
path: entry.path,
|
|
323
|
-
run: (br: LspBatchRequest | undefined) =>
|
|
333
|
+
const { edits, path } = params as PatchParams;
|
|
334
|
+
const runs = (edits as PatchEditEntry[]).map(
|
|
335
|
+
entry => (br: LspBatchRequest | undefined) =>
|
|
324
336
|
executePatchSingle({
|
|
325
337
|
session: tool.session,
|
|
338
|
+
path,
|
|
326
339
|
params: entry,
|
|
327
340
|
signal,
|
|
328
341
|
batchRequest: br,
|
|
@@ -331,8 +344,8 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
331
344
|
writethrough: tool.#writethrough,
|
|
332
345
|
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
333
346
|
}),
|
|
334
|
-
|
|
335
|
-
return
|
|
347
|
+
);
|
|
348
|
+
return executeSinglePathEntries(path, runs, batchRequest, onUpdate);
|
|
336
349
|
},
|
|
337
350
|
},
|
|
338
351
|
apply_patch: {
|
|
@@ -346,21 +359,25 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
346
359
|
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
347
360
|
) => {
|
|
348
361
|
const entries = expandApplyPatchToEntries(params as ApplyPatchParams);
|
|
349
|
-
const perFile = entries.map(entry =>
|
|
350
|
-
path
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
362
|
+
const perFile = entries.map(entry => {
|
|
363
|
+
const { path, ...patchParams } = entry;
|
|
364
|
+
return {
|
|
365
|
+
path,
|
|
366
|
+
run: (br: LspBatchRequest | undefined) =>
|
|
367
|
+
executePatchSingle({
|
|
368
|
+
session: tool.session,
|
|
369
|
+
path,
|
|
370
|
+
params: patchParams,
|
|
371
|
+
signal,
|
|
372
|
+
batchRequest: br,
|
|
373
|
+
allowFuzzy: tool.#allowFuzzy,
|
|
374
|
+
fuzzyThreshold: tool.#fuzzyThreshold,
|
|
375
|
+
writethrough: tool.#writethrough,
|
|
376
|
+
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
377
|
+
}),
|
|
378
|
+
};
|
|
379
|
+
});
|
|
380
|
+
return executeApplyPatchPerFile(perFile, batchRequest, onUpdate);
|
|
364
381
|
},
|
|
365
382
|
},
|
|
366
383
|
hashline: {
|
|
@@ -371,25 +388,18 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
371
388
|
params: EditParams,
|
|
372
389
|
signal: AbortSignal | undefined,
|
|
373
390
|
batchRequest: LspBatchRequest | undefined,
|
|
374
|
-
|
|
391
|
+
_onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
375
392
|
) => {
|
|
376
|
-
const { edits, path
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
const entries = [...byFile.entries()].map(([path, fileEdits]) => ({
|
|
393
|
+
const { edits, path } = params as HashlineParams;
|
|
394
|
+
return executeHashlineSingle({
|
|
395
|
+
session: tool.session,
|
|
380
396
|
path,
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
batchRequest: br,
|
|
388
|
-
writethrough: tool.#writethrough,
|
|
389
|
-
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
390
|
-
}),
|
|
391
|
-
}));
|
|
392
|
-
return executePerFile(entries, batchRequest, onUpdate);
|
|
397
|
+
edits: edits as HashlineToolEdit[],
|
|
398
|
+
signal,
|
|
399
|
+
batchRequest,
|
|
400
|
+
writethrough: tool.#writethrough,
|
|
401
|
+
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
402
|
+
});
|
|
393
403
|
},
|
|
394
404
|
},
|
|
395
405
|
atom: {
|
|
@@ -400,25 +410,18 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
400
410
|
params: EditParams,
|
|
401
411
|
signal: AbortSignal | undefined,
|
|
402
412
|
batchRequest: LspBatchRequest | undefined,
|
|
403
|
-
|
|
413
|
+
_onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
404
414
|
) => {
|
|
405
|
-
const {
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
415
|
+
const { input, path } = params as AtomParams & { path?: string };
|
|
416
|
+
return executeAtomSingle({
|
|
417
|
+
session: tool.session,
|
|
418
|
+
input,
|
|
409
419
|
path,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
signal,
|
|
416
|
-
batchRequest: br,
|
|
417
|
-
writethrough: tool.#writethrough,
|
|
418
|
-
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
419
|
-
}),
|
|
420
|
-
}));
|
|
421
|
-
return executePerFile(entries, batchRequest, onUpdate);
|
|
420
|
+
signal,
|
|
421
|
+
batchRequest,
|
|
422
|
+
writethrough: tool.#writethrough,
|
|
423
|
+
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
424
|
+
});
|
|
422
425
|
},
|
|
423
426
|
},
|
|
424
427
|
replace: {
|
|
@@ -431,13 +434,12 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
431
434
|
batchRequest: LspBatchRequest | undefined,
|
|
432
435
|
onUpdate?: (partialResult: AgentToolResult<EditToolDetails, TInput>) => void,
|
|
433
436
|
) => {
|
|
434
|
-
const { edits, path
|
|
435
|
-
const
|
|
436
|
-
|
|
437
|
-
path: entry.path,
|
|
438
|
-
run: (br: LspBatchRequest | undefined) =>
|
|
437
|
+
const { edits, path } = params as ReplaceParams;
|
|
438
|
+
const runs = (edits as ReplaceEditEntry[]).map(
|
|
439
|
+
entry => (br: LspBatchRequest | undefined) =>
|
|
439
440
|
executeReplaceSingle({
|
|
440
441
|
session: tool.session,
|
|
442
|
+
path,
|
|
441
443
|
params: entry,
|
|
442
444
|
signal,
|
|
443
445
|
batchRequest: br,
|
|
@@ -446,8 +448,8 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
446
448
|
writethrough: tool.#writethrough,
|
|
447
449
|
beginDeferredDiagnosticsForPath: p => tool.#beginDeferredDiagnosticsForPath(p),
|
|
448
450
|
}),
|
|
449
|
-
|
|
450
|
-
return
|
|
451
|
+
);
|
|
452
|
+
return executeSinglePathEntries(path, runs, batchRequest, onUpdate);
|
|
451
453
|
},
|
|
452
454
|
},
|
|
453
455
|
vim: {
|