# Hooks: Case Status Webhooks Implementation Plan > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** כאשר status של תיק משתנה ב-legal-ai (e.g. `qa_failed`, `exported`), ה-plugin מקבל webhook, מעדכן את ה-issue ב-Paperclip, ומעיר את ה-CEO במקרה הצורך. **Architecture:** Legal-ai REST API קורא `pc_request("POST", "/api/plugins/marcusgroup.legal-ai/webhooks/case-status", ...)` אחרי כל שינוי status. ה-plugin מטפל ב-`onWebhook()` ומגיב: מוסיף תגובה לissue, מעיר CEO אם QA נכשל. **Tech Stack:** TypeScript (plugin-legal-ai), Python/FastAPI (legal-ai web), `@paperclipai/plugin-sdk@2026.325.0`, `httpx` (Python), `pc_request` helper. --- ## File Map | Action | File | |--------|------| | Modify | `plugin-legal-ai/src/worker.ts` — add `onWebhook()` to `definePlugin({})` | | Modify | `plugin-legal-ai/plugin.json` — add `"webhooks.receive"` capability | | Modify | `plugin-legal-ai/src/manifest.ts` — add webhook capability | | Modify | `legal-ai/web/app.py` — emit webhook after `PUT /api/cases/{case_number}` | | Modify | `legal-ai/web/paperclip_api.py` — add `emit_webhook()` helper | --- ## Task 1: Add `emit_webhook` helper ב-Python **Files:** - Modify: `legal-ai/web/paperclip_api.py` - [ ] **Step 1: קרא את הקובץ הקיים** ```bash head -90 /home/chaim/legal-ai/web/paperclip_api.py ``` - [ ] **Step 2: הוסף את ה-helper בסוף הקובץ** פתח `/home/chaim/legal-ai/web/paperclip_api.py` והוסף אחרי הפונקציה `pc_request`: ```python async def emit_case_status_webhook( case_number: str, old_status: str, new_status: str, company_id: str | None = None, run_id: str | None = None, ) -> None: """Notify the Paperclip plugin that a case status changed. Fire-and-forget: logs errors but never raises, so callers aren't blocked. """ try: await pc_request( "POST", "/api/plugins/marcusgroup.legal-ai/webhooks/case-status", json={ "caseNumber": case_number, "oldStatus": old_status, "newStatus": new_status, "companyId": company_id, "timestamp": datetime.utcnow().isoformat() + "Z", }, run_id=run_id, timeout=5.0, ) except Exception as exc: logger.warning("emit_case_status_webhook failed: %s", exc) ``` > **הערה:** `datetime` ו-`logger` כבר מיובאים ב-`app.py`. בדוק שהם מיובאים גם ב-`paperclip_api.py` — אם לא, הוסף `from datetime import datetime` ו-`import logging; logger = logging.getLogger(__name__)` בראש הקובץ. - [ ] **Step 3: Commit** ```bash cd /home/chaim/legal-ai git add web/paperclip_api.py git commit -m "feat: add emit_case_status_webhook helper" ``` --- ## Task 2: צרף webhook לendpoint `PUT /api/cases/{case_number}` **Files:** - Modify: `legal-ai/web/app.py` - [ ] **Step 1: מצא את ה-endpoint** ```bash grep -n "PUT\|case_number\|update_case" /home/chaim/legal-ai/web/app.py | head -30 ``` - [ ] **Step 2: קרא את הenable endpoint המלא** זהה את הסקציה המלאה של ה-endpoint ואת הייבוא הקיים. - [ ] **Step 3: הוסף import ל-emit_webhook** בראש `app.py`, בסקציית ה-imports מ-`paperclip_api`: ```python from .paperclip_api import pc_request, emit_case_status_webhook ``` - [ ] **Step 4: הוסף webhook emit בתוך הendpoint** אחרי שהקוד מעדכן את ה-case (לפני ה-`return`), הוסף: ```python # Notify plugin about status change (fire-and-forget) if updates.get("status") and old_status != updates["status"]: background_tasks.add_task( emit_case_status_webhook, case_number=case_number, old_status=old_status, new_status=updates["status"], company_id=str(case.get("company_id")), ) ``` > אם ה-endpoint כבר מקבל `background_tasks: BackgroundTasks` — השתמש בו. אם לא, הוסף `background_tasks: BackgroundTasks` לחתימת הפונקציה. הוסף `from fastapi import BackgroundTasks` ל-imports. - [ ] **Step 5: שמור את ה-`old_status` לפני ה-update** בתחילת ה-endpoint handler, לפני קריאת ה-DB update: ```python old_status = (await db.get_case(case_number) or {}).get("status", "") ``` - [ ] **Step 6: בדיקה בסיסית — שלח PUT ידני** ```bash curl -s -X PUT https://legal-ai.nautilus.marcusgroup.org/api/cases/1130-25 \ -H "Content-Type: application/json" \ -d '{"status": "in_progress"}' | jq .status ``` בדוק ב-Paperclip logs שהwebhook נשלח (עדיין לא מטופל בצד ה-plugin): ```bash pm2 logs paperclip --lines 20 ``` - [ ] **Step 7: Commit** ```bash cd /home/chaim/legal-ai git add web/app.py git commit -m "feat: emit case-status webhook on PUT /api/cases/:case" ``` --- ## Task 3: הוסף `onWebhook()` ל-plugin **Files:** - Modify: `plugin-legal-ai/src/worker.ts` - [ ] **Step 1: קרא את `definePlugin({})` הקיים** ```bash grep -n "definePlugin\|onWebhook\|onHealth\|onShutdown" /home/chaim/plugin-legal-ai/src/worker.ts ``` - [ ] **Step 2: הוסף את `onWebhook` handler** בתוך הobject שמועבר ל-`definePlugin({})`, אחרי `setup(ctx)`: ```typescript onWebhook: async (input) => { const { endpointKey, payload, companyId } = input as { endpointKey: string; payload: { caseNumber: string; oldStatus: string; newStatus: string; companyId: string; timestamp: string; }; companyId: string; }; if (endpointKey !== "case-status") return; const { caseNumber, oldStatus, newStatus } = payload; ctx.logger.info(`Webhook: ${caseNumber} ${oldStatus} → ${newStatus}`); // Find the Paperclip issue linked to this case const stateKey = `case:${caseNumber}`; const issueId = await ctx.state.get({ companyId }, stateKey); if (!issueId) { ctx.logger.warn(`No issue found for case ${caseNumber}`); return; } const statusLabels: Record = { in_progress: "🔄 בעבודה", drafted: "✍️ טיוטה מוכנה", qa_failed: "❌ QA נכשל", exported: "📄 יוצא ל-DOCX", reviewed: "✅ נבדק", final: "🎯 סופי", }; const label = statusLabels[newStatus] ?? newStatus; // Post a status comment on the issue await ctx.issues.createComment({ issueId: issueId as string, body: `**עדכון סטטוס:** ${label} (היה: ${oldStatus})`, }); // Wake CEO if QA failed if (newStatus === "qa_failed") { const companies = await ctx.companies.list(); const company = companies.find((c) => c.id === companyId); if (!company) return; const CEO_IDS: Record = { "42a7acd0-30c5-4cbd-ac97-7424f65df294": "752cebdd-6748-4a04-aacd-c7ab0294ef33", "8639e837-4c9d-47fa-a76b-95788d651896": "cdbfa8bc-3d61-41a4-a2e7-677ec7d34562", }; const ceoId = CEO_IDS[companyId]; if (ceoId) { await ctx.agents.invoke(ceoId, companyId, { prompt: `תיק ${caseNumber} נכשל בבדיקת QA. עיין בתוצאות QA ותקן את הבעיות.`, reason: "qa_failed webhook", }); } } }, ``` > **הערה:** `ctx` חייב להיות נגיש ב-`onWebhook`. אם `ctx` מוגדר בתוך `setup()` בלבד — הוצא אותו ל-closure חיצוני של ה-plugin object (ראה את הדפוס הקיים ב-`worker.ts`). - [ ] **Step 3: בדק TypeScript** ```bash cd /home/chaim/plugin-legal-ai && npx tsc --noEmit ``` Expected: 0 errors. - [ ] **Step 4: Build** ```bash cd /home/chaim/plugin-legal-ai && npm run build ``` Expected: `dist/worker.js` נוצר ללא שגיאות. - [ ] **Step 5: Commit** ```bash cd /home/chaim/plugin-legal-ai git add src/worker.ts git commit -m "feat: add onWebhook handler for case-status events" ``` --- ## Task 4: הוסף capability ל-`plugin.json` ול-`manifest.ts` **Files:** - Modify: `plugin-legal-ai/plugin.json` - Modify: `plugin-legal-ai/src/manifest.ts` - [ ] **Step 1: הוסף `"webhooks.receive"` ל-capabilities** ב-`plugin-legal-ai/plugin.json`, בarray `"capabilities"`, הוסף: ```json "webhooks.receive" ``` - [ ] **Step 2: הוסף גם ב-`manifest.ts`** ```bash grep -n "capabilities\|webhooks" /home/chaim/plugin-legal-ai/src/manifest.ts ``` הוסף `"webhooks.receive"` לarray שם. - [ ] **Step 3: Re-install plugin** ```bash cd /home/chaim/plugin-legal-ai && npm run build npx paperclipai plugin uninstall marcusgroup.legal-ai \ --api-base http://localhost:3100 --api-key pcapi_legal_install_key_2026 npx paperclipai plugin install /home/chaim/plugin-legal-ai \ --api-base http://localhost:3100 --api-key pcapi_legal_install_key_2026 pm2 restart paperclip ``` - [ ] **Step 4: Commit** ```bash cd /home/chaim/plugin-legal-ai git add plugin.json src/manifest.ts git commit -m "feat: add webhooks.receive capability to plugin manifest" ``` --- ## Task 5: בדיקה end-to-end - [ ] **Step 1: Deploy legal-ai** ```bash cd /home/chaim/legal-ai git push origin main # המתן לבנייה (~2-4 דקות) ``` - [ ] **Step 2: שנה סטטוס תיק** ```bash curl -s -X PUT https://legal-ai.nautilus.marcusgroup.org/api/cases/1130-25 \ -H "Content-Type: application/json" \ -d '{"status": "qa_failed"}' | jq .status ``` - [ ] **Step 3: בדוק שהתגובה הוספה ל-Paperclip issue** ```bash # מצא את ה-issue הקשור לתיק 1130-25 pm2 logs paperclip --lines 30 | grep "1130-25\|webhook\|qa_failed" ``` Expected: תגובה "❌ QA נכשל" הוספה לissue. CEO הועיר. - [ ] **Step 4: בדוק שינוי סטטוס שגרתי (לא QA)** ```bash curl -s -X PUT https://legal-ai.nautilus.marcusgroup.org/api/cases/1130-25 \ -H "Content-Type: application/json" \ -d '{"status": "drafted"}' | jq .status ``` Expected: תגובה "✍️ טיוטה מוכנה" בissue. CEO **לא** הועיר. --- ## אימות סופי | בדיקה | פקודה | תוצאה מצופה | |-------|-------|-------------| | QA נכשל → CEO מועיר | `PUT status=qa_failed` | תגובה + agent invocation | | exported → תגובה בלבד | `PUT status=exported` | תגובה בלבד | | שינוי ללא status | `PUT title=...` | שום webhook | | תיק ללא issue | webhook לתיק חדש | לוג warning, ללא crash |