decorated-pi 0.7.0 → 0.7.2
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/README.md +38 -12
- package/hooks/inject-agents-md.ts +20 -12
- package/hooks/mcp.ts +61 -64
- package/hooks/pi-tool-filter.ts +2 -1
- package/hooks/rtk.ts +3 -2
- package/hooks/skeleton.ts +46 -18
- package/hooks/smart-at.ts +122 -264
- package/hooks/wakatime.ts +3 -2
- package/index.ts +117 -3
- package/package.json +9 -4
- package/settings.ts +5 -3
- package/skills/pi-docs/SKILL.md +13 -0
- package/tools/lsp/tools.ts +0 -1
- package/tools/mcp/builtin/codegraph.ts +1 -1
- package/tools/mcp/client.ts +1 -1
- package/tools/patch/core.ts +17 -1
- package/tools/patch/index.ts +338 -225
package/tools/patch/index.ts
CHANGED
|
@@ -7,278 +7,391 @@
|
|
|
7
7
|
|
|
8
8
|
import { defineTool, type ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
9
9
|
import { renderDiff } from "@earendil-works/pi-coding-agent";
|
|
10
|
-
import { Box, Container, Spacer, Text, truncateToWidth } from "@earendil-works/pi-tui";
|
|
11
|
-
import { Type } from "typebox";
|
|
12
10
|
import {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
11
|
+
Box,
|
|
12
|
+
Container,
|
|
13
|
+
Spacer,
|
|
14
|
+
Text,
|
|
15
|
+
truncateToWidth,
|
|
16
|
+
} from "@earendil-works/pi-tui";
|
|
17
|
+
import { Type } from "typebox";
|
|
18
|
+
import { applyPatch, generatePatchDiff, type PatchPreview } from "./core.js";
|
|
17
19
|
|
|
18
20
|
const EditSchema = Type.Object({
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
anchor: Type.Optional(
|
|
22
|
+
Type.String({
|
|
23
|
+
description:
|
|
24
|
+
"Optional unique string that appears BEFORE old_str in the file. Narrows the search range.",
|
|
25
|
+
}),
|
|
26
|
+
),
|
|
27
|
+
old_str: Type.String({
|
|
28
|
+
description:
|
|
29
|
+
"Exact text to find. Must be unique within the search range. String, not regex.",
|
|
30
|
+
}),
|
|
31
|
+
new_str: Type.String({
|
|
32
|
+
description: "Replacement text. String. Use empty string to delete.",
|
|
33
|
+
}),
|
|
28
34
|
});
|
|
29
35
|
|
|
30
36
|
const PatchSchema = Type.Object({
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
+
path: Type.String({
|
|
38
|
+
description: "Path to the file to edit (relative or absolute).",
|
|
39
|
+
}),
|
|
40
|
+
edits: Type.Array(EditSchema, {
|
|
41
|
+
description:
|
|
42
|
+
"Targeted replacements applied sequentially. Each edit does exact string replacement with optional anchor.",
|
|
43
|
+
}),
|
|
37
44
|
});
|
|
38
45
|
|
|
39
46
|
function fixJsonNewlines(str: string): string {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
47
|
+
let result = "";
|
|
48
|
+
let inString = false;
|
|
49
|
+
let escaped = false;
|
|
50
|
+
for (let i = 0; i < str.length; i++) {
|
|
51
|
+
const ch = str[i];
|
|
52
|
+
if (escaped) {
|
|
53
|
+
result += ch;
|
|
54
|
+
escaped = false;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (ch === "\\") {
|
|
58
|
+
result += ch;
|
|
59
|
+
escaped = true;
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
if (ch === '"') {
|
|
63
|
+
inString = !inString;
|
|
64
|
+
result += ch;
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
if (inString && (ch === "\n" || ch === "\r")) {
|
|
68
|
+
result += ch === "\n" ? "\\n" : "\\r";
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
result += ch;
|
|
51
72
|
}
|
|
52
|
-
result
|
|
53
|
-
}
|
|
54
|
-
return result;
|
|
73
|
+
return result;
|
|
55
74
|
}
|
|
56
75
|
|
|
57
76
|
function jsonParseWithNewlineFix(str: string): any {
|
|
58
|
-
|
|
59
|
-
|
|
77
|
+
try {
|
|
78
|
+
return JSON.parse(str);
|
|
79
|
+
} catch {
|
|
80
|
+
try {
|
|
81
|
+
return JSON.parse(fixJsonNewlines(str));
|
|
82
|
+
} catch {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
60
86
|
}
|
|
61
87
|
|
|
62
88
|
export function preparePatchArguments(input: any): any {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
if (typeof args.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
89
|
+
if (!input || typeof input !== "object") return input;
|
|
90
|
+
const args = input as Record<string, any>;
|
|
91
|
+
if (typeof args.edits === "string") {
|
|
92
|
+
try {
|
|
93
|
+
const parsed = jsonParseWithNewlineFix(args.edits);
|
|
94
|
+
if (Array.isArray(parsed)) args.edits = parsed;
|
|
95
|
+
} catch {
|
|
96
|
+
/* keep original */
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (typeof args.old_str === "string" && typeof args.new_str === "string") {
|
|
100
|
+
const edit: any = { old_str: args.old_str, new_str: args.new_str };
|
|
101
|
+
if (typeof args.anchor === "string") edit.anchor = args.anchor;
|
|
102
|
+
args.edits = args.edits ? [...args.edits, edit] : [edit];
|
|
103
|
+
delete args.old_str;
|
|
104
|
+
delete args.new_str;
|
|
105
|
+
delete args.anchor;
|
|
106
|
+
}
|
|
107
|
+
return args;
|
|
80
108
|
}
|
|
81
109
|
|
|
82
110
|
interface PatchCallComponent extends Box {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
111
|
+
preview?: PatchPreview;
|
|
112
|
+
previewArgsKey?: string;
|
|
113
|
+
previewPending?: boolean;
|
|
114
|
+
settledError: boolean;
|
|
87
115
|
}
|
|
88
116
|
|
|
89
117
|
export function createPatchCallComponent(): PatchCallComponent {
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
118
|
+
return Object.assign(new Box(1, 1, (text: string) => text), {
|
|
119
|
+
preview: undefined,
|
|
120
|
+
previewArgsKey: undefined,
|
|
121
|
+
previewPending: false,
|
|
122
|
+
settledError: false,
|
|
123
|
+
});
|
|
96
124
|
}
|
|
97
125
|
|
|
98
|
-
function getPatchCallComponent(
|
|
99
|
-
|
|
100
|
-
|
|
126
|
+
function getPatchCallComponent(
|
|
127
|
+
state: any,
|
|
128
|
+
lastComponent: any,
|
|
129
|
+
): PatchCallComponent {
|
|
130
|
+
if (lastComponent instanceof Box) {
|
|
131
|
+
const comp = lastComponent as PatchCallComponent;
|
|
132
|
+
state.callComponent = comp;
|
|
133
|
+
return comp;
|
|
134
|
+
}
|
|
135
|
+
if (state.callComponent) return state.callComponent;
|
|
136
|
+
const comp = createPatchCallComponent();
|
|
101
137
|
state.callComponent = comp;
|
|
102
138
|
return comp;
|
|
103
|
-
}
|
|
104
|
-
if (state.callComponent) return state.callComponent;
|
|
105
|
-
const comp = createPatchCallComponent();
|
|
106
|
-
state.callComponent = comp;
|
|
107
|
-
return comp;
|
|
108
139
|
}
|
|
109
140
|
|
|
110
141
|
function replaceTabs(text: string): string {
|
|
111
|
-
|
|
142
|
+
return text.replace(/\t/g, " ");
|
|
112
143
|
}
|
|
113
144
|
|
|
114
145
|
function getPatchHeaderBg(component: PatchCallComponent, theme: any) {
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
146
|
+
if (component.settledError)
|
|
147
|
+
return (text: string) => theme.bg("toolErrorBg", text);
|
|
148
|
+
if (component.preview) {
|
|
149
|
+
if ("error" in component.preview && component.preview.error)
|
|
150
|
+
return (text: string) => theme.bg("toolErrorBg", text);
|
|
151
|
+
return (text: string) => theme.bg("toolSuccessBg", text);
|
|
152
|
+
}
|
|
153
|
+
return (text: string) => theme.bg("toolPendingBg", text);
|
|
121
154
|
}
|
|
122
155
|
|
|
123
156
|
function createSingleLineComponent(text: string) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
157
|
+
return {
|
|
158
|
+
render(width: number) {
|
|
159
|
+
return [truncateToWidth(text, width)];
|
|
160
|
+
},
|
|
161
|
+
invalidate() {},
|
|
162
|
+
};
|
|
128
163
|
}
|
|
129
164
|
|
|
130
165
|
function formatPatchMetaLine(line: string, theme: any): string {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
166
|
+
const missingSuffix = " (missing)";
|
|
167
|
+
if (line.endsWith(missingSuffix)) {
|
|
168
|
+
return (
|
|
169
|
+
theme.fg("accent", line.slice(0, -missingSuffix.length)) +
|
|
170
|
+
theme.fg("warning", missingSuffix)
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
return theme.fg("accent", line);
|
|
136
174
|
}
|
|
137
175
|
|
|
138
176
|
function appendPatchDiffChildren(parent: Box, body: string, theme: any): void {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
177
|
+
const rawLines = body.split("\n");
|
|
178
|
+
let buffer: string[] = [];
|
|
179
|
+
const flush = () => {
|
|
180
|
+
if (buffer.length === 0) return;
|
|
181
|
+
parent.addChild(
|
|
182
|
+
new Text(renderDiff(replaceTabs(buffer.join("\n"))), 0, 0),
|
|
183
|
+
);
|
|
184
|
+
buffer = [];
|
|
185
|
+
};
|
|
186
|
+
for (const line of rawLines) {
|
|
187
|
+
if (line.startsWith("@@ lines ")) {
|
|
188
|
+
flush();
|
|
189
|
+
parent.addChild(
|
|
190
|
+
createSingleLineComponent(
|
|
191
|
+
formatPatchMetaLine(line, theme),
|
|
192
|
+
) as any,
|
|
193
|
+
);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (line === "anchors:") {
|
|
197
|
+
flush();
|
|
198
|
+
parent.addChild(
|
|
199
|
+
createSingleLineComponent(
|
|
200
|
+
formatPatchMetaLine(line, theme),
|
|
201
|
+
) as any,
|
|
202
|
+
);
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
205
|
+
if (line.startsWith(" - ")) {
|
|
206
|
+
flush();
|
|
207
|
+
parent.addChild(
|
|
208
|
+
createSingleLineComponent(
|
|
209
|
+
formatPatchMetaLine(line, theme),
|
|
210
|
+
) as any,
|
|
211
|
+
);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
buffer.push(line);
|
|
215
|
+
}
|
|
216
|
+
flush();
|
|
153
217
|
}
|
|
154
218
|
|
|
155
|
-
function buildPatchCallComponent(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
219
|
+
function buildPatchCallComponent(
|
|
220
|
+
component: PatchCallComponent,
|
|
221
|
+
args: any,
|
|
222
|
+
theme: any,
|
|
223
|
+
expanded = false,
|
|
224
|
+
) {
|
|
225
|
+
component.setBgFn(getPatchHeaderBg(component, theme));
|
|
226
|
+
component.clear();
|
|
227
|
+
let label = "";
|
|
228
|
+
if (args?.path) {
|
|
229
|
+
label = theme.fg("accent", args.path);
|
|
230
|
+
if (Array.isArray(args.edits) && args.edits.length > 0) {
|
|
231
|
+
label += theme.fg(
|
|
232
|
+
"dim",
|
|
233
|
+
` (${args.edits.length} edit${args.edits.length > 1 ? "s" : ""})`,
|
|
234
|
+
);
|
|
235
|
+
}
|
|
163
236
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
237
|
+
const headerText =
|
|
238
|
+
theme.fg("toolTitle", theme.bold("patch")) + (label ? " " + label : "");
|
|
239
|
+
component.addChild(new Text(headerText, 0, 0));
|
|
240
|
+
if (component.settledError || !component.preview) return component;
|
|
241
|
+
const preview = component.preview;
|
|
242
|
+
let body = "";
|
|
243
|
+
if ("diff" in preview && preview.diff) body = preview.diff;
|
|
244
|
+
else if ("error" in preview && preview.error) {
|
|
245
|
+
component.addChild(new Spacer(1));
|
|
246
|
+
component.addChild(
|
|
247
|
+
new Text(theme.fg("error", ` Error: ${preview.error}`), 0, 0),
|
|
248
|
+
);
|
|
249
|
+
return component;
|
|
250
|
+
}
|
|
251
|
+
if (!body) {
|
|
252
|
+
component.addChild(new Spacer(1));
|
|
253
|
+
component.addChild(new Text(theme.fg("dim", ` (no changes)`), 0, 0));
|
|
254
|
+
return component;
|
|
255
|
+
}
|
|
256
|
+
const lines = body.split("\n");
|
|
257
|
+
const FOLD_THRESHOLD = 45;
|
|
177
258
|
component.addChild(new Spacer(1));
|
|
178
|
-
|
|
259
|
+
if (lines.length > FOLD_THRESHOLD && !expanded) {
|
|
260
|
+
const shown = lines.slice(0, 10).join("\n");
|
|
261
|
+
appendPatchDiffChildren(component, shown, theme);
|
|
262
|
+
component.addChild(
|
|
263
|
+
new Text(
|
|
264
|
+
theme.fg("dim", ` ... ${lines.length - 10} more lines (`) +
|
|
265
|
+
"(expand)" +
|
|
266
|
+
theme.fg("dim", ")"),
|
|
267
|
+
0,
|
|
268
|
+
0,
|
|
269
|
+
),
|
|
270
|
+
);
|
|
271
|
+
} else {
|
|
272
|
+
appendPatchDiffChildren(component, body, theme);
|
|
273
|
+
}
|
|
179
274
|
return component;
|
|
180
|
-
}
|
|
181
|
-
const lines = body.split("\n");
|
|
182
|
-
const FOLD_THRESHOLD = 45;
|
|
183
|
-
component.addChild(new Spacer(1));
|
|
184
|
-
if (lines.length > FOLD_THRESHOLD && !expanded) {
|
|
185
|
-
const shown = lines.slice(0, 10).join("\n");
|
|
186
|
-
appendPatchDiffChildren(component, shown, theme);
|
|
187
|
-
component.addChild(new Text(
|
|
188
|
-
theme.fg("dim", ` ... ${lines.length - 10} more lines (`) + "(expand)" + theme.fg("dim", ")"),
|
|
189
|
-
0, 0,
|
|
190
|
-
));
|
|
191
|
-
} else {
|
|
192
|
-
appendPatchDiffChildren(component, body, theme);
|
|
193
|
-
}
|
|
194
|
-
return component;
|
|
195
275
|
}
|
|
196
276
|
|
|
197
277
|
export function registerPatchTool(pi: ExtensionAPI): void {
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
278
|
+
pi.registerTool(
|
|
279
|
+
defineTool({
|
|
280
|
+
name: "patch",
|
|
281
|
+
label: "Patch",
|
|
282
|
+
description: [
|
|
283
|
+
"Edits a file using exact string replacement, with anchor support.",
|
|
284
|
+
"When old_str is not unique, add more surrounding context or use anchor to narrow search.",
|
|
285
|
+
"",
|
|
286
|
+
"Examples:",
|
|
287
|
+
' { path: "src/foo.ts", edits: [{ old_str: "return 1", new_str: "return 42" }] }',
|
|
288
|
+
' { path: "src/foo.ts", edits: [{ anchor: "function bar() {", old_str: "return x", new_str: "return x + 1" }] }',
|
|
289
|
+
' { path: "src/foo.ts", edits: [{ anchor: "function init() {", old_str: "const DEBUG = true;", new_str: "const DEBUG = false;" }, { old_str: "log(\\"debug\\");", new_str: "// debug disabled" }] }',
|
|
290
|
+
"",
|
|
291
|
+
"Anchor (optional): narrows old_str search to lines after a unique marker.",
|
|
292
|
+
" Code: use the enclosing definition — function/class/struct/method signature.",
|
|
293
|
+
' e.g. "function handleClick() {" or "class UserService {" or "struct Config {".',
|
|
294
|
+
" Non-code (markdown, config, etc.): use section headings, key names, or distinctive lines.",
|
|
295
|
+
' e.g. "## API Reference" in .md or "[dependencies]" in .toml files.',
|
|
296
|
+
].join("\n"),
|
|
297
|
+
promptSnippet:
|
|
298
|
+
"Edits a file using exact string replacement, with anchor support.",
|
|
299
|
+
promptGuidelines: [
|
|
300
|
+
"The patch tool is the preferred way to modify files — use it over the bash tool (sed/heredoc/python etc.).",
|
|
301
|
+
"When editing an existing file, prefer patch over overwrite to avoid overwriting prior changes.",
|
|
302
|
+
"To prevent hallucinations: 1. Keep each edit batch ≤ 5 changes; 2. Process remaining revisions in sequential steps.",
|
|
303
|
+
"On repeated failures: read the file first to confirm information accuracy.",
|
|
304
|
+
],
|
|
305
|
+
parameters: PatchSchema,
|
|
306
|
+
renderShell: "self",
|
|
307
|
+
prepareArguments: preparePatchArguments,
|
|
308
|
+
execute: async (
|
|
309
|
+
_toolCallId: string,
|
|
310
|
+
input: { path: string; edits: any[] },
|
|
311
|
+
_signal: any,
|
|
312
|
+
_onUpdate: any,
|
|
313
|
+
ctx: any,
|
|
314
|
+
) => {
|
|
315
|
+
const cwd: string = ctx.cwd ?? process.cwd();
|
|
316
|
+
// Stale-read check is in hooks/track-mtime.ts (tool_call phase).
|
|
317
|
+
// Just apply the patch here.
|
|
318
|
+
const result = await applyPatch(input as any, cwd);
|
|
319
|
+
const diff = generatePatchDiff(result);
|
|
320
|
+
// Return "Success" on the LLM-facing channel — the model already knows
|
|
321
|
+
// what it asked to change (it sent the edits). The full diff stays in
|
|
322
|
+
// `details` for the TUI renderer. This keeps the prompt-cache segment
|
|
323
|
+
// stable: success always costs 1 token regardless of N hunks edited.
|
|
324
|
+
return {
|
|
325
|
+
content: [{ type: "text", text: "Success" }],
|
|
326
|
+
details: { diff },
|
|
327
|
+
};
|
|
328
|
+
},
|
|
329
|
+
renderCall(args: any, theme: any, context: any) {
|
|
330
|
+
const state = context.state;
|
|
331
|
+
const component = getPatchCallComponent(
|
|
332
|
+
state,
|
|
333
|
+
context.lastComponent,
|
|
334
|
+
);
|
|
335
|
+
const argsKey = args ? JSON.stringify(args) : undefined;
|
|
336
|
+
if (component.previewArgsKey !== argsKey) {
|
|
337
|
+
component.preview = undefined;
|
|
338
|
+
component.previewArgsKey = argsKey;
|
|
339
|
+
component.previewPending = false;
|
|
340
|
+
component.settledError = false;
|
|
341
|
+
}
|
|
342
|
+
return buildPatchCallComponent(
|
|
343
|
+
component,
|
|
344
|
+
args,
|
|
345
|
+
theme,
|
|
346
|
+
context.expanded,
|
|
347
|
+
);
|
|
348
|
+
},
|
|
349
|
+
renderResult(result: any, options: any, theme: any, context: any) {
|
|
350
|
+
const callComponent: PatchCallComponent | undefined =
|
|
351
|
+
context.state.callComponent;
|
|
352
|
+
let changed = false;
|
|
353
|
+
if (callComponent) {
|
|
354
|
+
const resultDiff = !context.isError && result.details?.diff;
|
|
355
|
+
if (typeof resultDiff === "string") {
|
|
356
|
+
const newPreview = { diff: resultDiff };
|
|
357
|
+
if (callComponent.preview?.diff !== resultDiff) {
|
|
358
|
+
callComponent.preview = newPreview;
|
|
359
|
+
changed = true;
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
if (callComponent.settledError !== context.isError) {
|
|
363
|
+
callComponent.settledError = context.isError;
|
|
364
|
+
changed = true;
|
|
365
|
+
}
|
|
366
|
+
if (changed) {
|
|
367
|
+
buildPatchCallComponent(
|
|
368
|
+
callComponent,
|
|
369
|
+
context.args,
|
|
370
|
+
theme,
|
|
371
|
+
options.expanded,
|
|
372
|
+
);
|
|
373
|
+
if (context.isError) {
|
|
374
|
+
const errorText = result.content
|
|
375
|
+
.filter((c: any) => c.type === "text")
|
|
376
|
+
.map((c: any) => c.text || "")
|
|
377
|
+
.join("\n");
|
|
378
|
+
if (errorText) {
|
|
379
|
+
callComponent.addChild(new Spacer(1));
|
|
380
|
+
callComponent.addChild(
|
|
381
|
+
new Text(
|
|
382
|
+
theme.fg("error", errorText),
|
|
383
|
+
0,
|
|
384
|
+
0,
|
|
385
|
+
),
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
const component = context.lastComponent ?? new Container();
|
|
392
|
+
component.clear();
|
|
393
|
+
return component;
|
|
394
|
+
},
|
|
395
|
+
}),
|
|
396
|
+
);
|
|
284
397
|
}
|