Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions webviews/src/agent-session/acpmux/toolPaths.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ export function toolLabel(
// An MCP call reads as its server and action, never as `mcp__<server>__<tool>`.
const call = mcpCall(tool);
if (call) return mcpPlainName(call);
// An edit of several files keeps a title that says more than its verb ("Edit 2 files"): naming
// the first file alone would read as an edit of that one file.
if (paths.length > 1 && /\s/.test(title)) return title;
if (paths.length) {
const action = firstWord(title || fallbackName);
return title.includes(paths[0]!) ? title : `${action} ${fromFolder(paths[0]!, cwd)}`;
Expand Down
10 changes: 6 additions & 4 deletions webviews/test/agent-pane-adopted-tool-calls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,14 +148,13 @@ await requireBrowserLane("agent-pane-adopted-tool-calls.test.ts", async () => {
await worked.click();
expect(await worked.getAttribute("aria-expanded")).toBe("true");

// The settled run's line, then its groups, as a live turn's run settles.
await page.getByRole("button", { name: "Edited files, ran commands" }).click();
// Its groups, each on its own line and open: an ended turn's run no longer folds under one
// summary line (#22658; only the Debug toggle's Codex-style runs fold it).
const commands = page.locator("button.cv-tool", { hasText: "Ran 3 commands" });
const edits = page.locator("button.cv-tool", { hasText: "Edited 2 files" });
await commands.waitFor();
await expect(edits.textContent()).resolves.toBe("Edited 2 files+4-1");

await commands.click();
const lines = await page.locator(".cv-command__line").allTextContents();
expect(lines).toEqual(["git status --short", "grep -n '^version' Cargo.toml", "sed -n 1,8p CHANGELOG.md"]);
// The answer stays outside the fold, after the work.
Expand All @@ -166,7 +165,10 @@ await requireBrowserLane("agent-pane-adopted-tool-calls.test.ts", async () => {
test("a patch whose files only its input names counts each file, not one", async () => {
const page = await open(PATCH_CHAT);
await page.locator("button.cv-worked").click();
await page.getByRole("button", { name: "Edited files, ran commands" }).waitFor();
await page.locator("button.cv-tool", { hasText: "Ran 3 commands" }).waitFor();
// With no diff the edit keeps its own row; it names both files' count, not the first file.
const edit = page.locator(".cv-tool", { hasText: /^Edit \d+ files$/ });
await expect(edit.textContent()).resolves.toBe("Edit 2 files");
await page.close();
});
});
22 changes: 22 additions & 0 deletions webviews/test/tool-label-several-files.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// l10n-allow-file: test fixture titles, not shipped UI.
// A call that edits several files keeps a title that says so ("Edit 2 files") instead of naming
// only the first file, but a title that is only a verb or a raw tool id ("Edit", "patch") still
// names its first file, as it did before.
import { expect, test } from "bun:test";
import type { AcpmuxActivity } from "../src/agent-session/acpmux/model";
import { toolLabel } from "../src/agent-session/acpmux/toolPaths";

type Tool = NonNullable<AcpmuxActivity["tool"]>;
const label = (tool: Partial<Tool>) => toolLabel(tool as Tool, "", (line) => line);
const twoFiles = [{ path: "a.ts" }, { path: "b.ts" }];
const patch = "*** Begin Patch\n*** Update File: a.ts\n*** Update File: b.ts\n*** End Patch";

test("an edit of several files keeps a title that counts them", () => {
expect(label({ title: "Edit 2 files", kind: "edit", locations: twoFiles })).toBe("Edit 2 files");
});

test("a bare verb or tool id over several files names the first file", () => {
expect(label({ title: "Edit", kind: "edit", status: "in_progress", locations: twoFiles })).toBe("Edit a.ts");
expect(label({ title: "Read", kind: "read", locations: twoFiles })).toBe("Read a.ts");
expect(label({ title: "patch", kind: "edit", inputSummary: JSON.stringify({ patchText: patch }) })).toBe("patch a.ts");
});