Files
PaperClipAI/ui/storybook/support/agent-commentary/fixtures.ts
T
DottaandPaperclip e6dcd1e8e9 feat: add Commentary to Audit with filters and shareable messages (#16884)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Agents can submit suggestions and complaints while they work.
> - Operators currently need database access to read those messages.
> - That makes it hard to find feedback from one agent or share a
specific message.
> - This pull request adds a standard Commentary tab in Audit.
> - Operators can filter messages, browse 50 at a time, and share links
to full message dialogs.

## Linked Issues or Issue Description

Refs #15367, which added feedback storage and agent submission. The
duplicate search found no open PR for a commentary reader.

**Subsystem affected**

Server, shared API contracts, and the board UI.

**Problem or motivation**

Agent suggestions and complaints are stored but have no board reader.
Operators must query the database to inspect feedback.

**Proposed solution**

Add Commentary to Audit without an experimental toggle. Show previews,
agent and message-type filters, text search, and 50-message pages.
Opening a row updates the URL. A copied URL opens the full message with
its agent, task, and run context.

**Alternatives considered**

Direct database queries remain useful for operators, but do not provide
a normal product workflow. An experimental setting would add setup to a
read-only audit page.

**Roadmap alignment**

This completes the operator view for the existing feedback feature. It
supports human oversight and does not change agent execution or task
ownership.

## What Changed

- Add company-scoped, board-only list and detail endpoints with audit
permission and source-task privacy checks.
- Return bounded previews and counts. Fetch full bodies only when a
message opens.
- Add the Commentary tab, search, filters, 50-row pagination, and
URL-based selection. Audit tab destinations work in both layouts, with a
classic sidebar entry for Commentary.
- Add production Storybook examples for populated, filtered, linked,
long, paginated, empty, loading, failed, denied, mobile, and light
states.
- Update API documentation and the feature and agent access maps.

## Verification

- Final head `abc4a251c14cfbfec007542214beec528f2b18bf`: 55 checks pass;
two conditional Storybook checks are skipped. The PR workflow is
successful. Greptile is 5/5 with all findings resolved.
- Focused database, HTTP, submission, OpenAPI, and UI regression tests
cover company isolation, task privacy, filter bounds, 50-row pagination,
and copied message links.
- Browser Storybook journeys cover row opening, direct links, combined
filters, search, and pagination. Storybook uses synthetic API fixtures;
database authorization is tested separately.
- Token gates, feature maps, and Storybook catalog validation pass.
- `pnpm -r typecheck`, `pnpm build`, and `pnpm build-storybook` pass on
the rebased branch. All 80 focused tests and all 37 Audit tests pass,
including the real App route table in both layouts, task
grant/revocation through HTTP, deleted-task privacy, and URL UI
regressions. The full suite passes in the parallel PR CI matrix. The
local serial full-suite run was stopped before completion after checking
the recorded 94-minute serial duration. Local focused tests, workspace
typecheck, production build, and final server/UI checks pass.
- The linked-message Storybook test initially asserted against the
loading dialog. It now waits for the message body; both direct-link and
row-open journeys pass.
- Native and legacy deleted-task privacy regressions failed before the
fix and pass afterward. The reader checks the run task binding after the
task foreign key clears.
- A rapid search-then-agent-filter browser regression reproduced the
search overwriting the selected agent. The pending search now restarts
when URL filters change. The same browser test passes with both filters
applied.
- CI found two existing AuditHub assertions that still expected five
tabs. They now assert the six-tab model and selected-company Commentary
rendering. All 37 Audit tests pass. All other test and build jobs passed
before this test-only correction.

- The [agent-chat attachment browser
test](https://github.com/paperclipai/paperclip/actions/runs/39307881171/job/118250189664)
timed out waiting for an attachment preview. The [same shard passed on
the prior
head](https://github.com/paperclipai/paperclip/actions/runs/39306658703/job/118245498343)
with identical application code; the intervening change only updates
AuditHub unit tests. The single retry passed on the same head. The
precise cause of the earlier failure remains unconfirmed.

## Risks

- Commentary may contain sensitive task context. The reader requires
board access and audit permission, and filters source-task visibility
before returning previews, counts, or full bodies.
- Text search scans stored message bodies. Pages are bounded and full
bodies are fetched on demand.
- Offset pagination can shift if new messages arrive while paging.
Ordering uses timestamp and ID for stable ties.
- No database migration or agent submission behavior changes are
required.

## Model Used

- OpenAI Codex, GPT-6 family, with reasoning, tool use, browser
interaction, and code execution. The exact backend model ID and
context-window size are not exposed in this session.

## 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
- [x] 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>
2026-10-19 14:21:13 -05:00

48 lines
5.1 KiB
TypeScript

import { type AgentCommentaryDetail } from "@paperclipai/shared";
import { storybookAgents } from "../../fixtures/paperclipData";
import { prepareFixtureApi, type FixtureState } from "../capability-gaps/api";
export const commentaryAgents = ["Amy (Opus)", "Codie", "VoC", "Fry (Codex)"].map((name, index) => ({
...storybookAgents[index % storybookAgents.length],
id: `10000000-0000-4000-8000-00000000000${index + 1}`,
name,
}));
const messages = [
{ agent: 0, kind: "suggestion", task: "Keep remote tools available after transient failures", body: "One failed Sentry request removes every tool on the connection for the rest of the run. A tool that worked moments earlier returns `tool_not_found`, even though the connection is still authorized.\n\n### What would help\n\nTreat upstream fetch failures as transient. Keep the tool catalog available, retry with a bounded backoff, and request reconnection only after a confirmed authentication error.\n\n### Impact\n\nThe fix was ready, but I could not verify recovery or resolve the incident. Another scheduled wake repeated the same setup work." },
{ agent: 1, kind: "suggestion", task: "Make monitor validation errors actionable", body: "The monitor tool returned `invalid_tool_call` without naming the rejected field. Please include the validation path and accepted limit so an agent can correct the call without guessing.\n\nA shorter notes field succeeded, but the tool schema did not expose that limit." },
{ agent: 0, kind: "complaint", task: "Run targeted tests in the managed sandbox", body: "I found the bug in ten minutes and then spent the rest of the run fighting the installer. The sandbox ran out of memory twice before I could run one targeted test.\n\nA fresh workspace needs a dependency cache and enough memory to run the checks the repository requires." },
{ agent: 2, kind: "suggestion", task: "Preserve a usable checkout across resumed runs", body: "The resumed workspace contains the source files but no Git metadata or installed dependencies. Preserve the branch, commit, and dependency cache so a small review fix does not require reconstructing the entire checkout." },
{ agent: 3, kind: "complaint", task: "Verify the managed GitHub launcher at startup", body: "The connection says ready, but the CLI still asks me to log in. The working launcher is in a directory that is not on PATH. Every doorway opens onto another hallway." },
{ agent: 0, kind: "suggestion", task: "Append new evidence without replacing the case history", body: "Updating a case with one new evidence entry replaces the whole evidence array. The workflow describes an accumulating history, so this is easy to miss.\n\nAdd an explicit append operation and reserve replacement for intentional edits. That would preserve earlier diagnosis and verification evidence." },
] as const;
export const commentaryEntries: AgentCommentaryDetail[] = messages.map((message, index) => ({
id: `20000000-0000-4000-8000-00000000000${index + 1}`,
kind: message.kind,
preview: message.body.slice(0, 280),
body: message.body,
createdAt: new Date(Date.UTC(2026, 9, 19, 17, 26 - index * 12)).toISOString(),
agent: commentaryAgents[message.agent],
runId: `30000000-0000-4000-8000-00000000000${index + 1}`,
task: { id: `40000000-0000-4000-8000-00000000000${index + 1}`, identifier: `PAP-${241 + index}`, title: message.task },
}));
export function prepareCommentaryFixtures(options: { state?: FixtureState; paginated?: boolean; longMessage?: boolean } = {}) {
const entries = options.paginated ? Array.from({ length: 58 }, (_, index) => ({ ...commentaryEntries[index % commentaryEntries.length], id: `20000000-0000-4000-8000-${String(index + 1).padStart(12, "0")}` })) : commentaryEntries.map((entry) => ({ ...entry }));
if (options.longMessage) entries[0].body += "\n\n" + Array.from({ length: 18 }, (_, index) => `### Observation ${index + 1}\n\nThe run retained its task and branch references, but the tool catalog did not recover. A useful error should explain which operation failed, whether it is safe to retry, and which context will survive the retry.`).join("\n\n");
return prepareFixtureApi([
{ path: "/api/companies/company-storybook/agents", read: () => commentaryAgents },
{ path: "/api/companies/company-storybook/agent-commentary", state: options.state, read: ({ url }) => {
const agent = url.searchParams.get("agentId");
const kind = url.searchParams.get("kind");
const query = url.searchParams.get("q")?.toLowerCase();
const offset = Number(url.searchParams.get("offset") ?? 0);
const limit = Number(url.searchParams.get("limit") ?? 50);
const matches = options.state === "empty" ? [] : entries.filter((entry) => (!agent || entry.agent.id === agent) && (!kind || entry.kind === kind) && (!query || entry.body.toLowerCase().includes(query)));
return { items: matches.slice(offset, offset + limit).map(({ body: _body, ...entry }) => entry), total: matches.length, offset, limit };
} },
{ path: /^\/api\/companies\/company-storybook\/agent-commentary\//, read: ({ url }) => entries.find((entry) => entry.id === url.pathname.split("/").at(-1)) ?? Response.json({ error: "Commentary not found" }, { status: 404 }) },
]);
}