mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 17:45:53 +01:00
fix human reopen with pending finalize blockers
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
f51cba33fa
commit
402f0bf0a2
2 files changed
+99
-2
No files matched your search
@@ -325,6 +325,7 @@ describe.sequential("issue comment reopen routes", () => {
|
||||
blockerIssueIds: [],
|
||||
unresolvedBlockerIssueIds: [],
|
||||
unresolvedBlockerCount: 0,
|
||||
pendingFinalizeBlockerIssueIds: [],
|
||||
allBlockersDone: true,
|
||||
isDependencyReady: true,
|
||||
});
|
||||
@@ -853,6 +854,60 @@ describe.sequential("issue comment reopen routes", () => {
|
||||
));
|
||||
});
|
||||
|
||||
it("moves blocked issues with only pending-finalize done blockers back to todo via POST human comments", async () => {
|
||||
mockIssueService.getById.mockResolvedValue(makeIssue("blocked"));
|
||||
mockIssueService.getDependencyReadiness.mockResolvedValue({
|
||||
issueId: "11111111-1111-4111-8111-111111111111",
|
||||
blockerIssueIds: ["33333333-3333-4333-8333-333333333333"],
|
||||
unresolvedBlockerIssueIds: ["33333333-3333-4333-8333-333333333333"],
|
||||
unresolvedBlockerCount: 1,
|
||||
pendingFinalizeBlockerIssueIds: ["33333333-3333-4333-8333-333333333333"],
|
||||
allBlockersDone: false,
|
||||
isDependencyReady: false,
|
||||
});
|
||||
mockIssueService.update.mockImplementation(async (_id: string, patch: Record<string, unknown>) => ({
|
||||
...makeIssue("blocked"),
|
||||
...patch,
|
||||
}));
|
||||
|
||||
const res = await request(await installActor(createApp()))
|
||||
.post("/api/issues/11111111-1111-4111-8111-111111111111/comments")
|
||||
.send({ body: "please continue while finalization recovers" });
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
expect(mockIssueService.update).toHaveBeenCalledWith(
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
{ status: "todo" },
|
||||
);
|
||||
await waitForWakeup(() => expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith(
|
||||
"22222222-2222-4222-8222-222222222222",
|
||||
expect.objectContaining({ reason: "issue_reopened_via_comment" }),
|
||||
));
|
||||
});
|
||||
|
||||
it("does not move assigned blocked issues to todo via POST agent comments", async () => {
|
||||
const assigneeAgentId = "22222222-2222-4222-8222-222222222222";
|
||||
mockIssueService.getById.mockResolvedValue(makeIssue("blocked"));
|
||||
mockIssueService.addComment.mockResolvedValue({
|
||||
id: "comment-1",
|
||||
issueId: "11111111-1111-4111-8111-111111111111",
|
||||
companyId: "company-1",
|
||||
body: "agent progress note",
|
||||
createdAt: new Date(),
|
||||
updatedAt: new Date(),
|
||||
authorAgentId: assigneeAgentId,
|
||||
authorUserId: null,
|
||||
});
|
||||
|
||||
const res = await request(await installActor(createApp(), agentActor(assigneeAgentId)))
|
||||
.post("/api/issues/11111111-1111-4111-8111-111111111111/comments")
|
||||
.send({ body: "agent progress note" });
|
||||
|
||||
expect(res.status).toBe(201);
|
||||
expect(mockIssueService.update).not.toHaveBeenCalled();
|
||||
expect(mockIssueService.getDependencyReadiness).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("moves in-progress issues with a scheduled retry back to todo via POST human comments", async () => {
|
||||
const issue = {
|
||||
...makeIssue("in_progress"),
|
||||
@@ -1329,6 +1384,41 @@ describe.sequential("issue comment reopen routes", () => {
|
||||
));
|
||||
});
|
||||
|
||||
it("moves blocked issues with only pending-finalize done blockers back to todo via PATCH human comments", async () => {
|
||||
mockIssueService.getById.mockResolvedValue(makeIssue("blocked"));
|
||||
mockIssueService.getDependencyReadiness.mockResolvedValue({
|
||||
issueId: "11111111-1111-4111-8111-111111111111",
|
||||
blockerIssueIds: ["33333333-3333-4333-8333-333333333333"],
|
||||
unresolvedBlockerIssueIds: ["33333333-3333-4333-8333-333333333333"],
|
||||
unresolvedBlockerCount: 1,
|
||||
pendingFinalizeBlockerIssueIds: ["33333333-3333-4333-8333-333333333333"],
|
||||
allBlockersDone: false,
|
||||
isDependencyReady: false,
|
||||
});
|
||||
mockIssueService.update.mockImplementation(async (_id: string, patch: Record<string, unknown>) => ({
|
||||
...makeIssue("blocked"),
|
||||
...patch,
|
||||
}));
|
||||
|
||||
const res = await request(await installActor(createApp()))
|
||||
.patch("/api/issues/11111111-1111-4111-8111-111111111111")
|
||||
.send({ comment: "please continue while finalization recovers" });
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(mockIssueService.update).toHaveBeenCalledWith(
|
||||
"11111111-1111-4111-8111-111111111111",
|
||||
expect.objectContaining({
|
||||
status: "todo",
|
||||
actorAgentId: null,
|
||||
actorUserId: "local-board",
|
||||
}),
|
||||
);
|
||||
await waitForWakeup(() => expect(mockHeartbeatService.wakeup).toHaveBeenCalledWith(
|
||||
"22222222-2222-4222-8222-222222222222",
|
||||
expect.objectContaining({ reason: "issue_reopened_via_comment" }),
|
||||
));
|
||||
});
|
||||
|
||||
it("moves in-progress issues with a scheduled retry back to todo via the PATCH comment path", async () => {
|
||||
const issue = {
|
||||
...makeIssue("in_progress"),
|
||||
|
||||
@@ -1771,6 +1771,13 @@ function shouldHumanCommentResumeInProgressScheduledRetry(input: {
|
||||
return typeof input.assigneeAgentId === "string" && input.assigneeAgentId.length > 0;
|
||||
}
|
||||
|
||||
function hasUnresolvedBlockerEdges(readiness: {
|
||||
unresolvedBlockerCount: number;
|
||||
pendingFinalizeBlockerIssueIds?: string[];
|
||||
}) {
|
||||
return readiness.unresolvedBlockerCount > (readiness.pendingFinalizeBlockerIssueIds?.length ?? 0);
|
||||
}
|
||||
|
||||
function isExplicitResumeCapableStatus(status: string | null | undefined) {
|
||||
return status === "done" || status === "blocked" || status === "todo" || status === "in_progress";
|
||||
}
|
||||
@@ -7859,7 +7866,7 @@ export function issueRoutes(
|
||||
: null;
|
||||
const hasUnresolvedFirstClassBlockers =
|
||||
isBlocked && effectiveMoveToTodoRequested
|
||||
? (await svc.getDependencyReadiness(existing.id)).unresolvedBlockerCount > 0
|
||||
? hasUnresolvedBlockerEdges(await svc.getDependencyReadiness(existing.id))
|
||||
: false;
|
||||
if (resumeRequested === true && isBlocked && hasUnresolvedFirstClassBlockers) {
|
||||
res.status(409).json({ error: "Issue follow-up blocked by unresolved blockers" });
|
||||
@@ -10097,7 +10104,7 @@ export function issueRoutes(
|
||||
shouldResumeInProgressScheduledRetry);
|
||||
const hasUnresolvedFirstClassBlockers =
|
||||
isBlocked && effectiveMoveToTodoRequested
|
||||
? (await svc.getDependencyReadiness(issue.id)).unresolvedBlockerCount > 0
|
||||
? hasUnresolvedBlockerEdges(await svc.getDependencyReadiness(issue.id))
|
||||
: false;
|
||||
if (resumeRequested === true && isBlocked && hasUnresolvedFirstClassBlockers) {
|
||||
res.status(409).json({ error: "Issue follow-up blocked by unresolved blockers" });
|
||||
|
||||
Reference in new issue
Block a user