diff --git a/server/src/__tests__/document-annotation-routes.test.ts b/server/src/__tests__/document-annotation-routes.test.ts index f7f4d199..f37ae0f8 100644 --- a/server/src/__tests__/document-annotation-routes.test.ts +++ b/server/src/__tests__/document-annotation-routes.test.ts @@ -237,7 +237,7 @@ describe("document annotation routes", () => { }); }); - it("creates annotation threads, syncs references, logs activity, and does not wake the assignee", async () => { + it("creates annotation threads, syncs references, logs activity, and wakes the assignee", async () => { mockIssueService.getById.mockResolvedValue({ id: issueId, companyId, @@ -261,7 +261,15 @@ describe("document annotation routes", () => { expect(mockLogActivity).toHaveBeenCalledWith(expect.anything(), expect.objectContaining({ action: "issue.document_annotation_thread_created", })); - expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled(); + expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith( + "99999999-9999-4999-8999-999999999999", + expect.objectContaining({ + payload: expect.objectContaining({ + annotationThreadId: annotationThread.id, + annotationCommentId: annotationComment.id, + }), + }), + ); }); it("rejects agent cross-company annotation reads", async () => { diff --git a/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts b/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts index 831a6c95..11f91785 100644 --- a/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts +++ b/server/src/__tests__/heartbeat-accepted-plan-workspace-refresh.test.ts @@ -737,17 +737,6 @@ describeEmbeddedPostgres("accepted plan workspace refresh", () => { createdAt: new Date(), updatedAt: new Date(), }); - const planCommentId = randomUUID(); - await db.insert(issueComments).values({ - id: planCommentId, - companyId, - issueId, - authorUserId: "board-user-1", - authorType: "user", - body: "Keep the accepted plan rollout split into separate child tasks.", - createdAt: new Date("2026-06-07T00:00:00.000Z"), - updatedAt: new Date("2026-06-07T00:00:00.000Z"), - }); await seedAcceptedPlanClaim({ companyId, issueId, @@ -814,14 +803,5 @@ describeEmbeddedPostgres("accepted plan workspace refresh", () => { expect(adapterInput.runtime.sessionId).toBe("accepted-plan-retry-session"); expect(adapterInput.context.acceptedPlanWakeRouting).toBeUndefined(); expect(adapterInput.context.paperclipTaskMarkdown).toContain("Create child issues from the approved plan only"); - expect(adapterInput.context.paperclipTaskMarkdown).toContain("Comments included with the confirmed plan:"); - expect(adapterInput.context.paperclipTaskMarkdown).toContain(planCommentId); - expect(adapterInput.context.paperclipTaskMarkdown).toContain( - "Keep the accepted plan rollout split into separate child tasks.", - ); - expect(adapterInput.context.paperclipWake).toEqual(expect.objectContaining({ - commentIds: [planCommentId], - commentContextSource: "accepted_plan_confirmation", - })); }, 20_000); }); diff --git a/server/src/__tests__/heartbeat-context-summary.test.ts b/server/src/__tests__/heartbeat-context-summary.test.ts index 84d7b0ce..1fa9b800 100644 --- a/server/src/__tests__/heartbeat-context-summary.test.ts +++ b/server/src/__tests__/heartbeat-context-summary.test.ts @@ -72,34 +72,6 @@ describe("buildPaperclipTaskMarkdown", () => { expect(acceptedConfirmation).not.toContain("- Work mode: \"planning\""); }); - it("includes confirmed-plan comments in accepted-plan continuation task context", () => { - const acceptedConfirmation = buildPaperclipTaskMarkdown({ - issue: { - id: "issue-1", - identifier: "PAP-3404", - title: "Plan first", - workMode: "planning", - description: null, - }, - interaction: { - kind: "request_confirmation", - status: "accepted", - }, - acceptedPlanComments: [ - { - id: "comment-1", - authorType: "user", - body: "Please keep the migration backwards compatible.", - }, - ], - }); - - expect(acceptedConfirmation).toContain("Create child issues from the approved plan only"); - expect(acceptedConfirmation).toContain("Comments included with the confirmed plan:"); - expect(acceptedConfirmation).toContain("Comment 1 - user - comment-1:"); - expect(acceptedConfirmation).toContain("Please keep the migration backwards compatible."); - }); - it("prefers ordinary comment planning guidance over stale accepted confirmation state", () => { const commentWake = buildPaperclipTaskMarkdown({ issue: { diff --git a/server/src/__tests__/issue-comment-redaction.test.ts b/server/src/__tests__/issue-comment-redaction.test.ts index 2a439e85..45e08d18 100644 --- a/server/src/__tests__/issue-comment-redaction.test.ts +++ b/server/src/__tests__/issue-comment-redaction.test.ts @@ -175,69 +175,6 @@ describeEmbeddedPostgres("deleted issue comment redaction", () => { expect(JSON.stringify(wakePayload)).not.toContain("secret metadata"); }); - it("adds recent issue comments to accepted-plan continuation wake payloads", async () => { - const { companyId, issueId } = await seedIssue(); - const firstCommentId = randomUUID(); - const secondCommentId = randomUUID(); - const deletedCommentId = randomUUID(); - await db.insert(issueComments).values([ - { - id: firstCommentId, - companyId, - issueId, - authorUserId: "board-user-1", - body: "Keep the migration backwards compatible.", - createdAt: new Date("2026-06-03T12:00:00.000Z"), - }, - { - id: deletedCommentId, - companyId, - issueId, - authorUserId: "board-user-1", - body: "Do not pass this deleted text downstream.", - deletedAt: new Date("2026-06-03T12:01:00.000Z"), - deletedByType: "user", - deletedByUserId: "board-user-1", - createdAt: new Date("2026-06-03T12:01:00.000Z"), - }, - { - id: secondCommentId, - companyId, - issueId, - authorUserId: "board-user-1", - body: "Split the rollout into a verification child task.", - createdAt: new Date("2026-06-03T12:02:00.000Z"), - }, - ]); - - const wakePayload = await buildPaperclipWakePayload({ - db, - companyId, - contextSnapshot: { - issueId, - interactionKind: "request_confirmation", - interactionStatus: "accepted", - workspaceRefreshReason: "accepted_plan_confirmation", - }, - issueSummary: { - id: issueId, - identifier: "RED-1", - title: "Deleted comment redaction", - status: "in_progress", - priority: "medium", - workMode: "planning", - }, - }); - - expect(wakePayload?.commentContextSource).toBe("accepted_plan_confirmation"); - expect(wakePayload?.commentIds).toEqual([firstCommentId, secondCommentId]); - expect(wakePayload?.comments.map((comment) => comment.body)).toEqual([ - "Keep the migration backwards compatible.", - "Split the rollout into a verification child task.", - ]); - expect(JSON.stringify(wakePayload)).not.toContain("Do not pass this deleted text downstream."); - }); - it("excludes deleted comment bodies from company search", async () => { const { companyId, issueId } = await seedIssue(); await db.insert(issueComments).values({ diff --git a/server/src/__tests__/issue-comment-reopen-routes.test.ts b/server/src/__tests__/issue-comment-reopen-routes.test.ts index d56164ca..4043d86a 100644 --- a/server/src/__tests__/issue-comment-reopen-routes.test.ts +++ b/server/src/__tests__/issue-comment-reopen-routes.test.ts @@ -639,8 +639,20 @@ describe.sequential("issue comment reopen routes", () => { }), }), ); - await waitForWakeup(() => expect(mockIssueService.findMentionedAgents).toHaveBeenCalled()); - expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled(); + await waitForWakeup(() => expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith( + "22222222-2222-4222-8222-222222222222", + expect.objectContaining({ + reason: "issue_commented", + payload: expect.objectContaining({ + commentId: "comment-1", + mutation: "comment", + }), + contextSnapshot: expect.objectContaining({ + wakeReason: "issue_commented", + source: "issue.comment", + }), + }), + )); }); it("does not move scheduled-retry issues to todo when POST comment retry cancellation fails", async () => { @@ -689,8 +701,12 @@ describe.sequential("issue comment reopen routes", () => { expect(mockIssueService.getCurrentScheduledRetry).toHaveBeenCalledWith("11111111-1111-4111-8111-111111111111"); expect(mockIssueService.update).not.toHaveBeenCalled(); expect(mockHeartbeatService.cancelRun).not.toHaveBeenCalled(); - await waitForWakeup(() => expect(mockIssueService.findMentionedAgents).toHaveBeenCalled()); - expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled(); + await waitForWakeup(() => expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith( + "22222222-2222-4222-8222-222222222222", + expect.objectContaining({ + reason: "issue_commented", + }), + )); }); it("passes validated comment presentation fields to trusted board comment writes", async () => { @@ -792,8 +808,21 @@ describe.sequential("issue comment reopen routes", () => { expect(res.status).toBe(201); expect(mockIssueService.update).not.toHaveBeenCalled(); - await waitForWakeup(() => expect(mockIssueService.findMentionedAgents).toHaveBeenCalled()); - expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled(); + await waitForWakeup(() => expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith( + "22222222-2222-4222-8222-222222222222", + expect.objectContaining({ + reason: "issue_commented", + payload: expect.objectContaining({ + commentId: "comment-1", + mutation: "comment", + }), + contextSnapshot: expect.objectContaining({ + issueId: "11111111-1111-4111-8111-111111111111", + wakeCommentId: "comment-1", + wakeReason: "issue_commented", + }), + }), + )); }); it("does not implicitly reopen closed issues via POST comments when no agent is assigned", async () => { @@ -888,8 +917,16 @@ describe.sequential("issue comment reopen routes", () => { }), ); expect(mockHeartbeatService.cancelRun).toHaveBeenCalledWith("retry-run-1"); - await waitForWakeup(() => expect(mockIssueService.findMentionedAgents).toHaveBeenCalled()); - expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled(); + await waitForWakeup(() => expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith( + "22222222-2222-4222-8222-222222222222", + expect.objectContaining({ + reason: "issue_commented", + payload: expect.objectContaining({ + commentId: "comment-1", + mutation: "comment", + }), + }), + )); }); it("does not move scheduled-retry issues to todo when PATCH comment retry cancellation fails", async () => { @@ -991,8 +1028,16 @@ describe.sequential("issue comment reopen routes", () => { "11111111-1111-4111-8111-111111111111", expect.objectContaining({ status: "todo" }), ); - await waitForWakeup(() => expect(mockIssueService.findMentionedAgents).toHaveBeenCalled()); - expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled(); + await waitForWakeup(() => expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith( + "22222222-2222-4222-8222-222222222222", + expect.objectContaining({ + reason: "issue_commented", + payload: expect.objectContaining({ + commentId: "comment-1", + mutation: "comment", + }), + }), + )); }); it("wakes the assignee when an assigned blocked issue moves back to todo", async () => { diff --git a/server/src/__tests__/issue-update-comment-wakeup-routes.test.ts b/server/src/__tests__/issue-update-comment-wakeup-routes.test.ts index dceb8e4e..89b1b257 100644 --- a/server/src/__tests__/issue-update-comment-wakeup-routes.test.ts +++ b/server/src/__tests__/issue-update-comment-wakeup-routes.test.ts @@ -269,7 +269,7 @@ describe("issue update comment wakeups", () => { ); }); - it("does not wake the assignee on comment-only issue updates", async () => { + it("wakes the assignee on comment-only issue updates", async () => { const existing = makeIssue({ assigneeAgentId: ASSIGNEE_AGENT_ID, assigneeUserId: null, @@ -292,10 +292,26 @@ describe("issue update comment wakeups", () => { }); expect(res.status).toBe(200); - await vi.waitFor(() => expect(mockIssueService.findMentionedAgents).toHaveBeenCalledWith( - existing.companyId, - "please revise this", - )); - expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled(); + expect(mockHeartbeatService.wakeup).toHaveBeenCalledTimes(1); + expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith( + ASSIGNEE_AGENT_ID, + expect.objectContaining({ + source: "automation", + reason: "issue_commented", + payload: expect.objectContaining({ + issueId: existing.id, + commentId: "comment-2", + mutation: "comment", + }), + contextSnapshot: expect.objectContaining({ + issueId: existing.id, + taskId: existing.id, + commentId: "comment-2", + wakeCommentId: "comment-2", + wakeReason: "issue_commented", + source: "issue.comment", + }), + }), + ); }); }); diff --git a/server/src/routes/issues.ts b/server/src/routes/issues.ts index 47d84ed3..40768a74 100644 --- a/server/src/routes/issues.ts +++ b/server/src/routes/issues.ts @@ -1431,6 +1431,46 @@ export function issueRoutes( }; } + function queueAnnotationCommentWakeup(input: { + issue: { id: string; assigneeAgentId: string | null; status: string }; + actor: { actorType: "user" | "agent"; actorId: string }; + threadId: string; + commentId: string; + documentKey: string; + }) { + const assigneeId = input.issue.assigneeAgentId; + const selfComment = input.actor.actorType === "agent" && input.actor.actorId === assigneeId; + if (!assigneeId || selfComment || isClosedIssueStatus(input.issue.status)) return; + void heartbeat.wakeup(assigneeId, { + source: "automation", + triggerDetail: "system", + reason: "issue_commented", + payload: { + issueId: input.issue.id, + annotationThreadId: input.threadId, + annotationCommentId: input.commentId, + documentKey: input.documentKey, + mutation: "document_annotation_comment", + }, + requestedByActorType: input.actor.actorType, + requestedByActorId: input.actor.actorId, + contextSnapshot: { + issueId: input.issue.id, + taskId: input.issue.id, + annotationThreadId: input.threadId, + annotationCommentId: input.commentId, + documentKey: input.documentKey, + source: "issue.document.annotation", + wakeReason: "issue_commented", + }, + }).catch((err) => logger.warn({ + err, + issueId: input.issue.id, + annotationThreadId: input.threadId, + annotationCommentId: input.commentId, + }, "failed to wake assignee on document annotation comment")); + } + async function canonicalizePaperclipArtifactMetadata(input: { issue: { id: string; companyId: string }; metadata: Record | null | undefined; @@ -3095,6 +3135,16 @@ export function issueRoutes( }, }); + if (firstComment) { + queueAnnotationCommentWakeup({ + issue, + actor, + threadId: thread.id, + commentId: firstComment.id, + documentKey: thread.documentKey, + }); + } + res.status(201).json(thread); }, ); @@ -3177,6 +3227,14 @@ export function issueRoutes( }, }); + queueAnnotationCommentWakeup({ + issue, + actor, + threadId: comment.threadId, + commentId: comment.id, + documentKey: keyParsed.data, + }); + res.status(201).json(comment); }, ); @@ -5488,17 +5546,20 @@ export function issueRoutes( if (commentBody && comment) { const assigneeId = issue.assigneeAgentId; + const actorIsAgent = actor.actorType === "agent"; + const selfComment = actorIsAgent && actor.actorId === assigneeId; + const skipAssigneeCommentWake = selfComment || isClosed; - if (assigneeId && !assigneeChanged && reopened) { + if (assigneeId && !assigneeChanged && (reopened || !skipAssigneeCommentWake)) { addWakeup(assigneeId, { source: "automation", triggerDetail: "system", - reason: "issue_reopened_via_comment", + reason: reopened ? "issue_reopened_via_comment" : "issue_commented", payload: { issueId: id, commentId: comment.id, mutation: "comment", - reopenedFrom: reopenFromStatus, + ...(reopened ? { reopenedFrom: reopenFromStatus } : {}), ...(resumeRequested === true ? { resumeIntent: true, followUpRequested: true } : {}), ...(interruptedRunId ? { interruptedRunId } : {}), }, @@ -5509,9 +5570,9 @@ export function issueRoutes( taskId: id, commentId: comment.id, wakeCommentId: comment.id, - source: "issue.comment.reopen", - wakeReason: "issue_reopened_via_comment", - reopenedFrom: reopenFromStatus, + source: reopened ? "issue.comment.reopen" : "issue.comment", + wakeReason: reopened ? "issue_reopened_via_comment" : "issue_commented", + ...(reopened ? { reopenedFrom: reopenFromStatus } : {}), ...(resumeRequested === true ? { resumeIntent: true, followUpRequested: true } : {}), ...(interruptedRunId ? { interruptedRunId } : {}), }, @@ -6653,33 +6714,62 @@ export function issueRoutes( const wakeups = new Map[1]>(); const assigneeId = currentIssue.assigneeAgentId; const actorIsAgent = actor.actorType === "agent"; - if (assigneeId && reopened) { - wakeups.set(assigneeId, { - source: "automation", - triggerDetail: "system", - reason: "issue_reopened_via_comment", - payload: { - issueId: currentIssue.id, - commentId: comment.id, - reopenedFrom: reopenFromStatus, - mutation: "comment", - ...(resumeRequested === true ? { resumeIntent: true, followUpRequested: true } : {}), - ...(interruptedRunId ? { interruptedRunId } : {}), - }, - requestedByActorType: actor.actorType, - requestedByActorId: actor.actorId, - contextSnapshot: { - issueId: currentIssue.id, - taskId: currentIssue.id, - commentId: comment.id, - wakeCommentId: comment.id, - source: "issue.comment.reopen", - wakeReason: "issue_reopened_via_comment", - reopenedFrom: reopenFromStatus, - ...(resumeRequested === true ? { resumeIntent: true, followUpRequested: true } : {}), - ...(interruptedRunId ? { interruptedRunId } : {}), - }, - }); + const selfComment = actorIsAgent && actor.actorId === assigneeId; + const skipWake = selfComment || isClosed; + if (assigneeId && (reopened || !skipWake)) { + if (reopened) { + wakeups.set(assigneeId, { + source: "automation", + triggerDetail: "system", + reason: "issue_reopened_via_comment", + payload: { + issueId: currentIssue.id, + commentId: comment.id, + reopenedFrom: reopenFromStatus, + mutation: "comment", + ...(resumeRequested === true ? { resumeIntent: true, followUpRequested: true } : {}), + ...(interruptedRunId ? { interruptedRunId } : {}), + }, + requestedByActorType: actor.actorType, + requestedByActorId: actor.actorId, + contextSnapshot: { + issueId: currentIssue.id, + taskId: currentIssue.id, + commentId: comment.id, + wakeCommentId: comment.id, + source: "issue.comment.reopen", + wakeReason: "issue_reopened_via_comment", + reopenedFrom: reopenFromStatus, + ...(resumeRequested === true ? { resumeIntent: true, followUpRequested: true } : {}), + ...(interruptedRunId ? { interruptedRunId } : {}), + }, + }); + } else { + wakeups.set(assigneeId, { + source: "automation", + triggerDetail: "system", + reason: "issue_commented", + payload: { + issueId: currentIssue.id, + commentId: comment.id, + mutation: "comment", + ...(resumeRequested === true ? { resumeIntent: true, followUpRequested: true } : {}), + ...(interruptedRunId ? { interruptedRunId } : {}), + }, + requestedByActorType: actor.actorType, + requestedByActorId: actor.actorId, + contextSnapshot: { + issueId: currentIssue.id, + taskId: currentIssue.id, + commentId: comment.id, + wakeCommentId: comment.id, + source: "issue.comment", + wakeReason: "issue_commented", + ...(resumeRequested === true ? { resumeIntent: true, followUpRequested: true } : {}), + ...(interruptedRunId ? { interruptedRunId } : {}), + }, + }); + } } let mentionedIds: string[] = []; diff --git a/server/src/services/heartbeat.ts b/server/src/services/heartbeat.ts index b54cb59c..e54bfdb4 100644 --- a/server/src/services/heartbeat.ts +++ b/server/src/services/heartbeat.ts @@ -528,9 +528,8 @@ export function extractMentionedSkillIdsFromSources( for (const source of sources) { if (typeof source !== "string" || source.length === 0) continue; for (const skillId of extractSkillMentionIds(source)) { - if (isUuidLike(skillId)) { - mentionedIds.add(skillId); - } + if (!isUuidLike(skillId)) continue; + mentionedIds.add(skillId); } } return [...mentionedIds]; @@ -2291,20 +2290,6 @@ function normalizeInteractionContinuationWakeContext( clearInteractionContinuationWakeContext(contextSnapshot); } -function isAcceptedPlanContinuationWakeContext( - contextSnapshot: Record, - issueWorkMode?: string | null, -) { - return ( - readNonEmptyString(contextSnapshot.workspaceRefreshReason) === "accepted_plan_confirmation" || - ( - issueWorkMode === "planning" && - readNonEmptyString(contextSnapshot.interactionKind) === "request_confirmation" && - readNonEmptyString(contextSnapshot.interactionStatus) === "accepted" - ) - ); -} - type AcceptedPlanWakeRoutingDecision = { otherActiveClaimIssueId: string; otherActiveClaimIdentifier: string | null; @@ -2411,7 +2396,7 @@ export async function buildPaperclipWakePayload(input: { exposeLowTrustRaw?: boolean; }) { const executionStage = parseObject(input.contextSnapshot.executionStage); - let commentIds = extractWakeCommentIds(input.contextSnapshot); + const commentIds = extractWakeCommentIds(input.contextSnapshot); const annotationCommentId = readNonEmptyString(input.contextSnapshot.annotationCommentId); const issueId = readNonEmptyString(input.contextSnapshot.issueId); const continuationSummary = input.continuationSummary ?? null; @@ -2431,28 +2416,6 @@ export async function buildPaperclipWakePayload(input: { .where(and(eq(issues.id, issueId), eq(issues.companyId, input.companyId))) .then((rows) => rows[0] ?? null) : null); - let acceptedPlanCommentWindowTruncated = false; - const acceptedPlanContinuationWake = isAcceptedPlanContinuationWakeContext( - input.contextSnapshot, - issueSummary?.workMode, - ); - if (commentIds.length === 0 && acceptedPlanContinuationWake && issueSummary?.id) { - const recentPlanCommentRows = await input.db - .select({ id: issueComments.id }) - .from(issueComments) - .where(and( - eq(issueComments.companyId, input.companyId), - eq(issueComments.issueId, issueSummary.id), - isNull(issueComments.deletedAt), - )) - .orderBy(desc(issueComments.createdAt)) - .limit(MAX_INLINE_WAKE_COMMENTS + 1); - acceptedPlanCommentWindowTruncated = recentPlanCommentRows.length > MAX_INLINE_WAKE_COMMENTS; - commentIds = recentPlanCommentRows - .slice(0, MAX_INLINE_WAKE_COMMENTS) - .reverse() - .map((comment) => comment.id); - } if (commentIds.length === 0 && Object.keys(executionStage).length === 0 && !issueSummary) return null; const commentRows = @@ -2487,7 +2450,7 @@ export async function buildPaperclipWakePayload(input: { const commentsById = new Map(commentRows.map((comment) => [comment.id, comment])); const comments: Array> = []; let remainingBodyChars = MAX_INLINE_WAKE_COMMENT_BODY_TOTAL_CHARS; - let truncated = acceptedPlanCommentWindowTruncated; + let truncated = false; let missingCommentCount = 0; const safeContinuationSummary = continuationSummary && !input.exposeLowTrustRaw @@ -2625,9 +2588,6 @@ export async function buildPaperclipWakePayload(input: { : null, interactionKind: readNonEmptyString(input.contextSnapshot.interactionKind), interactionStatus: readNonEmptyString(input.contextSnapshot.interactionStatus), - commentContextSource: acceptedPlanContinuationWake && commentIds.length > 0 - ? "accepted_plan_confirmation" - : null, checkedOutByHarness: input.contextSnapshot[PAPERCLIP_HARNESS_CHECKOUT_KEY] === true, dependencyBlockedInteraction: input.contextSnapshot.dependencyBlockedInteraction === true, treeHoldInteraction: input.contextSnapshot.treeHoldInteraction === true, @@ -2702,11 +2662,6 @@ export function buildPaperclipTaskMarkdown(input: { kind?: string | null; status?: string | null; } | null; - acceptedPlanComments?: Array<{ - id?: string | null; - authorType?: string | null; - body?: string | null; - }> | null; acceptedPlanContinuation?: boolean; }) { const quoteTaskScalar = (value: string) => JSON.stringify(value); @@ -2767,28 +2722,6 @@ export function buildPaperclipTaskMarkdown(input: { if (wakeComment?.body.trim()) { lines.push("", "Latest wake comment:", fenceTaskText(wakeComment.body.trim())); } - if (acceptedPlanContinuation) { - const acceptedPlanComments = (input.acceptedPlanComments ?? []) - .map((comment) => ({ - ...comment, - body: comment.body?.trim() ?? "", - })) - .filter((comment) => comment.body.length > 0) - .slice(0, MAX_INLINE_WAKE_COMMENTS); - if (acceptedPlanComments.length > 0) { - lines.push("", "Comments included with the confirmed plan:"); - for (const [index, comment] of acceptedPlanComments.entries()) { - const authorType = comment.authorType?.trim(); - const commentId = comment.id?.trim(); - const labelParts = [ - `Comment ${index + 1}`, - ...(authorType ? [authorType] : []), - ...(commentId ? [commentId] : []), - ]; - lines.push("", `${labelParts.join(" - ")}:`, fenceTaskText(comment.body)); - } - } - } lines.push("", "Use this task context as the current assignment."); return lines.join("\n"); } @@ -7815,7 +7748,13 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {}) companyId: agent.companyId, agentId: agent.id, issueId, - acceptedPlanContinuationWake: isAcceptedPlanContinuationWakeContext(context, issueContext.workMode), + acceptedPlanContinuationWake: + readNonEmptyString(context.workspaceRefreshReason) === "accepted_plan_confirmation" + || ( + issueContext.workMode === "planning" + && readNonEmptyString(context.interactionKind) === "request_confirmation" + && readNonEmptyString(context.interactionStatus) === "accepted" + ), contextSnapshot: context, }) : null; @@ -7954,18 +7893,6 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {}) } else { delete context[PAPERCLIP_WAKE_PAYLOAD_KEY]; } - const acceptedPlanWakeRouting = parseObject(context.acceptedPlanWakeRouting); - const acceptedPlanContinuationForTask = - isAcceptedPlanContinuationWakeContext(context, issueRef?.workMode) && - Object.keys(acceptedPlanWakeRouting).length === 0; - const acceptedPlanCommentsForTask = - acceptedPlanContinuationForTask && Array.isArray(paperclipWakePayload?.comments) - ? paperclipWakePayload.comments.map((comment) => ({ - id: readNonEmptyString(comment.id), - authorType: readNonEmptyString(comment.authorType), - body: readNonEmptyString(comment.body), - })) - : null; const taskMarkdown = buildPaperclipTaskMarkdown({ issue: issueRef ? { @@ -7981,8 +7908,9 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {}) kind: readNonEmptyString(context.interactionKind), status: readNonEmptyString(context.interactionStatus), }, - acceptedPlanComments: acceptedPlanCommentsForTask, - acceptedPlanContinuation: acceptedPlanContinuationForTask, + acceptedPlanContinuation: + readNonEmptyString(context.workspaceRefreshReason) === "accepted_plan_confirmation" + && Object.keys(parseObject(context.acceptedPlanWakeRouting)).length === 0, }); if (issueRef) { context.paperclipIssue = {