fix(routing): comment→CEO wakeup reads issue id from event.entityId #2
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user