Add Paperclip agent activity mirror to case detail page
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 3m16s

New "Agents" tab in case detail shows all Paperclip agent comments,
issue status, and agent status for each case — eliminating the need
to switch between Legal-AI and Paperclip UIs.

Backend: 4 new DB query functions in paperclip_client.py (issues,
comments, agents, post_comment) + 2 new API endpoints (GET/POST
/api/cases/{case_number}/agents). Comment posting uses Board API
with DB+wakeup fallback to ensure CEO routing.

Frontend: agents.ts hooks (10s polling), AgentActivityFeed component
(markdown timeline + comment input), AgentStatusWidget (sidebar),
4th tab in case detail page.

Also includes new-company-setup-guide.md documenting the process
for setting up the betterment levy (CMPA) company.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-15 10:44:42 +00:00
parent 2e2d2d42b6
commit 1e4c5c1518
7 changed files with 1051 additions and 1 deletions

View File

@@ -0,0 +1,87 @@
/**
* Paperclip agent activity hooks — mirror agent work into Legal-AI UI.
*/
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { apiRequest } from "./client";
// ── Types ────────────────────────────────────────────────────────
export type PaperclipIssue = {
id: string;
title: string;
status: string;
identifier: string;
priority: string;
assignee_name: string | null;
started_at: string | null;
completed_at: string | null;
created_at: string | null;
company_id: string;
};
export type PaperclipComment = {
id: string;
issue_id: string;
body: string;
created_at: string | null;
author_agent_id: string | null;
author_user_id: string | null;
agent_name: string | null;
agent_role: string | null;
agent_icon: string | null;
};
export type PaperclipAgent = {
id: string;
name: string;
role: string;
title: string | null;
status: string;
icon: string | null;
last_heartbeat_at: string | null;
};
export type AgentActivityResponse = {
issues: PaperclipIssue[];
comments: PaperclipComment[];
agents: PaperclipAgent[];
};
// ── Query Keys ───────────────────────────────────────────────────
export const agentKeys = {
activity: (caseNumber: string) =>
["agents", "activity", caseNumber] as const,
};
// ── Hooks ────────────────────────────────────────────────────────
export function useAgentActivity(caseNumber: string | undefined) {
return useQuery({
queryKey: agentKeys.activity(caseNumber ?? ""),
queryFn: ({ signal }) =>
apiRequest<AgentActivityResponse>(
`/api/cases/${caseNumber}/agents`,
{ signal },
),
enabled: !!caseNumber,
refetchInterval: 10_000,
});
}
export function useSendComment(caseNumber: string | undefined) {
const qc = useQueryClient();
return useMutation({
mutationFn: (vars: { body: string; issue_id?: string }) =>
apiRequest<{ comment_id: string; issue_id: string; issue_identifier: string }>(
`/api/cases/${caseNumber}/agents/comment`,
{ method: "POST", body: vars },
),
onSuccess: () => {
if (caseNumber) {
qc.invalidateQueries({ queryKey: agentKeys.activity(caseNumber) });
}
},
});
}