diff --git a/src/worker.ts b/src/worker.ts index a4fd7c2..23266af 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -1,8 +1,19 @@ import { definePlugin, runWorker } from "@paperclipai/plugin-sdk"; +import type { PluginContext, PluginWebhookInput } from "@paperclipai/plugin-sdk"; import { LegalApi } from "./legal-api.js"; +// Hoisted so onWebhook can access the context after setup() completes. +let pluginCtx: PluginContext | null = null; + +// Per-company CEO agent IDs (shared between setup and onWebhook). +const CEO_AGENT_IDS: Record = { + "42a7acd0-30c5-4cbd-ac97-7424f65df294": "752cebdd-6748-4a04-aacd-c7ab0294ef33", // CMP (רישוי ובניה) + "8639e837-4c9d-47fa-a76b-95788d651896": "cdbfa8bc-3d61-41a4-a2e7-677ec7d34562", // CMPA (היטלי השבחה) +}; + const plugin = definePlugin({ async setup(ctx) { + pluginCtx = ctx; // save for onWebhook const config = (await ctx.config.get()) as { legalApiBaseUrl?: string; } | null; @@ -517,8 +528,7 @@ const plugin = definePlugin({ } }); - // Route user comments through CEO agent - const CEO_AGENT_ID = "752cebdd-6748-4a04-aacd-c7ab0294ef33"; + // Route user comments through CEO agent — per company ctx.events.on("issue.comment.created", async (event) => { // Only intercept human comments — not agent comments (prevents loops) @@ -570,10 +580,18 @@ const plugin = definePlugin({ } } - // Wake the CEO agent with the comment context + // Wake the CEO agent for this company + const ceoAgentId = CEO_AGENT_IDS[event.companyId]; + if (!ceoAgentId) { + ctx.logger.warn("No CEO agent mapped for company", { + companyId: event.companyId, + }); + return; + } + try { const { runId } = await ctx.agents.invoke( - CEO_AGENT_ID, + ceoAgentId, event.companyId, { prompt: [ @@ -694,6 +712,107 @@ const plugin = definePlugin({ async onHealth() { return { status: "ok" as const }; }, + + async onWebhook(input: PluginWebhookInput): Promise { + if (!pluginCtx) return; // not yet initialized + + const { endpointKey, parsedBody } = input; + + if (endpointKey !== "case-status") return; + + const payload = parsedBody as { + caseNumber: string; + oldStatus: string; + newStatus: string; + companyId: string | null; + timestamp: string; + }; + + const { caseNumber, oldStatus, newStatus, companyId } = payload; + pluginCtx.logger.info(`Webhook: case ${caseNumber} ${oldStatus} → ${newStatus}`, { + companyId, + }); + + if (!companyId) { + pluginCtx.logger.warn("onWebhook: missing companyId in payload", { caseNumber }); + return; + } + + // Find the Paperclip issue linked to this case number by scanning plugin state. + // State stores: issue.id → case_number (scopeKind=issue, stateKey=legal-case-number) + const issues = await pluginCtx.issues.list({ companyId }); + let linkedIssueId: string | null = null; + for (const issue of issues) { + const linkedCase = await pluginCtx.state.get({ + scopeKind: "issue", + scopeId: issue.id, + stateKey: "legal-case-number", + }); + if (linkedCase === caseNumber) { + linkedIssueId = issue.id; + break; + } + } + + if (!linkedIssueId) { + pluginCtx.logger.warn(`onWebhook: no Paperclip issue linked to case ${caseNumber}`); + return; + } + + // Status label map (Hebrew) + const statusLabels: Record = { + new: "📂 תיק חדש", + uploading: "📤 העלאת מסמכים", + processing: "⚙️ עיבוד מסמכים", + documents_ready: "📁 מסמכים מוכנים", + outcome_set: "🎯 תוצאה הוזנה", + brainstorming: "💡 סיעור מוחות", + direction_approved: "✅ כיוון אושר", + drafting: "✍️ כתיבה בתהליך", + qa_review: "🔍 בדיקת איכות", + in_progress: "🔄 בעבודה", + drafted: "✍️ טיוטה מוכנה", + qa_failed: "❌ QA נכשל", + exported: "📄 יוצא ל-DOCX", + reviewed: "✅ נבדק", + final: "🎯 סופי", + }; + const label = statusLabels[newStatus] ?? newStatus; + + // Post a Hebrew status comment on the linked issue + await pluginCtx.issues.createComment( + linkedIssueId, + `**עדכון סטטוס תיק ${caseNumber}:** ${label} (היה: ${oldStatus})`, + companyId, + ); + + // Wake the CEO agent if QA failed + if (newStatus === "qa_failed") { + const ceoId = CEO_AGENT_IDS[companyId]; + if (ceoId) { + try { + const { runId } = await pluginCtx.agents.invoke(ceoId, companyId, { + prompt: `תיק ${caseNumber} נכשל בבדיקת QA. עיין בתוצאות QA ותקן את הבעיות לפני שתמשיך.`, + reason: "qa_failed webhook", + }); + pluginCtx.logger.info(`Invoked CEO agent for qa_failed`, { + caseNumber, + ceoId, + runId, + }); + } catch (err) { + pluginCtx.logger.error("Failed to invoke CEO agent for qa_failed", { + caseNumber, + error: String(err), + }); + } + } else { + pluginCtx.logger.warn("onWebhook: no CEO agent mapped for company", { + companyId, + }); + } + } + }, }); export default plugin;