mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
fix(runner): simplify expanded activity history
This commit is contained in:
1 parent
4c3e30cdad
commit
fab305db71
6 files changed
+66
-5
No files matched your search
@@ -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;
|
||||
|
||||
Reference in new issue
Block a user