diff --git a/.changeset/collapsible-agent-reports.md b/.changeset/collapsible-agent-reports.md new file mode 100644 index 000000000..d8e31abd1 --- /dev/null +++ b/.changeset/collapsible-agent-reports.md @@ -0,0 +1,5 @@ +--- +"@aoagents/ao-web": patch +--- + +Make session detail agent reports collapsible and add explicit audit attribution for the session, actor, and report source command. diff --git a/packages/web/src/components/SessionDetail.tsx b/packages/web/src/components/SessionDetail.tsx index d675b8ae3..ff4b10974 100644 --- a/packages/web/src/components/SessionDetail.tsx +++ b/packages/web/src/components/SessionDetail.tsx @@ -231,68 +231,126 @@ function formatAuditTimestamp(isoDate: string): string { return new Date(parsed).toLocaleString(); } +function getAuditCommandLabel(entry: DashboardAgentReportAuditEntry): string { + return entry.source === "acknowledge" ? "ao acknowledge" : `ao report ${entry.reportState}`; +} + function SessionReportAuditPanel({ + sessionId, entries, }: { + sessionId: string; entries: DashboardAgentReportAuditEntry[]; }) { + const [isExpanded, setIsExpanded] = useState(true); + if (entries.length === 0) { return null; } return (
-

- Agent Report Audit -

-
- {entries.map((entry, index) => ( -
setIsExpanded((current) => !current)} + > +
+

+ Agent Reports +

+

+ {entries.length} audit {entries.length === 1 ? "entry" : "entries"} +

+
+
+ ) : null}
); } @@ -814,7 +872,10 @@ export function SessionDetail({ {!isOrchestrator ? : null} {!isOrchestrator ? ( - + ) : null}
diff --git a/packages/web/src/components/__tests__/SessionDetail.desktop.test.tsx b/packages/web/src/components/__tests__/SessionDetail.desktop.test.tsx index fe40e887d..f928540f5 100644 --- a/packages/web/src/components/__tests__/SessionDetail.desktop.test.tsx +++ b/packages/web/src/components/__tests__/SessionDetail.desktop.test.tsx @@ -147,12 +147,59 @@ describe("SessionDetail desktop layout", () => { expect(screen.getByText(/Unresolved Comments/i)).toBeInTheDocument(); expect(screen.getByText("Tighten the copy")).toBeInTheDocument(); expect(screen.getByText("The empty state text needs to be shorter.")).toBeInTheDocument(); - expect(screen.getByText("Agent Report Audit")).toBeInTheDocument(); - expect(screen.getByText(/ao report working/i)).toBeInTheDocument(); - expect(screen.getByText(/by codex/i)).toBeInTheDocument(); + expect(screen.getByRole("button", { name: /Agent Reports/i })).toHaveAttribute( + "aria-expanded", + "true", + ); + expect(screen.getAllByText("worker-desktop").length).toBeGreaterThanOrEqual(1); + expect(screen.getByText("ao report working")).toBeInTheDocument(); + expect(screen.getByText("codex")).toBeInTheDocument(); expect(screen.getByText("Live Terminal")).toBeInTheDocument(); }); + it("toggles the agent reports section", () => { + render( + , + ); + + const toggle = screen.getByRole("button", { name: /Agent Reports/i }); + expect(screen.getByText("ao acknowledge")).toBeInTheDocument(); + + fireEvent.click(toggle); + expect(toggle).toHaveAttribute("aria-expanded", "false"); + expect(screen.queryByText("ao acknowledge")).not.toBeInTheDocument(); + + fireEvent.click(toggle); + expect(toggle).toHaveAttribute("aria-expanded", "true"); + expect(screen.getByText("ao acknowledge")).toBeInTheDocument(); + }); + it("sends unresolved comments back to the agent and shows sent state", async () => { vi.useFakeTimers(); @@ -236,6 +283,6 @@ describe("SessionDetail desktop layout", () => { expect(screen.queryByRole("link", { name: "Orchestrator" })).not.toBeInTheDocument(); expect(screen.getByText("orchestrator")).toBeInTheDocument(); expect(screen.queryByText("Lifecycle Truth")).not.toBeInTheDocument(); - expect(screen.queryByText("Agent Report Audit")).not.toBeInTheDocument(); + expect(screen.queryByRole("button", { name: /Agent Reports/i })).not.toBeInTheDocument(); }); });