@oh-my-pi/pi-coding-agent 14.3.0 → 14.4.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 +84 -1
- package/package.json +7 -7
- package/src/autoresearch/prompt.md +1 -1
- package/src/commit/agentic/prompts/analyze-file.md +1 -1
- package/src/config/model-registry.ts +67 -15
- package/src/config/prompt-templates.ts +5 -5
- package/src/config/settings-schema.ts +4 -4
- package/src/cursor.ts +3 -8
- package/src/discovery/helpers.ts +3 -3
- package/src/edit/diff.ts +50 -47
- package/src/edit/index.ts +86 -57
- package/src/edit/line-hash.ts +735 -19
- package/src/edit/modes/apply-patch.ts +0 -9
- package/src/edit/modes/atom.ts +658 -0
- package/src/edit/modes/chunk.ts +14 -24
- package/src/edit/modes/hashline.ts +188 -136
- package/src/edit/modes/patch.ts +5 -9
- package/src/edit/modes/replace.ts +6 -11
- package/src/edit/renderer.ts +14 -10
- package/src/edit/streaming.ts +50 -16
- package/src/exec/bash-executor.ts +2 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +4 -12
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/internal-urls/docs-index.generated.ts +2 -2
- package/src/lsp/index.ts +1 -1
- package/src/mcp/render.ts +1 -8
- package/src/modes/components/assistant-message.ts +4 -0
- package/src/modes/components/diff.ts +23 -14
- package/src/modes/components/footer.ts +21 -16
- package/src/modes/components/settings-defs.ts +6 -1
- package/src/modes/components/todo-reminder.ts +1 -8
- package/src/modes/components/tool-execution.ts +1 -4
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/print-mode.ts +8 -0
- package/src/prompts/agents/librarian.md +1 -1
- package/src/prompts/agents/reviewer.md +4 -4
- package/src/prompts/ci-green-request.md +1 -1
- package/src/prompts/review-request.md +1 -1
- package/src/prompts/system/subagent-system-prompt.md +3 -3
- package/src/prompts/system/subagent-yield-reminder.md +11 -0
- package/src/prompts/system/system-prompt.md +3 -0
- package/src/prompts/tools/ask.md +3 -2
- package/src/prompts/tools/ast-edit.md +15 -19
- package/src/prompts/tools/ast-grep.md +18 -24
- package/src/prompts/tools/atom.md +96 -0
- package/src/prompts/tools/chunk-edit.md +37 -161
- package/src/prompts/tools/debug.md +4 -5
- package/src/prompts/tools/exit-plan-mode.md +4 -5
- package/src/prompts/tools/find.md +4 -8
- package/src/prompts/tools/github.md +18 -0
- package/src/prompts/tools/grep.md +4 -5
- package/src/prompts/tools/hashline.md +22 -89
- package/src/prompts/tools/{gemini-image.md → image-gen.md} +1 -1
- package/src/prompts/tools/inspect-image.md +6 -6
- package/src/prompts/tools/lsp.md +1 -1
- package/src/prompts/tools/patch.md +12 -19
- package/src/prompts/tools/python.md +3 -2
- package/src/prompts/tools/read-chunk.md +2 -3
- package/src/prompts/tools/read.md +2 -2
- package/src/prompts/tools/ssh.md +8 -17
- package/src/prompts/tools/todo-write.md +54 -41
- package/src/sdk.ts +14 -9
- package/src/session/agent-session.ts +25 -2
- package/src/task/executor.ts +43 -48
- package/src/task/render.ts +11 -13
- package/src/tools/ask.ts +7 -7
- package/src/tools/ast-edit.ts +45 -41
- package/src/tools/ast-grep.ts +77 -85
- package/src/tools/bash.ts +8 -9
- package/src/tools/browser.ts +32 -30
- package/src/tools/calculator.ts +4 -4
- package/src/tools/cancel-job.ts +1 -1
- package/src/tools/checkpoint.ts +2 -2
- package/src/tools/debug.ts +41 -37
- package/src/tools/exit-plan-mode.ts +1 -1
- package/src/tools/find.ts +4 -4
- package/src/tools/gh-renderer.ts +12 -4
- package/src/tools/gh.ts +509 -697
- package/src/tools/grep.ts +115 -130
- package/src/tools/{gemini-image.ts → image-gen.ts} +459 -60
- package/src/tools/index.ts +14 -32
- package/src/tools/inspect-image.ts +3 -3
- package/src/tools/json-tree.ts +114 -114
- package/src/tools/match-line-format.ts +9 -8
- package/src/tools/notebook.ts +8 -7
- package/src/tools/poll-tool.ts +2 -1
- package/src/tools/python.ts +9 -23
- package/src/tools/read.ts +32 -21
- package/src/tools/render-mermaid.ts +1 -1
- package/src/tools/render-utils.ts +18 -0
- package/src/tools/renderers.ts +2 -2
- package/src/tools/report-tool-issue.ts +3 -2
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +12 -10
- package/src/tools/search-tool-bm25.ts +2 -4
- package/src/tools/ssh.ts +4 -4
- package/src/tools/todo-write.ts +172 -147
- package/src/tools/vim.ts +14 -15
- package/src/tools/write.ts +4 -4
- package/src/tools/{submit-result.ts → yield.ts} +11 -13
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/file-display-mode.ts +10 -5
- package/src/utils/git.ts +9 -5
- package/src/utils/shell-snapshot.ts +2 -3
- package/src/vim/render.ts +4 -4
- package/src/prompts/system/subagent-submit-reminder.md +0 -11
- package/src/prompts/tools/gh-issue-view.md +0 -11
- package/src/prompts/tools/gh-pr-checkout.md +0 -12
- package/src/prompts/tools/gh-pr-diff.md +0 -12
- package/src/prompts/tools/gh-pr-push.md +0 -12
- package/src/prompts/tools/gh-pr-view.md +0 -11
- package/src/prompts/tools/gh-repo-view.md +0 -11
- package/src/prompts/tools/gh-run-watch.md +0 -12
- package/src/prompts/tools/gh-search-issues.md +0 -11
- package/src/prompts/tools/gh-search-prs.md +0 -11
|
@@ -977,13 +977,14 @@ export function findContextLine(
|
|
|
977
977
|
}
|
|
978
978
|
|
|
979
979
|
export const replaceEditEntrySchema = Type.Object({
|
|
980
|
-
path: Type.String({ description: "File path (
|
|
980
|
+
path: Type.Optional(Type.String({ description: "File path (omit to use top-level `path`)" })),
|
|
981
981
|
old_text: Type.String({ description: "Text to find (fuzzy whitespace matching enabled)" }),
|
|
982
982
|
new_text: Type.String({ description: "Replacement text" }),
|
|
983
983
|
all: Type.Optional(Type.Boolean({ description: "Replace all occurrences (default: unique match required)" })),
|
|
984
984
|
});
|
|
985
985
|
|
|
986
986
|
export const replaceEditSchema = Type.Object({
|
|
987
|
+
path: Type.Optional(Type.String({ description: "Default file path used when an edit omits its own `path`" })),
|
|
987
988
|
edits: Type.Array(replaceEditEntrySchema, { description: "Replacements", minItems: 1 }),
|
|
988
989
|
});
|
|
989
990
|
|
|
@@ -1001,13 +1002,6 @@ export interface ExecuteReplaceSingleOptions {
|
|
|
1001
1002
|
beginDeferredDiagnosticsForPath: (path: string) => WritethroughDeferredHandle;
|
|
1002
1003
|
}
|
|
1003
1004
|
|
|
1004
|
-
export function isReplaceParams(params: unknown): params is ReplaceParams {
|
|
1005
|
-
if (typeof params !== "object" || params === null) return false;
|
|
1006
|
-
if (!("edits" in params) || !Array.isArray((params as any).edits)) return false;
|
|
1007
|
-
const first = (params as any).edits[0];
|
|
1008
|
-
return first && typeof first === "object" && "old_text" in first && "new_text" in first;
|
|
1009
|
-
}
|
|
1010
|
-
|
|
1011
1005
|
export async function executeReplaceSingle(
|
|
1012
1006
|
options: ExecuteReplaceSingleOptions,
|
|
1013
1007
|
): Promise<AgentToolResult<EditToolDetails, typeof replaceEditEntrySchema>> {
|
|
@@ -1022,6 +1016,9 @@ export async function executeReplaceSingle(
|
|
|
1022
1016
|
beginDeferredDiagnosticsForPath,
|
|
1023
1017
|
} = options;
|
|
1024
1018
|
const { path, old_text, new_text, all } = params;
|
|
1019
|
+
if (typeof path !== "string" || path.length === 0) {
|
|
1020
|
+
throw new Error("replace edit: missing `path`. Provide `path` on the edit or supply a top-level `path`.");
|
|
1021
|
+
}
|
|
1025
1022
|
|
|
1026
1023
|
enforcePlanModeWrite(session, path);
|
|
1027
1024
|
|
|
@@ -1065,9 +1062,7 @@ export async function executeReplaceSingle(
|
|
|
1065
1062
|
}
|
|
1066
1063
|
|
|
1067
1064
|
if (normalizedContent === result.content) {
|
|
1068
|
-
throw new Error(
|
|
1069
|
-
`No changes made to ${path}. The replacement produced identical content. This might indicate an issue with special characters or the text not existing as expected.`,
|
|
1070
|
-
);
|
|
1065
|
+
throw new Error(`Edits to ${path} resulted in no changes being made.`);
|
|
1071
1066
|
}
|
|
1072
1067
|
|
|
1073
1068
|
const finalContent = bom + restoreLineEndings(result.content, originalEnding);
|
package/src/edit/renderer.ts
CHANGED
|
@@ -50,6 +50,9 @@ export interface EditToolPerFileResult {
|
|
|
50
50
|
move?: string;
|
|
51
51
|
isError?: boolean;
|
|
52
52
|
errorText?: string;
|
|
53
|
+
/** TUI-friendly error text. When present, rendered to the user instead of `errorText`.
|
|
54
|
+
* Set when the underlying error carries a `displayMessage` (e.g. {@link HashlineMismatchError}). */
|
|
55
|
+
displayErrorText?: string;
|
|
53
56
|
meta?: OutputMeta;
|
|
54
57
|
}
|
|
55
58
|
|
|
@@ -377,18 +380,18 @@ function wrapEditRendererLine(line: string, width: number): string[] {
|
|
|
377
380
|
const startAnsi = line.match(/^((?:\x1b\[[0-9;]*m)*)/)?.[1] ?? "";
|
|
378
381
|
const bodyWithReset = line.slice(startAnsi.length);
|
|
379
382
|
const body = bodyWithReset.endsWith("\x1b[39m") ? bodyWithReset.slice(0, -"\x1b[39m".length) : bodyWithReset;
|
|
380
|
-
const diffMatch = /^([+\-\s])(\s*\d+)
|
|
383
|
+
const diffMatch = /^([+\-\s])(\s*\d+)([|│])(.*)$/s.exec(body);
|
|
381
384
|
|
|
382
385
|
if (!diffMatch) {
|
|
383
386
|
return wrapTextWithAnsi(line, width);
|
|
384
387
|
}
|
|
385
388
|
|
|
386
|
-
const [, marker, lineNum, content] = diffMatch;
|
|
387
|
-
const prefix = `${marker}${lineNum}
|
|
389
|
+
const [, marker, lineNum, separator, content] = diffMatch;
|
|
390
|
+
const prefix = `${marker}${lineNum}${separator}`;
|
|
388
391
|
const prefixWidth = visibleWidth(prefix);
|
|
389
392
|
const contentWidth = Math.max(1, width - prefixWidth);
|
|
390
|
-
const continuationPrefix = `${" ".repeat(Math.max(0, prefixWidth - 1))}
|
|
391
|
-
const wrappedContent = wrapTextWithAnsi(content, contentWidth);
|
|
393
|
+
const continuationPrefix = `${" ".repeat(Math.max(0, prefixWidth - 1))}${separator}`;
|
|
394
|
+
const wrappedContent = wrapTextWithAnsi(content ?? "", contentWidth);
|
|
392
395
|
|
|
393
396
|
return wrappedContent.map(
|
|
394
397
|
(segment, index) => `${startAnsi}${index === 0 ? prefix : continuationPrefix}${segment}\x1b[39m`,
|
|
@@ -489,13 +492,14 @@ function renderSingleFileResult(
|
|
|
489
492
|
const rename = args?.rename || firstEdit?.rename || firstEdit?.move || details?.move;
|
|
490
493
|
const { language } = formatEditDescription(rawPath, uiTheme, { rename });
|
|
491
494
|
|
|
492
|
-
const
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
: "";
|
|
495
|
+
const editTextSource = args?.newText ?? args?.oldText ?? args?.diff ?? args?.patch;
|
|
496
|
+
const metadataLineCount = editTextSource ? countLines(editTextSource) : null;
|
|
497
|
+
const metadataLine = op !== "delete" ? `\n${formatMetadataLine(metadataLineCount, language, uiTheme)}` : "";
|
|
496
498
|
|
|
499
|
+
const displayErrorText = isError && details && "displayErrorText" in details ? details.displayErrorText : undefined;
|
|
497
500
|
const errorText = isError
|
|
498
|
-
?
|
|
501
|
+
? displayErrorText ||
|
|
502
|
+
(details && "errorText" in details && details.errorText) ||
|
|
499
503
|
(result.content?.find(c => c.type === "text")?.text ?? "")
|
|
500
504
|
: "";
|
|
501
505
|
|
package/src/edit/streaming.ts
CHANGED
|
@@ -131,6 +131,7 @@ export function dropIncompleteLastEdit<T>(edits: readonly T[], partialJson: stri
|
|
|
131
131
|
// -----------------------------------------------------------------------------
|
|
132
132
|
|
|
133
133
|
interface ReplaceArgs {
|
|
134
|
+
path?: string;
|
|
134
135
|
edits?: ReplaceEditEntry[];
|
|
135
136
|
__partialJson?: string;
|
|
136
137
|
}
|
|
@@ -142,10 +143,12 @@ const replaceStrategy: EditStreamingStrategy<ReplaceArgs> = {
|
|
|
142
143
|
},
|
|
143
144
|
async computeDiffPreview(args, ctx) {
|
|
144
145
|
const first = args.edits?.[0];
|
|
145
|
-
if (!first
|
|
146
|
+
if (!first) return null;
|
|
147
|
+
const path = first.path ?? args.path;
|
|
148
|
+
if (!path || first.old_text === undefined || first.new_text === undefined) return null;
|
|
146
149
|
ctx.signal.throwIfAborted();
|
|
147
150
|
const result = await computeEditDiff(
|
|
148
|
-
|
|
151
|
+
path,
|
|
149
152
|
first.old_text,
|
|
150
153
|
first.new_text,
|
|
151
154
|
ctx.cwd,
|
|
@@ -154,7 +157,7 @@ const replaceStrategy: EditStreamingStrategy<ReplaceArgs> = {
|
|
|
154
157
|
ctx.fuzzyThreshold,
|
|
155
158
|
);
|
|
156
159
|
ctx.signal.throwIfAborted();
|
|
157
|
-
return [toPerFilePreview(
|
|
160
|
+
return [toPerFilePreview(path, result)];
|
|
158
161
|
},
|
|
159
162
|
renderStreamingFallback() {
|
|
160
163
|
return "";
|
|
@@ -162,6 +165,7 @@ const replaceStrategy: EditStreamingStrategy<ReplaceArgs> = {
|
|
|
162
165
|
};
|
|
163
166
|
|
|
164
167
|
interface PatchArgs {
|
|
168
|
+
path?: string;
|
|
165
169
|
edits?: PatchEditEntry[];
|
|
166
170
|
__partialJson?: string;
|
|
167
171
|
}
|
|
@@ -173,15 +177,16 @@ const patchStrategy: EditStreamingStrategy<PatchArgs> = {
|
|
|
173
177
|
},
|
|
174
178
|
async computeDiffPreview(args, ctx) {
|
|
175
179
|
const first = args.edits?.[0];
|
|
176
|
-
|
|
180
|
+
const path = first?.path ?? args.path;
|
|
181
|
+
if (!path) return null;
|
|
177
182
|
ctx.signal.throwIfAborted();
|
|
178
183
|
const result = await computePatchDiff(
|
|
179
|
-
{ path
|
|
184
|
+
{ path, op: first?.op ?? "update", rename: first?.rename, diff: first?.diff },
|
|
180
185
|
ctx.cwd,
|
|
181
186
|
{ fuzzyThreshold: ctx.fuzzyThreshold, allowFuzzy: ctx.allowFuzzy },
|
|
182
187
|
);
|
|
183
188
|
ctx.signal.throwIfAborted();
|
|
184
|
-
return [toPerFilePreview(
|
|
189
|
+
return [toPerFilePreview(path, result)];
|
|
185
190
|
},
|
|
186
191
|
renderStreamingFallback() {
|
|
187
192
|
return "";
|
|
@@ -189,8 +194,8 @@ const patchStrategy: EditStreamingStrategy<PatchArgs> = {
|
|
|
189
194
|
};
|
|
190
195
|
|
|
191
196
|
interface HashlineArgs {
|
|
197
|
+
path?: string;
|
|
192
198
|
edits?: HashlineToolEdit[];
|
|
193
|
-
move?: string;
|
|
194
199
|
__partialJson?: string;
|
|
195
200
|
}
|
|
196
201
|
|
|
@@ -201,13 +206,18 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
201
206
|
},
|
|
202
207
|
async computeDiffPreview(args, ctx) {
|
|
203
208
|
const first = args.edits?.[0] as (HashlineToolEdit & { path?: string }) | undefined;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
const fileEdits = (args.edits ?? [])
|
|
207
|
-
|
|
208
|
-
|
|
209
|
+
const path = first?.path ?? args.path;
|
|
210
|
+
if (!path) return null;
|
|
211
|
+
const fileEdits = (args.edits ?? [])
|
|
212
|
+
.map(e => {
|
|
213
|
+
if (!e || typeof e !== "object") return undefined;
|
|
214
|
+
const entryPath = (e as { path?: string }).path ?? args.path;
|
|
215
|
+
if (!entryPath || entryPath !== path) return undefined;
|
|
216
|
+
return { ...(e as HashlineToolEdit), path } as HashlineToolEdit & { path: string };
|
|
217
|
+
})
|
|
218
|
+
.filter((e): e is HashlineToolEdit & { path: string } => e !== undefined);
|
|
209
219
|
ctx.signal.throwIfAborted();
|
|
210
|
-
const result = await computeHashlineDiff({ path, edits: fileEdits
|
|
220
|
+
const result = await computeHashlineDiff({ path, edits: fileEdits }, ctx.cwd);
|
|
211
221
|
ctx.signal.throwIfAborted();
|
|
212
222
|
return [toPerFilePreview(path, result)];
|
|
213
223
|
},
|
|
@@ -217,6 +227,7 @@ const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
|
|
|
217
227
|
};
|
|
218
228
|
|
|
219
229
|
interface ChunkArgs {
|
|
230
|
+
path?: string;
|
|
220
231
|
edits?: ChunkToolEdit[];
|
|
221
232
|
__partialJson?: string;
|
|
222
233
|
}
|
|
@@ -248,8 +259,10 @@ const chunkStrategy: EditStreamingStrategy<ChunkArgs> = {
|
|
|
248
259
|
const groups = new Map<string, ChunkToolEdit[]>();
|
|
249
260
|
const fileOrder: string[] = [];
|
|
250
261
|
for (const edit of edits) {
|
|
251
|
-
if (!edit
|
|
252
|
-
const
|
|
262
|
+
if (!edit) continue;
|
|
263
|
+
const editPath = edit.path ?? args.path;
|
|
264
|
+
if (!editPath) continue;
|
|
265
|
+
const { filePath } = parseChunkEditPath(editPath);
|
|
253
266
|
if (!filePath) continue;
|
|
254
267
|
let bucket = groups.get(filePath);
|
|
255
268
|
if (!bucket) {
|
|
@@ -257,7 +270,7 @@ const chunkStrategy: EditStreamingStrategy<ChunkArgs> = {
|
|
|
257
270
|
groups.set(filePath, bucket);
|
|
258
271
|
fileOrder.push(filePath);
|
|
259
272
|
}
|
|
260
|
-
bucket.push(edit);
|
|
273
|
+
bucket.push({ ...edit, path: editPath });
|
|
261
274
|
}
|
|
262
275
|
if (fileOrder.length === 0) return null;
|
|
263
276
|
|
|
@@ -328,6 +341,26 @@ const vimStrategy: EditStreamingStrategy<unknown> = {
|
|
|
328
341
|
},
|
|
329
342
|
};
|
|
330
343
|
|
|
344
|
+
interface AtomArgs {
|
|
345
|
+
path?: string;
|
|
346
|
+
edits?: unknown[];
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const atomStrategy: EditStreamingStrategy<AtomArgs> = {
|
|
350
|
+
extractCompleteEdits(args, partialJson) {
|
|
351
|
+
if (!args.edits) return args;
|
|
352
|
+
return { ...args, edits: dropIncompleteLastEdit(args.edits, partialJson, "edits") };
|
|
353
|
+
},
|
|
354
|
+
async computeDiffPreview() {
|
|
355
|
+
// Atom edits are line-anchored and validated against live file hashes; a
|
|
356
|
+
// streaming preview without that validation could mislead. Skip for now.
|
|
357
|
+
return null;
|
|
358
|
+
},
|
|
359
|
+
renderStreamingFallback() {
|
|
360
|
+
return "";
|
|
361
|
+
},
|
|
362
|
+
};
|
|
363
|
+
|
|
331
364
|
export const EDIT_MODE_STRATEGIES: Record<EditMode, EditStreamingStrategy<unknown>> = {
|
|
332
365
|
replace: replaceStrategy as EditStreamingStrategy<unknown>,
|
|
333
366
|
patch: patchStrategy as EditStreamingStrategy<unknown>,
|
|
@@ -335,6 +368,7 @@ export const EDIT_MODE_STRATEGIES: Record<EditMode, EditStreamingStrategy<unknow
|
|
|
335
368
|
chunk: chunkStrategy as EditStreamingStrategy<unknown>,
|
|
336
369
|
apply_patch: applyPatchStrategy as EditStreamingStrategy<unknown>,
|
|
337
370
|
vim: vimStrategy,
|
|
371
|
+
atom: atomStrategy as EditStreamingStrategy<unknown>,
|
|
338
372
|
};
|
|
339
373
|
|
|
340
374
|
export { resolveEditMode };
|
|
@@ -239,7 +239,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
239
239
|
// artifact, and splice an `artifact://<id>` footer into the visible text so
|
|
240
240
|
// the agent can retrieve the raw bytes losslessly.
|
|
241
241
|
const minimized = winner.result.minimized;
|
|
242
|
-
if (minimized) {
|
|
242
|
+
if (minimized && minimized.text !== minimized.originalText) {
|
|
243
243
|
sink.replace(minimized.text);
|
|
244
244
|
if (options?.onMinimizedSave) {
|
|
245
245
|
const artifactId = await options.onMinimizedSave(minimized.originalText, {
|
|
@@ -248,9 +248,7 @@ export async function executeBash(command: string, options?: BashExecutorOptions
|
|
|
248
248
|
outputBytes: minimized.outputBytes,
|
|
249
249
|
});
|
|
250
250
|
if (artifactId) {
|
|
251
|
-
sink.push(
|
|
252
|
-
`\n… full output: artifact://${artifactId} (${minimized.inputBytes} → ${minimized.outputBytes} bytes)\n`,
|
|
253
|
-
);
|
|
251
|
+
sink.push(`\n[raw output: artifact://${artifactId}]\n`);
|
|
254
252
|
}
|
|
255
253
|
}
|
|
256
254
|
}
|