From 06679bb0616b2b11431f34ca2d07372928f77a07 Mon Sep 17 00:00:00 2001 From: Chaim Marcus Date: Sat, 16 May 2026 17:40:33 +0000 Subject: [PATCH] feat: add stale-case-reminder and weekly-feedback-analysis jobs Declare two new cron jobs in plugin.json and manifest.ts, and implement their handlers in worker.ts. stale-case-reminder runs daily at 08:00 and posts a warning comment on any Paperclip issue linked to a legal-ai case that has not been updated in 3+ days. weekly-feedback-analysis runs every Sunday at 19:00, fetches the weekly chair-feedback summary from legal-ai, and invokes the CEO agent to update decision-lessons.md with new lessons. --- plugin.json | 12 +++++++ src/manifest.ts | 12 +++++++ src/worker.ts | 85 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/plugin.json b/plugin.json index 893c6ea..de75039 100644 --- a/plugin.json +++ b/plugin.json @@ -63,6 +63,18 @@ "displayName": "סנכרון סטטוס תיקים", "description": "סנכרון סטטוס בין legal-ai ל-Paperclip כל 15 דקות", "schedule": "*/15 * * * *" + }, + { + "jobKey": "stale-case-reminder", + "displayName": "תזכורת תיקים תקועים", + "description": "מזהה תיקים שלא עודכנו 3+ ימים ומוסיף תגובה ל-issue", + "schedule": "0 8 * * *" + }, + { + "jobKey": "weekly-feedback-analysis", + "displayName": "ניתוח פידבק שבועי", + "description": "מסכם פידבק יו\"ר מהשבוע האחרון ומעדכן את decision-lessons.md", + "schedule": "0 19 * * 0" } ], "webhooks": [ diff --git a/src/manifest.ts b/src/manifest.ts index 53daeb7..e490d4e 100644 --- a/src/manifest.ts +++ b/src/manifest.ts @@ -158,6 +158,18 @@ export default { "Polls legal-ai for case status changes and updates Paperclip issues", schedule: "*/15 * * * *", }, + { + jobKey: "stale-case-reminder", + displayName: "תזכורת תיקים תקועים", + description: "מזהה תיקים שלא עודכנו 3+ ימים ומוסיף תגובה ל-issue", + schedule: "0 8 * * *", + }, + { + jobKey: "weekly-feedback-analysis", + displayName: "ניתוח פידבק שבועי", + description: 'מסכם פידבק יו"ר מהשבוע האחרון ומעדכן את decision-lessons.md', + schedule: "0 19 * * 0", + }, ], webhooks: [ { diff --git a/src/worker.ts b/src/worker.ts index fc2f3ad..e97266a 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -706,6 +706,91 @@ const plugin = definePlugin({ } }); + ctx.jobs.register("stale-case-reminder", async (_job) => { + ctx.logger.info("stale-case-reminder: starting"); + const config = await ctx.config.get(); + const apiBase = (config.legalApiBaseUrl as string) ?? "http://localhost:8085"; + + const resp = await ctx.http.fetch(`${apiBase}/api/cases/stale?days=3`); + if (!resp.ok) { + ctx.logger.error(`stale-case-reminder: API error ${resp.status}`); + return; + } + + const data = (await resp.json()) as { + cases: Array<{ case_number: string; title: string; status: string; days_stale: number }>; + total: number; + }; + + let reminded = 0; + for (const staleCase of data.cases) { + const companies = await ctx.companies.list(); + for (const company of companies) { + const issues = await ctx.issues.list({ companyId: company.id }); + let linkedIssueId: string | null = null; + for (const issue of issues) { + const linkedCase = await ctx.state.get({ + scopeKind: "issue", + scopeId: issue.id, + stateKey: "legal-case-number", + }); + if (linkedCase === staleCase.case_number) { + linkedIssueId = issue.id; + break; + } + } + if (!linkedIssueId) continue; + + await ctx.issues.createComment( + linkedIssueId, + `⚠️ **תיק תקוע ${staleCase.case_number}** — ${staleCase.days_stale} ימים ללא עדכון (סטטוס: ${staleCase.status}). האם נדרשת פעולה?`, + company.id, + ); + reminded++; + ctx.logger.info(`stale-case-reminder: reminded case ${staleCase.case_number} (${staleCase.days_stale}d)`); + break; // Found the company, move to next case + } + } + + ctx.logger.info(`stale-case-reminder: done. ${reminded}/${data.total} cases reminded`); + }); + + ctx.jobs.register("weekly-feedback-analysis", async (_job) => { + ctx.logger.info("weekly-feedback-analysis: starting"); + const config = await ctx.config.get(); + const apiBase = (config.legalApiBaseUrl as string) ?? "http://localhost:8085"; + + const resp = await ctx.http.fetch(`${apiBase}/api/chair-feedback/weekly-summary`); + if (!resp.ok) { + ctx.logger.error(`weekly-feedback-analysis: API error ${resp.status}`); + return; + } + + const data = (await resp.json()) as { summary: string; entry_count: number }; + + if (data.entry_count === 0) { + ctx.logger.info("weekly-feedback-analysis: no feedback this week, skipping"); + return; + } + + const companies = await ctx.companies.list(); + const company = companies[0]; + if (!company) return; + + const ceoId = CEO_AGENT_IDS[company.id]; + if (!ceoId) { + ctx.logger.warn(`weekly-feedback-analysis: no CEO agent for company ${company.id}`); + return; + } + + await ctx.agents.invoke(ceoId, company.id, { + prompt: `ניתוח פידבק שבועי יו"ר (${data.entry_count} פריטים):\n\n${data.summary}\n\nהמשימה: עדכן את /home/chaim/legal-ai/docs/legal-decision-lessons.md עם הלקחים החדשים שעולים מהפידבק. הוסף רק לקחים חדשים שלא קיימים כבר. קבץ לפי נושא.`, + reason: "weekly-feedback-analysis scheduled job", + }); + + ctx.logger.info(`weekly-feedback-analysis: invoked CEO ${ceoId} with ${data.entry_count} feedback entries`); + }); + ctx.logger.info("Legal AI plugin ready"); },