diff --git a/server/src/__tests__/issue-thread-interactions-service.test.ts b/server/src/__tests__/issue-thread-interactions-service.test.ts index 7773770910..364d2bbe64 100644 --- a/server/src/__tests__/issue-thread-interactions-service.test.ts +++ b/server/src/__tests__/issue-thread-interactions-service.test.ts @@ -1867,7 +1867,7 @@ describeEmbeddedPostgres("issueThreadInteractionService", () => { }); }); - it("returns agent-authored request confirmations to the creating agent when a board user accepts", async () => { + it("returns accepted agent confirmations from review without resetting active work", async () => { const companyId = randomUUID(); const goalId = randomUUID(); const issueId = randomUUID(); @@ -1947,6 +1947,90 @@ describeEmbeddedPostgres("issueThreadInteractionService", () => { assigneeAgentId: agentId, assigneeUserId: null, }); + + await db + .update(issues) + .set({ + status: "in_review", + assigneeAgentId: agentId, + assigneeUserId: null, + }) + .where(eq(issues.id, issueId)); + + const agentOwnedConfirmation = await interactionsSvc.create({ + id: issueId, + companyId, + }, { + kind: "request_confirmation", + continuationPolicy: "wake_assignee_on_accept", + payload: { + version: 1, + prompt: "Approve the next step?", + }, + }, { + agentId, + }); + + const resumed = await interactionsSvc.acceptInteraction({ + id: issueId, + companyId, + goalId, + projectId: null, + }, agentOwnedConfirmation.id, {}, { + userId: "local-board", + }); + + expect(resumed.continuationIssue).toEqual({ + id: issueId, + assigneeAgentId: agentId, + assigneeUserId: null, + status: "todo", + }); + + const resumedIssue = (await db.select().from(issues)).find((issue) => issue.id === issueId); + expect(resumedIssue).toMatchObject({ + id: issueId, + status: "todo", + assigneeAgentId: agentId, + assigneeUserId: null, + }); + + await db + .update(issues) + .set({ status: "in_progress" }) + .where(eq(issues.id, issueId)); + + const activeConfirmation = await interactionsSvc.create({ + id: issueId, + companyId, + }, { + kind: "request_confirmation", + continuationPolicy: "wake_assignee_on_accept", + payload: { + version: 1, + prompt: "Approve while work is active?", + }, + }, { + agentId, + }); + + const acceptedWhileActive = await interactionsSvc.acceptInteraction({ + id: issueId, + companyId, + goalId, + projectId: null, + }, activeConfirmation.id, {}, { + userId: "local-board", + }); + + expect(acceptedWhileActive.continuationIssue).toBeNull(); + const activeIssue = (await db.select().from(issues)).find((issue) => issue.id === issueId); + expect(activeIssue).toMatchObject({ + id: issueId, + status: "in_progress", + assigneeAgentId: agentId, + assigneeUserId: null, + }); }); it("expires request confirmations by default when a user comments after creation", async () => { diff --git a/server/src/services/issue-thread-interactions.ts b/server/src/services/issue-thread-interactions.ts index dd862d9121..ff7f001d2c 100644 --- a/server/src/services/issue-thread-interactions.ts +++ b/server/src/services/issue-thread-interactions.ts @@ -310,10 +310,12 @@ function shouldReturnAcceptedConfirmationToCreatorAgent(args: { if (!isRequestConfirmationLikeKind(args.current.kind)) return false; if (!args.current.createdByAgentId) return false; if (!args.actor.userId) return false; - if (!args.issue.assigneeUserId) return false; - if (args.issue.assigneeAgentId) return false; if (isTerminalIssueStatus(args.issue.status)) return false; - return true; + if (args.issue.assigneeAgentId) { + return args.issue.status === "in_review" + && args.issue.assigneeAgentId === args.current.createdByAgentId; + } + return Boolean(args.issue.assigneeUserId); } function shouldSupersedeInteractionOnUserComment(interaction: UserCommentSupersedableInteraction) {