diff --git a/src/agentMode/ui/toolSummaries.test.ts b/src/agentMode/ui/toolSummaries.test.ts index ff806a49..f34e9150 100644 --- a/src/agentMode/ui/toolSummaries.test.ts +++ b/src/agentMode/ui/toolSummaries.test.ts @@ -41,13 +41,26 @@ describe("lookupToolSummary", () => { expect(lookupToolSummary(t).collapsedLine(t, CTX)).toBe("Read notes/music-theory.md"); }); - it("falls back to the original path when the file is outside the vault", () => { + it("hides parent directories outside the vault without changing the navigation target", () => { const t = tool({ vendorToolName: "Read", title: "read", locations: [{ path: "/etc/passwd" }], }); - expect(lookupToolSummary(t).collapsedLine(t, CTX)).toBe("Read /etc/passwd"); + const summary = lookupToolSummary(t); + expect(summary.collapsedLine(t, CTX)).toBe("Read …/passwd"); + expect(summary.targetPath?.(t, CTX)).toBe("/etc/passwd"); + }); + + it("hides parent directories for ACP read paths without vault context", () => { + const t = tool({ + toolKind: "read", + title: "Read", + input: { filePath: "/Users/me/private/draft.md" }, + }); + expect(lookupToolSummary(t).collapsedLine(t, { vaultBase: null })).toBe("Read …/draft.md"); + const windows = { ...t, input: { filePath: "C:\\Users\\me\\private\\draft.md" } }; + expect(lookupToolSummary(windows).collapsedLine(windows, CTX)).toBe("Read …/draft.md"); }); it("aggregates Edits with combined +/- line counts and counts notes", () => { diff --git a/src/agentMode/ui/toolSummaries.ts b/src/agentMode/ui/toolSummaries.ts index efa05810..452b2a99 100644 --- a/src/agentMode/ui/toolSummaries.ts +++ b/src/agentMode/ui/toolSummaries.ts @@ -2,7 +2,7 @@ import type { LucideIcon } from "lucide-react"; import { Bot, MessageCircleQuestion } from "lucide-react"; import { pickToolIcon } from "@/agentMode/ui/toolIcons"; import type { ToolCallPart } from "@/agentMode/ui/agentTrail"; -import { toVaultRelative } from "@/utils/vaultPath"; +import { isAbsolutePath, toVaultRelative } from "@/utils/vaultPath"; /** * Render-time context passed through to the summary callbacks that need @@ -202,6 +202,13 @@ function targetFromPath(part: ToolCallPart, vaultBase: string | null): string | return null; } +function displayTargetFromPath(part: ToolCallPart, vaultBase: string | null): string | null { + const path = targetFromPath(part, vaultBase); + if (!path || !isAbsolutePath(path)) return path; + const leaf = path.replace(/\\/g, "/").replace(/\/+$/, "").split("/").pop(); + return leaf ? `…/${leaf}` : "…"; +} + /** * The skill identifier from a `Skill` tool call's input (Claude Code logs the * slash-command name as `input.skill`, e.g. "copilot-read-pdf"). Returns the @@ -253,7 +260,7 @@ function approxTokens(part: ToolCallPart): number { const READ_SUMMARY: ToolSummary = { icon: pickToolIcon({ vendorToolName: "Read" }), collapsedLine: (p, ctx) => - `${verb(p, "Reading", "Read")} ${targetFromPath(p, ctx?.vaultBase ?? null) ?? targetFromTitle(p)}`, + `${verb(p, "Reading", "Read")} ${displayTargetFromPath(p, ctx?.vaultBase ?? null) ?? targetFromTitle(p)}`, outcome: (p) => { const t = approxTokens(p); return t > 0 ? `~${formatTokens(t)} tokens` : null; @@ -272,7 +279,7 @@ const LIST_SUMMARY: ToolSummary = { icon: pickToolIcon({ vendorToolName: "LS" }), collapsedLine: (p, ctx) => { const v = verb(p, "Listing", "Listed"); - const path = targetFromPath(p, ctx?.vaultBase ?? null); + const path = displayTargetFromPath(p, ctx?.vaultBase ?? null); return path ? `${v} ${path}` : `${v} vault root`; }, outcome: () => null, @@ -285,7 +292,7 @@ const LIST_SUMMARY: ToolSummary = { const EDIT_SUMMARY: ToolSummary = { icon: pickToolIcon({ vendorToolName: "Edit" }), collapsedLine: (p, ctx) => - `${verb(p, "Editing", "Edited")} ${targetFromPath(p, ctx?.vaultBase ?? null) ?? targetFromTitle(p)}`, + `${verb(p, "Editing", "Edited")} ${displayTargetFromPath(p, ctx?.vaultBase ?? null) ?? targetFromTitle(p)}`, outcome: (p) => { const { added, removed } = diffStats(p); if (added === 0 && removed === 0) return null; @@ -506,7 +513,7 @@ const KIND_FETCH_SUMMARY: ToolSummary = { const KIND_DELETE_SUMMARY: ToolSummary = { icon: pickToolIcon({ toolKind: "delete" }), collapsedLine: (p, ctx) => - `${verb(p, "Deleting", "Deleted")} ${targetFromPath(p, ctx?.vaultBase ?? null) ?? targetFromTitle(p)}`, + `${verb(p, "Deleting", "Deleted")} ${displayTargetFromPath(p, ctx?.vaultBase ?? null) ?? targetFromTitle(p)}`, outcome: () => null, aggregate: (parts) => ({ line: `Deleted ${pluralize(parts.length, "item")}${statusSuffix(parts)}`, @@ -516,7 +523,7 @@ const KIND_DELETE_SUMMARY: ToolSummary = { const KIND_MOVE_SUMMARY: ToolSummary = { icon: pickToolIcon({ toolKind: "move" }), collapsedLine: (p, ctx) => - `${verb(p, "Moving", "Moved")} ${targetFromPath(p, ctx?.vaultBase ?? null) ?? targetFromTitle(p)}`, + `${verb(p, "Moving", "Moved")} ${displayTargetFromPath(p, ctx?.vaultBase ?? null) ?? targetFromTitle(p)}`, outcome: () => null, aggregate: (parts) => ({ line: `Moved ${pluralize(parts.length, "item")}${statusSuffix(parts)}`,