From fab305db718e00d2812e8ebe9da0451b61e6831a Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 21 Aug 2026 15:26:40 -0500 Subject: [PATCH] fix(runner): simplify expanded activity history --- ui/src/components/TaskChatThread.tsx | 7 ++++++- .../task-chat/TaskChatRunnerTurn.test.tsx | 18 +++++++++++++++++ .../task-chat/TaskChatRunnerTurn.tsx | 4 +++- .../components/task-chat/TaskChatToolCard.tsx | 6 +++--- .../task-chat/transcript-adapter.test.ts | 20 +++++++++++++++++++ .../task-chat/transcript-adapter.ts | 16 +++++++++++++++ 6 files changed, 66 insertions(+), 5 deletions(-) diff --git a/ui/src/components/TaskChatThread.tsx b/ui/src/components/TaskChatThread.tsx index c9657e10b16..fc0d6aed3f2 100644 --- a/ui/src/components/TaskChatThread.tsx +++ b/ui/src/components/TaskChatThread.tsx @@ -13,6 +13,7 @@ import { buildTurnSummary, coalesceSettledTurns, isTerminalRunStatus, + paperclipRunnerHistoryItems, prependIssueBrief, settledRunChildren, transcriptToTaskChatItems, @@ -402,7 +403,11 @@ export function TaskChatThread(props: TaskChatThreadProps) { agentName: meta?.agentName, running: false, }); - const children = settledRunChildren(parsed); + const children = settledRunChildren( + source.adapterType === "paperclip_runner" + ? paperclipRunnerHistoryItems(parsed) + : parsed, + ); if (children.length === 0 && source.adapterType !== "paperclip_runner") continue; settledRunIds.add(source.id); const failed = source.status !== "succeeded"; diff --git a/ui/src/components/task-chat/TaskChatRunnerTurn.test.tsx b/ui/src/components/task-chat/TaskChatRunnerTurn.test.tsx index 4800ae338c9..70b9b6eb43c 100644 --- a/ui/src/components/task-chat/TaskChatRunnerTurn.test.tsx +++ b/ui/src/components/task-chat/TaskChatRunnerTurn.test.tsx @@ -53,4 +53,22 @@ describe("TaskChatRunnerTurn", () => { expect(container.querySelector('[data-testid="task-chat-final-response"]')?.textContent).toContain("Completed successfully."); expect(container.querySelector('[data-testid="task-chat-current-activity"]')).toBeNull(); }); + + it("omits runner lifecycle and token noise while preserving useful history", () => { + render([ + { id: "session", kind: "marker", variant: "session_start", label: "Session started", detail: "session-id" }, + { id: "start", kind: "marker", variant: "turn_boundary", label: "Turn started" }, + { id: "usage", kind: "usage", usage: { used: 10_520, size: 0, inputTokens: 10_254, outputTokens: 266 } }, + { id: "tool", kind: "tool", name: "Paperclip_finish", rawName: "paperclip_finish", target: "reportedWorkDisposition: done", status: "completed" }, + { id: "interrupt", kind: "marker", variant: "interrupted", label: "Interrupted" }, + { id: "complete", kind: "marker", variant: "turn_boundary", label: "Turn completed" }, + ]); + + expect(container.textContent).toContain("Paperclip_finish"); + expect(container.textContent).toContain("Interrupted"); + expect(container.textContent).not.toContain("Session started"); + expect(container.textContent).not.toContain("Turn started"); + expect(container.textContent).not.toContain("Turn completed"); + expect(container.textContent).not.toContain("10,520"); + }); }); diff --git a/ui/src/components/task-chat/TaskChatRunnerTurn.tsx b/ui/src/components/task-chat/TaskChatRunnerTurn.tsx index 0c5176403b8..bf984688f63 100644 --- a/ui/src/components/task-chat/TaskChatRunnerTurn.tsx +++ b/ui/src/components/task-chat/TaskChatRunnerTurn.tsx @@ -5,6 +5,7 @@ import { cn } from "@/lib/utils"; import type { TaskChatItem, TaskChatMessageItem, TaskChatThinkingItem, TaskChatToolItem } from "./task-chat-model"; import { TaskChatLiveRunPill } from "./TaskChatLiveRunPill"; import { TaskChatLiveTail } from "./TaskChatLiveTail"; +import { paperclipRunnerHistoryItems } from "./transcript-adapter"; import { toolTaxonomy } from "./tool-taxonomy"; function lastOf(items: readonly TaskChatItem[], predicate: (item: TaskChatItem) => item is T): T | undefined { @@ -54,6 +55,7 @@ export function TaskChatRunnerTurn({ toolSummary: string | null; }) { const [open, setOpen] = useState(false); + const historyItems = paperclipRunnerHistoryItems(items); const progress = lastOf(items, (item): item is TaskChatMessageItem => item.kind === "message" && Boolean(item.interstitial)); const final = lastOf(items, (item): item is TaskChatMessageItem => item.kind === "message" && item.channel === "final"); @@ -79,7 +81,7 @@ export function TaskChatRunnerTurn({
- +
diff --git a/ui/src/components/task-chat/TaskChatToolCard.tsx b/ui/src/components/task-chat/TaskChatToolCard.tsx index 49c89d7bfc2..ae962c5fad8 100644 --- a/ui/src/components/task-chat/TaskChatToolCard.tsx +++ b/ui/src/components/task-chat/TaskChatToolCard.tsx @@ -37,14 +37,14 @@ export function TaskChatToolCard({ item }: { item: TaskChatToolItem }) { onClick={expandable ? () => setShowDetail((v) => !v) : undefined} aria-expanded={expandable ? showDetail : undefined} className={cn( - "group/tool -mx-1.5 flex min-w-0 items-center gap-2 rounded-sm px-1.5 py-0.5 text-left text-muted-foreground", + "group/tool -mx-1.5 flex min-h-6 min-w-0 items-center gap-2 rounded-sm px-1.5 py-1 text-left leading-none text-muted-foreground", expandable ? "cursor-pointer transition-colors hover:bg-muted/60 hover:text-foreground" : "cursor-default", )} > - {item.name} + {item.name} {item.target ? ( - {item.target} + {item.target} ) : null} {item.decision ? ( diff --git a/ui/src/components/task-chat/transcript-adapter.test.ts b/ui/src/components/task-chat/transcript-adapter.test.ts index 203efa23ac4..9f6b0899d24 100644 --- a/ui/src/components/task-chat/transcript-adapter.test.ts +++ b/ui/src/components/task-chat/transcript-adapter.test.ts @@ -10,6 +10,7 @@ import { flattenSelfTalk, isNestableLiveChild, ISSUE_BRIEF_ITEM_ID, + paperclipRunnerHistoryItems, prependIssueBrief, settledRunChildren, toolDisplayName, @@ -423,6 +424,25 @@ describe("paperclip runner semantic channels", () => { const history = settledRunChildren(parsed); expect(JSON.stringify(history)).not.toContain("Done."); }); + + it("filters lifecycle and usage noise from only the runner history surface", () => { + const items: TaskChatItem[] = [ + { id: "session", kind: "marker", variant: "session_start", label: "Session started", detail: "session-id" }, + { id: "start", kind: "marker", variant: "turn_boundary", label: "Turn started" }, + { id: "usage", kind: "usage", usage: { used: 1_024, size: 128_000, inputTokens: 1_000, outputTokens: 24 } }, + { id: "tool", kind: "tool", name: "Paperclip_finish", status: "completed", target: "done" }, + { id: "interrupt", kind: "marker", variant: "interrupted", label: "Interrupted" }, + { id: "runner", kind: "marker", variant: "turn_boundary", label: "Runner update" }, + { id: "complete", kind: "marker", variant: "turn_boundary", label: "Turn completed" }, + ]; + + expect(paperclipRunnerHistoryItems(items).map((item) => item.id)).toEqual([ + "tool", + "interrupt", + "runner", + ]); + expect(items).toHaveLength(7); + }); }); describe("buildMergedTurnSummary (PAP-362)", () => { diff --git a/ui/src/components/task-chat/transcript-adapter.ts b/ui/src/components/task-chat/transcript-adapter.ts index fd361ca5506..e4ca44ce728 100644 --- a/ui/src/components/task-chat/transcript-adapter.ts +++ b/ui/src/components/task-chat/transcript-adapter.ts @@ -404,6 +404,22 @@ export function settledRunChildren(parsed: readonly TaskChatItem[]): TaskChatTur return buildActivityPhases(parsed, false); } +/** + * Keep the paperclip runner's expanded activity history focused on work the + * user can act on. The normalized transcript remains lossless; this is only a + * presentation filter for the new-runner turn surface. Legacy adapters keep + * their existing lifecycle and usage rows. + */ +export function paperclipRunnerHistoryItems(parsed: readonly TaskChatItem[]): TaskChatItem[] { + return parsed.filter((item) => { + if (item.kind === "usage") return false; + if (item.kind !== "marker") return true; + if (item.variant === "session_start") return false; + return item.variant !== "turn_boundary" + || (item.label !== "Turn started" && item.label !== "Turn completed"); + }); +} + function phaseSummary(items: readonly (TaskChatToolItem | { kind: "usage" } | { kind: "thinking" } | { kind: "marker" })[]): string { const counts = new Map(); let generic = 0;