decorated-pi 0.7.1 → 0.7.3

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.
@@ -7,279 +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
- applyPatch,
14
- generatePatchDiff,
15
- type PatchPreview,
16
- } from "./core.js";
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
- anchor: Type.Optional(Type.String({
20
- description: "Optional unique string that appears BEFORE old_str in the file. Narrows the search range.",
21
- })),
22
- old_str: Type.String({
23
- description: "Exact text to find. Must be unique within the search range. String, not regex.",
24
- }),
25
- new_str: Type.String({
26
- description: "Replacement text. String. Use empty string to delete.",
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
- path: Type.String({
32
- description: "Path to the file to edit (relative or absolute).",
33
- }),
34
- edits: Type.Array(EditSchema, {
35
- description: "Targeted replacements applied sequentially. Each edit does exact string replacement with optional anchor.",
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
- let result = '';
41
- let inString = false;
42
- let escaped = false;
43
- for (let i = 0; i < str.length; i++) {
44
- const ch = str[i];
45
- if (escaped) { result += ch; escaped = false; continue; }
46
- if (ch === '\\') { result += ch; escaped = true; continue; }
47
- if (ch === '"') { inString = !inString; result += ch; continue; }
48
- if (inString && (ch === '\n' || ch === '\r')) {
49
- result += ch === '\n' ? '\\n' : '\\r';
50
- continue;
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 += ch;
53
- }
54
- return result;
73
+ return result;
55
74
  }
56
75
 
57
76
  function jsonParseWithNewlineFix(str: string): any {
58
- try { return JSON.parse(str); }
59
- catch { try { return JSON.parse(fixJsonNewlines(str)); } catch { return undefined; } }
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
- if (!input || typeof input !== "object") return input;
64
- const args = input as Record<string, any>;
65
- if (typeof args.edits === "string") {
66
- try {
67
- const parsed = jsonParseWithNewlineFix(args.edits);
68
- if (Array.isArray(parsed)) args.edits = parsed;
69
- } catch { /* keep original */ }
70
- }
71
- if (typeof args.old_str === "string" && typeof args.new_str === "string") {
72
- const edit: any = { old_str: args.old_str, new_str: args.new_str };
73
- if (typeof args.anchor === "string") edit.anchor = args.anchor;
74
- args.edits = args.edits ? [...args.edits, edit] : [edit];
75
- delete args.old_str;
76
- delete args.new_str;
77
- delete args.anchor;
78
- }
79
- return args;
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
- preview?: PatchPreview;
84
- previewArgsKey?: string;
85
- previewPending?: boolean;
86
- settledError: boolean;
111
+ preview?: PatchPreview;
112
+ previewArgsKey?: string;
113
+ previewPending?: boolean;
114
+ settledError: boolean;
87
115
  }
88
116
 
89
117
  export function createPatchCallComponent(): PatchCallComponent {
90
- return Object.assign(new Box(1, 1, (text: string) => text), {
91
- preview: undefined,
92
- previewArgsKey: undefined,
93
- previewPending: false,
94
- settledError: false,
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(state: any, lastComponent: any): PatchCallComponent {
99
- if (lastComponent instanceof Box) {
100
- const comp = lastComponent as PatchCallComponent;
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
- return text.replace(/\t/g, " ");
142
+ return text.replace(/\t/g, " ");
112
143
  }
113
144
 
114
145
  function getPatchHeaderBg(component: PatchCallComponent, theme: any) {
115
- if (component.settledError) return (text: string) => theme.bg("toolErrorBg", text);
116
- if (component.preview) {
117
- if ("error" in component.preview && component.preview.error) return (text: string) => theme.bg("toolErrorBg", text);
118
- return (text: string) => theme.bg("toolSuccessBg", text);
119
- }
120
- return (text: string) => theme.bg("toolPendingBg", text);
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
- return {
125
- render(width: number) { return [truncateToWidth(text, width)]; },
126
- invalidate() {},
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
- const missingSuffix = " (missing)";
132
- if (line.endsWith(missingSuffix)) {
133
- return theme.fg("accent", line.slice(0, -missingSuffix.length)) + theme.fg("warning", missingSuffix);
134
- }
135
- return theme.fg("accent", line);
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
- const rawLines = body.split("\n");
140
- let buffer: string[] = [];
141
- const flush = () => {
142
- if (buffer.length === 0) return;
143
- parent.addChild(new Text(renderDiff(replaceTabs(buffer.join("\n"))), 0, 0));
144
- buffer = [];
145
- };
146
- for (const line of rawLines) {
147
- if (line.startsWith("@@ lines ")) { flush(); parent.addChild(createSingleLineComponent(formatPatchMetaLine(line, theme)) as any); continue; }
148
- if (line === "anchors:") { flush(); parent.addChild(createSingleLineComponent(formatPatchMetaLine(line, theme)) as any); continue; }
149
- if (line.startsWith(" - ")) { flush(); parent.addChild(createSingleLineComponent(formatPatchMetaLine(line, theme)) as any); continue; }
150
- buffer.push(line);
151
- }
152
- flush();
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(component: PatchCallComponent, args: any, theme: any, expanded = false) {
156
- component.setBgFn(getPatchHeaderBg(component, theme));
157
- component.clear();
158
- let label = "";
159
- if (args?.path) {
160
- label = theme.fg("accent", args.path);
161
- if (Array.isArray(args.edits) && args.edits.length > 0) {
162
- label += theme.fg("dim", ` (${args.edits.length} edit${args.edits.length > 1 ? "s" : ""})`);
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
- const headerText = theme.fg("toolTitle", theme.bold("patch")) + (label ? " " + label : "");
166
- component.addChild(new Text(headerText, 0, 0));
167
- if (component.settledError || !component.preview) return component;
168
- const preview = component.preview;
169
- let body = "";
170
- if ("diff" in preview && preview.diff) body = preview.diff;
171
- else if ("error" in preview && preview.error) {
172
- component.addChild(new Spacer(1));
173
- component.addChild(new Text(theme.fg("error", ` Error: ${preview.error}`), 0, 0));
174
- return component;
175
- }
176
- if (!body) {
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
- component.addChild(new Text(theme.fg("dim", ` (no changes)`), 0, 0));
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
- pi.registerTool(defineTool({
199
- name: "patch",
200
- label: "Patch",
201
- description: [
202
- "Edits a file using exact string replacement, with anchor support.",
203
- "When old_str is not unique, add more surrounding context or use anchor to narrow search.",
204
- "",
205
- "Examples:",
206
- ' { path: "src/foo.ts", edits: [{ old_str: "return 1", new_str: "return 42" }] }',
207
- ' { path: "src/foo.ts", edits: [{ anchor: "function bar() {", old_str: "return x", new_str: "return x + 1" }] }',
208
- ' { 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" }] }',
209
- "",
210
- "Anchor (optional): narrows old_str search to lines after a unique marker.",
211
- " Code: use the enclosing definition function/class/struct/method signature.",
212
- ' e.g. "function handleClick() {" or "class UserService {" or "struct Config {".',
213
- " Non-code (markdown, config, etc.): use section headings, key names, or distinctive lines.",
214
- ' e.g. "## API Reference" in .md or "[dependencies]" in .toml files.',
215
- ].join("\n"),
216
- promptSnippet: "Edits a file using exact string replacement, with anchor support.",
217
- promptGuidelines: [
218
- "The patch tool is the preferred way to modify files — use it over the write tool (full overwrite) or the bash tool (sed/echo).",
219
- "When editing an existing file, use patch instead of write to avoid overwriting changes made since the file was last read.",
220
- "To prevent hallucinations: 1. Keep each edit batch 5 changes; 2. Process remaining revisions in sequential steps.",
221
- "On repeated failures: read the file first to confirm information accuracy.",
222
- ],
223
- parameters: PatchSchema,
224
- renderShell: "self",
225
- prepareArguments: preparePatchArguments,
226
- execute: async (_toolCallId: string, input: { path: string; edits: any[] }, _signal: any, _onUpdate: any, ctx: any) => {
227
- const cwd: string = ctx.cwd ?? process.cwd();
228
- // Stale-read check is in hooks/track-mtime.ts (tool_call phase).
229
- // Just apply the patch here.
230
- const result = await applyPatch(input as any, cwd);
231
- const diff = generatePatchDiff(result);
232
- // Return "Success" on the LLM-facing channel — the model already knows
233
- // what it asked to change (it sent the edits). The full diff stays in
234
- // `details` for the TUI renderer. This keeps the prompt-cache segment
235
- // stable: success always costs 1 token regardless of N hunks edited.
236
- return { content: [{ type: "text", text: "Success" }], details: { diff } };
237
- },
238
- renderCall(args: any, theme: any, context: any) {
239
- const state = context.state;
240
- const component = getPatchCallComponent(state, context.lastComponent);
241
- const argsKey = args ? JSON.stringify(args) : undefined;
242
- if (component.previewArgsKey !== argsKey) {
243
- component.preview = undefined;
244
- component.previewArgsKey = argsKey;
245
- component.previewPending = false;
246
- component.settledError = false;
247
- }
248
- return buildPatchCallComponent(component, args, theme, context.expanded);
249
- },
250
- renderResult(result: any, options: any, theme: any, context: any) {
251
- const callComponent: PatchCallComponent | undefined = context.state.callComponent;
252
- let changed = false;
253
- if (callComponent) {
254
- const resultDiff = !context.isError && result.details?.diff;
255
- if (typeof resultDiff === "string") {
256
- const newPreview = { diff: resultDiff };
257
- if (callComponent.preview?.diff !== resultDiff) {
258
- callComponent.preview = newPreview;
259
- changed = true;
260
- }
261
- }
262
- if (callComponent.settledError !== context.isError) {
263
- callComponent.settledError = context.isError;
264
- changed = true;
265
- }
266
- if (changed) {
267
- buildPatchCallComponent(callComponent, context.args, theme, options.expanded);
268
- if (context.isError) {
269
- const errorText = result.content
270
- .filter((c: any) => c.type === "text")
271
- .map((c: any) => c.text || "")
272
- .join("\n");
273
- if (errorText) {
274
- callComponent.addChild(new Spacer(1));
275
- callComponent.addChild(new Text(theme.fg("error", errorText), 0, 0));
276
- }
277
- }
278
- }
279
- }
280
- const component = context.lastComponent ?? new Container();
281
- component.clear();
282
- return component;
283
- },
284
- }));
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
+ );
285
397
  }
package/tsconfig.json CHANGED
@@ -19,6 +19,7 @@
19
19
  "commands/**/*.ts",
20
20
  "providers/**/*.ts",
21
21
  "ui/**/*.ts",
22
+ "utils/**/*.ts",
22
23
  "test/**/*.ts"
23
24
  ],
24
25
  "exclude": ["node_modules", "dist"]