mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 00:53:38 +02:00
Address Greptile interaction review feedback
This commit is contained in:
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 () => {
|
||||
|
||||
@@ -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);
|
||||
},
|
||||
|
||||
Reference in new issue
Block a user