feat(agents): mirror Paperclip interactions in case page
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 47s
All checks were successful
Build & Deploy / build-and-deploy (push) Successful in 47s
Surface issue_thread_interactions (ask_user_questions / request_confirmation /
suggest_tasks) directly inside legal-ai's case detail feed so the user can
answer agent prompts without switching to Paperclip's UI.
Backend (FastAPI):
- paperclip_client.py: 4 new helpers — get_issue_interactions (DB),
respond_to_interaction / accept_interaction / reject_interaction (REST).
- app.py: extends GET /api/cases/{case_number}/agents to include
`interactions`, and adds POST /api/cases/{case_number}/agents/interaction-response
routing to /respond, /accept, /reject in Paperclip.
- paperclip_client.py: also pulls existing httpx calls onto the centralized
pc_request helper (paperclip_api.py) for consistent auth + run-id headers.
Frontend (web-ui, Next.js 16 + TanStack Query):
- agents.ts: Interaction / InteractionPayload / InteractionStatus types,
useSubmitInteraction mutation hook (invalidates the activity query).
- agent-activity-feed.tsx: InteractionCard renders radio (single) /
checkbox (multi) for ask_user_questions, accept/reject + reason for
request_confirmation, task selection for suggest_tasks. Resolved
interactions show a read-only summary. Cards are interleaved with
comments by created_at, so the feed reads chronologically.
Paperclip auto-wakes the issue assignee on a successful response
(queueResolvedInteractionContinuationWakeup) — no explicit wakeup needed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -42,10 +42,80 @@ export type PaperclipAgent = {
|
||||
last_heartbeat_at: string | null;
|
||||
};
|
||||
|
||||
export type InteractionKind =
|
||||
| "ask_user_questions"
|
||||
| "request_confirmation"
|
||||
| "suggest_tasks";
|
||||
|
||||
export type InteractionStatus =
|
||||
| "pending"
|
||||
| "answered"
|
||||
| "accepted"
|
||||
| "rejected"
|
||||
| "expired"
|
||||
| "failed";
|
||||
|
||||
export type InteractionOption = {
|
||||
id: string;
|
||||
label: string;
|
||||
description?: string | null;
|
||||
};
|
||||
|
||||
export type InteractionQuestion = {
|
||||
id: string;
|
||||
prompt: string;
|
||||
selectionMode?: "single" | "multi";
|
||||
required?: boolean;
|
||||
options: InteractionOption[];
|
||||
};
|
||||
|
||||
export type InteractionTask = {
|
||||
clientKey: string;
|
||||
parentClientKey?: string | null;
|
||||
title: string;
|
||||
description?: string | null;
|
||||
};
|
||||
|
||||
/** Free-form payload — shape depends on `kind`. Common fields surfaced for the
|
||||
* UI; everything else is preserved on the wire. */
|
||||
export type InteractionPayload = {
|
||||
version?: number;
|
||||
submitLabel?: string;
|
||||
acceptLabel?: string;
|
||||
rejectLabel?: string;
|
||||
questions?: InteractionQuestion[];
|
||||
tasks?: InteractionTask[];
|
||||
body?: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
export type Interaction = {
|
||||
id: string;
|
||||
issue_id: string;
|
||||
kind: InteractionKind;
|
||||
status: InteractionStatus;
|
||||
title: string | null;
|
||||
summary: string | null;
|
||||
payload: InteractionPayload;
|
||||
result: Record<string, unknown> | null;
|
||||
created_at: string | null;
|
||||
resolved_at: string | null;
|
||||
};
|
||||
|
||||
export type AgentActivityResponse = {
|
||||
issues: PaperclipIssue[];
|
||||
comments: PaperclipComment[];
|
||||
agents: PaperclipAgent[];
|
||||
interactions: Interaction[];
|
||||
};
|
||||
|
||||
export type InteractionAction = "respond" | "accept" | "reject";
|
||||
|
||||
export type InteractionSubmitVars = {
|
||||
issue_id: string;
|
||||
interaction_id: string;
|
||||
action: InteractionAction;
|
||||
payload: Record<string, unknown>;
|
||||
};
|
||||
|
||||
// ── Query Keys ───────────────────────────────────────────────────
|
||||
@@ -85,3 +155,19 @@ export function useSendComment(caseNumber: string | undefined) {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export function useSubmitInteraction(caseNumber: string | undefined) {
|
||||
const qc = useQueryClient();
|
||||
return useMutation({
|
||||
mutationFn: (vars: InteractionSubmitVars) =>
|
||||
apiRequest<Interaction>(
|
||||
`/api/cases/${caseNumber}/agents/interaction-response`,
|
||||
{ method: "POST", body: vars },
|
||||
),
|
||||
onSuccess: () => {
|
||||
if (caseNumber) {
|
||||
qc.invalidateQueries({ queryKey: agentKeys.activity(caseNumber) });
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user