fix(routing): comment→CEO wakeup reads issue id from event.entityId #2

Merged
chaim merged 1 commits from fix/comment-routing-ceo-entityid into main 2026-06-17 04:55:46 +00:00

View File

@@ -540,27 +540,36 @@ const plugin = definePlugin({
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 {
// Event/payload shape for `issue.comment.created` (Paperclip host):
// event.entityId → the ISSUE id (the primary entity of the event)
// event.payload.commentId → the comment id
// event.payload.bodySnippet → TRUNCATED body (not the full text)
// event.payload.reopened / reopenedFrom → set when the comment
// reopened a `done` issue (host then natively wakes its assignee)
// NOTE: the host does NOT send `payload.issueId` and does NOT send the
// full `body`. The earlier code read `payload.issueId` (always absent)
// and skipped every event — silently disabling comment→CEO routing.
// See @paperclipai/plugin-sdk index.d.ts: `issueId: event.entityId`.
const payload = (event.payload ?? null) as {
issueId?: string;
commentId?: string;
body?: string;
bodySnippet?: string;
reopened?: boolean;
reopenedFrom?: string;
} | null;
const issueId = payload?.issueId;
let commentBody = payload?.body;
// If issueId is not in payload, try to find it from the comment entity
// issueId: prefer entityId (current host), fall back to payload for
// forward/backward compatibility with other host versions.
const issueId = event.entityId || payload?.issueId;
if (!issueId) {
ctx.logger.warn(
"issue.comment.created event missing issueId in payload, skipping",
{ entityId, payload },
"issue.comment.created event missing issueId (entityId+payload empty), skipping",
{ entityId: event.entityId, payload },
);
return;
}
const commentId = payload?.commentId;
// Fetch issue details for context
const issue = await ctx.issues.get(issueId, event.companyId);
@@ -571,20 +580,6 @@ const plugin = definePlugin({
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 for this company
const ceoAgentId = CEO_AGENT_IDS[event.companyId];
if (!ceoAgentId) {
@@ -594,6 +589,45 @@ const plugin = definePlugin({
return;
}
// Dedup against the host's native reopen-on-comment wake: when a comment
// reopens a `done` issue, the host already wakes that issue's assignee.
// If the assignee IS this company's CEO, that native wake covers us —
// invoking again would double-run the CEO. Skip ONLY in that exact case.
// For issues owned by any other agent (e.g. an analyst sub-task), the
// native wake targets the wrong agent (and the queued run is cancelled
// with `issue_assignee_changed`), so we MUST route the comment to the CEO.
if (issue.assigneeAgentId === ceoAgentId && payload?.reopened === true) {
ctx.logger.info(
"Comment reopened CEO-owned issue; native wake handles it, skipping plugin route",
{ issueId, ceoAgentId },
);
return;
}
// Resolve the full comment body. The payload only carries a truncated
// snippet, so fetch the comment list and match by id (fall back to the
// latest comment, then the snippet).
let commentBody = payload?.body;
if (!commentBody) {
try {
const comments = await ctx.issues.listComments(
issueId,
event.companyId,
);
const matched = commentId
? comments.find((c) => c.id === commentId)
: undefined;
const latest = comments[comments.length - 1];
commentBody =
matched?.body ||
latest?.body ||
payload?.bodySnippet ||
"(לא ניתן לקרוא את התגובה)";
} catch {
commentBody = payload?.bodySnippet || "(שגיאה בקריאת התגובה)";
}
}
try {
const { runId } = await ctx.agents.invoke(ceoAgentId, event.companyId, {
prompt: [
@@ -608,7 +642,7 @@ const plugin = definePlugin({
});
ctx.logger.info("Routed user comment to CEO agent", {
issueId,
commentId: entityId,
commentId: commentId || null,
runId,
});
} catch (err) {