Initial commit: Paperclip plugin for Legal AI integration
16 agent tools, event handler for auto-linking, sync job every 15m. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
311
dist/worker.js
vendored
Normal file
311
dist/worker.js
vendored
Normal file
@@ -0,0 +1,311 @@
|
||||
import { definePlugin, runWorker } from "@paperclipai/plugin-sdk";
|
||||
import { LegalApi } from "./legal-api.js";
|
||||
const plugin = definePlugin({
|
||||
async setup(ctx) {
|
||||
const config = (await ctx.config.get());
|
||||
const baseUrl = config?.legalApiBaseUrl || "http://localhost:8085";
|
||||
const api = new LegalApi(baseUrl);
|
||||
ctx.logger.info("Legal AI plugin starting", { url: baseUrl });
|
||||
// ── Tools ──────────────────────────────────────────────────────
|
||||
ctx.tools.register("legal_case_list", {
|
||||
displayName: "רשימת תיקי ערר",
|
||||
description: "List all appeal cases in the legal system. Returns case number, title, and status (new/in_progress/drafted/reviewed/final).",
|
||||
parametersSchema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
}, async () => {
|
||||
const cases = await api.listCases();
|
||||
return {
|
||||
content: JSON.stringify(cases, null, 2),
|
||||
data: cases,
|
||||
};
|
||||
});
|
||||
ctx.tools.register("legal_case_get", {
|
||||
displayName: "פרטי תיק ערר",
|
||||
description: "Get full details of a legal case including documents list. Provide the case number (e.g. 123/24).",
|
||||
parametersSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
case_number: {
|
||||
type: "string",
|
||||
description: "Case number (e.g. 123/24)",
|
||||
},
|
||||
},
|
||||
required: ["case_number"],
|
||||
},
|
||||
}, async (params) => {
|
||||
const { case_number } = params;
|
||||
const result = await api.getCase(case_number);
|
||||
return {
|
||||
content: JSON.stringify(result, null, 2),
|
||||
data: result,
|
||||
};
|
||||
});
|
||||
ctx.tools.register("legal_case_create", {
|
||||
displayName: "יצירת תיק ערר",
|
||||
description: "Create a new appeal case. Case numbers: 1xxx=licensing, 8xxx=betterment levy, 9xxx=compensation. Also creates a linked Paperclip issue.",
|
||||
parametersSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
case_number: {
|
||||
type: "string",
|
||||
description: "Case number (e.g. 1234/24)",
|
||||
},
|
||||
title: { type: "string", description: "Case title" },
|
||||
appellants: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Appellant names",
|
||||
},
|
||||
respondents: {
|
||||
type: "array",
|
||||
items: { type: "string" },
|
||||
description: "Respondent names",
|
||||
},
|
||||
subject: { type: "string", description: "Case subject" },
|
||||
property_address: {
|
||||
type: "string",
|
||||
description: "Property address",
|
||||
},
|
||||
expected_outcome: {
|
||||
type: "string",
|
||||
enum: [
|
||||
"rejection",
|
||||
"partial_acceptance",
|
||||
"full_acceptance",
|
||||
"betterment_levy",
|
||||
],
|
||||
description: "Expected outcome type",
|
||||
},
|
||||
},
|
||||
required: ["case_number", "title"],
|
||||
},
|
||||
}, async (params, runCtx) => {
|
||||
const input = params;
|
||||
// Create case in legal-ai
|
||||
const legalCase = await api.createCase(input);
|
||||
// Create linked Paperclip issue
|
||||
const issue = await ctx.issues.create({
|
||||
companyId: runCtx.companyId,
|
||||
title: `[ערר ${input.case_number}] ${input.title}`,
|
||||
description: `תיק ערר חדש\nנושא: ${input.subject || ""}\nתוצאה צפויה: ${input.expected_outcome || "לא הוגדרה"}`,
|
||||
});
|
||||
// Store mapping in plugin state
|
||||
await ctx.state.set({
|
||||
scopeKind: "issue",
|
||||
scopeId: issue.id,
|
||||
stateKey: "legal-case-number",
|
||||
}, input.case_number);
|
||||
await ctx.activity.log({
|
||||
companyId: runCtx.companyId,
|
||||
message: `נוצר תיק ערר ${input.case_number} וקושר ל-issue ${issue.id}`,
|
||||
});
|
||||
return {
|
||||
content: `Case ${input.case_number} created and linked to Paperclip issue.\n\n${JSON.stringify(legalCase, null, 2)}`,
|
||||
data: { legalCase, issueId: issue.id },
|
||||
};
|
||||
});
|
||||
ctx.tools.register("legal_case_update", {
|
||||
displayName: "עדכון תיק ערר",
|
||||
description: "Update a legal case's status, title, subject, or expected outcome.",
|
||||
parametersSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
case_number: {
|
||||
type: "string",
|
||||
description: "Case number (e.g. 123/24)",
|
||||
},
|
||||
status: {
|
||||
type: "string",
|
||||
enum: [
|
||||
"new",
|
||||
"in_progress",
|
||||
"drafted",
|
||||
"reviewed",
|
||||
"final",
|
||||
],
|
||||
description: "New case status",
|
||||
},
|
||||
title: { type: "string" },
|
||||
subject: { type: "string" },
|
||||
notes: { type: "string" },
|
||||
expected_outcome: {
|
||||
type: "string",
|
||||
enum: [
|
||||
"rejection",
|
||||
"partial_acceptance",
|
||||
"full_acceptance",
|
||||
"betterment_levy",
|
||||
],
|
||||
},
|
||||
},
|
||||
required: ["case_number"],
|
||||
},
|
||||
}, async (params) => {
|
||||
const { case_number, ...updates } = params;
|
||||
const result = await api.updateCase(case_number, updates);
|
||||
return {
|
||||
content: JSON.stringify(result, null, 2),
|
||||
data: result,
|
||||
};
|
||||
});
|
||||
ctx.tools.register("legal_case_status", {
|
||||
displayName: "סטטוס תהליך עבודה",
|
||||
description: "Get full workflow status for a case: documents processed, chunks created, draft progress, and suggested next steps.",
|
||||
parametersSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
case_number: {
|
||||
type: "string",
|
||||
description: "Case number (e.g. 123/24)",
|
||||
},
|
||||
},
|
||||
required: ["case_number"],
|
||||
},
|
||||
}, async (params) => {
|
||||
const { case_number } = params;
|
||||
const result = await api.getCaseStatus(case_number);
|
||||
return {
|
||||
content: JSON.stringify(result, null, 2),
|
||||
data: result,
|
||||
};
|
||||
});
|
||||
ctx.tools.register("legal_search", {
|
||||
displayName: "חיפוש תקדימים משפטיים",
|
||||
description: "Semantic search (RAG) across previous decisions and documents. Query in Hebrew for best results.",
|
||||
parametersSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query in Hebrew",
|
||||
},
|
||||
limit: { type: "number", description: "Max results (default 10)" },
|
||||
section_type: {
|
||||
type: "string",
|
||||
description: "Filter by section type: facts, legal_analysis, conclusion, ruling",
|
||||
},
|
||||
},
|
||||
required: ["query"],
|
||||
},
|
||||
}, async (params) => {
|
||||
const { query, limit, section_type } = params;
|
||||
const results = await api.search(query, limit || 10, section_type || "");
|
||||
return {
|
||||
content: JSON.stringify(results, null, 2),
|
||||
data: results,
|
||||
};
|
||||
});
|
||||
ctx.tools.register("legal_case_template", {
|
||||
displayName: "תבנית החלטה",
|
||||
description: "Get an outcome-aware decision template for a case, with guidance for the 12-block structure.",
|
||||
parametersSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
case_number: {
|
||||
type: "string",
|
||||
description: "Case number (e.g. 123/24)",
|
||||
},
|
||||
},
|
||||
required: ["case_number"],
|
||||
},
|
||||
}, async (params) => {
|
||||
const { case_number } = params;
|
||||
const result = await api.getTemplate(case_number);
|
||||
return {
|
||||
content: result.template,
|
||||
data: result,
|
||||
};
|
||||
});
|
||||
ctx.tools.register("legal_processing_status", {
|
||||
displayName: "סטטוס עיבוד כללי",
|
||||
description: "Get overall processing status: total cases, documents, pending processing, chunks, and style corpus entries.",
|
||||
parametersSchema: {
|
||||
type: "object",
|
||||
properties: {},
|
||||
},
|
||||
}, async () => {
|
||||
const result = await api.getProcessingStatus();
|
||||
return {
|
||||
content: JSON.stringify(result, null, 2),
|
||||
data: result,
|
||||
};
|
||||
});
|
||||
// ── Events ─────────────────────────────────────────────────────
|
||||
ctx.events.on("issue.created", async (event) => {
|
||||
// Auto-link issues with case number in title
|
||||
if (!event.companyId || !event.entityId)
|
||||
return;
|
||||
const issue = await ctx.issues.get(event.entityId, event.companyId);
|
||||
if (!issue)
|
||||
return;
|
||||
const match = issue.title.match(/ערר\s+(\d+\/\d+)/);
|
||||
if (match) {
|
||||
const caseNumber = match[1];
|
||||
await ctx.state.set({
|
||||
scopeKind: "issue",
|
||||
scopeId: issue.id,
|
||||
stateKey: "legal-case-number",
|
||||
}, caseNumber);
|
||||
ctx.logger.info("Auto-linked issue to legal case", {
|
||||
issueId: issue.id,
|
||||
caseNumber,
|
||||
});
|
||||
}
|
||||
});
|
||||
// ── Jobs ───────────────────────────────────────────────────────
|
||||
ctx.jobs.register("sync-case-status", async (job) => {
|
||||
ctx.logger.info("Starting case status sync", { runId: job.runId });
|
||||
try {
|
||||
const cases = await api.listCases();
|
||||
const companies = await ctx.companies.list();
|
||||
if (!companies.length)
|
||||
return;
|
||||
const companyId = companies[0].id;
|
||||
const issues = await ctx.issues.list({ companyId });
|
||||
for (const legalCase of cases) {
|
||||
for (const issue of issues) {
|
||||
const linkedCase = await ctx.state.get({
|
||||
scopeKind: "issue",
|
||||
scopeId: issue.id,
|
||||
stateKey: "legal-case-number",
|
||||
});
|
||||
if (linkedCase === legalCase.case_number) {
|
||||
// Map legal-ai status to Paperclip issue status
|
||||
const statusMap = {
|
||||
new: "todo",
|
||||
in_progress: "in_progress",
|
||||
drafted: "in_progress",
|
||||
reviewed: "in_progress",
|
||||
final: "done",
|
||||
};
|
||||
const targetStatus = statusMap[legalCase.status];
|
||||
if (targetStatus && issue.status !== targetStatus) {
|
||||
await ctx.issues.update(issue.id, { status: targetStatus }, companyId);
|
||||
await ctx.issues.createComment(issue.id, `סטטוס תיק עודכן ל: ${legalCase.status}`, companyId);
|
||||
ctx.logger.info("Synced issue status", {
|
||||
issueId: issue.id,
|
||||
caseNumber: legalCase.case_number,
|
||||
newStatus: targetStatus,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ctx.logger.info("Case status sync completed", {
|
||||
casesChecked: cases.length,
|
||||
});
|
||||
}
|
||||
catch (err) {
|
||||
ctx.logger.error("Case status sync failed", { error: String(err) });
|
||||
}
|
||||
});
|
||||
ctx.logger.info("Legal AI plugin ready");
|
||||
},
|
||||
async onHealth() {
|
||||
return { status: "ok" };
|
||||
},
|
||||
});
|
||||
export default plugin;
|
||||
runWorker(plugin, import.meta.url);
|
||||
Reference in New Issue
Block a user