Skip to content

Commit 35e52f0

Browse files
committed
feat(ui): tool-named result bubbles, per-tool truncation, polish
- Tool result bubbles use the originating tool name as the header label ("│ grep", "│ read_file") instead of the generic "│ tool". shell renders as "bash" and dispatch_agent as "subagent" since those read more naturally in the gutter. - Per-tool output caps: grep/search_files at 6 lines, glob/find at 8, list_files at 10, everything else stays at 12. Search-style tools produce many matches the user doesn't need to scan inline — the model still gets the full result. - Header now reads '… Codebase Auto Model' instead of '… (proxy)'. - OAuth callback success message is centered.
1 parent 402339d commit 35e52f0

3 files changed

Lines changed: 61 additions & 15 deletions

File tree

src/auth/flow.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ h1 {
201201
padding: 12px 14px;
202202
border-radius: 8px;
203203
font-size: 13px;
204-
text-align: left;
204+
text-align: center;
205205
white-space: pre-wrap;
206206
}
207207
@media (prefers-color-scheme: light) {

src/ui/App.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -377,10 +377,7 @@ function ChatApp({ bundle, onExit }: ChatAppProps) {
377377
<Text bold color="cyan">
378378
codebase
379379
</Text>
380-
<Text dimColor>
381-
{" "}
382-
· {bundle.model.name} ({bundle.source})
383-
</Text>
380+
<Text dimColor> · {bundle.model.name} Model</Text>
384381
</Box>
385382
)}
386383
<MessageList messages={state.messages} streaming={state.streaming} tools={state.tools} />

src/ui/Message.tsx

Lines changed: 59 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,17 @@ const ROLE_STYLE = {
3939
toolResult: { accent: "magenta", label: "tool" },
4040
} as const;
4141

42+
/**
43+
* Per-tool name overrides for the toolResult header label. Default falls
44+
* back to the raw tool name (read_file, grep, shell, …) which is more
45+
* useful than a generic "tool". A few tools get friendlier presentation
46+
* labels because their raw name reads oddly in the gutter.
47+
*/
48+
const TOOL_RESULT_LABEL: Record<string, string> = {
49+
shell: "bash",
50+
dispatch_agent: "subagent",
51+
};
52+
4253
export function Message({ message, streaming, width = 80, tools }: MessageProps) {
4354
const role = message.role;
4455
const style = ROLE_STYLE[role as keyof typeof ROLE_STYLE];
@@ -48,6 +59,13 @@ export function Message({ message, streaming, width = 80, tools }: MessageProps)
4859
// gap, plus the parent App's paddingX of 1 each side. Reserve 4 cols so
4960
// the wrapped text never tries to occupy the accent gutter.
5061
const bodyWidth = Math.max(20, width - 4);
62+
// Tool results carry the originating tool name on the message itself
63+
// (set by pi-agent-core). Surface that instead of the generic "tool"
64+
// label so users can see at a glance which tool produced this output.
65+
const headerLabel =
66+
role === "toolResult" && "toolName" in message && typeof message.toolName === "string"
67+
? (TOOL_RESULT_LABEL[message.toolName] ?? message.toolName)
68+
: style.label;
5169

5270
return (
5371
<Box flexDirection="row" marginY={0}>
@@ -56,7 +74,7 @@ export function Message({ message, streaming, width = 80, tools }: MessageProps)
5674
</Box>
5775
<Box flexDirection="column" flexGrow={1}>
5876
<Text color={style.accent} bold>
59-
{style.label}
77+
{headerLabel}
6078
{streaming ? " …" : ""}
6179
</Text>
6280
<MessageBody message={message} width={bodyWidth} tools={tools} />
@@ -97,7 +115,16 @@ function MessageBody({
97115
const text = message.content
98116
.map((block) => (block.type === "text" ? block.text : `[image:${block.mimeType}]`))
99117
.join("");
100-
return <TruncatedOutput text={text} width={width} keyPrefix="tool" color={message.isError ? "red" : undefined} />;
118+
const toolName = "toolName" in message && typeof message.toolName === "string" ? message.toolName : undefined;
119+
return (
120+
<TruncatedOutput
121+
text={text}
122+
width={width}
123+
keyPrefix="tool"
124+
color={message.isError ? "red" : undefined}
125+
toolName={toolName}
126+
/>
127+
);
101128
}
102129

103130
return null;
@@ -545,12 +572,23 @@ function DiffSummary({ diff, width, keyPrefix }: { diff: DiffInfo; width: number
545572
);
546573
}
547574

548-
const MAX_TOOL_OUTPUT_LINES = 12;
549-
const HEAD_TOOL_OUTPUT_LINES = 8;
550-
const TAIL_TOOL_OUTPUT_LINES = 3;
575+
const DEFAULT_MAX_TOOL_OUTPUT_LINES = 12;
576+
577+
/**
578+
* Per-tool display caps. Search-style tools (grep, find, glob) produce
579+
* many matches, most of which the user doesn't need to read inline —
580+
* the model still sees the full result. Default is 12 lines.
581+
*/
582+
const TOOL_OUTPUT_LIMITS: Record<string, number> = {
583+
grep: 6,
584+
search_files: 6,
585+
glob: 8,
586+
find: 8,
587+
list_files: 10,
588+
};
551589

552590
/**
553-
* Truncate tool output past MAX_TOOL_OUTPUT_LINES into "head + (N hidden)
591+
* Truncate tool output past the per-tool limit into "head + (N hidden)
554592
* + tail" — long shell or grep output otherwise dominates the
555593
* transcript and pushes context off-screen. The agent still gets the
556594
* full output; this is purely a display trim. Errors are NEVER
@@ -561,19 +599,30 @@ function TruncatedOutput({
561599
width,
562600
keyPrefix,
563601
color,
602+
toolName,
564603
}: {
565604
text: string;
566605
width: number;
567606
keyPrefix: string;
568607
color?: string;
608+
toolName?: string;
569609
}) {
610+
const max =
611+
toolName && TOOL_OUTPUT_LIMITS[toolName] !== undefined
612+
? TOOL_OUTPUT_LIMITS[toolName]
613+
: DEFAULT_MAX_TOOL_OUTPUT_LINES;
614+
// Reserve at least 1 head + 1 tail line so the user can see the
615+
// shape of the truncation; rest is head-weighted (where the
616+
// interesting content usually is).
617+
const tailLines = max >= 8 ? 3 : 2;
618+
const headLines = Math.max(1, max - tailLines - 1);
570619
const lines = text.split("\n");
571-
if (color === "red" || lines.length <= MAX_TOOL_OUTPUT_LINES) {
620+
if (color === "red" || lines.length <= max) {
572621
return <WrappedLines text={text} width={width} keyPrefix={keyPrefix} color={color} />;
573622
}
574-
const head = lines.slice(0, HEAD_TOOL_OUTPUT_LINES).join("\n");
575-
const tail = lines.slice(lines.length - TAIL_TOOL_OUTPUT_LINES).join("\n");
576-
const hidden = lines.length - HEAD_TOOL_OUTPUT_LINES - TAIL_TOOL_OUTPUT_LINES;
623+
const head = lines.slice(0, headLines).join("\n");
624+
const tail = lines.slice(lines.length - tailLines).join("\n");
625+
const hidden = lines.length - headLines - tailLines;
577626
return (
578627
<>
579628
<WrappedLines text={head} width={width} keyPrefix={`${keyPrefix}-h`} color={color} />

0 commit comments

Comments
 (0)