fix: tool call dot phases and colors for clearer execution feedback

Tool call dot/phase behavior:
- Tool calls start in streaming phase (static grey) instead of ready
- Added approvalsPending flag to prevent server tools from blinking during approval
- Server tools promoted to running only after approvals complete

Tool dot colors:
- Fixed missing # in statusWarning hex literal
- Running phase uses grey blinking instead of yellow

Args rendering + crash fixes:
- Args considered "complete" by JSON parseability, not just phase
- Coerce argsText to string to avoid runtime errors
- Fixed TDZ error from shadowed variable
- Ready phase only blinks once streaming finished

Behavioral fixes:
- Server-side tools don't show "Cancelled" after approvals
- Mixed server/client tools: server stays static during approval, blinks after
- Args remain visible once complete

👾 Generated with [Letta Code](https://letta.com)

Co-Authored-By: Letta <noreply@letta.com>
This commit is contained in:
cpacker
2026-02-04 23:37:45 -08:00
parent 48ccd8f220
commit 59a1e41308
4 changed files with 99 additions and 13 deletions

View File

@@ -104,7 +104,12 @@ export const ToolCallMessage = memo(
// Parse and format the tool call
const rawName = line.name ?? "?";
const argsText = line.argsText ?? "...";
const argsText =
typeof line.argsText === "string"
? line.argsText
: line.argsText == null
? ""
: JSON.stringify(line.argsText);
// Task tool rendering decision:
// - Cancelled/rejected: render as error tool call (won't appear in SubagentGroupDisplay)
@@ -165,16 +170,35 @@ export const ToolCallMessage = memo(
// - Phase "running"/"finished" or stream done: args complete, show formatted
let args = "";
if (!isQuestionTool(rawName)) {
// Args are complete once running, finished, or stream is done
const parseArgs = (): {
formatted: ReturnType<typeof formatArgsDisplay> | null;
parseable: boolean;
} => {
if (!argsText.trim()) {
return { formatted: null, parseable: true };
}
try {
const formatted = formatArgsDisplay(argsText, rawName);
return { formatted, parseable: true };
} catch {
return { formatted: null, parseable: false };
}
};
// Args are complete once running/finished, stream done, or JSON is parseable.
const { formatted, parseable } = parseArgs();
const argsComplete =
line.phase === "running" || line.phase === "finished" || !isStreaming;
parseable ||
line.phase === "running" ||
line.phase === "finished" ||
!isStreaming;
if (!argsComplete) {
args = "(…)";
} else {
const formatted = formatArgsDisplay(argsText, rawName);
const formattedArgs = formatted ?? formatArgsDisplay(argsText, rawName);
// Normalize newlines to spaces to prevent forced line breaks
const normalizedDisplay = formatted.display.replace(/\n/g, " ");
const normalizedDisplay = formattedArgs.display.replace(/\n/g, " ");
// For max 2 lines: boxWidth * 2, minus parens (2) and margin (2)
const argsBoxWidth = rightWidth - displayName.length;
const maxArgsChars = Math.max(0, argsBoxWidth * 2 - 4);
@@ -206,7 +230,8 @@ export const ToolCallMessage = memo(
return undefined;
}
})();
const dotShouldAnimate = line.phase === "ready" || line.phase === "running";
const dotShouldAnimate =
line.phase === "running" || (line.phase === "ready" && !isStreaming);
// Format result for display
const getResultElement = () => {

View File

@@ -47,7 +47,7 @@ export const brandColors = {
textDisabled: "#46484A", // dark grey
// status colors
statusSuccess: "#64CF64", // green
statusWarning: "FEE19C", // yellow
statusWarning: "#FEE19C", // yellow
statusError: "#F1689F", // red
} as const;
@@ -126,8 +126,8 @@ const _colors = {
tool: {
pending: brandColors.textSecondary, // blinking dot (ready/waiting for approval)
completed: brandColors.statusSuccess, // solid green dot (finished successfully)
streaming: brandColors.textDisabled, // solid gray dot (streaming/in progress)
running: brandColors.statusWarning, // blinking yellow dot (executing)
streaming: brandColors.textSecondary, // solid gray dot (streaming/in progress)
running: brandColors.textSecondary, // blinking gray dot (executing)
error: brandColors.statusError, // solid red dot (failed)
memoryName: brandColors.primaryAccent, // memory tool name highlight (matches thinking spinner)
},