From ce55577e6d26714923b226d2c1deab7c48bb90d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Flor=C3=AAncio?= Date: Thu, 16 Apr 2026 11:37:15 +0100 Subject: [PATCH] feat(web): render short tool-call output inline (#604) --- .../task/chat/messages/tool-call-message.tsx | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/apps/web/components/task/chat/messages/tool-call-message.tsx b/apps/web/components/task/chat/messages/tool-call-message.tsx index 776815402e..b85f59e152 100644 --- a/apps/web/components/task/chat/messages/tool-call-message.tsx +++ b/apps/web/components/task/chat/messages/tool-call-message.tsx @@ -142,6 +142,19 @@ function parseToolCallOutput(metadata: ToolCallMetadata | undefined) { return { output, isHttpError }; } +// Short, single-line tool output renders inline in the header (e.g. "Launching skill: e2e") +// rather than behind an expand chevron. +const INLINE_OUTPUT_MAX_LENGTH = 120; + +function getInlineOutput(output: unknown): string | null { + if (typeof output !== "string") return null; + const trimmed = output.trim(); + // Reject both \n and \r — shell tool output can carry \r progress-bar overwrites + // that would render as multiple visual lines even without a \n. + if (!trimmed || /[\n\r]/.test(trimmed) || trimmed.length > INLINE_OUTPUT_MAX_LENGTH) return null; + return trimmed; +} + function parseToolCallMetadata(comment: Message, permissionMessage: Message | undefined) { const metadata = comment.metadata as ToolCallMetadata | undefined; const toolName = metadata?.tool_name ?? ""; @@ -150,7 +163,10 @@ function parseToolCallMetadata(comment: Message, permissionMessage: Message | un const { permissionMetadata, permissionStatus, isPermissionPending } = parsePermission(permissionMessage); const hasOutput = hasToolOutput(output); - const hasExpandableContent = hasOutput || isPermissionPending; + const inlineOutput = getInlineOutput(output); + // When output is shown inline, there's nothing more to expand — skip the expand affordance + // unless a permission prompt still needs to be rendered. + const hasExpandableContent = (hasOutput && !inlineOutput) || isPermissionPending; const isSuccess = status === "complete" && !permissionStatus; return { toolName, @@ -162,6 +178,7 @@ function parseToolCallMetadata(comment: Message, permissionMessage: Message | un isPermissionPending, hasOutput, hasExpandableContent, + inlineOutput, isSuccess, }; } @@ -238,6 +255,7 @@ export const ToolCallMessage = memo(function ToolCallMessage({ isPermissionPending, hasOutput, hasExpandableContent, + inlineOutput, isSuccess, } = parseToolCallMetadata(comment, permissionMessage); const { isResponding, handleApprove, handleReject } = usePermissionResponseHandlers({ @@ -251,7 +269,7 @@ export const ToolCallMessage = memo(function ToolCallMessage({ const rawTitle = metadata?.title ?? comment.content ?? "Tool call"; const title = transformPathsInText(rawTitle, worktreePath); - const formattedOutput = hasOutput ? formatToolOutput(output) : null; + const formattedOutput = hasOutput && !inlineOutput ? formatToolOutput(output) : null; return ( {title} + {inlineOutput && ( + + {inlineOutput} + + )} {!isSuccess && getToolCallStatusIcon(status, permissionStatus)}