From a32ce172a441df4fa90ebefdbb25b154dfc7813e Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 2 Oct 2026 05:25:25 -0500 Subject: [PATCH] fix(ui): show provider notice summaries with informational severity Keep existing normalized notice text visible in completed activity summaries and use an info icon for informational notices while preserving warning and error presentation. Co-Authored-By: Paperclip --- .../TaskChatProtocolActivityRow.test.tsx | 17 ++++++++++ .../task-chat/TaskChatProtocolActivityRow.tsx | 4 +-- .../completed-activity-summary.test.ts | 32 +++++++++++++++++++ .../task-chat/completed-activity-summary.ts | 8 ++++- .../task-chat-activity-presentation.ts | 3 +- 5 files changed, 60 insertions(+), 4 deletions(-) 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 }; } }