mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-31 14:16:39 +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>
145 lines
4.2 KiB
TypeScript
145 lines
4.2 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
import { completedActivitySummary } from "./completed-activity-summary";
|
|
import type {
|
|
TaskChatActivityPhaseItem,
|
|
TaskChatProviderActivityItem,
|
|
TaskChatToolItem,
|
|
} from "./task-chat-model";
|
|
|
|
type Activity = TaskChatActivityPhaseItem["items"][number];
|
|
const tool = (
|
|
name: string,
|
|
status: TaskChatToolItem["status"] = "completed",
|
|
): TaskChatToolItem => ({
|
|
id: name,
|
|
kind: "tool",
|
|
name,
|
|
status,
|
|
target: "private-command-or-path",
|
|
detail: "private-output",
|
|
});
|
|
const provider = (
|
|
family: TaskChatProviderActivityItem["family"],
|
|
status: TaskChatProviderActivityItem["status"] = "completed",
|
|
): TaskChatProviderActivityItem => ({
|
|
id: family,
|
|
kind: "protocol",
|
|
surface: "provider_activity",
|
|
family,
|
|
status,
|
|
title: "provider internal title",
|
|
eventType: "activity",
|
|
details: [],
|
|
steps: [],
|
|
links: [],
|
|
children: [],
|
|
});
|
|
const thought: Activity = {
|
|
id: "thought",
|
|
kind: "thinking",
|
|
lines: ["Reasoning text"],
|
|
};
|
|
|
|
describe("completedActivitySummary", () => {
|
|
it.each([
|
|
"completed",
|
|
"failed",
|
|
"interrupted",
|
|
"pending",
|
|
"in_progress",
|
|
] as const)(
|
|
"describes command attempts without claiming success (%s)",
|
|
(status) => {
|
|
expect(
|
|
completedActivitySummary([
|
|
tool("exec_command", status),
|
|
tool("bash", status),
|
|
]).label,
|
|
).toBe("Ran commands");
|
|
},
|
|
);
|
|
it("merges retries and hides reasoning, raw targets and output from the summary", () => {
|
|
const summary = completedActivitySummary([
|
|
thought,
|
|
tool("read"),
|
|
tool("exec_command", "failed"),
|
|
tool("exec_command"),
|
|
]);
|
|
expect(summary.label).toBe("Read files, ran commands");
|
|
expect(summary.fullLabel).not.toMatch(/private|Reasoning|failed/);
|
|
});
|
|
it.each([
|
|
["read", "failed", "Checked files"],
|
|
["read", "interrupted", "Checked files"],
|
|
["apply_patch", "failed", "Worked on files"],
|
|
["apply_patch", "interrupted", "Worked on files"],
|
|
["read", "completed", "Read files"],
|
|
["apply_patch", "completed", "Edited files"],
|
|
] as const)(
|
|
"does not claim an unsuccessful %s completed",
|
|
(name, status, expected) => {
|
|
expect(completedActivitySummary([tool(name, status)]).label).toBe(
|
|
expected,
|
|
);
|
|
},
|
|
);
|
|
it("uses completed work once when a read or edit retry succeeds", () => {
|
|
expect(
|
|
completedActivitySummary([
|
|
tool("read", "failed"),
|
|
tool("apply_patch", "failed"),
|
|
tool("read"),
|
|
tool("apply_patch"),
|
|
]).label,
|
|
).toBe("Read files, edited files");
|
|
});
|
|
it("classifies native provider tools through the same taxonomy and keeps encounter order", () => {
|
|
const native = {
|
|
...provider("tool_execution", "failed"),
|
|
details: [{ label: "Name", value: "exec_command" }],
|
|
};
|
|
expect(
|
|
completedActivitySummary([
|
|
tool("read"),
|
|
provider("research"),
|
|
native,
|
|
tool("bash"),
|
|
]).label,
|
|
).toBe("Read files, searched the web, ran commands");
|
|
});
|
|
it("supports canonical operation identity when a native tool has no useful name", () => {
|
|
const native = {
|
|
...provider("tool_execution"),
|
|
details: [
|
|
{ label: "Name", value: "tool" },
|
|
{ label: "Operation", value: "execute" },
|
|
],
|
|
};
|
|
expect(completedActivitySummary([native]).label).toBe("Ran commands");
|
|
});
|
|
it("bounds many categories while keeping the complete accessible description", () => {
|
|
const summary = completedActivitySummary([
|
|
tool("read"),
|
|
tool("exec_command"),
|
|
tool("grep"),
|
|
tool("apply_patch"),
|
|
provider("research"),
|
|
]);
|
|
expect(summary.label).toBe("Read files, ran commands, and more");
|
|
expect(summary.fullLabel).toBe(
|
|
"Read files, ran commands, searched files, edited files, searched the web",
|
|
);
|
|
});
|
|
it("does not invent tool activity for thoughts-only groups or expose unknown identifiers", () => {
|
|
expect(completedActivitySummary([thought]).label).toBe(
|
|
"Thought through the task",
|
|
);
|
|
expect(completedActivitySummary([tool("opaque_8792")]).label).toBe(
|
|
"Used tools",
|
|
);
|
|
expect(
|
|
completedActivitySummary([tool("mcp__github__get_pull_request")]).label,
|
|
).toBe("Used connected tools");
|
|
});
|
|
});
|