From 3b3d45cd949f1136beb1a9d5c8a55a0ee7ea9d31 Mon Sep 17 00:00:00 2001 From: Adam Moussa Date: Fri, 17 Jul 2026 16:22:38 -0400 Subject: [PATCH] fix: capitalize dashboard tool labels (#1761) * fix: capitalize tool labels in dashboard Co-authored-by: open-swe[bot] * fix: sentence-case dashboard tool labels Co-authored-by: open-swe[bot] --------- Co-authored-by: Ramon Nogueira <270434257+ramon-langchain@users.noreply.github.com> Co-authored-by: open-swe[bot] (cherry picked from commit dd5b7becb4dc9c633730fb4092a3cbd0c50dae9d) Co-authored-by: Ramon Nogueira --- .../agents/components/chat/ToolExecution.test.ts | 10 ++++++++++ .../agents/components/chat/toolExecutionDisplay.ts | 11 ++++++++++- .../agents/components/subagents/SubagentActivity.tsx | 4 +--- ui/src/features/agents/lib/streamMessagesToUi.ts | 3 ++- ui/src/features/agents/lib/toolNames.ts | 10 ++++++++++ 5 files changed, 33 insertions(+), 5 deletions(-) create mode 100644 ui/src/features/agents/lib/toolNames.ts diff --git a/ui/src/features/agents/components/chat/ToolExecution.test.ts b/ui/src/features/agents/components/chat/ToolExecution.test.ts index e0cde37f..9d54cfb8 100644 --- a/ui/src/features/agents/components/chat/ToolExecution.test.ts +++ b/ui/src/features/agents/components/chat/ToolExecution.test.ts @@ -36,4 +36,14 @@ describe("formatToolDisplay", () => { it("normalizes write_todos", () => { expect(formatToolDisplay("write todos", "other", {}, projectPath)).toBe("Update todos"); }); + + it("sentence-cases raw tool names", () => { + expect(formatToolDisplay("enter_plan_mode", "other", {}, projectPath)).toBe( + "Enter plan mode", + ); + expect(formatToolDisplay("save_plan", "other", {}, projectPath)).toBe("Save plan"); + expect(formatToolDisplay("slack_thread_reply", "other", {}, projectPath)).toBe( + "Slack thread reply", + ); + }); }); diff --git a/ui/src/features/agents/components/chat/toolExecutionDisplay.ts b/ui/src/features/agents/components/chat/toolExecutionDisplay.ts index f629b5d8..45dc41a3 100644 --- a/ui/src/features/agents/components/chat/toolExecutionDisplay.ts +++ b/ui/src/features/agents/components/chat/toolExecutionDisplay.ts @@ -1,3 +1,4 @@ +import { humanizeToolName } from "@/features/agents/lib/toolNames"; import type { AcpToolKind } from "@/features/agents/lib/types"; function stripProjectPath(path: string, projectPath?: string): string { @@ -27,7 +28,15 @@ function normalizedToolName(title: string): string { } function humanizeToolTitle(title: string): string { - return title.replace(/_/g, " ").trim() || "Tool"; + const trimmed = title.trim(); + if (!trimmed) return "Tool"; + + const [name, ...rest] = trimmed.split(/\s+/); + const suffix = rest.join(" "); + if (name && suffix && /^(?:[./~]|[a-z]+:\/\/)/i.test(suffix)) { + return `${humanizeToolName(name)} ${suffix}`; + } + return humanizeToolName(trimmed); } export function formatToolDisplay( diff --git a/ui/src/features/agents/components/subagents/SubagentActivity.tsx b/ui/src/features/agents/components/subagents/SubagentActivity.tsx index 88e22e54..5fa9e97b 100644 --- a/ui/src/features/agents/components/subagents/SubagentActivity.tsx +++ b/ui/src/features/agents/components/subagents/SubagentActivity.tsx @@ -1,9 +1,7 @@ import { useStreamContext as useAgentThreadStream, useToolCalls } from "@langchain/react" import { Check, Loader2, X } from "lucide-react" -function humanizeToolName(name: string): string { - return name.replace(/_/g, " ").trim() || "tool" -} +import { humanizeToolName } from "@/features/agents/lib/toolNames" /** * Live status for a single subagent, read straight from the SDK's scoped diff --git a/ui/src/features/agents/lib/streamMessagesToUi.ts b/ui/src/features/agents/lib/streamMessagesToUi.ts index f4bd0c95..373199c7 100644 --- a/ui/src/features/agents/lib/streamMessagesToUi.ts +++ b/ui/src/features/agents/lib/streamMessagesToUi.ts @@ -1,5 +1,6 @@ import { AIMessage, HumanMessage, ToolMessage } from "@langchain/core/messages"; import { messageArrivalTimestamp } from "./messageTimestamps"; +import { humanizeToolName } from "./toolNames"; import type { BaseMessage, ContentBlock } from "@langchain/core/messages"; import type { AssembledToolCall, SubagentDiscoverySnapshot } from "@langchain/react"; @@ -39,7 +40,7 @@ function toolTitle(name: string, args: Record): string { if (typeof command === "string" && command.trim()) { return command.trim().split("\n")[0]?.slice(0, 120) ?? ""; } - return name.replace(/_/g, " ").trim() || "Tool"; + return humanizeToolName(name); } function parseToolArgs(raw: unknown): Record { diff --git a/ui/src/features/agents/lib/toolNames.ts b/ui/src/features/agents/lib/toolNames.ts new file mode 100644 index 00000000..276cfbd6 --- /dev/null +++ b/ui/src/features/agents/lib/toolNames.ts @@ -0,0 +1,10 @@ +export function humanizeToolName(name: string, fallback = "Tool"): string { + const normalized = name + .replace(/[_-]+/g, " ") + .trim() + .replace(/\s+/g, " ") + .toLowerCase() + + if (!normalized) return fallback + return `${normalized.charAt(0).toUpperCase()}${normalized.slice(1)}` +}