@oh-my-pi/pi-coding-agent 11.10.2 → 11.10.4
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 +20 -0
- package/package.json +11 -7
- package/src/config/model-registry.ts +1 -1
- package/src/config/settings.ts +2 -2
- package/src/modes/components/tool-execution.ts +25 -1
- package/src/patch/diff.ts +44 -1
- package/src/patch/index.ts +8 -1
- package/src/patch/shared.ts +73 -5
- package/src/web/search/render.ts +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [11.10.4] - 2026-02-10
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Hashline diff computation with `computeHashlineDiff` function for preview rendering of hashline-mode edits
|
|
10
|
+
- Streaming preview display for hashline edits in tool execution UI showing edit sources and destinations
|
|
11
|
+
- Streaming hash line computation with progress updates via `onUpdate` callback in read tool
|
|
12
|
+
- Optional `onCollectedLine` callback parameter to `streamLinesFromFile` for line collection tracking
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Edit tool renderer now displays computed preview diffs for hashline operations before execution
|
|
17
|
+
- Read tool now streams hash lines incrementally instead of computing them all at once, improving responsiveness for large files
|
|
18
|
+
- Refactored hash line formatting to use async `streamHashLinesFromLines` for better performance
|
|
19
|
+
|
|
20
|
+
## [11.10.3] - 2026-02-10
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Exported `./patch/*` subpath for direct access to patch utilities
|
|
24
|
+
|
|
5
25
|
## [11.10.2] - 2026-02-10
|
|
6
26
|
|
|
7
27
|
### Added
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "11.10.
|
|
3
|
+
"version": "11.10.4",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -58,6 +58,10 @@
|
|
|
58
58
|
"types": "./src/internal-urls/*.ts",
|
|
59
59
|
"import": "./src/internal-urls/*.ts"
|
|
60
60
|
},
|
|
61
|
+
"./patch/*": {
|
|
62
|
+
"types": "./src/patch/*.ts",
|
|
63
|
+
"import": "./src/patch/*.ts"
|
|
64
|
+
},
|
|
61
65
|
"./*": {
|
|
62
66
|
"types": "./src/*.ts",
|
|
63
67
|
"import": "./src/*.ts"
|
|
@@ -80,12 +84,12 @@
|
|
|
80
84
|
},
|
|
81
85
|
"dependencies": {
|
|
82
86
|
"@mozilla/readability": "0.6.0",
|
|
83
|
-
"@oh-my-pi/omp-stats": "11.10.
|
|
84
|
-
"@oh-my-pi/pi-agent-core": "11.10.
|
|
85
|
-
"@oh-my-pi/pi-ai": "11.10.
|
|
86
|
-
"@oh-my-pi/pi-natives": "11.10.
|
|
87
|
-
"@oh-my-pi/pi-tui": "11.10.
|
|
88
|
-
"@oh-my-pi/pi-utils": "11.10.
|
|
87
|
+
"@oh-my-pi/omp-stats": "11.10.4",
|
|
88
|
+
"@oh-my-pi/pi-agent-core": "11.10.4",
|
|
89
|
+
"@oh-my-pi/pi-ai": "11.10.4",
|
|
90
|
+
"@oh-my-pi/pi-natives": "11.10.4",
|
|
91
|
+
"@oh-my-pi/pi-tui": "11.10.4",
|
|
92
|
+
"@oh-my-pi/pi-utils": "11.10.4",
|
|
89
93
|
"@sinclair/typebox": "^0.34.48",
|
|
90
94
|
"ajv": "^8.17.1",
|
|
91
95
|
"chalk": "^5.6.2",
|
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
type Model,
|
|
7
7
|
normalizeDomain,
|
|
8
8
|
} from "@oh-my-pi/pi-ai";
|
|
9
|
-
import { type ConfigError, ConfigFile } from "@oh-my-pi/pi-coding-agent/config";
|
|
10
9
|
import { type Static, Type } from "@sinclair/typebox";
|
|
11
10
|
import AjvModule from "ajv";
|
|
11
|
+
import { type ConfigError, ConfigFile } from "../config";
|
|
12
12
|
import type { ThemeColor } from "../modes/theme/theme";
|
|
13
13
|
import type { AuthStorage } from "../session/auth-storage";
|
|
14
14
|
|
package/src/config/settings.ts
CHANGED
|
@@ -13,14 +13,14 @@
|
|
|
13
13
|
|
|
14
14
|
import * as fs from "node:fs";
|
|
15
15
|
import * as path from "node:path";
|
|
16
|
-
import type { ModelRole } from "@oh-my-pi/pi-coding-agent/config/model-registry";
|
|
17
|
-
import { type EditMode, normalizeEditMode } from "@oh-my-pi/pi-coding-agent/patch";
|
|
18
16
|
import { isEnoent, logger, procmgr } from "@oh-my-pi/pi-utils";
|
|
19
17
|
import { YAML } from "bun";
|
|
20
18
|
import { type Settings as SettingsCapabilityItem, settingsCapability } from "../capability/settings";
|
|
21
19
|
import { getAgentDbPath, getAgentDir } from "../config";
|
|
20
|
+
import type { ModelRole } from "../config/model-registry";
|
|
22
21
|
import { loadCapability } from "../discovery";
|
|
23
22
|
import { setColorBlindMode, setSymbolPreset, setTheme } from "../modes/theme/theme";
|
|
23
|
+
import { type EditMode, normalizeEditMode } from "../patch";
|
|
24
24
|
import { AgentStorage } from "../session/agent-storage";
|
|
25
25
|
import { withFileLock } from "./file-lock";
|
|
26
26
|
import {
|
|
@@ -15,7 +15,13 @@ import {
|
|
|
15
15
|
import { logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
16
16
|
import type { Theme } from "../../modes/theme/theme";
|
|
17
17
|
import { theme } from "../../modes/theme/theme";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
computeEditDiff,
|
|
20
|
+
computeHashlineDiff,
|
|
21
|
+
computePatchDiff,
|
|
22
|
+
type EditDiffError,
|
|
23
|
+
type EditDiffResult,
|
|
24
|
+
} from "../../patch";
|
|
19
25
|
import { BASH_DEFAULT_PREVIEW_LINES } from "../../tools/bash";
|
|
20
26
|
import {
|
|
21
27
|
formatArgsInline,
|
|
@@ -192,6 +198,24 @@ export class ToolExecutionComponent extends Container {
|
|
|
192
198
|
});
|
|
193
199
|
return;
|
|
194
200
|
}
|
|
201
|
+
const edits = this.#args?.edits;
|
|
202
|
+
if (path && Array.isArray(edits)) {
|
|
203
|
+
const argsKey = JSON.stringify({ path, edits });
|
|
204
|
+
if (this.#editDiffArgsKey === argsKey) return;
|
|
205
|
+
this.#editDiffArgsKey = argsKey;
|
|
206
|
+
|
|
207
|
+
computeHashlineDiff({ path, edits }, this.#cwd).then(result => {
|
|
208
|
+
if (this.#editDiffArgsKey === argsKey) {
|
|
209
|
+
this.#editDiffPreview = result;
|
|
210
|
+
if ("diff" in result && result.diff) {
|
|
211
|
+
(this.#args as Record<string, unknown>).previewDiff = result.diff;
|
|
212
|
+
}
|
|
213
|
+
this.#updateDisplay();
|
|
214
|
+
this.#ui.requestRender();
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
195
219
|
|
|
196
220
|
const oldText = this.#args?.old_text;
|
|
197
221
|
const newText = this.#args?.new_text;
|
package/src/patch/diff.ts
CHANGED
|
@@ -8,8 +8,9 @@ import * as Diff from "diff";
|
|
|
8
8
|
import { resolveToCwd } from "../tools/path-utils";
|
|
9
9
|
import { previewPatch } from "./applicator";
|
|
10
10
|
import { DEFAULT_FUZZY_THRESHOLD, findMatch } from "./fuzzy";
|
|
11
|
+
import { applyHashlineEdits } from "./hashline";
|
|
11
12
|
import { adjustIndentation, normalizeToLF, stripBom } from "./normalize";
|
|
12
|
-
import type { DiffError, DiffResult, PatchInput } from "./types";
|
|
13
|
+
import type { DiffError, DiffResult, HashlineEdit, PatchInput } from "./types";
|
|
13
14
|
import { EditMatchError } from "./types";
|
|
14
15
|
|
|
15
16
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -363,3 +364,45 @@ export async function computePatchDiff(
|
|
|
363
364
|
return { error: err instanceof Error ? err.message : String(err) };
|
|
364
365
|
}
|
|
365
366
|
}
|
|
367
|
+
/**
|
|
368
|
+
* Compute the diff for a hashline operation without applying it.
|
|
369
|
+
* Used for preview rendering in the TUI before hashline-mode edits execute.
|
|
370
|
+
*/
|
|
371
|
+
export async function computeHashlineDiff(
|
|
372
|
+
input: { path: string; edits: HashlineEdit[] },
|
|
373
|
+
cwd: string,
|
|
374
|
+
): Promise<DiffResult | DiffError> {
|
|
375
|
+
const { path, edits } = input;
|
|
376
|
+
const absolutePath = resolveToCwd(path, cwd);
|
|
377
|
+
|
|
378
|
+
try {
|
|
379
|
+
const file = Bun.file(absolutePath);
|
|
380
|
+
try {
|
|
381
|
+
if (!(await file.exists())) {
|
|
382
|
+
return { error: `File not found: ${path}` };
|
|
383
|
+
}
|
|
384
|
+
} catch {
|
|
385
|
+
return { error: `File not found: ${path}` };
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let rawContent: string;
|
|
389
|
+
try {
|
|
390
|
+
rawContent = await file.text();
|
|
391
|
+
} catch (error) {
|
|
392
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
393
|
+
return { error: message || `Unable to read ${path}` };
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const { text: content } = stripBom(rawContent);
|
|
397
|
+
const normalizedContent = normalizeToLF(content);
|
|
398
|
+
|
|
399
|
+
const result = applyHashlineEdits(normalizedContent, edits);
|
|
400
|
+
if (normalizedContent === result.content) {
|
|
401
|
+
return { error: `No changes would be made to ${path}. The edits produce identical content.` };
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
return generateDiffString(normalizedContent, result.content);
|
|
405
|
+
} catch (err) {
|
|
406
|
+
return { error: err instanceof Error ? err.message : String(err) };
|
|
407
|
+
}
|
|
408
|
+
}
|
package/src/patch/index.ts
CHANGED
|
@@ -44,7 +44,14 @@ import { EditMatchError } from "./types";
|
|
|
44
44
|
// Application
|
|
45
45
|
export { applyPatch, defaultFileSystem, previewPatch } from "./applicator";
|
|
46
46
|
// Diff generation
|
|
47
|
-
export {
|
|
47
|
+
export {
|
|
48
|
+
computeEditDiff,
|
|
49
|
+
computeHashlineDiff,
|
|
50
|
+
computePatchDiff,
|
|
51
|
+
generateDiffString,
|
|
52
|
+
generateUnifiedDiffString,
|
|
53
|
+
replaceText,
|
|
54
|
+
} from "./diff";
|
|
48
55
|
|
|
49
56
|
// Fuzzy matching
|
|
50
57
|
export { DEFAULT_FUZZY_THRESHOLD, findContextLine, findMatch as findEditMatch, findMatch, seekSequence } from "./fuzzy";
|
package/src/patch/shared.ts
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
} from "../tools/render-utils";
|
|
22
22
|
import type { RenderCallOptions } from "../tools/renderers";
|
|
23
23
|
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
24
|
-
import type { DiffError, DiffResult, Operation } from "./types";
|
|
24
|
+
import type { DiffError, DiffResult, HashlineEdit, Operation } from "./types";
|
|
25
25
|
|
|
26
26
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
27
27
|
// LSP Batching
|
|
@@ -77,6 +77,12 @@ interface EditRenderArgs {
|
|
|
77
77
|
op?: Operation;
|
|
78
78
|
rename?: string;
|
|
79
79
|
diff?: string;
|
|
80
|
+
/**
|
|
81
|
+
* Computed preview diff (used when tool args don't include a diff, e.g. hashline mode).
|
|
82
|
+
*/
|
|
83
|
+
previewDiff?: string;
|
|
84
|
+
// Hashline mode fields
|
|
85
|
+
edits?: HashlineEdit[];
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
/** Extended context for edit tool rendering */
|
|
@@ -94,22 +100,80 @@ function countLines(text: string): number {
|
|
|
94
100
|
return text.split("\n").length;
|
|
95
101
|
}
|
|
96
102
|
|
|
97
|
-
function formatStreamingDiff(diff: string, rawPath: string, uiTheme: Theme): string {
|
|
103
|
+
function formatStreamingDiff(diff: string, rawPath: string, uiTheme: Theme, label = "streaming"): string {
|
|
98
104
|
if (!diff) return "";
|
|
99
105
|
const lines = diff.split("\n");
|
|
100
106
|
const total = lines.length;
|
|
101
107
|
const displayLines = lines.slice(-EDIT_STREAMING_PREVIEW_LINES);
|
|
102
108
|
const hidden = total - displayLines.length;
|
|
103
|
-
|
|
104
109
|
let text = "\n\n";
|
|
105
110
|
if (hidden > 0) {
|
|
106
111
|
text += uiTheme.fg("dim", `… (${hidden} earlier lines)\n`);
|
|
107
112
|
}
|
|
108
113
|
text += renderDiffColored(displayLines.join("\n"), { filePath: rawPath });
|
|
109
|
-
text += uiTheme.fg("dim", `\n… (
|
|
114
|
+
text += uiTheme.fg("dim", `\n… (${label})`);
|
|
110
115
|
return text;
|
|
111
116
|
}
|
|
112
117
|
|
|
118
|
+
function formatStreamingHashlineEdits(edits: HashlineEdit[], uiTheme: Theme, ui: ToolUIKit): string {
|
|
119
|
+
const MAX_EDITS = 4;
|
|
120
|
+
const MAX_DST_LINES = 8;
|
|
121
|
+
|
|
122
|
+
let text = "\n\n";
|
|
123
|
+
text += uiTheme.fg("dim", `[${edits.length} hashline edit${edits.length === 1 ? "" : "s"}]`);
|
|
124
|
+
text += "\n";
|
|
125
|
+
|
|
126
|
+
let shownEdits = 0;
|
|
127
|
+
let shownDstLines = 0;
|
|
128
|
+
|
|
129
|
+
for (const edit of edits) {
|
|
130
|
+
shownEdits++;
|
|
131
|
+
if (shownEdits > MAX_EDITS) break;
|
|
132
|
+
|
|
133
|
+
text += uiTheme.fg("toolOutput", ui.truncate(replaceTabs(formatHashlineSrc(edit.src)), 120));
|
|
134
|
+
text += "\n";
|
|
135
|
+
|
|
136
|
+
if (edit.dst === "") {
|
|
137
|
+
text += uiTheme.fg("dim", ui.truncate(" (delete)", 120));
|
|
138
|
+
text += "\n";
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const dstLines = edit.dst.split("\n");
|
|
143
|
+
for (const dstLine of dstLines) {
|
|
144
|
+
shownDstLines++;
|
|
145
|
+
if (shownDstLines > MAX_DST_LINES) break;
|
|
146
|
+
text += uiTheme.fg("toolOutput", ui.truncate(replaceTabs(`+ ${dstLine}`), 120));
|
|
147
|
+
text += "\n";
|
|
148
|
+
}
|
|
149
|
+
if (shownDstLines > MAX_DST_LINES) break;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (edits.length > MAX_EDITS) {
|
|
153
|
+
text += uiTheme.fg("dim", `… (${edits.length - MAX_EDITS} more edits)`);
|
|
154
|
+
}
|
|
155
|
+
if (shownDstLines > MAX_DST_LINES) {
|
|
156
|
+
text += uiTheme.fg("dim", `\n… (${shownDstLines - MAX_DST_LINES} more dst lines)`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return text.trimEnd();
|
|
160
|
+
|
|
161
|
+
function formatHashlineSrc(src: HashlineEdit["src"]): string {
|
|
162
|
+
switch (src.kind) {
|
|
163
|
+
case "single":
|
|
164
|
+
return `• single ${src.ref}`;
|
|
165
|
+
case "range":
|
|
166
|
+
return `• range ${src.start}..${src.end}`;
|
|
167
|
+
case "insertAfter":
|
|
168
|
+
return `• insertAfter ${src.after}..`;
|
|
169
|
+
case "insertBefore":
|
|
170
|
+
return `• insertBefore ..${src.before}`;
|
|
171
|
+
case "substring":
|
|
172
|
+
return `• substring ${JSON.stringify(src.needle)}`;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
113
177
|
function formatMetadataLine(lineCount: number | null, language: string | undefined, uiTheme: Theme): string {
|
|
114
178
|
const icon = uiTheme.getLangIcon(language);
|
|
115
179
|
if (lineCount !== null) {
|
|
@@ -175,8 +239,12 @@ export const editToolRenderer = {
|
|
|
175
239
|
let text = `${ui.title(opTitle)} ${spinner ? `${spinner} ` : ""}${editIcon} ${pathDisplay}`;
|
|
176
240
|
|
|
177
241
|
// Show streaming preview of diff/content
|
|
178
|
-
if (args.
|
|
242
|
+
if (args.previewDiff) {
|
|
243
|
+
text += formatStreamingDiff(args.previewDiff, rawPath, uiTheme, "preview");
|
|
244
|
+
} else if (args.diff && args.op) {
|
|
179
245
|
text += formatStreamingDiff(args.diff, rawPath, uiTheme);
|
|
246
|
+
} else if (args.edits && args.edits.length > 0) {
|
|
247
|
+
text += formatStreamingHashlineEdits(args.edits, uiTheme, ui);
|
|
180
248
|
} else if (args.diff) {
|
|
181
249
|
const previewLines = args.diff.split("\n");
|
|
182
250
|
const maxLines = 6;
|
package/src/web/search/render.ts
CHANGED
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
* Tree-based rendering with collapsed/expanded states for web search results.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { getSearchProvider } from "@oh-my-pi/pi-coding-agent/web/search/provider";
|
|
8
7
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
9
8
|
import { Text, visibleWidth, wrapTextWithAnsi } from "@oh-my-pi/pi-tui";
|
|
10
9
|
import type { RenderResultOptions } from "../../extensibility/custom-tools/types";
|
|
@@ -23,6 +22,7 @@ import {
|
|
|
23
22
|
} from "../../tools/render-utils";
|
|
24
23
|
import { renderStatusLine, renderTreeList } from "../../tui";
|
|
25
24
|
import { CachedOutputBlock } from "../../tui/output-block";
|
|
25
|
+
import { getSearchProvider } from "./provider";
|
|
26
26
|
import type { SearchResponse } from "./types";
|
|
27
27
|
|
|
28
28
|
const MAX_COLLAPSED_ANSWER_LINES = PREVIEW_LIMITS.COLLAPSED_LINES;
|