Compare commits
3 Commits
bf639063f7
...
f5a4cd1c62
| Author | SHA1 | Date | |
|---|---|---|---|
| f5a4cd1c62 | |||
| 23fda8da8c | |||
| 6b856dbe85 |
@@ -23,7 +23,8 @@
|
|||||||
"jobs.schedule",
|
"jobs.schedule",
|
||||||
"activity.log.write",
|
"activity.log.write",
|
||||||
"companies.read",
|
"companies.read",
|
||||||
"projects.read"
|
"projects.read",
|
||||||
|
"webhooks.receive"
|
||||||
],
|
],
|
||||||
"entrypoints": {
|
"entrypoints": {
|
||||||
"worker": "dist/worker.js"
|
"worker": "dist/worker.js"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export default {
|
|||||||
"activity.log.write",
|
"activity.log.write",
|
||||||
"companies.read",
|
"companies.read",
|
||||||
"projects.read",
|
"projects.read",
|
||||||
|
"webhooks.receive",
|
||||||
] as const,
|
] as const,
|
||||||
entrypoints: {
|
entrypoints: {
|
||||||
worker: "dist/worker.js",
|
worker: "dist/worker.js",
|
||||||
|
|||||||
128
src/worker.ts
128
src/worker.ts
@@ -1,8 +1,19 @@
|
|||||||
import { definePlugin, runWorker } from "@paperclipai/plugin-sdk";
|
import { definePlugin, runWorker } from "@paperclipai/plugin-sdk";
|
||||||
|
import type { PluginContext, PluginWebhookInput } from "@paperclipai/plugin-sdk";
|
||||||
import { LegalApi } from "./legal-api.js";
|
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<string, string> = {
|
||||||
|
"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({
|
const plugin = definePlugin({
|
||||||
async setup(ctx) {
|
async setup(ctx) {
|
||||||
|
pluginCtx = ctx; // save for onWebhook
|
||||||
const config = (await ctx.config.get()) as {
|
const config = (await ctx.config.get()) as {
|
||||||
legalApiBaseUrl?: string;
|
legalApiBaseUrl?: string;
|
||||||
} | null;
|
} | null;
|
||||||
@@ -517,8 +528,7 @@ const plugin = definePlugin({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Route user comments through CEO agent
|
// Route user comments through CEO agent — per company
|
||||||
const CEO_AGENT_ID = "752cebdd-6748-4a04-aacd-c7ab0294ef33";
|
|
||||||
|
|
||||||
ctx.events.on("issue.comment.created", async (event) => {
|
ctx.events.on("issue.comment.created", async (event) => {
|
||||||
// Only intercept human comments — not agent comments (prevents loops)
|
// 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 {
|
try {
|
||||||
const { runId } = await ctx.agents.invoke(
|
const { runId } = await ctx.agents.invoke(
|
||||||
CEO_AGENT_ID,
|
ceoAgentId,
|
||||||
event.companyId,
|
event.companyId,
|
||||||
{
|
{
|
||||||
prompt: [
|
prompt: [
|
||||||
@@ -694,6 +712,108 @@ const plugin = definePlugin({
|
|||||||
async onHealth() {
|
async onHealth() {
|
||||||
return { status: "ok" as const };
|
return { status: "ok" as const };
|
||||||
},
|
},
|
||||||
|
|
||||||
|
async onWebhook(input: PluginWebhookInput): Promise<void> {
|
||||||
|
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;
|
||||||
|
|
||||||
|
if (!caseNumber || !newStatus || !companyId) {
|
||||||
|
pluginCtx.logger.warn("onWebhook: malformed payload", { caseNumber, newStatus, companyId });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pluginCtx.logger.info(`Webhook: case ${caseNumber} ${oldStatus} → ${newStatus}`, {
|
||||||
|
companyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 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<string, string> = {
|
||||||
|
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;
|
export default plugin;
|
||||||
|
|||||||
Reference in New Issue
Block a user