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:
59
web/app.py
59
web/app.py
@@ -22,7 +22,7 @@ import zipfile
|
||||
|
||||
from fastapi import FastAPI, File, Form, HTTPException, UploadFile
|
||||
from fastapi.responses import FileResponse, StreamingResponse
|
||||
from typing import Any
|
||||
from typing import Any, Literal
|
||||
from pydantic import BaseModel
|
||||
|
||||
import asyncpg
|
||||
@@ -45,6 +45,7 @@ from web.mcp_env_catalog import (
|
||||
)
|
||||
from web.progress_store import ProgressStore
|
||||
from web.paperclip_client import (
|
||||
accept_interaction as pc_accept_interaction,
|
||||
archive_project as pc_archive_project,
|
||||
create_project as pc_create_project,
|
||||
create_workflow_issue as pc_create_workflow_issue,
|
||||
@@ -52,8 +53,11 @@ from web.paperclip_client import (
|
||||
get_agents_for_company as pc_get_agents,
|
||||
get_case_issues as pc_get_case_issues,
|
||||
get_issue_comments as pc_get_issue_comments,
|
||||
get_issue_interactions as pc_get_issue_interactions,
|
||||
get_project_url,
|
||||
post_comment as pc_post_comment,
|
||||
reject_interaction as pc_reject_interaction,
|
||||
respond_to_interaction as pc_respond_to_interaction,
|
||||
restore_project as pc_restore_project,
|
||||
wake_ceo_agent as pc_wake_ceo,
|
||||
wake_for_precedent_extraction as pc_wake_for_precedent_extraction,
|
||||
@@ -2507,17 +2511,26 @@ async def api_start_workflow(case_number: str):
|
||||
|
||||
@app.get("/api/cases/{case_number}/agents")
|
||||
async def api_case_agents(case_number: str):
|
||||
"""Get all Paperclip agent activity for a case: issues, comments, agent status."""
|
||||
"""Get all Paperclip agent activity for a case: issues, comments, interactions, agent status."""
|
||||
issues = await pc_get_case_issues(case_number)
|
||||
if not issues:
|
||||
return {"issues": [], "comments": [], "agents": []}
|
||||
return {"issues": [], "comments": [], "agents": [], "interactions": []}
|
||||
|
||||
issue_ids = [i["id"] for i in issues]
|
||||
company_id = issues[0]["company_id"]
|
||||
|
||||
comments, agents = await pc_get_issue_comments(issue_ids), await pc_get_agents_for_case(company_id, issue_ids)
|
||||
comments, agents, interactions = await asyncio.gather(
|
||||
pc_get_issue_comments(issue_ids),
|
||||
pc_get_agents_for_case(company_id, issue_ids),
|
||||
pc_get_issue_interactions(issue_ids),
|
||||
)
|
||||
|
||||
return {"issues": issues, "comments": comments, "agents": agents}
|
||||
return {
|
||||
"issues": issues,
|
||||
"comments": comments,
|
||||
"agents": agents,
|
||||
"interactions": interactions,
|
||||
}
|
||||
|
||||
|
||||
class AgentCommentRequest(BaseModel):
|
||||
@@ -2551,6 +2564,42 @@ async def api_post_agent_comment(case_number: str, req: AgentCommentRequest):
|
||||
return result
|
||||
|
||||
|
||||
class InteractionResponseRequest(BaseModel):
|
||||
issue_id: str
|
||||
interaction_id: str
|
||||
action: Literal["respond", "accept", "reject"]
|
||||
payload: dict[str, Any]
|
||||
|
||||
|
||||
@app.post("/api/cases/{case_number}/agents/interaction-response")
|
||||
async def api_post_interaction_response(
|
||||
case_number: str, req: InteractionResponseRequest,
|
||||
):
|
||||
"""Submit a user's answer to a Paperclip issue-thread interaction.
|
||||
|
||||
Routes to /respond | /accept | /reject based on `action`. Paperclip
|
||||
auto-wakes the issue assignee after a successful submission.
|
||||
"""
|
||||
issues = await pc_get_case_issues(case_number)
|
||||
if not any(i["id"] == req.issue_id for i in issues):
|
||||
raise HTTPException(404, f"Issue {req.issue_id} לא שייך לתיק {case_number}")
|
||||
|
||||
handlers = {
|
||||
"respond": pc_respond_to_interaction,
|
||||
"accept": pc_accept_interaction,
|
||||
"reject": pc_reject_interaction,
|
||||
}
|
||||
try:
|
||||
return await handlers[req.action](
|
||||
req.issue_id, req.interaction_id, req.payload,
|
||||
)
|
||||
except httpx.HTTPStatusError as e:
|
||||
body = e.response.text or ""
|
||||
raise HTTPException(e.response.status_code, body[:500])
|
||||
except Exception as e:
|
||||
raise HTTPException(502, f"שגיאת Paperclip: {e}")
|
||||
|
||||
|
||||
# ── Settings: MCP Server Configuration ────────────────────────────
|
||||
#
|
||||
# Source of truth for legal-ai env vars is Coolify (see memory:
|
||||
|
||||
Reference in New Issue
Block a user