feat(webui): polish agent output and app discovery

This commit is contained in:
Xubin Ren
2026-07-22 22:42:31 +08:00
parent b189a37648
commit aa8387fb4d
87 changed files with 6225 additions and 2379 deletions
+590 -135
View File
@@ -340,7 +340,7 @@ describe("AgentActivityCluster", () => {
vi.advanceTimersByTime(901);
});
expect(screen.queryByTestId("agent-activity-scroll")).not.toBeInTheDocument();
expect(screen.getByRole("button", { name: /1 steps/i })).toHaveAttribute(
expect(screen.getByRole("button", { name: "Thought" })).toHaveAttribute(
"aria-expanded",
"false",
);
@@ -401,7 +401,7 @@ describe("AgentActivityCluster", () => {
expect(screen.queryByText("Thought for 0s")).not.toBeInTheDocument();
});
it("renders file edit totals and a compact expanded file list", async () => {
it("renders file edits as one-line activity rows", async () => {
const restoreMotion = installReducedMotion();
try {
render(
@@ -430,33 +430,25 @@ describe("AgentActivityCluster", () => {
/>,
);
expect(screen.getByRole("button", { name: /edited app\.tsx/i })).toBeInTheDocument();
expect(screen.getByTestId("activity-header-file-reference")).toHaveTextContent("app.tsx");
expect(screen.getByTestId("activity-header-file-reference")).toHaveAttribute(
"aria-label",
"/Users/renxubin/project/src/app.tsx",
);
fireEvent.click(screen.getByRole("button", { name: /edited app\.tsx/i }));
expect(screen.queryByText("Edited files")).not.toBeInTheDocument();
const fileRef = screen.getByTestId("activity-file-reference");
expect(fileRef).toHaveTextContent("src/app.tsx");
expect(fileRef).toHaveAttribute("aria-label", "/Users/renxubin/project/src/app.tsx");
expect(fileRef).toHaveAttribute("aria-label", "src/app.tsx");
expect(screen.queryByTestId("activity-header-file-reference")).not.toBeInTheDocument();
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
for (const diffPair of screen.getAllByTestId("activity-diff-pair")) {
expect(diffPair).toHaveClass("items-baseline");
expect(diffPair).toHaveClass("leading-[inherit]");
expect(diffPair.className).not.toContain("translate-y");
}
await waitFor(() => {
expect(screen.getAllByText("+12").length).toBeGreaterThan(0);
expect(screen.getAllByText("-3").length).toBeGreaterThan(0);
});
expect(screen.getByText("+12")).toBeInTheDocument();
expect(screen.getByText("-3")).toBeInTheDocument();
} finally {
restoreMotion();
}
});
it("renders GitHub-like file edit diffs when the local preference is enabled", () => {
it("keeps file edits flat even when the legacy diff preference is enabled", () => {
localStorage.setItem(
"nanobot-webui.settings-preferences",
JSON.stringify({ fileEditDisplayMode: "diff" }),
@@ -496,20 +488,17 @@ describe("AgentActivityCluster", () => {
/>,
);
expect(screen.getByTestId("file-edit-diff")).toBeInTheDocument();
expect(screen.queryByText("@@ -10,2 +10,2 @@")).not.toBeInTheDocument();
expect(screen.getByText("return <Old />;")).toBeInTheDocument();
expect(screen.getByText("return <New />;")).toBeInTheDocument();
expect(screen.getAllByText("11").length).toBeGreaterThanOrEqual(2);
expect(screen.getAllByTestId("activity-header-file-reference")).toHaveLength(1);
expect(screen.queryByTestId("activity-file-reference")).not.toBeInTheDocument();
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
expect(screen.queryByText("return <Old />;")).not.toBeInTheDocument();
expect(screen.queryByText("return <New />;")).not.toBeInTheDocument();
expect(screen.getByTestId("activity-file-reference")).toHaveTextContent("src/app.tsx");
expect(screen.getAllByTestId("activity-diff-pair")).toHaveLength(1);
} finally {
localStorage.removeItem("nanobot-webui.settings-preferences");
}
});
it("renders folded separators between separated file edit hunks", () => {
it("does not render diff hunks inside the activity list", () => {
localStorage.setItem(
"nanobot-webui.settings-preferences",
JSON.stringify({ fileEditDisplayMode: "diff" }),
@@ -555,17 +544,16 @@ describe("AgentActivityCluster", () => {
/>,
);
expect(screen.getByTestId("file-edit-diff-hunk-gap")).toHaveTextContent(
"21 unchanged lines hidden",
);
expect(screen.queryByTestId("file-edit-diff-hunk-gap")).not.toBeInTheDocument();
expect(screen.queryByText("@@ -25,3 +25,3 @@")).not.toBeInTheDocument();
expect(screen.getByText("return newSecond;")).toBeInTheDocument();
expect(screen.queryByText("return newSecond;")).not.toBeInTheDocument();
expect(screen.getByTestId("activity-file-reference")).toHaveTextContent("src/app.tsx");
} finally {
localStorage.removeItem("nanobot-webui.settings-preferences");
}
});
it("keeps long file edit diffs collapsed until opened", () => {
it("summarizes long file edit diffs without an expansion control", () => {
localStorage.setItem(
"nanobot-webui.settings-preferences",
JSON.stringify({ fileEditDisplayMode: "diff" }),
@@ -604,40 +592,16 @@ describe("AgentActivityCluster", () => {
/>,
);
const toggle = screen.getByTestId("file-edit-diff-toggle");
expect(toggle).toHaveAttribute("aria-expanded", "false");
expect(toggle).toHaveTextContent("View large diff");
expect(toggle).toHaveTextContent("165 lines");
expect(screen.queryByTestId("file-edit-diff-toggle")).not.toBeInTheDocument();
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
expect(screen.queryByText("line-1")).not.toBeInTheDocument();
fireEvent.click(toggle);
expect(toggle).toHaveAttribute("aria-expanded", "true");
expect(screen.getByText("line-160")).toBeInTheDocument();
expect(screen.queryByText("line-161")).not.toBeInTheDocument();
expect(screen.getByTestId("file-edit-diff-expand-lines")).toHaveTextContent("Show 5 more lines");
fireEvent.click(screen.getByTestId("file-edit-diff-expand-lines"));
expect(screen.getByText("line-165")).toBeInTheDocument();
expect(screen.getByTestId("file-edit-diff-collapse-lines")).toHaveTextContent("Show fewer lines");
fireEvent.click(screen.getByTestId("file-edit-diff-collapse-lines"));
expect(screen.queryByText("line-165")).not.toBeInTheDocument();
expect(screen.getByTestId("file-edit-diff-expand-lines")).toHaveTextContent("Show 5 more lines");
fireEvent.click(toggle);
expect(toggle).toHaveAttribute("aria-expanded", "false");
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
expect(screen.getByText("+165")).toBeInTheDocument();
} finally {
localStorage.removeItem("nanobot-webui.settings-preferences");
}
});
it("does not mount collapsed file edit diff rows until opened", () => {
it("ignores the legacy collapsed diff mode in the activity list", () => {
localStorage.setItem(
"nanobot-webui.settings-preferences",
JSON.stringify({ fileEditDisplayMode: "collapsed_diff" }),
@@ -677,24 +641,16 @@ describe("AgentActivityCluster", () => {
/>,
);
const toggle = screen.getByTestId("file-edit-diff-toggle");
expect(toggle).toHaveAttribute("aria-expanded", "false");
expect(toggle).toHaveTextContent("View diff");
expect(toggle).toHaveTextContent("3 lines");
expect(screen.queryByTestId("file-edit-diff-toggle")).not.toBeInTheDocument();
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
expect(screen.queryByText("return <New />;")).not.toBeInTheDocument();
fireEvent.click(toggle);
expect(toggle).toHaveAttribute("aria-expanded", "true");
expect(screen.getByTestId("file-edit-diff")).toBeInTheDocument();
expect(screen.getByText("return <New />;")).toBeInTheDocument();
expect(screen.getByTestId("activity-file-reference")).toHaveTextContent("src/app.tsx");
} finally {
localStorage.removeItem("nanobot-webui.settings-preferences");
}
});
it("offers the file preview entry point when a diff payload is truncated", () => {
it("opens the edited file directly instead of expanding a truncated diff", () => {
localStorage.setItem(
"nanobot-webui.settings-preferences",
JSON.stringify({ fileEditDisplayMode: "diff" }),
@@ -735,15 +691,9 @@ describe("AgentActivityCluster", () => {
/>,
);
const toggle = screen.getByTestId("file-edit-diff-toggle");
expect(toggle).toHaveAttribute("aria-expanded", "false");
expect(toggle).toHaveTextContent("View large diff");
expect(screen.queryByTestId("file-edit-diff-toggle")).not.toBeInTheDocument();
expect(screen.queryByTestId("file-edit-diff-truncated")).not.toBeInTheDocument();
fireEvent.click(toggle);
expect(screen.getByTestId("file-edit-diff-truncated")).toHaveTextContent("Diff truncated");
fireEvent.click(screen.getByTestId("file-edit-diff-open-file"));
fireEvent.click(screen.getByTestId("activity-file-reference"));
expect(onOpenFilePreview).toHaveBeenCalledWith("/repo/src/app.tsx");
} finally {
@@ -778,8 +728,8 @@ describe("AgentActivityCluster", () => {
/>,
);
expect(screen.getByRole("button", { name: /deleted angry-birds\.html/i })).toBeInTheDocument();
expect(screen.queryByRole("button", { name: /edited angry-birds\.html/i })).not.toBeInTheDocument();
expect(screen.getByText("Deleted")).toBeInTheDocument();
expect(screen.queryByText("Edited")).not.toBeInTheDocument();
});
it("renders file-only edits without a redundant disclosure", () => {
@@ -812,7 +762,8 @@ describe("AgentActivityCluster", () => {
expect(screen.queryByRole("button", { name: /edited app\.tsx/i })).not.toBeInTheDocument();
expect(screen.queryByTestId("agent-activity-scroll")).not.toBeInTheDocument();
expect(screen.getByText("Edited")).toBeInTheDocument();
expect(screen.getByTestId("activity-header-file-reference")).toHaveTextContent("app.tsx");
expect(screen.queryByTestId("activity-header-file-reference")).not.toBeInTheDocument();
expect(screen.getByTestId("activity-file-reference")).toHaveTextContent("src/app.tsx");
expect(screen.getByText("+12")).toBeInTheDocument();
expect(screen.getByText("-3")).toBeInTheDocument();
});
@@ -879,10 +830,7 @@ describe("AgentActivityCluster", () => {
/>,
);
const cliRuns = screen.getByTestId("activity-cli-runs");
expect(cliRuns).toHaveTextContent("Using");
expect(cliRuns).toHaveTextContent("@blender");
expect(cliRuns).toHaveTextContent("--json --background scene.blend");
expect(screen.getByText("Using Blender · --json --background scene.blend")).toBeInTheDocument();
expect(screen.getByTestId("activity-cli-logo-blender")).toBeInTheDocument();
expect(screen.queryByText(/run_cli_app/)).not.toBeInTheDocument();
});
@@ -930,9 +878,9 @@ describe("AgentActivityCluster", () => {
/>,
);
const searchRow = screen.getByText("Searching").closest("li");
const cliRow = screen.getByText("@blender").closest("li");
const fetchRow = screen.getByText("Reading").closest("li");
const searchRow = screen.getByText("Searched nanobot architecture").closest('[data-testid="activity-step"]');
const cliRow = screen.getByText("Used Blender · --json project new").closest('[data-testid="activity-step"]');
const fetchRow = screen.getByText("example.com/diagram").closest('[data-testid="activity-step"]');
expect(searchRow).not.toBeNull();
expect(cliRow).not.toBeNull();
@@ -941,6 +889,181 @@ describe("AgentActivityCluster", () => {
expect(cliRow!.compareDocumentPosition(fetchRow!) & Node.DOCUMENT_POSITION_FOLLOWING).toBeTruthy();
});
it("renders web search results as lightweight branded source rows", () => {
const line = 'web_search({"query":"agent frameworks"})';
render(
<AgentActivityCluster
messages={[{
id: "t-web-search-results",
role: "tool",
kind: "trace",
content: line,
traces: [line],
toolEvents: [{
phase: "end",
call_id: "call-web-search",
name: "web_search",
arguments: { query: "agent frameworks" },
result: [
"Results for: agent frameworks",
"",
"1. OpenAI Agents SDK",
" https://openai.com/index/new-tools-for-building-agents/?utm_source=test",
" Build and deploy agentic applications.",
"2. Building effective agents",
" https://www.anthropic.com/engineering/building-effective-agents",
" Practical patterns for reliable agents.",
"3. Internal dashboard",
" http://localhost:3000/search",
].join("\n"),
}],
createdAt: 1,
}]}
isTurnStreaming
hasBodyBelow={false}
/>,
);
expect(screen.getByText("Searched agent frameworks")).toBeInTheDocument();
expect(screen.queryByText("2 sources")).not.toBeInTheDocument();
const openAiLink = screen.getByText("OpenAI Agents SDK").closest("a");
const anthropicLink = screen.getByText("Building effective agents").closest("a");
expect(openAiLink).toHaveAttribute(
"href",
"https://openai.com/index/new-tools-for-building-agents/",
);
expect(openAiLink).not.toHaveAttribute("title");
expect(anthropicLink).toHaveAttribute(
"href",
"https://www.anthropic.com/engineering/building-effective-agents",
);
expect(screen.getByText("openai.com/index/new-tools-for-building-agents")).toBeInTheDocument();
expect(screen.getByText("anthropic.com/engineering/building-effective-agents")).toBeInTheDocument();
expect(screen.getByTestId("activity-web-favicon-openai.com")).toBeInTheDocument();
expect(screen.getByTestId("activity-web-favicon-anthropic.com")).toBeInTheDocument();
expect(screen.queryByText("Internal dashboard")).not.toBeInTheDocument();
expect(screen.queryByText("Build and deploy agentic applications.")).not.toBeInTheDocument();
const searchStep = screen.getByText("Searched agent frameworks").closest(
'[data-testid="activity-step"]',
);
const openAiStep = openAiLink!.closest('[data-testid="activity-step"]');
const anthropicStep = anthropicLink!.closest('[data-testid="activity-step"]');
expect(openAiStep).toContainElement(
screen.getByText("openai.com/index/new-tools-for-building-agents"),
);
expect(anthropicStep).toContainElement(
screen.getByText("anthropic.com/engineering/building-effective-agents"),
);
expect(searchStep?.parentElement).toBe(openAiStep?.parentElement);
expect(searchStep?.parentElement).toBe(anthropicStep?.parentElement);
expect(searchStep?.parentElement?.querySelector("ul, li, section")).toBeNull();
expect(screen.getAllByTestId("activity-step")).toHaveLength(3);
});
it("redacts credentials from web search queries, titles, and links", () => {
const query = "release notes access_token=signed-secret";
const line = `web_search(${JSON.stringify({ query })})`;
render(
<AgentActivityCluster
messages={[{
id: "t-web-search-secret",
role: "tool",
kind: "trace",
content: line,
traces: [line],
toolEvents: [{
phase: "end",
call_id: "call-web-search-secret",
name: "web_search",
arguments: { query },
result: [
"1. Release sk-proj-secret1234",
" https://example.com/release?api_key=url-secret#details",
].join("\n"),
}],
createdAt: 1,
}]}
isTurnStreaming={false}
hasBodyBelow={false}
/>,
);
expect(screen.queryByText(/signed-secret|secret1234|url-secret/)).not.toBeInTheDocument();
expect(screen.getByText("Searched release notes access_token=<redacted>")).toBeInTheDocument();
expect(screen.getByText("Release <redacted>")).toBeInTheDocument();
expect(screen.getByText("Release <redacted>").closest("a")).toHaveAttribute(
"href",
"https://example.com/release",
);
});
it("renders persisted search progress as one human-readable action", () => {
const line = 'web_search({"query":"site:linkedin.com/company Evomap startup"})';
render(
<AgentActivityCluster
messages={[
{
id: "search-start",
role: "tool",
kind: "trace",
content: line,
traces: [line],
toolEvents: [{
phase: "start",
name: "web_search",
arguments: { query: "site:linkedin.com/company Evomap startup" },
}],
createdAt: 1,
},
{
id: "search-end",
role: "tool",
kind: "trace",
content: line,
traces: [line],
toolEvents: [{
phase: "error",
name: "web_search",
arguments: { query: "site:linkedin.com/company Evomap startup" },
error: "Search provider rate limited the request",
}],
createdAt: 2,
},
]}
isTurnStreaming
hasBodyBelow={false}
/>,
);
expect(screen.getAllByTestId("activity-step")).toHaveLength(1);
expect(screen.getByText("Could not search LinkedIn · Evomap startup")).toBeInTheDocument();
expect(screen.queryByText(/site:linkedin/i)).not.toBeInTheDocument();
expect(screen.queryByText("Web research")).not.toBeInTheDocument();
});
it("renders reasoning as a single flat activity row", () => {
render(
<AgentActivityCluster
messages={[{
id: "r-flat",
role: "assistant",
content: "",
reasoning: "**Planning** a focused search\nfor official sources",
reasoningStreaming: true,
isStreaming: true,
createdAt: 1,
}]}
isTurnStreaming
hasBodyBelow={false}
/>,
);
expect(screen.getByText("Planning a focused search for official sources")).toBeInTheDocument();
expect(screen.queryByText("Thinking…")).not.toBeInTheDocument();
expect(screen.queryByText("Thinking")).not.toBeInTheDocument();
});
it("labels rejected CLI app calls as failed instead of ran", () => {
render(
<AgentActivityCluster
@@ -966,11 +1089,14 @@ describe("AgentActivityCluster", () => {
/>,
);
fireEvent.click(screen.getByRole("button", { name: /failed @github/i }));
fireEvent.click(screen.getByRole("button", { name: "Worked" }));
expect(screen.getByTestId("activity-cli-runs")).toHaveTextContent("Failed");
expect(screen.getByTestId("activity-cli-runs")).toHaveTextContent("@github");
expect(screen.getByTestId("activity-cli-runs")).toHaveTextContent("Error: CLI app 'github' not found");
const row = screen.getByText("Could not use GitHub · --json repo view").closest(
'[data-testid="activity-step"]',
);
expect(row).toBeInTheDocument();
expect(row).not.toHaveAttribute("title");
expect(screen.queryByText("Error: CLI app 'github' not found")).not.toBeInTheDocument();
expect(screen.queryByText("Ran CLI")).not.toBeInTheDocument();
});
@@ -999,11 +1125,9 @@ describe("AgentActivityCluster", () => {
/>,
);
const mcpRuns = screen.getByTestId("activity-mcp-runs");
expect(mcpRuns).toHaveTextContent("Using");
expect(mcpRuns).toHaveTextContent("Browserbase");
expect(mcpRuns).toHaveTextContent("browser_navigate");
expect(mcpRuns).toHaveTextContent("url: https://example.com");
expect(screen.getByText("Opening example.com · Browserbase")).toBeInTheDocument();
expect(screen.queryByText("Using")).not.toBeInTheDocument();
expect(screen.queryByText(/browser_navigate/)).not.toBeInTheDocument();
expect(screen.getByTestId("activity-mcp-logo-browserbase")).toBeInTheDocument();
expect(screen.queryByText(/mcp_browserbase_browser_navigate/)).not.toBeInTheDocument();
});
@@ -1025,9 +1149,11 @@ describe("AgentActivityCluster", () => {
);
const favicon = screen.getByTestId("activity-web-favicon-auth0.com");
expect(favicon.querySelector("img")?.getAttribute("src")).toContain("auth0.com");
expect(screen.getByText("Reading")).toBeInTheDocument();
expect(screen.getByText("auth0.com/blog/jwt-security-best-practices")).toBeInTheDocument();
expect(favicon).toHaveAttribute("src", expect.stringContaining("auth0.com"));
const row = screen.getByText("auth0.com/blog/jwt-security-best-practices").closest(
'[data-testid="activity-step"]',
);
expect(row).toHaveTextContent("Reading");
});
it("renders plain-text fetch progress with the site favicon", () => {
@@ -1047,8 +1173,42 @@ describe("AgentActivityCluster", () => {
);
expect(screen.getByTestId("activity-web-favicon-auth0.com")).toBeInTheDocument();
expect(screen.getByText("Reading")).toBeInTheDocument();
expect(screen.getByText("auth0.com/blog/jwt-security-best-practices")).toBeInTheDocument();
const row = screen.getByText("auth0.com/blog/jwt-security-best-practices").closest(
'[data-testid="activity-step"]',
);
expect(row).toHaveTextContent("Reading");
});
it("renders a completed fetch as one linked title and URL row", () => {
const line = 'web_fetch({"url":"https://example.com/docs"})';
render(
<AgentActivityCluster
messages={[{
id: "t-web-fetch-title",
role: "tool",
kind: "trace",
content: line,
traces: [line],
toolEvents: [{
phase: "end",
call_id: "fetch-title",
name: "web_fetch",
arguments: { url: "https://example.com/docs" },
result: "# Example documentation\n\nPage body",
}],
createdAt: 1,
}]}
isTurnStreaming={false}
hasBodyBelow={false}
/>,
);
const title = screen.getByText("Example documentation");
const url = screen.getByText("example.com/docs");
const row = title.closest('[data-testid="activity-step"]');
expect(row).toContainElement(url);
expect(title.closest("a")).toHaveAttribute("href", "https://example.com/docs");
expect(screen.getAllByTestId("activity-step")).toHaveLength(1);
});
it("does not request favicons for private web fetch targets", () => {
@@ -1068,10 +1228,11 @@ describe("AgentActivityCluster", () => {
);
expect(screen.queryByTestId("activity-web-favicon-localhost")).not.toBeInTheDocument();
expect(screen.getByText("url: http://localhost:3000/dashboard")).toBeInTheDocument();
expect(screen.getByText("Reading Private address")).toBeInTheDocument();
expect(screen.queryByText("http://localhost:3000/dashboard")).not.toBeInTheDocument();
});
it("shows readable argument previews for generic tool traces", () => {
it("presents generic tool traces as one-line semantic actions", () => {
render(
<AgentActivityCluster
messages={[{
@@ -1091,9 +1252,109 @@ describe("AgentActivityCluster", () => {
/>,
);
expect(screen.getByText("find_files query: thread · glob: *.tsx")).toBeInTheDocument();
expect(screen.getByText("list_dir path: memory")).toBeInTheDocument();
expect(screen.getByText("grep pattern: dream_cursor")).toBeInTheDocument();
expect(screen.getByText("Found files *.tsx")).toBeInTheDocument();
expect(screen.getByText("Listed files memory")).toBeInTheDocument();
expect(screen.getByText("Searching files “dream_cursor")).toBeInTheDocument();
expect(screen.queryByText("Technical details")).not.toBeInTheDocument();
expect(document.querySelector("details")).not.toBeInTheDocument();
});
it("groups repeated searches over internal tool results without exposing raw paths", () => {
const pattern = "Jul (1[0-7]), 2026|July (1[0-7]), 2026|2026-07-(1[0-7])";
const secondPattern = "Anthropic|OpenAI|DeepMind";
const firstPath = "/Users/test/.nanobot/workspace/.nanobot/tool-results/websocket_session/call_first-result.txt";
const secondPath = "/Users/test/.nanobot/workspace/.nanobot/tool-results/websocket_session/call_second-result.txt";
const traces = [
`grep(${JSON.stringify({ pattern, path: firstPath })})`,
`grep(${JSON.stringify({ pattern: secondPattern, path: secondPath })})`,
];
render(
<AgentActivityCluster
messages={[{
id: "t-grouped-grep",
role: "tool",
kind: "trace",
content: traces.join("\n"),
traces,
createdAt: 1,
}]}
isTurnStreaming={false}
hasBodyBelow={false}
/>,
);
const run = screen.getByText(/Reviewed sources.*2 files/).closest('[data-testid="activity-step"]');
expect(run).toBeInTheDocument();
expect(screen.queryByText(firstPath)).not.toBeInTheDocument();
expect(screen.queryByText(secondPath)).not.toBeInTheDocument();
expect(screen.queryByText(pattern)).not.toBeInTheDocument();
expect(screen.queryByText(secondPattern)).not.toBeInTheDocument();
expect(screen.queryByText("call_first-result.txt")).not.toBeInTheDocument();
expect(screen.queryByText("call_second-result.txt")).not.toBeInTheDocument();
});
it("surfaces generic tool failures without dumping their arguments", () => {
const args = { pattern: "needle", path: "workspace/file.txt" };
const line = `grep(${JSON.stringify(args)})`;
render(
<AgentActivityCluster
messages={[{
id: "t-grep-error",
role: "tool",
kind: "trace",
content: line,
traces: [line],
toolEvents: [{
phase: "error",
call_id: "call-grep-error",
name: "grep",
arguments: args,
error: JSON.stringify({
message: "Permission denied",
headers: { Authorization: "Bearer sk-live-secret" },
token: "super-secret",
}),
}],
createdAt: 1,
}]}
isTurnStreaming={false}
hasBodyBelow={false}
/>,
);
const row = screen.getByText("Could not search files “needle”").closest(
'[data-testid="activity-step"]',
);
expect(row).toBeInTheDocument();
expect(row).not.toHaveAttribute("title");
expect(screen.queryByText(/Permission denied/)).not.toBeInTheDocument();
expect(screen.queryByText(/super-secret/)).not.toBeInTheDocument();
expect(screen.queryByText(/sk-live-secret/)).not.toBeInTheDocument();
expect(screen.queryByText(/Authorization/)).not.toBeInTheDocument();
});
it("redacts credentials from generic tool URL details", () => {
const line = 'download_asset({"url":"https://user:password@example.com/file?access_token=signed-secret&format=png"})';
render(
<AgentActivityCluster
messages={[{
id: "t-generic-url-secret",
role: "tool",
kind: "trace",
content: line,
traces: [line],
createdAt: 1,
}]}
isTurnStreaming={false}
hasBodyBelow={false}
/>,
);
expect(screen.queryByText(/password|signed-secret/)).not.toBeInTheDocument();
expect(screen.getByText("Completed Download asset")).toBeInTheDocument();
expect(screen.queryByText(/example\.com/)).not.toBeInTheDocument();
});
it("summarizes long shell traces instead of dumping scripts", () => {
@@ -1121,15 +1382,36 @@ describe("AgentActivityCluster", () => {
/>,
);
fireEvent.click(screen.getByRole("button", { name: /1 tool calls/i }));
fireEvent.click(screen.getByRole("button", { name: "Worked" }));
expect(screen.getByText("Command")).toBeInTheDocument();
expect(screen.getByText(/cat << 'EOF' \| bash · script, 6 lines/)).toBeInTheDocument();
expect(screen.getByText("Ran command cat << 'EOF' | bash · script, 6 lines")).toBeInTheDocument();
expect(screen.queryByText(/SECRET_TOKEN/)).not.toBeInTheDocument();
expect(screen.queryByText(/for id in/)).not.toBeInTheDocument();
expect(screen.queryByText(/^Done$/)).not.toBeInTheDocument();
});
it("presents time checks as an intent instead of a raw command", () => {
const line = `exec(${JSON.stringify({ command: "date '+%Y-%m-%d %H:%M:%S %Z'" })})`;
render(
<AgentActivityCluster
messages={[{
id: "t-date",
role: "tool",
kind: "trace",
content: line,
traces: [line],
createdAt: 1,
}]}
isTurnStreaming
hasBodyBelow={false}
/>,
);
expect(screen.getByText("Checking current time")).toBeInTheDocument();
expect(screen.queryByText(/%Y-%m-%d/)).not.toBeInTheDocument();
expect(screen.queryByText("Web")).not.toBeInTheDocument();
});
it("does not render zero diff counters for completed edits", () => {
render(
<AgentActivityCluster
@@ -1156,7 +1438,7 @@ describe("AgentActivityCluster", () => {
/>,
);
expect(screen.getByRole("button", { name: /edited app\.tsx/i })).toBeInTheDocument();
expect(screen.getByText("Edited")).toBeInTheDocument();
expect(screen.queryByText("+0")).not.toBeInTheDocument();
expect(screen.queryByText("-0")).not.toBeInTheDocument();
});
@@ -1220,7 +1502,6 @@ describe("AgentActivityCluster", () => {
/>,
);
expect(screen.getByRole("button", { name: /preparing edit/i })).toBeInTheDocument();
expect(screen.getByText("Preparing file edit…")).toBeInTheDocument();
});
@@ -1251,9 +1532,10 @@ describe("AgentActivityCluster", () => {
/>,
);
fireEvent.click(screen.getByRole("button", { name: /failed angry-birds\.html/i }));
expect(screen.getByText("Target text was not found in angry-birds.html.")).toBeInTheDocument();
const row = screen.getByText("Could not edit").closest('[data-testid="activity-step"]');
expect(row).toBeInTheDocument();
expect(row).not.toHaveAttribute("title");
expect(screen.queryByText("Target text was not found in angry-birds.html.")).not.toBeInTheDocument();
});
it("keeps permission errors readable for failed file edits", () => {
@@ -1283,9 +1565,10 @@ describe("AgentActivityCluster", () => {
/>,
);
fireEvent.click(screen.getByRole("button", { name: /failed composition\.html/i }));
expect(screen.getByText("No permission to change this location.")).toBeInTheDocument();
const row = screen.getByText("Could not edit").closest('[data-testid="activity-step"]');
expect(row).toBeInTheDocument();
expect(row).not.toHaveAttribute("title");
expect(screen.queryByText("No permission to change this location.")).not.toBeInTheDocument();
expect(screen.queryByText(/\[Errno 13\]/)).not.toBeInTheDocument();
});
@@ -1358,18 +1641,18 @@ describe("AgentActivityCluster", () => {
/>,
);
const toggle = screen.getByRole("button", { name: "Edited 3 changes" });
expect(toggle).toHaveTextContent("+8");
expect(toggle).toHaveTextContent("-7");
fireEvent.click(toggle);
const fileRefs = screen.getAllByTestId("activity-file-reference");
expect(fileRefs).toHaveLength(3);
expect(fileRefs.every((ref) => ref.textContent?.includes("minecraft-fps/index.html"))).toBe(true);
expect(screen.getByText("patch failed")).toBeInTheDocument();
expect(screen.getAllByTestId("file-edit-diff")).toHaveLength(2);
expect(screen.getByText("<canvas />")).toBeInTheDocument();
expect(screen.getByText("const fps = 60;")).toBeInTheDocument();
const failedRow = screen.getByText("Could not edit").closest(
'[data-testid="activity-step"]',
);
expect(failedRow).toBeInTheDocument();
expect(failedRow).not.toHaveAttribute("title");
expect(screen.queryByText("patch failed")).not.toBeInTheDocument();
expect(screen.queryByTestId("file-edit-diff")).not.toBeInTheDocument();
expect(screen.queryByText("<canvas />")).not.toBeInTheDocument();
expect(screen.queryByText("const fps = 60;")).not.toBeInTheDocument();
expect(screen.getAllByText("+2").length).toBeGreaterThan(0);
expect(screen.getAllByText("-1").length).toBeGreaterThan(0);
expect(screen.getAllByText("+6").length).toBeGreaterThan(0);
@@ -1379,7 +1662,7 @@ describe("AgentActivityCluster", () => {
}
});
it("renders tool event embeds as inline activity evidence", () => {
it("keeps tool event embeds out of the flat activity list", () => {
render(
<AgentActivityCluster
messages={[{
@@ -1406,15 +1689,91 @@ describe("AgentActivityCluster", () => {
/>,
);
expect(screen.getByText("Web")).toBeInTheDocument();
expect(screen.getByTestId("activity-evidence-preview")).toBeInTheDocument();
expect(screen.getByRole("img", { name: "Homepage screenshot" })).toHaveAttribute(
"src",
"/api/media/signed/screenshot.png",
);
expect(screen.queryByText("Web")).not.toBeInTheDocument();
const row = screen.getByText("example.com").closest('[data-testid="activity-step"]');
expect(row).toHaveTextContent("Read");
expect(screen.queryByTestId("activity-evidence-preview")).not.toBeInTheDocument();
expect(screen.queryByText(/Found image/i)).not.toBeInTheDocument();
expect(screen.queryByRole("img", { name: "Homepage screenshot" })).not.toBeInTheDocument();
});
it("shows missing evidence as a file-safe placeholder", () => {
it("keeps image generation status to one activity line", () => {
const message: UIMessage = {
id: "image-run",
role: "tool",
kind: "trace",
content: 'generate_image({"prompt":"an orange nanobot on a desk","aspect_ratio":"4:3"})',
traces: ['generate_image({"prompt":"an orange nanobot on a desk","aspect_ratio":"4:3"})'],
toolEvents: [{
phase: "start",
call_id: "image-call",
name: "generate_image",
arguments: { prompt: "an orange nanobot on a desk", aspect_ratio: "4:3" },
}],
createdAt: 1,
};
const { rerender } = render(
<AgentActivityCluster messages={[message]} isTurnStreaming hasBodyBelow={false} />,
);
expect(screen.getByText("Generating image")).toBeInTheDocument();
rerender(
<AgentActivityCluster
messages={[{
...message,
toolEvents: [{
...message.toolEvents![0],
phase: "end",
files: [{
url: "/api/media/signed/generated.png",
name: "generated.png",
type: "image/png",
}],
}],
}]}
isTurnStreaming={false}
hasBodyBelow={false}
/>,
);
expect(screen.getByText("Generated image")).toBeInTheDocument();
expect(screen.queryByRole("img", { name: "generated.png" })).not.toBeInTheDocument();
});
it("keeps image-generation failures visible and actionable", () => {
render(
<AgentActivityCluster
messages={[{
id: "image-error",
role: "tool",
kind: "trace",
content: 'generate_image({"prompt":"a launch poster"})',
traces: ['generate_image({"prompt":"a launch poster"})'],
toolEvents: [{
phase: "error",
call_id: "image-error-call",
name: "generate_image",
arguments: { prompt: "a launch poster" },
error: "Image provider quota exceeded",
}],
createdAt: 1,
}]}
isTurnStreaming={false}
hasBodyBelow={false}
/>,
);
expect(screen.getByText("Could not generate image")).toBeInTheDocument();
const row = screen.getByText("Could not generate image").closest(
'[data-testid="activity-step"]',
);
expect(row).toBeInTheDocument();
expect(row).not.toHaveAttribute("title");
expect(screen.queryByText("Image provider quota exceeded")).not.toBeInTheDocument();
});
it("does not add a secondary evidence row when evidence is missing", () => {
render(
<AgentActivityCluster
messages={[{
@@ -1437,8 +1796,104 @@ describe("AgentActivityCluster", () => {
/>,
);
expect(screen.getByText("Vision")).toBeInTheDocument();
expect(screen.getByTestId("activity-evidence-preview")).toBeInTheDocument();
expect(screen.getByText("missing.png")).toBeInTheDocument();
expect(screen.queryByText("Vision")).not.toBeInTheDocument();
expect(screen.getByText("Captured screenshot")).toBeInTheDocument();
expect(screen.queryByTestId("activity-evidence-preview")).not.toBeInTheDocument();
expect(screen.queryByText("missing.png")).not.toBeInTheDocument();
});
it("keeps every default activity action on one structural line", () => {
render(
<AgentActivityCluster
messages={[
{
id: "reasoning-line",
role: "assistant",
content: "",
reasoning: "**Planning** the next step\nwithout a nested title",
reasoningStreaming: false,
createdAt: 1,
},
{
id: "tool-line",
role: "tool",
kind: "trace",
content: 'grep({"pattern":"needle","path":"workspace/file.txt"})',
traces: ['grep({"pattern":"needle","path":"workspace/file.txt"})'],
createdAt: 2,
},
{
id: "fetch-line",
role: "tool",
kind: "trace",
content: 'web_fetch({"url":"https://example.com/docs"})',
traces: ['web_fetch({"url":"https://example.com/docs"})'],
createdAt: 3,
},
]}
isTurnStreaming={false}
hasBodyBelow={false}
/>,
);
const steps = screen.getAllByTestId("activity-step");
expect(steps.length).toBeGreaterThanOrEqual(3);
for (const step of steps) {
expect(step).toHaveClass("grid-cols-[1.125rem_minmax(0,1fr)]");
const line = step.children[1]?.firstElementChild;
expect(line).toHaveClass("overflow-hidden");
expect(line).toHaveClass("whitespace-nowrap");
expect(step.querySelector("br")).not.toBeInTheDocument();
expect(step.querySelector('[data-testid="activity-evidence-preview"]')).not.toBeInTheDocument();
}
expect(document.querySelector("details")).not.toBeInTheDocument();
expect(document.querySelector("ul, li, section")).not.toBeInTheDocument();
});
it("does not expose tool inputs or credentials in the activity surface", () => {
const cliLine = 'run_cli_app({"name":"blender","args":["--token","xoxb-1234567890-secret","render"],"json":true})';
const mcpLine = 'mcp_browserbase_browser_fill({"element":"Password","text":"mcp-private-value"})';
const genericLine = 'third_party_sync({"token":"sk-proj-1234567890-secret","payload":"private-payload"})';
const { container } = render(
<AgentActivityCluster
messages={[
{
id: "private-cli",
role: "tool",
kind: "trace",
content: cliLine,
traces: [cliLine],
createdAt: 1,
},
{
id: "private-mcp",
role: "tool",
kind: "trace",
content: mcpLine,
traces: [mcpLine],
createdAt: 2,
},
{
id: "private-generic",
role: "tool",
kind: "trace",
content: genericLine,
traces: [genericLine],
createdAt: 3,
},
]}
isTurnStreaming
hasBodyBelow={false}
cliApps={[BLENDER_CLI_APP]}
mcpPresets={[BROWSERBASE_MCP]}
/>,
);
expect(container.innerHTML).not.toContain("xoxb-1234567890-secret");
expect(container.innerHTML).not.toContain("mcp-private-value");
expect(container.innerHTML).not.toContain("sk-proj-1234567890-secret");
expect(container.innerHTML).not.toContain("private-payload");
expect(container.textContent).not.toMatch(/run_cli_app\(|browser_fill\(|third_party_sync\(/);
expect(container.textContent).toMatch(/<redacted>|••••/);
});
});