Address Greptile interaction review feedback

This commit is contained in:
Dotta committed 2026-04-21 17:46:09 -05:00
1 parent 70cc02333b
commit baa1c33127
5 files changed
+82 -10

No files matched your search

@@ -515,6 +515,39 @@ describe("issue thread interaction routes", () => {
expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled();
});
it("does not emit an accept-only continuation wake for rejected suggested tasks", async () => {
mockInteractionService.rejectInteraction.mockResolvedValueOnce({
id: "interaction-5",
companyId: "company-1",
issueId: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
kind: "suggest_tasks",
status: "rejected",
continuationPolicy: "wake_assignee_on_accept",
idempotencyKey: null,
sourceCommentId: null,
sourceRunId: "run-5",
payload: {
version: 1,
tasks: [{ clientKey: "task-1", title: "One" }],
},
result: {
version: 1,
rejectionReason: "Not now",
},
createdAt: "2026-04-20T12:00:00.000Z",
updatedAt: "2026-04-20T12:05:00.000Z",
resolvedAt: "2026-04-20T12:05:00.000Z",
});
const app = await createApp();
const res = await request(app)
.post("/api/issues/aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa/interactions/interaction-5/reject")
.send({ reason: "Not now" });
expect(res.status).toBe(200);
expect(mockHeartbeatService.wakeup).not.toHaveBeenCalled();
});
it("allows agent-authored interaction creation and stamps the active run id", async () => {
const app = await createApp({
type: "agent",
@@ -166,6 +166,18 @@ describeEmbeddedPostgres("issueThreadInteractionService", () => {
const listed = await interactionsSvc.listForIssue(issueId);
expect(listed).toHaveLength(1);
expect(listed[0]?.status).toBe("accepted");
await expect(interactionsSvc.acceptSuggestedTasks({
id: issueId,
companyId,
goalId,
projectId: null,
}, created.id, {}, {
userId: "local-board",
})).rejects.toThrow("Interaction has already been resolved");
const childrenAfterDuplicateAccept = await issuesSvc.list(companyId, { parentId: issueId });
expect(childrenAfterDuplicateAccept).toHaveLength(1);
});
it("accepts a selected subset of suggested tasks and records the skipped drafts", async () => {
+4 -1
View File
@@ -208,7 +208,10 @@ function queueResolvedInteractionContinuationWakeup(input: {
input.interaction.continuationPolicy !== "wake_assignee"
&& input.interaction.continuationPolicy !== "wake_assignee_on_accept"
) return;
if (input.interaction.kind === "request_confirmation" && input.interaction.status !== "accepted") return;
if (
input.interaction.continuationPolicy === "wake_assignee_on_accept"
&& input.interaction.status !== "accepted"
) return;
if (input.interaction.status === "expired") return;
if (!input.issue.assigneeAgentId || isClosedIssueStatus(input.issue.status)) return;
@@ -805,6 +805,26 @@ export function issueThreadInteractionService(db: Db) {
const createdWakeTargets: IssueWakeTarget[] = [];
await db.transaction(async (tx) => {
const resolvedAt = new Date();
const [claimed] = await tx
.update(issueThreadInteractions)
.set({
status: "accepted",
resolvedByAgentId: actor.agentId ?? null,
resolvedByUserId: actor.userId ?? null,
resolvedAt,
updatedAt: resolvedAt,
})
.where(and(
eq(issueThreadInteractions.id, interactionId),
eq(issueThreadInteractions.status, "pending"),
))
.returning();
if (!claimed) {
throw conflict("Interaction has already been resolved");
}
for (const task of orderedTasks) {
const parentIssueId = task.parentClientKey
? createdByClientKey.get(task.parentClientKey)?.issueId ?? null
@@ -850,15 +870,11 @@ export function issueThreadInteractionService(db: Db) {
const [updated] = await tx
.update(issueThreadInteractions)
.set({
status: "accepted",
result: {
version: 1,
createdTasks: [...createdByClientKey.values()],
...(skippedClientKeys.length > 0 ? { skippedClientKeys } : {}),
},
resolvedByAgentId: actor.agentId ?? null,
resolvedByUserId: actor.userId ?? null,
resolvedAt: new Date(),
updatedAt: new Date(),
})
.where(eq(issueThreadInteractions.id, interactionId))
@@ -889,7 +905,7 @@ export function issueThreadInteractionService(db: Db) {
const current = await getPendingInteractionForResolution({ issue, interactionId });
switch (current.kind) {
case "suggest_tasks":
return issueThreadInteractionService(db).rejectSuggestedTasks(issue, interactionId, data, actor);
return issueThreadInteractionService(db).rejectSuggestedTasks(issue, interactionId, data, actor, current);
case "request_confirmation":
return rejectRequestConfirmation({
issue,
@@ -907,8 +923,9 @@ export function issueThreadInteractionService(db: Db) {
interactionId: string,
input: RejectIssueThreadInteraction,
actor: InteractionActor,
currentRow?: IssueThreadInteractionRow,
) => {
const current = await db
const current = currentRow ?? await db
.select()
.from(issueThreadInteractions)
.where(eq(issueThreadInteractions.id, interactionId))
@@ -938,9 +955,16 @@ export function issueThreadInteractionService(db: Db) {
resolvedAt: new Date(),
updatedAt: new Date(),
})
.where(eq(issueThreadInteractions.id, interactionId))
.where(and(
eq(issueThreadInteractions.id, interactionId),
eq(issueThreadInteractions.status, "pending"),
))
.returning();
if (!updated) {
throw conflict("Interaction has already been resolved");
}
await touchIssue(db, issue.id);
return hydrateInteraction(updated);
},