diff --git a/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx b/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx index f9e956e2c4..716373aa92 100644 --- a/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx +++ b/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx @@ -231,4 +231,21 @@ describe("TaskChatProtocolActivityRow", () => { expect(row?.querySelector("details")).toBeNull(); expect(row?.querySelector('[aria-expanded]')).toBeNull(); }); + it.each([ + ["info", "informational", "Provider update", "lucide-info"], + ["warning", "informational", "Warning", "lucide-triangle-alert"], + ["error", "failed", "Error", "lucide-triangle-alert"], + ["info", "failed", "Error", "lucide-triangle-alert"], + ] as const)("renders %s/%s notice severity without hiding its summary", (severity, status, label, icon) => { + const summary = "Pi estimates this turn at $0.000617. Billing cost is unverified."; + render({ + id: "notice", kind: "protocol", surface: "provider_activity", family: "provider_notice", + eventType: "provider.notice.recorded", status, title: "Provider notice", summary, + details: [{ label: "Severity", value: severity }], steps: [], links: [], children: [], + }); + expect(container.textContent).toContain(label); + expect(container.querySelector("p")?.textContent).toBe(summary); + expect(container.querySelector('[data-testid="task-chat-protocol-activity-icon"]')?.classList.contains(icon)).toBe(true); + }); + }); diff --git a/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx b/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx index 5440a4408e..ca10ee67cd 100644 --- a/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx +++ b/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx @@ -1,6 +1,5 @@ import { useId, useState, type ReactNode } from "react"; import { - AlertTriangle, Check, ChevronRight, Circle, @@ -281,13 +280,14 @@ export function TaskChatProtocolActivityRow({ item }: { item: TaskChatProtocolIt const presentation = protocolActivityPresentation(item); if (!presentation) return null; if (item.surface === "provider_activity" && item.family === "provider_notice") { + const NoticeIcon = presentation.icon; const summary = item.summary ?? item.details.find((entry) => entry.label === "Summary")?.value ?? "The provider reported a notice without a message."; return (
- + {item.status === "failed" ? "Error" : item.details.find(detail => detail.label === "Severity")?.value === "info" ? "Provider update" : "Warning"}

{summary}

diff --git a/ui/src/components/task-chat/completed-activity-summary.test.ts b/ui/src/components/task-chat/completed-activity-summary.test.ts index 85c02dd034..3c22f601cd 100644 --- a/ui/src/components/task-chat/completed-activity-summary.test.ts +++ b/ui/src/components/task-chat/completed-activity-summary.test.ts @@ -1,3 +1,4 @@ +import { AlertTriangle, Info } from "lucide-react"; import { describe, expect, it } from "vitest"; import { completedActivitySummary } from "./completed-activity-summary"; import type { @@ -141,4 +142,35 @@ describe("completedActivitySummary", () => { completedActivitySummary([tool("mcp__github__get_pull_request")]).label, ).toBe("Used connected tools"); }); + it("shows the preserved pricing estimate as informational, not a billing receipt", () => { + const summary = "Pi estimates this turn at $0.000617 from its model prices. Billing cost is unverified."; + expect(completedActivitySummary([{ + ...provider("provider_notice", "informational"), summary, + details: [{ label: "Severity", value: "info" }], + }])).toEqual({ label: summary, fullLabel: summary, icon: Info }); + }); + it.each(["warning", "error", undefined])("retains warning/error icons and safe notice text (%s)", (severity) => { + const summary = "Provider request needs attention."; + const result = completedActivitySummary([{ + ...provider("provider_notice", severity === "error" ? "failed" : "informational"), + details: [ + { label: "Summary", value: summary }, + ...(severity ? [{ label: "Severity", value: severity }] : []), + ], + }]); + expect(result).toEqual({ label: summary, fullLabel: summary, icon: AlertTriangle }); + }); + it("uses a generic fallback without promoting arbitrary detail fields", () => { + expect(completedActivitySummary([{ + ...provider("provider_notice", "informational"), summary: " ", + details: [{ label: "Raw input", value: "private-command-or-path" }], + }]).label).toBe("Received a provider update"); + }); + it("does not hide a failed notice behind informational metadata", () => { + expect(completedActivitySummary([{ + ...provider("provider_notice", "failed"), summary: "Provider failed.", + details: [{ label: "Severity", value: "info" }], + }]).icon).toBe(AlertTriangle); + }); + }); diff --git a/ui/src/components/task-chat/completed-activity-summary.ts b/ui/src/components/task-chat/completed-activity-summary.ts index d21c786e66..d2223dbb4e 100644 --- a/ui/src/components/task-chat/completed-activity-summary.ts +++ b/ui/src/components/task-chat/completed-activity-summary.ts @@ -67,6 +67,13 @@ export function completedActivitySummary(items: Activity[]) { } else { const p = protocolActivityPresentation(item); if (!p) continue; + if (item.surface === "provider_activity" && item.family === "provider_notice") { + // Reuse only the normalized notice text already available in the row. + const summary = item.summary?.trim() + || item.details.find((entry) => entry.label === "Summary")?.value.trim(); + add(summary || "Received a provider update", p.icon, order); + continue; + } const family = item.surface === "provider_activity" ? item.family : item.surface; const label = @@ -84,7 +91,6 @@ export function completedActivitySummary(items: Activity[]) { safety: "Reviewed safety", terminal: "Ran commands", wait: "Waited", - provider_notice: "Received a provider update", workspace_change: "Worked on files", workspace_file: "Referenced files", resource: "Added resources", diff --git a/ui/src/components/task-chat/task-chat-activity-presentation.ts b/ui/src/components/task-chat/task-chat-activity-presentation.ts index 4d905b65ff..27193ba30d 100644 --- a/ui/src/components/task-chat/task-chat-activity-presentation.ts +++ b/ui/src/components/task-chat/task-chat-activity-presentation.ts @@ -9,6 +9,7 @@ import { FilePenLine, FileText, GitBranch, + Info, ListChecks, PackageCheck, Search, @@ -135,7 +136,7 @@ export function providerActivityPresentation(item: TaskChatProviderActivityItem) case "wait": return { icon: Clock3, runningLabel: "Waiting", completedLabel: "Finished waiting", failedLabel: "Wait failed", interruptedLabel: "Wait stopped", detail }; case "provider_notice": - return { icon: AlertTriangle, runningLabel: "Provider notice", completedLabel: "Provider notice", failedLabel: "Provider error", interruptedLabel: "Provider notice", detail }; + return { icon: item.status !== "failed" && providerDetail(item, "Severity") === "info" ? Info : AlertTriangle, runningLabel: "Provider notice", completedLabel: "Provider notice", failedLabel: "Provider error", interruptedLabel: "Provider notice", detail }; } }