fix(runner): simplify expanded activity history

This commit is contained in:
Dotta committed 2026-08-21 15:26:40 -05:00
1 parent 4c3e30cdad
commit fab305db71
6 files changed
+66 -5

No files matched your search

+6 -1
View File
@@ -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";
@@ -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");
});
});
@@ -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<T extends TaskChatItem>(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<TaskChatMessageItem>(items, (item): item is TaskChatMessageItem => item.kind === "message" && Boolean(item.interstitial));
const final = lastOf<TaskChatMessageItem>(items, (item): item is TaskChatMessageItem => item.kind === "message" && item.channel === "final");
@@ -79,7 +81,7 @@ export function TaskChatRunnerTurn({
<div className="tc-turn-fold" data-folded={open ? "false" : "true"} aria-hidden={!open}>
<div>
<div className="flex flex-col gap-2 py-2">
<TaskChatLiveTail items={items as TaskChatItem[]} excludeFinal />
<TaskChatLiveTail items={historyItems} excludeFinal />
</div>
</div>
</div>
@@ -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",
)}
>
<RowIcon className="h-3.5 w-3.5 shrink-0" aria-hidden />
<span className="shrink-0 font-medium">{item.name}</span>
<span className="shrink-0 font-medium leading-none">{item.name}</span>
{item.target ? (
<span className="min-w-0 truncate font-mono text-(length:--text-micro)">{item.target}</span>
<span className="min-w-0 truncate font-mono text-(length:--text-micro) leading-none">{item.target}</span>
) : null}
<span className="ml-auto flex shrink-0 items-center gap-2">
{item.decision ? (
@@ -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)", () => {
@@ -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<string, number>();
let generic = 0;