From bf639063f7f1e453db7b5c1320c8507e6dcbf151 Mon Sep 17 00:00:00 2001 From: Chaim Marcus Date: Tue, 14 Apr 2026 13:47:52 +0000 Subject: [PATCH] Route user comments through CEO agent via issue.comment.created event Add event handler that intercepts user comments on issues and wakes the CEO agent (instead of only the assigned agent). The CEO reads the comment, checks for attachments, and routes to the appropriate agent. - Add issue.comment.created event subscription - Add agents.read, agents.invoke, issue.comments.read capabilities - CEO receives comment body + issue context in the invoke prompt Co-Authored-By: Claude Opus 4.6 (1M context) --- plugin.json | 3 ++ src/worker.ts | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) diff --git a/plugin.json b/plugin.json index 98364d2..04a0b77 100644 --- a/plugin.json +++ b/plugin.json @@ -13,6 +13,9 @@ "issues.create", "issues.update", "issue.comments.create", + "issue.comments.read", + "agents.read", + "agents.invoke", "agent.tools.register", "http.outbound", "plugin.state.read", diff --git a/src/worker.ts b/src/worker.ts index d2fa2eb..a4fd7c2 100644 --- a/src/worker.ts +++ b/src/worker.ts @@ -517,6 +517,89 @@ const plugin = definePlugin({ } }); + // Route user comments through CEO agent + const CEO_AGENT_ID = "752cebdd-6748-4a04-aacd-c7ab0294ef33"; + + ctx.events.on("issue.comment.created", async (event) => { + // Only intercept human comments — not agent comments (prevents loops) + if (event.actorType !== "user") return; + if (!event.companyId) return; + + // entityId is the comment ID — fetch the comment to get issueId + body + const entityId = event.entityId; + if (!entityId) return; + + // The event payload may contain the issueId directly + const payload = event.payload as { + issueId?: string; + body?: string; + } | null; + + let issueId = payload?.issueId; + let commentBody = payload?.body; + + // If issueId is not in payload, try to find it from the comment entity + if (!issueId) { + ctx.logger.warn( + "issue.comment.created event missing issueId in payload, skipping", + { entityId, payload }, + ); + return; + } + + // Fetch issue details for context + const issue = await ctx.issues.get(issueId, event.companyId); + if (!issue) { + ctx.logger.warn("Could not fetch issue for comment routing", { + issueId, + }); + return; + } + + // If comment body not in payload, fetch from API + if (!commentBody) { + try { + const comments = await ctx.issues.listComments( + issueId, + event.companyId, + ); + const latest = comments[comments.length - 1]; + commentBody = latest?.body || "(לא ניתן לקרוא את התגובה)"; + } catch { + commentBody = "(שגיאה בקריאת התגובה)"; + } + } + + // Wake the CEO agent with the comment context + try { + const { runId } = await ctx.agents.invoke( + CEO_AGENT_ID, + event.companyId, + { + prompt: [ + `תגובה חדשה מחיים על issue "${issue.title}" (${issue.identifier || issueId}):`, + "", + commentBody, + "", + `קרא את ה-comments האחרונים על ה-issue, הבן מה חיים מבקש, והחלט מה לעשות.`, + `אם ההוראה ברורה — נתב לסוכן המתאים. אם לא ברור — שאל את חיים.`, + ].join("\n"), + reason: `user_commented_on_${issue.identifier || issueId}`, + }, + ); + ctx.logger.info("Routed user comment to CEO agent", { + issueId, + commentId: entityId, + runId, + }); + } catch (err) { + ctx.logger.error("Failed to invoke CEO agent for comment routing", { + issueId, + error: String(err), + }); + } + }); + // ── Jobs ─────────────────────────────────────────────────────── ctx.jobs.register("sync-case-status", async (job) => {