mini-coder 0.5.13 → 0.6.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/README.md +25 -108
- package/bin/mc.ts +8 -11
- package/bun.lock +79 -269
- package/package.json +17 -22
- package/src/agent.ts +242 -915
- package/src/args.ts +289 -0
- package/src/headless.ts +43 -385
- package/src/index.ts +29 -836
- package/src/oauth.ts +117 -0
- package/src/prompt.ts +227 -276
- package/src/session.ts +57 -961
- package/src/shared.ts +117 -38
- package/src/tool-bash.ts +110 -0
- package/src/tool-edit.ts +133 -0
- package/src/tool-task.ts +114 -0
- package/src/tui-components.ts +150 -0
- package/src/tui-conversation.ts +262 -0
- package/src/tui-editor.ts +29 -0
- package/src/tui-overlay.ts +403 -0
- package/src/tui.ts +236 -0
- package/src/types.ts +160 -0
- package/tsconfig.json +17 -0
- package/BENCHMARK.md +0 -107
- package/LICENSE +0 -9
- package/PROGRESS.md +0 -4
- package/assets/icon-1-minimal.svg +0 -31
- package/assets/icon-2-dark-terminal.svg +0 -48
- package/assets/icon-3-gradient-modern.svg +0 -45
- package/assets/icon-4-filled-bold.svg +0 -54
- package/assets/icon-5-community-badge.svg +0 -63
- package/assets/mc-claude-smart.png +0 -0
- package/assets/mc-gpt-smart.png +0 -0
- package/assets/preview-0-5-0.png +0 -0
- package/assets/preview.gif +0 -0
- package/benchmark-baseline.sh +0 -15
- package/benchmark-loop.sh +0 -19
- package/skills-lock.json +0 -15
- package/src/cli.ts +0 -134
- package/src/errors.ts +0 -15
- package/src/git.ts +0 -247
- package/src/input.ts +0 -168
- package/src/mcp.ts +0 -609
- package/src/paths.ts +0 -37
- package/src/session-message.ts +0 -393
- package/src/settings.ts +0 -449
- package/src/skills.ts +0 -271
- package/src/submit.ts +0 -371
- package/src/text.ts +0 -71
- package/src/theme.ts +0 -330
- package/src/tool-common.ts +0 -93
- package/src/tool-grep.ts +0 -606
- package/src/tool-read.ts +0 -313
- package/src/tool-shell.ts +0 -1001
- package/src/tools.ts +0 -854
- package/src/ui/agent.ts +0 -317
- package/src/ui/commands.test.ts +0 -913
- package/src/ui/commands.ts +0 -834
- package/src/ui/conversation.test.ts +0 -585
- package/src/ui/conversation.ts +0 -1836
- package/src/ui/help.ts +0 -158
- package/src/ui/input.test.ts +0 -64
- package/src/ui/input.ts +0 -138
- package/src/ui/overlay.ts +0 -59
- package/src/ui/runtime.ts +0 -69
- package/src/ui/status.ts +0 -220
- package/src/ui.ts +0 -1190
- package/src/version.ts +0 -48
|
@@ -1,585 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import type { Node, TextNode } from "@cel-tui/types";
|
|
3
|
-
import type { AssistantMessage, ToolResultMessage } from "@mariozechner/pi-ai";
|
|
4
|
-
import { DEFAULT_THEME } from "../theme.ts";
|
|
5
|
-
import {
|
|
6
|
-
buildConversationLogNodes,
|
|
7
|
-
renderAssistantMessage,
|
|
8
|
-
renderToolResult,
|
|
9
|
-
} from "./conversation.ts";
|
|
10
|
-
|
|
11
|
-
function collectInlineText(node: Node): string {
|
|
12
|
-
if (node.type === "text") {
|
|
13
|
-
return node.content;
|
|
14
|
-
}
|
|
15
|
-
if (node.type === "textinput") {
|
|
16
|
-
return "";
|
|
17
|
-
}
|
|
18
|
-
return node.children.map((child) => collectInlineText(child)).join("");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function collectRenderedLines(node: Node | null): string[] {
|
|
22
|
-
if (!node) {
|
|
23
|
-
return [];
|
|
24
|
-
}
|
|
25
|
-
if (node.type === "text") {
|
|
26
|
-
return [node.content];
|
|
27
|
-
}
|
|
28
|
-
if (node.type === "textinput") {
|
|
29
|
-
return [];
|
|
30
|
-
}
|
|
31
|
-
if (
|
|
32
|
-
node.type === "hstack" &&
|
|
33
|
-
node.children.length === 2 &&
|
|
34
|
-
node.children[0]?.type === "text" &&
|
|
35
|
-
node.children[1]?.type === "vstack"
|
|
36
|
-
) {
|
|
37
|
-
const prefix = node.children[0].content;
|
|
38
|
-
return collectRenderedLines(node.children[1]).map(
|
|
39
|
-
(line) => `${prefix}${line}`,
|
|
40
|
-
);
|
|
41
|
-
}
|
|
42
|
-
if (node.type === "hstack") {
|
|
43
|
-
return [collectInlineText(node)];
|
|
44
|
-
}
|
|
45
|
-
return node.children.flatMap((child) => collectRenderedLines(child));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function collectTextNodes(node: Node | null): TextNode[] {
|
|
49
|
-
if (!node || node.type === "textinput") {
|
|
50
|
-
return [];
|
|
51
|
-
}
|
|
52
|
-
if (node.type === "text") {
|
|
53
|
-
return [node];
|
|
54
|
-
}
|
|
55
|
-
return node.children.flatMap((child) => collectTextNodes(child));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function makeAssistantToolCallMessage(): AssistantMessage {
|
|
59
|
-
return {
|
|
60
|
-
role: "assistant",
|
|
61
|
-
content: [
|
|
62
|
-
{
|
|
63
|
-
type: "toolCall",
|
|
64
|
-
id: "call-read",
|
|
65
|
-
name: "read",
|
|
66
|
-
arguments: { path: "hint.txt", limit: 3 },
|
|
67
|
-
},
|
|
68
|
-
],
|
|
69
|
-
api: "anthropic-messages",
|
|
70
|
-
provider: "anthropic",
|
|
71
|
-
model: "claude-sonnet-4-20250514",
|
|
72
|
-
usage: {
|
|
73
|
-
input: 0,
|
|
74
|
-
output: 0,
|
|
75
|
-
cacheRead: 0,
|
|
76
|
-
cacheWrite: 0,
|
|
77
|
-
totalTokens: 0,
|
|
78
|
-
cost: {
|
|
79
|
-
input: 0,
|
|
80
|
-
output: 0,
|
|
81
|
-
cacheRead: 0,
|
|
82
|
-
cacheWrite: 0,
|
|
83
|
-
total: 0,
|
|
84
|
-
},
|
|
85
|
-
},
|
|
86
|
-
stopReason: "toolUse",
|
|
87
|
-
timestamp: 1,
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function makeReadToolResultMessage(
|
|
92
|
-
content: ToolResultMessage["content"],
|
|
93
|
-
): ToolResultMessage {
|
|
94
|
-
return {
|
|
95
|
-
role: "toolResult",
|
|
96
|
-
toolCallId: "call-read",
|
|
97
|
-
toolName: "read",
|
|
98
|
-
content,
|
|
99
|
-
isError: false,
|
|
100
|
-
timestamp: 2,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
describe("ui/conversation", () => {
|
|
105
|
-
test("read tool-call previews render structured arguments instead of raw JSON", () => {
|
|
106
|
-
const node = renderAssistantMessage(
|
|
107
|
-
{
|
|
108
|
-
content: [
|
|
109
|
-
{
|
|
110
|
-
type: "toolCall",
|
|
111
|
-
id: "call-read",
|
|
112
|
-
name: "read",
|
|
113
|
-
arguments: {
|
|
114
|
-
path: "src/ui/conversation.ts",
|
|
115
|
-
offset: 820,
|
|
116
|
-
limit: 80,
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
],
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
showReasoning: true,
|
|
123
|
-
verbose: false,
|
|
124
|
-
theme: DEFAULT_THEME,
|
|
125
|
-
cwd: "/tmp/project",
|
|
126
|
-
previewWidth: 80,
|
|
127
|
-
},
|
|
128
|
-
);
|
|
129
|
-
|
|
130
|
-
const lines = collectRenderedLines(node);
|
|
131
|
-
expect(lines).toContain("│ read ->");
|
|
132
|
-
expect(lines).toContain("│ src/ui/conversation.ts");
|
|
133
|
-
expect(lines).toContain("│ offset: 820");
|
|
134
|
-
expect(lines).toContain("│ limit: 80");
|
|
135
|
-
expect(lines.join("\n")).not.toContain('"path"');
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
test("shell tool-call previews render the command instead of raw JSON", () => {
|
|
139
|
-
const node = renderAssistantMessage(
|
|
140
|
-
{
|
|
141
|
-
content: [
|
|
142
|
-
{
|
|
143
|
-
type: "toolCall",
|
|
144
|
-
id: "call-shell",
|
|
145
|
-
name: "shell",
|
|
146
|
-
arguments: {
|
|
147
|
-
command: 'if true; then echo "$HOME"; fi',
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
showReasoning: true,
|
|
154
|
-
verbose: true,
|
|
155
|
-
theme: DEFAULT_THEME,
|
|
156
|
-
cwd: "/tmp/project",
|
|
157
|
-
previewWidth: 80,
|
|
158
|
-
},
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
const lines = collectRenderedLines(node);
|
|
162
|
-
expect(lines).toContain("│ shell ->");
|
|
163
|
-
expect(lines.join("\n")).toContain('if true; then echo "$HOME"; fi');
|
|
164
|
-
expect(lines.join("\n")).not.toContain('"command"');
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
test("compact shell tool-call previews only render the visible tail slice", () => {
|
|
168
|
-
const command = Array.from(
|
|
169
|
-
{ length: 14 },
|
|
170
|
-
(_, index) => `echo line ${index + 1}`,
|
|
171
|
-
).join("\n");
|
|
172
|
-
|
|
173
|
-
const compactLines = collectRenderedLines(
|
|
174
|
-
renderAssistantMessage(
|
|
175
|
-
{
|
|
176
|
-
content: [
|
|
177
|
-
{
|
|
178
|
-
type: "toolCall",
|
|
179
|
-
id: "call-shell",
|
|
180
|
-
name: "shell",
|
|
181
|
-
arguments: { command },
|
|
182
|
-
},
|
|
183
|
-
],
|
|
184
|
-
},
|
|
185
|
-
{
|
|
186
|
-
showReasoning: true,
|
|
187
|
-
verbose: false,
|
|
188
|
-
theme: DEFAULT_THEME,
|
|
189
|
-
cwd: "/tmp/project",
|
|
190
|
-
previewWidth: 80,
|
|
191
|
-
},
|
|
192
|
-
),
|
|
193
|
-
);
|
|
194
|
-
const verboseLines = collectRenderedLines(
|
|
195
|
-
renderAssistantMessage(
|
|
196
|
-
{
|
|
197
|
-
content: [
|
|
198
|
-
{
|
|
199
|
-
type: "toolCall",
|
|
200
|
-
id: "call-shell",
|
|
201
|
-
name: "shell",
|
|
202
|
-
arguments: { command },
|
|
203
|
-
},
|
|
204
|
-
],
|
|
205
|
-
},
|
|
206
|
-
{
|
|
207
|
-
showReasoning: true,
|
|
208
|
-
verbose: true,
|
|
209
|
-
theme: DEFAULT_THEME,
|
|
210
|
-
cwd: "/tmp/project",
|
|
211
|
-
previewWidth: 80,
|
|
212
|
-
},
|
|
213
|
-
),
|
|
214
|
-
);
|
|
215
|
-
|
|
216
|
-
expect(compactLines).toContain("│ shell ->");
|
|
217
|
-
expect(compactLines).toContain("│ echo line 14");
|
|
218
|
-
expect(compactLines).toContain("│ And 6 lines more");
|
|
219
|
-
expect(compactLines).not.toContain("│ echo line 1");
|
|
220
|
-
expect(verboseLines).toContain("│ echo line 1");
|
|
221
|
-
expect(compactLines.length).toBeLessThan(verboseLines.length);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
test("MCP tool-call previews honor verbose mode", () => {
|
|
225
|
-
const args = {
|
|
226
|
-
query: "routing",
|
|
227
|
-
section: "guides",
|
|
228
|
-
limit: 10,
|
|
229
|
-
offset: 0,
|
|
230
|
-
filter1: "alpha",
|
|
231
|
-
filter2: "beta",
|
|
232
|
-
filter3: "gamma",
|
|
233
|
-
filter4: "delta",
|
|
234
|
-
filter5: "epsilon",
|
|
235
|
-
filter6: "zeta",
|
|
236
|
-
filter7: "eta",
|
|
237
|
-
filter8: "theta",
|
|
238
|
-
};
|
|
239
|
-
|
|
240
|
-
const compactLines = collectRenderedLines(
|
|
241
|
-
renderAssistantMessage(
|
|
242
|
-
{
|
|
243
|
-
content: [
|
|
244
|
-
{
|
|
245
|
-
type: "toolCall",
|
|
246
|
-
id: "call-mcp",
|
|
247
|
-
name: "docs__search",
|
|
248
|
-
arguments: args,
|
|
249
|
-
},
|
|
250
|
-
],
|
|
251
|
-
},
|
|
252
|
-
{
|
|
253
|
-
showReasoning: true,
|
|
254
|
-
verbose: false,
|
|
255
|
-
theme: DEFAULT_THEME,
|
|
256
|
-
cwd: "/tmp/project",
|
|
257
|
-
previewWidth: 80,
|
|
258
|
-
},
|
|
259
|
-
),
|
|
260
|
-
);
|
|
261
|
-
const verboseLines = collectRenderedLines(
|
|
262
|
-
renderAssistantMessage(
|
|
263
|
-
{
|
|
264
|
-
content: [
|
|
265
|
-
{
|
|
266
|
-
type: "toolCall",
|
|
267
|
-
id: "call-mcp",
|
|
268
|
-
name: "docs__search",
|
|
269
|
-
arguments: args,
|
|
270
|
-
},
|
|
271
|
-
],
|
|
272
|
-
},
|
|
273
|
-
{
|
|
274
|
-
showReasoning: true,
|
|
275
|
-
verbose: true,
|
|
276
|
-
theme: DEFAULT_THEME,
|
|
277
|
-
cwd: "/tmp/project",
|
|
278
|
-
previewWidth: 80,
|
|
279
|
-
},
|
|
280
|
-
),
|
|
281
|
-
);
|
|
282
|
-
|
|
283
|
-
expect(compactLines).toContain("│ docs__search ->");
|
|
284
|
-
expect(compactLines.some((line) => line.includes("And "))).toBe(true);
|
|
285
|
-
expect(compactLines.join("\n")).not.toContain('"query": "routing"');
|
|
286
|
-
expect(verboseLines.join("\n")).toContain('"query": "routing"');
|
|
287
|
-
expect(compactLines.length).toBeLessThan(verboseLines.length);
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
test("shell tool results render structured stdout/stderr details without stderr labels or error styling", () => {
|
|
291
|
-
const stdout = Array.from(
|
|
292
|
-
{ length: 12 },
|
|
293
|
-
(_, index) => `line ${index + 1}`,
|
|
294
|
-
).join("\n");
|
|
295
|
-
const node = renderToolResult(
|
|
296
|
-
"shell",
|
|
297
|
-
{ command: "run-tests" },
|
|
298
|
-
"Exit code: 1\nlegacy text should be ignored when details exist",
|
|
299
|
-
true,
|
|
300
|
-
{
|
|
301
|
-
showReasoning: true,
|
|
302
|
-
verbose: false,
|
|
303
|
-
theme: DEFAULT_THEME,
|
|
304
|
-
cwd: "/tmp/project",
|
|
305
|
-
previewWidth: 48,
|
|
306
|
-
},
|
|
307
|
-
{
|
|
308
|
-
stdout,
|
|
309
|
-
stderr: "boom",
|
|
310
|
-
exitCode: 1,
|
|
311
|
-
},
|
|
312
|
-
);
|
|
313
|
-
const lines = collectRenderedLines(node);
|
|
314
|
-
const textNodes = collectTextNodes(node);
|
|
315
|
-
|
|
316
|
-
expect(lines).toContain("│ shell <-");
|
|
317
|
-
expect(lines).toContain("│ boom");
|
|
318
|
-
expect(lines).toContain("│ exit 1");
|
|
319
|
-
expect(lines).toContain("│ And 6 lines more");
|
|
320
|
-
expect(lines.join("\n")).not.toContain("stderr:");
|
|
321
|
-
expect(lines.join("\n")).not.toContain("legacy text should be ignored");
|
|
322
|
-
expect(
|
|
323
|
-
textNodes.some(
|
|
324
|
-
(textNode) =>
|
|
325
|
-
textNode.content === "boom" &&
|
|
326
|
-
textNode.props.fgColor === DEFAULT_THEME.toolText,
|
|
327
|
-
),
|
|
328
|
-
).toBe(true);
|
|
329
|
-
expect(
|
|
330
|
-
textNodes.some(
|
|
331
|
-
(textNode) =>
|
|
332
|
-
textNode.content === "exit 1" &&
|
|
333
|
-
textNode.props.fgColor === DEFAULT_THEME.toolText,
|
|
334
|
-
),
|
|
335
|
-
).toBe(true);
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
test("shell tool results still render legacy flattened results from persisted history without stderr labels", () => {
|
|
339
|
-
const node = renderToolResult(
|
|
340
|
-
"shell",
|
|
341
|
-
{ command: "run-tests" },
|
|
342
|
-
"Exit code: 1\nout\n\n[stderr]\nerr",
|
|
343
|
-
true,
|
|
344
|
-
{
|
|
345
|
-
showReasoning: true,
|
|
346
|
-
verbose: true,
|
|
347
|
-
theme: DEFAULT_THEME,
|
|
348
|
-
cwd: "/tmp/project",
|
|
349
|
-
previewWidth: 80,
|
|
350
|
-
},
|
|
351
|
-
);
|
|
352
|
-
const lines = collectRenderedLines(node);
|
|
353
|
-
const textNodes = collectTextNodes(node);
|
|
354
|
-
|
|
355
|
-
expect(lines).toContain("│ out");
|
|
356
|
-
expect(lines).toContain("│ err");
|
|
357
|
-
expect(lines).toContain("│ exit 1");
|
|
358
|
-
expect(lines.join("\n")).not.toContain("stderr:");
|
|
359
|
-
expect(lines.join("\n")).not.toContain("[stderr]");
|
|
360
|
-
expect(lines.join("\n")).not.toContain("Exit code:");
|
|
361
|
-
expect(
|
|
362
|
-
textNodes.some(
|
|
363
|
-
(textNode) =>
|
|
364
|
-
textNode.content === "err" &&
|
|
365
|
-
textNode.props.fgColor === DEFAULT_THEME.toolText,
|
|
366
|
-
),
|
|
367
|
-
).toBe(true);
|
|
368
|
-
expect(
|
|
369
|
-
textNodes.some(
|
|
370
|
-
(textNode) =>
|
|
371
|
-
textNode.content === "exit 1" &&
|
|
372
|
-
textNode.props.fgColor === DEFAULT_THEME.toolText,
|
|
373
|
-
),
|
|
374
|
-
).toBe(true);
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
test("read tool results include the resolved path, hide model paging hints, and render fewer body lines when verbose is off", () => {
|
|
378
|
-
const fileBody =
|
|
379
|
-
Array.from({ length: 14 }, (_, index) => `line ${index + 1}`).join("\n") +
|
|
380
|
-
"\n\n[use offset=14 limit=14 to continue]";
|
|
381
|
-
|
|
382
|
-
const compactLines = collectRenderedLines(
|
|
383
|
-
renderToolResult(
|
|
384
|
-
"read",
|
|
385
|
-
{ path: "src/ui/conversation.ts" },
|
|
386
|
-
fileBody,
|
|
387
|
-
false,
|
|
388
|
-
{
|
|
389
|
-
showReasoning: true,
|
|
390
|
-
verbose: false,
|
|
391
|
-
theme: DEFAULT_THEME,
|
|
392
|
-
cwd: "/tmp/project",
|
|
393
|
-
previewWidth: 48,
|
|
394
|
-
},
|
|
395
|
-
),
|
|
396
|
-
);
|
|
397
|
-
const verboseLines = collectRenderedLines(
|
|
398
|
-
renderToolResult(
|
|
399
|
-
"read",
|
|
400
|
-
{ path: "src/ui/conversation.ts" },
|
|
401
|
-
fileBody,
|
|
402
|
-
false,
|
|
403
|
-
{
|
|
404
|
-
showReasoning: true,
|
|
405
|
-
verbose: true,
|
|
406
|
-
theme: DEFAULT_THEME,
|
|
407
|
-
cwd: "/tmp/project",
|
|
408
|
-
previewWidth: 48,
|
|
409
|
-
},
|
|
410
|
-
),
|
|
411
|
-
);
|
|
412
|
-
|
|
413
|
-
expect(compactLines[0]).toBe(
|
|
414
|
-
"│ read <- /tmp/project/src/ui/conversation.ts",
|
|
415
|
-
);
|
|
416
|
-
expect(compactLines.join("\n")).not.toContain("Use offset=14 limit=14");
|
|
417
|
-
expect(verboseLines.join("\n")).not.toContain("Use offset=14 limit=14");
|
|
418
|
-
expect(verboseLines).toContain("│ line 14");
|
|
419
|
-
expect(compactLines.length).toBeLessThan(verboseLines.length);
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
test("read tool results preserve literal continuation-looking lines from the file body without showing the model paging hint", () => {
|
|
423
|
-
const nodes = buildConversationLogNodes(
|
|
424
|
-
{
|
|
425
|
-
messages: [
|
|
426
|
-
makeAssistantToolCallMessage(),
|
|
427
|
-
makeReadToolResultMessage([
|
|
428
|
-
{
|
|
429
|
-
type: "text",
|
|
430
|
-
text: "alpha\n\n[use offset=99 limit=10 to continue]\n",
|
|
431
|
-
},
|
|
432
|
-
{
|
|
433
|
-
type: "text",
|
|
434
|
-
text: "[use offset=3 limit=3 to continue]",
|
|
435
|
-
},
|
|
436
|
-
]),
|
|
437
|
-
],
|
|
438
|
-
showReasoning: true,
|
|
439
|
-
verbose: true,
|
|
440
|
-
theme: DEFAULT_THEME,
|
|
441
|
-
cwd: "/tmp/project",
|
|
442
|
-
versionLabel: "test",
|
|
443
|
-
},
|
|
444
|
-
{
|
|
445
|
-
isStreaming: false,
|
|
446
|
-
content: [],
|
|
447
|
-
pendingToolResults: [],
|
|
448
|
-
},
|
|
449
|
-
0,
|
|
450
|
-
80,
|
|
451
|
-
);
|
|
452
|
-
|
|
453
|
-
const lines = nodes.flatMap((node) => collectRenderedLines(node));
|
|
454
|
-
expect(lines).toContain("│ [use offset=99 limit=10 to continue]");
|
|
455
|
-
expect(lines.join("\n")).not.toContain("Use offset=3 limit=3 to continue.");
|
|
456
|
-
});
|
|
457
|
-
|
|
458
|
-
test("read tool fallback rendering normalizes CRLF line endings", () => {
|
|
459
|
-
const lines = collectRenderedLines(
|
|
460
|
-
renderToolResult(
|
|
461
|
-
"read",
|
|
462
|
-
{ path: "notes.txt" },
|
|
463
|
-
"alpha\r\nbeta\r\n",
|
|
464
|
-
false,
|
|
465
|
-
{
|
|
466
|
-
showReasoning: true,
|
|
467
|
-
verbose: true,
|
|
468
|
-
theme: DEFAULT_THEME,
|
|
469
|
-
cwd: "/tmp/project",
|
|
470
|
-
previewWidth: 80,
|
|
471
|
-
},
|
|
472
|
-
),
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
expect(lines).toContain("│ alpha");
|
|
476
|
-
expect(lines).toContain("│ beta");
|
|
477
|
-
expect(lines.some((line) => line.includes("\r"))).toBe(false);
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
test("read tool results use direct syntax-highlight rendering without chunking long tokens", () => {
|
|
481
|
-
const node = renderToolResult(
|
|
482
|
-
"read",
|
|
483
|
-
{ path: "src/example.ts" },
|
|
484
|
-
"const supercalifragilisticexpialidociousIdentifier = 42",
|
|
485
|
-
false,
|
|
486
|
-
{
|
|
487
|
-
showReasoning: true,
|
|
488
|
-
verbose: true,
|
|
489
|
-
theme: DEFAULT_THEME,
|
|
490
|
-
cwd: "/tmp/project",
|
|
491
|
-
previewWidth: 20,
|
|
492
|
-
},
|
|
493
|
-
);
|
|
494
|
-
|
|
495
|
-
expect(
|
|
496
|
-
collectTextNodes(node).some(
|
|
497
|
-
(textNode) =>
|
|
498
|
-
textNode.content === "supercalifragilisticexpialidociousIdentifier",
|
|
499
|
-
),
|
|
500
|
-
).toBe(true);
|
|
501
|
-
});
|
|
502
|
-
|
|
503
|
-
test("grep tool results render grouped files and lines instead of raw JSON", () => {
|
|
504
|
-
const resultText = JSON.stringify(
|
|
505
|
-
{
|
|
506
|
-
limit: 10,
|
|
507
|
-
truncated: false,
|
|
508
|
-
files: [
|
|
509
|
-
{
|
|
510
|
-
path: "src/ui/conversation.ts",
|
|
511
|
-
lines: [
|
|
512
|
-
{
|
|
513
|
-
kind: "match",
|
|
514
|
-
lineNumber: 857,
|
|
515
|
-
text: "function renderToolBlock(\n",
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
kind: "context",
|
|
519
|
-
lineNumber: 858,
|
|
520
|
-
text: " spec: ToolBlockSpec,\n",
|
|
521
|
-
},
|
|
522
|
-
],
|
|
523
|
-
},
|
|
524
|
-
],
|
|
525
|
-
},
|
|
526
|
-
null,
|
|
527
|
-
2,
|
|
528
|
-
);
|
|
529
|
-
|
|
530
|
-
const node = renderToolResult(
|
|
531
|
-
"grep",
|
|
532
|
-
{ pattern: "renderToolBlock" },
|
|
533
|
-
resultText,
|
|
534
|
-
false,
|
|
535
|
-
{
|
|
536
|
-
showReasoning: true,
|
|
537
|
-
verbose: true,
|
|
538
|
-
theme: DEFAULT_THEME,
|
|
539
|
-
cwd: "/tmp/project",
|
|
540
|
-
previewWidth: 80,
|
|
541
|
-
},
|
|
542
|
-
);
|
|
543
|
-
|
|
544
|
-
const lines = collectRenderedLines(node);
|
|
545
|
-
expect(lines).toContain("│ grep <-");
|
|
546
|
-
expect(lines).toContain("│ src/ui/conversation.ts");
|
|
547
|
-
expect(lines).toContain("│ 857: function renderToolBlock(");
|
|
548
|
-
expect(lines).toContain("│ 858: spec: ToolBlockSpec,");
|
|
549
|
-
expect(lines.join("\n")).not.toContain('"files"');
|
|
550
|
-
expect(lines.join("\n")).not.toContain('"kind"');
|
|
551
|
-
});
|
|
552
|
-
|
|
553
|
-
test("MCP tool results honor verbose mode", () => {
|
|
554
|
-
const output = Array.from(
|
|
555
|
-
{ length: 14 },
|
|
556
|
-
(_, index) => `result ${index + 1}`,
|
|
557
|
-
).join("\n");
|
|
558
|
-
|
|
559
|
-
const compactLines = collectRenderedLines(
|
|
560
|
-
renderToolResult("docs__search", { query: "routing" }, output, false, {
|
|
561
|
-
showReasoning: true,
|
|
562
|
-
verbose: false,
|
|
563
|
-
theme: DEFAULT_THEME,
|
|
564
|
-
cwd: "/tmp/project",
|
|
565
|
-
previewWidth: 80,
|
|
566
|
-
}),
|
|
567
|
-
);
|
|
568
|
-
const verboseLines = collectRenderedLines(
|
|
569
|
-
renderToolResult("docs__search", { query: "routing" }, output, false, {
|
|
570
|
-
showReasoning: true,
|
|
571
|
-
verbose: true,
|
|
572
|
-
theme: DEFAULT_THEME,
|
|
573
|
-
cwd: "/tmp/project",
|
|
574
|
-
previewWidth: 80,
|
|
575
|
-
}),
|
|
576
|
-
);
|
|
577
|
-
|
|
578
|
-
expect(compactLines).toContain("│ docs__search <-");
|
|
579
|
-
expect(compactLines).toContain("│ result 14");
|
|
580
|
-
expect(compactLines).toContain("│ And 6 lines more");
|
|
581
|
-
expect(compactLines).not.toContain("│ result 1");
|
|
582
|
-
expect(verboseLines).toContain("│ result 1");
|
|
583
|
-
expect(compactLines.length).toBeLessThan(verboseLines.length);
|
|
584
|
-
});
|
|
585
|
-
});
|