From b8a59b259c0f7a3da14bc2045577d9d966cd963a Mon Sep 17 00:00:00 2001 From: Dotta Date: Mon, 5 Oct 2026 13:09:08 -0500 Subject: [PATCH] fix(ui): preserve Pi notice severity and compact summaries Label error severity consistently and keep one strongest notice category in the collapsed activity summary while preserving the individual expanded messages. Co-Authored-By: Paperclip --- .../TaskChatProtocolActivityRow.test.tsx | 1 + .../task-chat/TaskChatProtocolActivityRow.tsx | 4 ++- .../completed-activity-summary.test.ts | 13 ++++++++++ .../task-chat/completed-activity-summary.ts | 25 +++++++++++++++++-- .../task-chat-activity-presentation.ts | 8 +++++- 5 files changed, 47 insertions(+), 4 deletions(-) diff --git a/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx b/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx index 716373aa92e..44414025d64 100644 --- a/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx +++ b/ui/src/components/task-chat/TaskChatProtocolActivityRow.test.tsx @@ -235,6 +235,7 @@ describe("TaskChatProtocolActivityRow", () => { ["info", "informational", "Provider update", "lucide-info"], ["warning", "informational", "Warning", "lucide-triangle-alert"], ["error", "failed", "Error", "lucide-triangle-alert"], + ["error", "informational", "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."; diff --git a/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx b/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx index ca10ee67cd6..b040c5253b5 100644 --- a/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx +++ b/ui/src/components/task-chat/TaskChatProtocolActivityRow.tsx @@ -22,6 +22,7 @@ import { protocolActivityIsRunning, protocolActivityLabel, protocolActivityPresentation, + providerNoticeSeverity, } from "./task-chat-activity-presentation"; const COMPACT_RESEARCH_RESULT_LIMIT = 5; @@ -281,6 +282,7 @@ export function TaskChatProtocolActivityRow({ item }: { item: TaskChatProtocolIt if (!presentation) return null; if (item.surface === "provider_activity" && item.family === "provider_notice") { const NoticeIcon = presentation.icon; + const severity = providerNoticeSeverity(item); const summary = item.summary ?? item.details.find((entry) => entry.label === "Summary")?.value ?? "The provider reported a notice without a message."; @@ -288,7 +290,7 @@ export function TaskChatProtocolActivityRow({ item }: { item: TaskChatProtocolIt
- {item.status === "failed" ? "Error" : item.details.find(detail => detail.label === "Severity")?.value === "info" ? "Provider update" : "Warning"} + {severity === "error" ? "Error" : severity === "info" ? "Provider update" : "Warning"}

{summary}

{item.details.some(detail => detail.label !== "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 3c22f601cde..b4493e85b87 100644 --- a/ui/src/components/task-chat/completed-activity-summary.test.ts +++ b/ui/src/components/task-chat/completed-activity-summary.test.ts @@ -173,4 +173,17 @@ describe("completedActivitySummary", () => { }]).icon).toBe(AlertTriangle); }); + it("groups distinct notices and keeps a later error visible beside actual work", () => { + const notices = ["info", "info", "warning", "error"].map((severity, index) => ({ + ...provider("provider_notice", "informational"), + id: `notice-${index}`, + summary: `Distinct provider message ${index}`, + details: [{ label: "Severity", value: severity }], + })); + const result = completedActivitySummary([tool("bash"), ...notices]); + expect(result.label).toBe("Provider error reported, ran commands"); + expect(result.fullLabel).toBe(result.label); + expect(result.label).not.toContain("Distinct provider message"); + }); + }); diff --git a/ui/src/components/task-chat/completed-activity-summary.ts b/ui/src/components/task-chat/completed-activity-summary.ts index d2223dbb4ed..776faabbfb3 100644 --- a/ui/src/components/task-chat/completed-activity-summary.ts +++ b/ui/src/components/task-chat/completed-activity-summary.ts @@ -5,7 +5,7 @@ import { type ToolFamily, type ToolIcon, } from "./tool-taxonomy"; -import { protocolActivityPresentation } from "./task-chat-activity-presentation"; +import { protocolActivityPresentation, providerNoticeSeverity } from "./task-chat-activity-presentation"; type Activity = TaskChatActivityPhaseItem["items"][number]; const labels: Record = { @@ -42,6 +42,12 @@ export function completedActivitySummary(items: Activity[]) { completed: boolean; order: number; }> = []; + const notices: Array<{ + summary: string | undefined; + severity: "info" | "warning" | "error"; + icon: ToolIcon; + order: number; + }> = []; for (const [order, item] of items.entries()) { if (item.kind === "tool") { const p = toolActivityPresentation({ @@ -71,7 +77,7 @@ export function completedActivitySummary(items: Activity[]) { // 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); + notices.push({ summary, severity: providerNoticeSeverity(item), icon: p.icon, order }); continue; } const family = @@ -100,6 +106,21 @@ export function completedActivitySummary(items: Activity[]) { } } } + if (notices.length) { + const strongest = notices.find((notice) => notice.severity === "error") + ?? notices.find((notice) => notice.severity === "warning") + ?? notices[0]!; + const label = notices.length === 1 + ? strongest.summary || "Received a provider update" + : strongest.severity === "error" + ? "Provider error reported" + : strongest.severity === "warning" + ? "Provider warning reported" + : "Received provider updates"; + // One notice category leaves room for actual work. Keep warnings and errors + // visible when the compact label truncates other activity categories. + add(label, strongest.icon, strongest.severity === "info" ? notices[0]!.order : -1); + } const completedFamilies = new Set( tools.filter((tool) => tool.completed).map((tool) => tool.family), ); 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 27193ba30d4..a0cb58d0d07 100644 --- a/ui/src/components/task-chat/task-chat-activity-presentation.ts +++ b/ui/src/components/task-chat/task-chat-activity-presentation.ts @@ -41,6 +41,12 @@ export interface TaskChatActivityPresentation { detail?: string; } +export function providerNoticeSeverity(item: TaskChatProviderActivityItem): "info" | "warning" | "error" { + const severity = item.details.find((entry) => entry.label === "Severity")?.value; + if (item.status === "failed" || severity === "error") return "error"; + return severity === "info" ? "info" : "warning"; +} + function providerDetail(item: TaskChatProviderActivityItem, ...labels: string[]): string | undefined { return item.details.find((entry) => labels.includes(entry.label))?.value; } @@ -136,7 +142,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: item.status !== "failed" && providerDetail(item, "Severity") === "info" ? Info : AlertTriangle, runningLabel: "Provider notice", completedLabel: "Provider notice", failedLabel: "Provider error", interruptedLabel: "Provider notice", detail }; + return { icon: providerNoticeSeverity(item) === "info" ? Info : AlertTriangle, runningLabel: "Provider notice", completedLabel: "Provider notice", failedLabel: "Provider error", interruptedLabel: "Provider notice", detail }; } }