mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-30 13:20:54 +01:00
## Thinking Path > - Paperclip is the open source app people use to manage AI agents for work. > - Task chat shows tool activity between agent updates. > - Finished groups currently keep the last raw command on screen. > - Failure totals add visual weight to normal retries. > - This pull request summarizes finished groups with short action phrases. > - Users can expand each group to inspect the original activity and output. > - The benefit is a quieter history that still explains the work. ## Linked Issues or Issue Description **What existing behavior does this improve?** Runner activity in live task chat and saved history. **Current behavior** A finished group shows its last command or tool target. A separate failure count remains visible after retries. **Proposed behavior** Show short phrases such as “Ran commands” or “Read files, ran commands” when a commentary group finishes. Remove failure counts in both active and finished groups. Keep original output in expandable history. **Reason and benefit** The activity summary describes the work without filling the conversation with shell arguments or treating each retry as an alarm. Unsuccessful reads and edits use “Checked files” and “Worked on files” to avoid claiming success. **Additional context** Refs: #13255. This builds on the merged rolling activity groups. Related open PR #13246 covers steering and status presentation; this change concerns completed summaries and failure counts. The design was reviewed in desktop and mobile Storybook previews. ## What Changed - Add a deterministic summary shared by builtin and provider-native activity. - Combine repeated categories and retries, preserve category order, and bound long summaries. - Summarize each inactive commentary group while the next group can still be running. - Keep expansion, individual output disclosures, and active rolling rows. - Use the production component in eight review stories, including animated desktop and mobile transitions. - Cover retries, unsuccessful edits, unknown tools, reasoning-only groups, saved history, and resuming work. ## Verification - Focused summary, group, and runner-turn tests pass: 62 tests. - Token gates pass. - The approved Storybook previews passed browser checks for desktop, mobile, keyboard expansion, live-to-completed transitions, and original retry output. - `pnpm -r typecheck`, `pnpm build`, and the production Storybook build pass. - The complete UI suite passes: 582 files and 6,005 tests. The full repository test run is in progress after repairing missing symlinks in the local PostgreSQL dependency. A native workspace suite that hit the setup failure now passes all six tests. - Greptile is 5/5 on the current commit, with no review threads. Security checks pass. All individual CI jobs pass except server shard 4/5, where one plugin-worker output timing assertion failed. The complete plugin-worker suite passes locally (83 tests). The workflow finished. One retry of the failed shard and aggregate check was dispatched and is queued. Squash auto-merge is enabled and remains gated on required checks. - Rechecked the production stories in the browser: mobile history stays on one line, and expansion survives completion while the next group remains active. - Review in Storybook under Tasks → Completed activity preview. Use Next to finish one group while the next group remains active. Open a completed summary to inspect its history. ## Risks - Summaries classify observed activity; “Ran commands” does not mean exit code zero. - Unknown tools use a generic description. Full names and outputs remain in history. - No API, database, or runner protocol contracts change. > Reviewed ROADMAP.md. This is a focused improvement to the existing task-chat presentation. ## Model Used OpenAI GPT-6 through Codex, with reasoning, tool use, code execution, and browser testing. The runtime does not expose a more specific model version or context-window size. ## Checklist - [x] I have included a thinking path that traces from project context to this change - [x] I have specified the model used (with version and capability details) - [x] I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work - [x] I have searched GitHub for duplicate or related PRs and linked them above - [x] I have either (a) linked existing issues with `Fixes: #` / `Closes #` / `Refs #` OR (b) described the issue in-PR following the relevant issue template - [x] I have not referenced internal/instance-local Paperclip issues or links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip` URLs) - [x] My branch name describes the change (e.g. `docs/...`, `fix/...`) and contains no internal Paperclip ticket id or instance-derived details - [x] I have run tests locally and they pass - [x] I have added or updated tests where applicable - [x] I have updated relevant documentation to reflect my changes - [x] I have considered and documented any risks above - [ ] All Paperclip CI gates are green - [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups - [x] I will address all Greptile and reviewer comments before requesting merge --------- Co-authored-by: Paperclip <noreply@paperclip.ing>
133 lines
4.5 KiB
TypeScript
133 lines
4.5 KiB
TypeScript
import { Brain, CirclePause, Gauge, Layers3 } from "lucide-react";
|
|
import type { TaskChatActivityPhaseItem } from "./task-chat-model";
|
|
import {
|
|
toolActivityPresentation,
|
|
type ToolFamily,
|
|
type ToolIcon,
|
|
} from "./tool-taxonomy";
|
|
import { protocolActivityPresentation } from "./task-chat-activity-presentation";
|
|
|
|
type Activity = TaskChatActivityPhaseItem["items"][number];
|
|
const labels: Record<ToolFamily, string> = {
|
|
terminal: "Ran commands",
|
|
grep: "Searched files",
|
|
search: "Searched files",
|
|
read: "Read files",
|
|
edit: "Edited files",
|
|
web: "Searched the web",
|
|
plan: "Worked on a plan",
|
|
question: "Requested input",
|
|
agent: "Worked with agents",
|
|
safety: "Reviewed safety",
|
|
image: "Worked with images",
|
|
wait: "Waited",
|
|
mcp: "Used connected tools",
|
|
other: "Used tools",
|
|
};
|
|
|
|
/** Describe observed activities, never infer success from a finished group. */
|
|
export function completedActivitySummary(items: Activity[]) {
|
|
const categories = new Map<
|
|
string,
|
|
{ label: string; icon: ToolIcon; order: number }
|
|
>();
|
|
const add = (label: string, icon: ToolIcon, order = 0) => {
|
|
const existing = categories.get(label);
|
|
if (!existing || order < existing.order)
|
|
categories.set(label, { label, icon, order });
|
|
};
|
|
const tools: Array<{
|
|
family: ToolFamily;
|
|
icon: ToolIcon;
|
|
completed: boolean;
|
|
order: number;
|
|
}> = [];
|
|
for (const [order, item] of items.entries()) {
|
|
if (item.kind === "tool") {
|
|
const p = toolActivityPresentation({
|
|
name: item.rawName ?? item.name,
|
|
target: item.target,
|
|
});
|
|
tools.push({ ...p, order, completed: item.status === "completed" });
|
|
} else if (item.kind === "protocol") {
|
|
if (
|
|
item.surface === "provider_activity" &&
|
|
item.family === "tool_execution"
|
|
) {
|
|
const value = (label: string) =>
|
|
item.details.find((d) => d.label === label)?.value;
|
|
const p = toolActivityPresentation({
|
|
name: value("Name"),
|
|
operation: value("Operation"),
|
|
transport: value("Transport"),
|
|
namespace: value("Namespace"),
|
|
target: value("Target"),
|
|
});
|
|
tools.push({ ...p, order, completed: item.status === "completed" });
|
|
} else {
|
|
const p = protocolActivityPresentation(item);
|
|
if (!p) continue;
|
|
const family =
|
|
item.surface === "provider_activity" ? item.family : item.surface;
|
|
const label =
|
|
(
|
|
{
|
|
research: "Searched the web",
|
|
plan: "Worked on a plan",
|
|
delegation: "Worked with agents",
|
|
artifact: "Worked with artifacts",
|
|
context: "Managed context",
|
|
memory: "Checked memory",
|
|
model_identity: "Checked model settings",
|
|
review: "Worked in review mode",
|
|
hook: "Ran hooks",
|
|
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",
|
|
} as Record<string, string>
|
|
)[family] ?? "Used tools";
|
|
add(label, p.icon, order);
|
|
}
|
|
}
|
|
}
|
|
const completedFamilies = new Set(
|
|
tools.filter((tool) => tool.completed).map((tool) => tool.family),
|
|
);
|
|
// Merge repeated families and retries. Terminal is an action, not an exit-status claim.
|
|
for (const tool of tools) {
|
|
const succeeded = completedFamilies.has(tool.family);
|
|
const label =
|
|
tool.family === "read" && !succeeded
|
|
? "Checked files"
|
|
: tool.family === "edit" && !succeeded
|
|
? "Worked on files"
|
|
: labels[tool.family];
|
|
add(label, tool.icon, tool.order);
|
|
}
|
|
if (!categories.size) {
|
|
if (items.some((item) => item.kind === "thinking"))
|
|
add("Thought through the task", Brain);
|
|
else if (items.some((item) => item.kind === "marker"))
|
|
add("Activity stopped", CirclePause);
|
|
else add("Recorded usage", Gauge);
|
|
}
|
|
const values = [...categories.values()].sort((a, b) => a.order - b.order);
|
|
const join = (parts: string[]) =>
|
|
parts
|
|
.map((p, i) => (i ? p.charAt(0).toLowerCase() + p.slice(1) : p))
|
|
.join(", ");
|
|
const fullLabel = join(values.map((v) => v.label));
|
|
return {
|
|
label:
|
|
values.length > 3
|
|
? `${join(values.slice(0, 2).map((v) => v.label))}, and more`
|
|
: fullLabel,
|
|
fullLabel,
|
|
icon: values.length === 1 ? values[0].icon : Layers3,
|
|
};
|
|
}
|