@oh-my-pi/pi-coding-agent 6.2.0 → 6.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -0
- package/docs/sdk.md +1 -1
- package/package.json +5 -5
- package/scripts/generate-template.ts +6 -6
- package/src/cli/args.ts +3 -0
- package/src/core/agent-session.ts +39 -0
- package/src/core/bash-executor.ts +3 -3
- package/src/core/cursor/exec-bridge.ts +95 -88
- package/src/core/custom-commands/bundled/review/index.ts +142 -145
- package/src/core/custom-commands/bundled/wt/index.ts +68 -66
- package/src/core/custom-commands/loader.ts +4 -6
- package/src/core/custom-tools/index.ts +2 -2
- package/src/core/custom-tools/loader.ts +66 -61
- package/src/core/custom-tools/types.ts +4 -4
- package/src/core/custom-tools/wrapper.ts +61 -25
- package/src/core/event-bus.ts +19 -47
- package/src/core/extensions/index.ts +8 -4
- package/src/core/extensions/loader.ts +160 -120
- package/src/core/extensions/types.ts +4 -4
- package/src/core/extensions/wrapper.ts +149 -100
- package/src/core/hooks/index.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +96 -70
- package/src/core/hooks/types.ts +1 -2
- package/src/core/index.ts +1 -0
- package/src/core/mcp/index.ts +6 -2
- package/src/core/mcp/json-rpc.ts +88 -0
- package/src/core/mcp/loader.ts +22 -4
- package/src/core/mcp/manager.ts +202 -48
- package/src/core/mcp/tool-bridge.ts +143 -55
- package/src/core/mcp/tool-cache.ts +122 -0
- package/src/core/python-executor.ts +3 -9
- package/src/core/sdk.ts +33 -32
- package/src/core/session-manager.ts +30 -0
- package/src/core/settings-manager.ts +34 -1
- package/src/core/ssh/ssh-executor.ts +6 -84
- package/src/core/streaming-output.ts +107 -53
- package/src/core/tools/ask.ts +92 -93
- package/src/core/tools/bash.ts +103 -94
- package/src/core/tools/calculator.ts +41 -26
- package/src/core/tools/complete.ts +76 -66
- package/src/core/tools/context.ts +22 -24
- package/src/core/tools/exa/index.ts +1 -1
- package/src/core/tools/exa/mcp-client.ts +56 -101
- package/src/core/tools/find.ts +250 -253
- package/src/core/tools/git.ts +39 -33
- package/src/core/tools/grep.ts +440 -427
- package/src/core/tools/index.ts +62 -61
- package/src/core/tools/ls.ts +119 -114
- package/src/core/tools/lsp/clients/biome-client.ts +5 -7
- package/src/core/tools/lsp/clients/index.ts +4 -4
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +5 -7
- package/src/core/tools/lsp/config.ts +2 -2
- package/src/core/tools/lsp/index.ts +604 -578
- package/src/core/tools/notebook.ts +121 -119
- package/src/core/tools/output.ts +163 -147
- package/src/core/tools/patch/applicator.ts +1100 -0
- package/src/core/tools/patch/diff.ts +362 -0
- package/src/core/tools/patch/fuzzy.ts +647 -0
- package/src/core/tools/patch/index.ts +430 -0
- package/src/core/tools/patch/normalize.ts +220 -0
- package/src/core/tools/patch/normative.ts +49 -0
- package/src/core/tools/patch/parser.ts +528 -0
- package/src/core/tools/patch/shared.ts +228 -0
- package/src/core/tools/patch/types.ts +244 -0
- package/src/core/tools/python.ts +139 -136
- package/src/core/tools/read.ts +237 -216
- package/src/core/tools/render-utils.ts +196 -77
- package/src/core/tools/renderers.ts +1 -1
- package/src/core/tools/ssh.ts +99 -80
- package/src/core/tools/task/executor.ts +11 -7
- package/src/core/tools/task/index.ts +352 -343
- package/src/core/tools/task/worker.ts +13 -23
- package/src/core/tools/todo-write.ts +74 -59
- package/src/core/tools/web-fetch.ts +54 -47
- package/src/core/tools/web-search/index.ts +27 -16
- package/src/core/tools/write.ts +73 -44
- package/src/core/ttsr.ts +106 -152
- package/src/core/voice.ts +49 -39
- package/src/index.ts +16 -12
- package/src/lib/worktree/index.ts +1 -9
- package/src/modes/interactive/components/diff.ts +15 -8
- package/src/modes/interactive/components/settings-defs.ts +24 -0
- package/src/modes/interactive/components/tool-execution.ts +34 -6
- package/src/modes/interactive/controllers/event-controller.ts +6 -19
- package/src/modes/interactive/controllers/input-controller.ts +1 -1
- package/src/modes/interactive/utils/ui-helpers.ts +5 -1
- package/src/modes/rpc/rpc-mode.ts +99 -81
- package/src/prompts/tools/patch.md +76 -0
- package/src/prompts/tools/read.md +1 -1
- package/src/prompts/tools/{edit.md → replace.md} +1 -0
- package/src/utils/shell.ts +0 -40
- package/src/core/tools/edit-diff.ts +0 -574
- package/src/core/tools/edit.ts +0 -345
|
@@ -134,6 +134,8 @@ export class EventController {
|
|
|
134
134
|
content.arguments,
|
|
135
135
|
{
|
|
136
136
|
showImages: this.ctx.settingsManager.getShowImages(),
|
|
137
|
+
editFuzzyThreshold: this.ctx.settingsManager.getEditFuzzyThreshold(),
|
|
138
|
+
editAllowFuzzy: this.ctx.settingsManager.getEditFuzzyMatch(),
|
|
137
139
|
},
|
|
138
140
|
tool,
|
|
139
141
|
this.ctx.ui,
|
|
@@ -174,26 +176,9 @@ export class EventController {
|
|
|
174
176
|
}
|
|
175
177
|
|
|
176
178
|
if (
|
|
177
|
-
this.ctx.streamingMessage.stopReason
|
|
178
|
-
this.ctx.streamingMessage.stopReason
|
|
179
|
+
this.ctx.streamingMessage.stopReason !== "aborted" &&
|
|
180
|
+
this.ctx.streamingMessage.stopReason !== "error"
|
|
179
181
|
) {
|
|
180
|
-
if (!this.ctx.session.isTtsrAbortPending) {
|
|
181
|
-
if (!errorMessage) {
|
|
182
|
-
errorMessage = this.ctx.streamingMessage.errorMessage || "Error";
|
|
183
|
-
}
|
|
184
|
-
for (const [toolCallId, component] of this.ctx.pendingTools.entries()) {
|
|
185
|
-
component.updateResult(
|
|
186
|
-
{
|
|
187
|
-
content: [{ type: "text", text: errorMessage }],
|
|
188
|
-
isError: true,
|
|
189
|
-
},
|
|
190
|
-
false,
|
|
191
|
-
toolCallId,
|
|
192
|
-
);
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
this.ctx.pendingTools.clear();
|
|
196
|
-
} else {
|
|
197
182
|
for (const [toolCallId, component] of this.ctx.pendingTools.entries()) {
|
|
198
183
|
component.setArgsComplete(toolCallId);
|
|
199
184
|
}
|
|
@@ -223,6 +208,8 @@ export class EventController {
|
|
|
223
208
|
event.args,
|
|
224
209
|
{
|
|
225
210
|
showImages: this.ctx.settingsManager.getShowImages(),
|
|
211
|
+
editFuzzyThreshold: this.ctx.settingsManager.getEditFuzzyThreshold(),
|
|
212
|
+
editAllowFuzzy: this.ctx.settingsManager.getEditFuzzyMatch(),
|
|
226
213
|
},
|
|
227
214
|
tool,
|
|
228
215
|
this.ctx.ui,
|
|
@@ -338,7 +338,7 @@ export class InputController {
|
|
|
338
338
|
|
|
339
339
|
// Generate session title on first message
|
|
340
340
|
const hasUserMessages = this.ctx.agent.state.messages.some((m: AgentMessage) => m.role === "user");
|
|
341
|
-
if (!hasUserMessages && !this.ctx.sessionManager.getSessionTitle()) {
|
|
341
|
+
if (!hasUserMessages && !this.ctx.sessionManager.getSessionTitle() && !process.env.OMP_NO_TITLE) {
|
|
342
342
|
const registry = this.ctx.session.modelRegistry;
|
|
343
343
|
const smolModel = this.ctx.settingsManager.getModelRole("smol");
|
|
344
344
|
generateSessionTitle(text, registry, smolModel, this.ctx.session.sessionId)
|
|
@@ -206,7 +206,11 @@ export class UiHelpers {
|
|
|
206
206
|
const component = new ToolExecutionComponent(
|
|
207
207
|
content.name,
|
|
208
208
|
content.arguments,
|
|
209
|
-
{
|
|
209
|
+
{
|
|
210
|
+
showImages: this.ctx.settingsManager.getShowImages(),
|
|
211
|
+
editFuzzyThreshold: this.ctx.settingsManager.getEditFuzzyThreshold(),
|
|
212
|
+
editAllowFuzzy: this.ctx.settingsManager.getEditFuzzyMatch(),
|
|
213
|
+
},
|
|
210
214
|
tool,
|
|
211
215
|
this.ctx.ui,
|
|
212
216
|
this.ctx.sessionManager.getCwd(),
|
|
@@ -67,55 +67,60 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
67
67
|
// Shutdown request flag (wrapped in object to allow mutation with const)
|
|
68
68
|
const shutdownState = { requested: false };
|
|
69
69
|
|
|
70
|
-
/**
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
70
|
+
/**
|
|
71
|
+
* Extension UI context that uses the RPC protocol.
|
|
72
|
+
*/
|
|
73
|
+
class RpcExtensionUIContext implements ExtensionUIContext {
|
|
74
|
+
constructor(
|
|
75
|
+
private pendingRequests: Map<string, PendingExtensionRequest>,
|
|
76
|
+
private output: (obj: RpcResponse | RpcExtensionUIRequest | object) => void,
|
|
77
|
+
) {}
|
|
78
|
+
|
|
79
|
+
/** Helper for dialog methods with signal/timeout support */
|
|
80
|
+
private createDialogPromise<T>(
|
|
81
|
+
opts: ExtensionUIDialogOptions | undefined,
|
|
82
|
+
defaultValue: T,
|
|
83
|
+
request: Record<string, unknown>,
|
|
84
|
+
parseResponse: (response: RpcExtensionUIResponse) => T,
|
|
85
|
+
): Promise<T> {
|
|
86
|
+
if (opts?.signal?.aborted) return Promise.resolve(defaultValue);
|
|
87
|
+
|
|
88
|
+
const id = nanoid();
|
|
89
|
+
return new Promise((resolve, reject) => {
|
|
90
|
+
let timeoutId: ReturnType<typeof setTimeout> | undefined;
|
|
91
|
+
|
|
92
|
+
const cleanup = () => {
|
|
93
|
+
if (timeoutId) clearTimeout(timeoutId);
|
|
94
|
+
opts?.signal?.removeEventListener("abort", onAbort);
|
|
95
|
+
this.pendingRequests.delete(id);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const onAbort = () => {
|
|
97
99
|
cleanup();
|
|
98
100
|
resolve(defaultValue);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
+
};
|
|
102
|
+
opts?.signal?.addEventListener("abort", onAbort, { once: true });
|
|
101
103
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
if (opts?.timeout !== undefined) {
|
|
105
|
+
timeoutId = setTimeout(() => {
|
|
106
|
+
cleanup();
|
|
107
|
+
resolve(defaultValue);
|
|
108
|
+
}, opts.timeout);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
this.pendingRequests.set(id, {
|
|
112
|
+
resolve: (response: RpcExtensionUIResponse) => {
|
|
113
|
+
cleanup();
|
|
114
|
+
resolve(parseResponse(response));
|
|
115
|
+
},
|
|
116
|
+
reject,
|
|
117
|
+
});
|
|
118
|
+
this.output({ type: "extension_ui_request", id, ...request } as RpcExtensionUIRequest);
|
|
108
119
|
});
|
|
109
|
-
|
|
110
|
-
});
|
|
111
|
-
}
|
|
120
|
+
}
|
|
112
121
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*/
|
|
116
|
-
const createExtensionUIContext = (): ExtensionUIContext => ({
|
|
117
|
-
select: (title, options, dialogOptions) =>
|
|
118
|
-
createDialogPromise(
|
|
122
|
+
select(title: string, options: string[], dialogOptions?: ExtensionUIDialogOptions): Promise<string | undefined> {
|
|
123
|
+
return this.createDialogPromise(
|
|
119
124
|
dialogOptions,
|
|
120
125
|
undefined,
|
|
121
126
|
{ method: "select", title, options, timeout: dialogOptions?.timeout },
|
|
@@ -125,10 +130,11 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
125
130
|
: "value" in response
|
|
126
131
|
? response.value
|
|
127
132
|
: undefined,
|
|
128
|
-
)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
129
135
|
|
|
130
|
-
confirm:
|
|
131
|
-
createDialogPromise(
|
|
136
|
+
confirm(title: string, message: string, dialogOptions?: ExtensionUIDialogOptions): Promise<boolean> {
|
|
137
|
+
return this.createDialogPromise(
|
|
132
138
|
dialogOptions,
|
|
133
139
|
false,
|
|
134
140
|
{ method: "confirm", title, message, timeout: dialogOptions?.timeout },
|
|
@@ -138,10 +144,15 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
138
144
|
: "confirmed" in response
|
|
139
145
|
? response.confirmed
|
|
140
146
|
: false,
|
|
141
|
-
)
|
|
147
|
+
);
|
|
148
|
+
}
|
|
142
149
|
|
|
143
|
-
input
|
|
144
|
-
|
|
150
|
+
input(
|
|
151
|
+
title: string,
|
|
152
|
+
placeholder?: string,
|
|
153
|
+
dialogOptions?: ExtensionUIDialogOptions,
|
|
154
|
+
): Promise<string | undefined> {
|
|
155
|
+
return this.createDialogPromise(
|
|
145
156
|
dialogOptions,
|
|
146
157
|
undefined,
|
|
147
158
|
{ method: "input", title, placeholder, timeout: dialogOptions?.timeout },
|
|
@@ -151,34 +162,35 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
151
162
|
: "value" in response
|
|
152
163
|
? response.value
|
|
153
164
|
: undefined,
|
|
154
|
-
)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
155
167
|
|
|
156
168
|
notify(message: string, type?: "info" | "warning" | "error"): void {
|
|
157
169
|
// Fire and forget - no response needed
|
|
158
|
-
output({
|
|
170
|
+
this.output({
|
|
159
171
|
type: "extension_ui_request",
|
|
160
172
|
id: nanoid(),
|
|
161
173
|
method: "notify",
|
|
162
174
|
message,
|
|
163
175
|
notifyType: type,
|
|
164
176
|
} as RpcExtensionUIRequest);
|
|
165
|
-
}
|
|
177
|
+
}
|
|
166
178
|
|
|
167
179
|
setStatus(key: string, text: string | undefined): void {
|
|
168
180
|
// Fire and forget - no response needed
|
|
169
|
-
output({
|
|
181
|
+
this.output({
|
|
170
182
|
type: "extension_ui_request",
|
|
171
183
|
id: nanoid(),
|
|
172
184
|
method: "setStatus",
|
|
173
185
|
statusKey: key,
|
|
174
186
|
statusText: text,
|
|
175
187
|
} as RpcExtensionUIRequest);
|
|
176
|
-
}
|
|
188
|
+
}
|
|
177
189
|
|
|
178
190
|
setWidget(key: string, content: unknown): void {
|
|
179
191
|
// Only support string arrays in RPC mode - factory functions are ignored
|
|
180
192
|
if (content === undefined || Array.isArray(content)) {
|
|
181
|
-
output({
|
|
193
|
+
this.output({
|
|
182
194
|
type: "extension_ui_request",
|
|
183
195
|
id: nanoid(),
|
|
184
196
|
method: "setWidget",
|
|
@@ -187,53 +199,53 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
187
199
|
} as RpcExtensionUIRequest);
|
|
188
200
|
}
|
|
189
201
|
// Component factories are not supported in RPC mode - would need TUI access
|
|
190
|
-
}
|
|
202
|
+
}
|
|
191
203
|
|
|
192
204
|
setFooter(_factory: unknown): void {
|
|
193
205
|
// Custom footer not supported in RPC mode - requires TUI access
|
|
194
|
-
}
|
|
206
|
+
}
|
|
195
207
|
|
|
196
208
|
setHeader(_factory: unknown): void {
|
|
197
209
|
// Custom header not supported in RPC mode - requires TUI access
|
|
198
|
-
}
|
|
210
|
+
}
|
|
199
211
|
|
|
200
212
|
setTitle(title: string): void {
|
|
201
213
|
// Fire and forget - host can implement terminal title control
|
|
202
|
-
output({
|
|
214
|
+
this.output({
|
|
203
215
|
type: "extension_ui_request",
|
|
204
216
|
id: nanoid(),
|
|
205
217
|
method: "setTitle",
|
|
206
218
|
title,
|
|
207
219
|
} as RpcExtensionUIRequest);
|
|
208
|
-
}
|
|
220
|
+
}
|
|
209
221
|
|
|
210
|
-
async custom() {
|
|
222
|
+
async custom(): Promise<never> {
|
|
211
223
|
// Custom UI not supported in RPC mode
|
|
212
224
|
return undefined as never;
|
|
213
|
-
}
|
|
225
|
+
}
|
|
214
226
|
|
|
215
227
|
setEditorText(text: string): void {
|
|
216
228
|
// Fire and forget - host can implement editor control
|
|
217
|
-
output({
|
|
229
|
+
this.output({
|
|
218
230
|
type: "extension_ui_request",
|
|
219
231
|
id: nanoid(),
|
|
220
232
|
method: "set_editor_text",
|
|
221
233
|
text,
|
|
222
234
|
} as RpcExtensionUIRequest);
|
|
223
|
-
}
|
|
235
|
+
}
|
|
224
236
|
|
|
225
237
|
getEditorText(): string {
|
|
226
238
|
// Synchronous method can't wait for RPC response
|
|
227
239
|
// Host should track editor state locally if needed
|
|
228
240
|
return "";
|
|
229
|
-
}
|
|
241
|
+
}
|
|
230
242
|
|
|
231
243
|
async editor(title: string, prefill?: string): Promise<string | undefined> {
|
|
232
244
|
const id = nanoid();
|
|
233
245
|
return new Promise((resolve, reject) => {
|
|
234
|
-
|
|
246
|
+
this.pendingRequests.set(id, {
|
|
235
247
|
resolve: (response: RpcExtensionUIResponse) => {
|
|
236
|
-
|
|
248
|
+
this.pendingRequests.delete(id);
|
|
237
249
|
if ("cancelled" in response && response.cancelled) {
|
|
238
250
|
resolve(undefined);
|
|
239
251
|
} else if ("value" in response) {
|
|
@@ -244,31 +256,37 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
244
256
|
},
|
|
245
257
|
reject,
|
|
246
258
|
});
|
|
247
|
-
output({
|
|
259
|
+
this.output({
|
|
260
|
+
type: "extension_ui_request",
|
|
261
|
+
id,
|
|
262
|
+
method: "editor",
|
|
263
|
+
title,
|
|
264
|
+
prefill,
|
|
265
|
+
} as RpcExtensionUIRequest);
|
|
248
266
|
});
|
|
249
|
-
}
|
|
267
|
+
}
|
|
250
268
|
|
|
251
|
-
get theme() {
|
|
269
|
+
get theme(): Theme {
|
|
252
270
|
return theme;
|
|
253
|
-
}
|
|
271
|
+
}
|
|
254
272
|
|
|
255
|
-
getAllThemes() {
|
|
273
|
+
getAllThemes(): { name: string; path: string | undefined }[] {
|
|
256
274
|
return [];
|
|
257
|
-
}
|
|
275
|
+
}
|
|
258
276
|
|
|
259
|
-
getTheme(_name: string) {
|
|
277
|
+
getTheme(_name: string): Theme | undefined {
|
|
260
278
|
return undefined;
|
|
261
|
-
}
|
|
279
|
+
}
|
|
262
280
|
|
|
263
|
-
setTheme(_theme: string | Theme) {
|
|
281
|
+
setTheme(_theme: string | Theme): { success: boolean; error?: string } {
|
|
264
282
|
// Theme switching not supported in RPC mode
|
|
265
283
|
return { success: false, error: "Theme switching not supported in RPC mode" };
|
|
266
|
-
}
|
|
284
|
+
}
|
|
267
285
|
|
|
268
286
|
setEditorComponent(): void {
|
|
269
287
|
// Custom editor components not supported in RPC mode
|
|
270
|
-
}
|
|
271
|
-
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
272
290
|
|
|
273
291
|
// Set up extensions with RPC-based UI context
|
|
274
292
|
const extensionRunner = session.extensionRunner;
|
|
@@ -352,7 +370,7 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
352
370
|
await session.compact(instructions, options);
|
|
353
371
|
},
|
|
354
372
|
},
|
|
355
|
-
|
|
373
|
+
new RpcExtensionUIContext(pendingExtensionRequests, output),
|
|
356
374
|
);
|
|
357
375
|
extensionRunner.onError((err) => {
|
|
358
376
|
output({ type: "extension_error", extensionPath: err.extensionPath, event: err.event, error: err.error });
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
Performs patch operations on a file given a diff.
|
|
2
|
+
This is your primary tool for making changes to existing files.
|
|
3
|
+
|
|
4
|
+
<critical>
|
|
5
|
+
- Always read the target file before editing.
|
|
6
|
+
- Copy anchors + context lines verbatim (including whitespace).
|
|
7
|
+
- Output the clean patch format below.
|
|
8
|
+
</critical>
|
|
9
|
+
|
|
10
|
+
<parameters>
|
|
11
|
+
```ts
|
|
12
|
+
type T =
|
|
13
|
+
// Diff is one or more hunks, within the same file.
|
|
14
|
+
// - Each hunk begins with "@@" (optionally with an anchor).
|
|
15
|
+
// - Each hunk body contains only lines starting with: ' ' | '+' | '-'.
|
|
16
|
+
// - Each hunk must include at least one real change (+ or -). No no-op hunks.
|
|
17
|
+
| { path: string, op: "update", diff: string }
|
|
18
|
+
// Diff is the full file content, no prefixes.
|
|
19
|
+
| { path: string, op: "create", diff: string }
|
|
20
|
+
// Omit diff for delete operation.
|
|
21
|
+
| { path: string, op: "delete" }
|
|
22
|
+
// New path for update-and-move operation.
|
|
23
|
+
| { path: string, op: "update", rename: string, diff: string }
|
|
24
|
+
```
|
|
25
|
+
</parameters>
|
|
26
|
+
|
|
27
|
+
<hunk_header>
|
|
28
|
+
Allowed:
|
|
29
|
+
- `@@`
|
|
30
|
+
- `@@ $ANCHOR`
|
|
31
|
+
|
|
32
|
+
ANCHOR RULES:
|
|
33
|
+
- `$ANCHOR` MUST be copied verbatim from the file as either:
|
|
34
|
+
- a full existing line, OR
|
|
35
|
+
- a unique substring of a single existing line.
|
|
36
|
+
- NEVER use it as a comment:
|
|
37
|
+
- line numbers / ranges: `line 207`, `lines 26-37`
|
|
38
|
+
- location labels: `top of file`, `start`, `near imports`
|
|
39
|
+
- placeholders: `@@ @@`, `...`
|
|
40
|
+
</hunk_header>
|
|
41
|
+
|
|
42
|
+
<anchor_selection>
|
|
43
|
+
ANCHOR SELECTION ALGORITHM (use in this order):
|
|
44
|
+
1) If the surrounding context lines are already unique in the file, use bare `@@`.
|
|
45
|
+
2) Else choose an anchor that is highly specific and stable, copied from the file, e.g.:
|
|
46
|
+
- full function signature line
|
|
47
|
+
- class declaration line
|
|
48
|
+
- a unique string literal / error message
|
|
49
|
+
- a config key with uncommon name
|
|
50
|
+
3) If you get "Found multiple matches", escalate by:
|
|
51
|
+
- adding more context lines, OR
|
|
52
|
+
- using multiple hunks with separate nearby anchors, OR
|
|
53
|
+
- using a more specific anchor substring (longer, includes identifiers).
|
|
54
|
+
NEVER use generic anchors like `import`, `export`, `describe`, `function`, `const`.
|
|
55
|
+
</anchor_selection>
|
|
56
|
+
|
|
57
|
+
<context_rules>
|
|
58
|
+
- Include enough context lines (' ' prefixed) to make the match unique (usually 2–8 total).
|
|
59
|
+
- Context lines must exist in the file exactly as written; preserve indentation/trailing spaces.
|
|
60
|
+
</context_rules>
|
|
61
|
+
|
|
62
|
+
<example name="create">
|
|
63
|
+
edit {"path":"hello.txt","op":"create","diff":"Hello\n"}
|
|
64
|
+
</example>
|
|
65
|
+
|
|
66
|
+
<example name="update">
|
|
67
|
+
edit {"path":"src/app.py","op":"update","diff":"@@ def greet():\n def greet():\n-print('Hi')\n+print('Hello')\n"}
|
|
68
|
+
</example>
|
|
69
|
+
|
|
70
|
+
<example name="rename">
|
|
71
|
+
edit {"path":"src/app.py","op":"update","rename":"src/main.py","diff":"@@\n ...\n"}
|
|
72
|
+
</example>
|
|
73
|
+
|
|
74
|
+
<example name="delete">
|
|
75
|
+
edit {"path":"obsolete.txt","op":"delete"}
|
|
76
|
+
</example>
|
|
@@ -5,7 +5,7 @@ Usage:
|
|
|
5
5
|
- By default, it reads up to {{DEFAULT_MAX_LINES}} lines starting from the beginning of the file
|
|
6
6
|
- You can optionally specify a line offset and limit (especially handy for long files), but it's recommended to read the whole file by not providing these parameters
|
|
7
7
|
- Any lines longer than 500 characters will be truncated
|
|
8
|
-
- By default, results
|
|
8
|
+
- By default, results omit line numbers. Use `lines: true` to include them
|
|
9
9
|
- This tool allows Claude Code to read images (eg PNG, JPG, etc). When reading an image file the contents are presented visually as Claude Code is a multimodal LLM.
|
|
10
10
|
- This tool can read PDF files (.pdf). PDFs are processed page by page, extracting both text and visual content for analysis.
|
|
11
11
|
- This tool can read Jupyter notebooks (.ipynb files) and returns all cells with their outputs, combining code, text, and visualizations.
|
|
@@ -2,6 +2,7 @@ Performs string replacements in files with fuzzy whitespace matching.
|
|
|
2
2
|
|
|
3
3
|
Usage:
|
|
4
4
|
- Use the smallest edit that uniquely identifies the change. To toggle a checkbox: `- [ ] Task` → `- [x] Task`, not the entire line.
|
|
5
|
+
- If the old text is not unique, expand the replacement to a larger block (function/class/section) so it is unique.
|
|
5
6
|
- You must use your read tool at least once in the conversation before editing. This tool will error if you attempt an edit without reading the file.
|
|
6
7
|
- Fuzzy matching handles minor whitespace/indentation differences automatically - you don't need to match indentation exactly.
|
|
7
8
|
- ALWAYS prefer editing existing files in the codebase. NEVER write new files unless explicitly required.
|
package/src/utils/shell.ts
CHANGED
|
@@ -181,46 +181,6 @@ export async function getShellConfig(): Promise<ShellConfig> {
|
|
|
181
181
|
return cachedShellConfig;
|
|
182
182
|
}
|
|
183
183
|
|
|
184
|
-
/**
|
|
185
|
-
* Sanitize binary output for display/storage.
|
|
186
|
-
* Removes characters that crash string-width or cause display issues:
|
|
187
|
-
* - Control characters (except tab, newline, carriage return)
|
|
188
|
-
* - Lone surrogates
|
|
189
|
-
* - Unicode Format characters (crash string-width due to a bug)
|
|
190
|
-
* - Characters with undefined code points
|
|
191
|
-
*/
|
|
192
|
-
export function sanitizeBinaryOutput(str: string): string {
|
|
193
|
-
// Use Array.from to properly iterate over code points (not code units)
|
|
194
|
-
// This handles surrogate pairs correctly and catches edge cases where
|
|
195
|
-
// codePointAt() might return undefined
|
|
196
|
-
return Array.from(str)
|
|
197
|
-
.filter((char) => {
|
|
198
|
-
// Filter out characters that cause string-width to crash
|
|
199
|
-
// This includes:
|
|
200
|
-
// - Unicode format characters
|
|
201
|
-
// - Lone surrogates (already filtered by Array.from)
|
|
202
|
-
// - Control chars except \t \n \r
|
|
203
|
-
// - Characters with undefined code points
|
|
204
|
-
|
|
205
|
-
const code = char.codePointAt(0);
|
|
206
|
-
|
|
207
|
-
// Skip if code point is undefined (edge case with invalid strings)
|
|
208
|
-
if (code === undefined) return false;
|
|
209
|
-
|
|
210
|
-
// Allow tab, newline, carriage return
|
|
211
|
-
if (code === 0x09 || code === 0x0a || code === 0x0d) return true;
|
|
212
|
-
|
|
213
|
-
// Filter out control characters (0x00-0x1F, except 0x09, 0x0a, 0x0x0d)
|
|
214
|
-
if (code <= 0x1f) return false;
|
|
215
|
-
|
|
216
|
-
// Filter out Unicode format characters
|
|
217
|
-
if (code >= 0xfff9 && code <= 0xfffb) return false;
|
|
218
|
-
|
|
219
|
-
return true;
|
|
220
|
-
})
|
|
221
|
-
.join("");
|
|
222
|
-
}
|
|
223
|
-
|
|
224
184
|
let pgrepAvailable: boolean | null = null;
|
|
225
185
|
|
|
226
186
|
/**
|