mirror of
https://github.com/alkimake/paperclip.git
synced 2026-06-14 18:10:39 +09:00
Fix routine assignment wakeups
Share issue-assignment wakeup logic between direct issue creation and routine-created execution issues, and add regression coverage for the routine path. Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
parent
fb760a63ab
commit
2d8c8abbfb
4 changed files with 109 additions and 16 deletions
43
server/src/services/issue-assignment-wakeup.ts
Normal file
43
server/src/services/issue-assignment-wakeup.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import { logger } from "../middleware/logger.js";
|
||||
|
||||
type WakeupTriggerDetail = "manual" | "ping" | "callback" | "system";
|
||||
type WakeupSource = "timer" | "assignment" | "on_demand" | "automation";
|
||||
|
||||
export interface IssueAssignmentWakeupDeps {
|
||||
wakeup: (
|
||||
agentId: string,
|
||||
opts: {
|
||||
source?: WakeupSource;
|
||||
triggerDetail?: WakeupTriggerDetail;
|
||||
reason?: string | null;
|
||||
payload?: Record<string, unknown> | null;
|
||||
requestedByActorType?: "user" | "agent" | "system";
|
||||
requestedByActorId?: string | null;
|
||||
contextSnapshot?: Record<string, unknown>;
|
||||
},
|
||||
) => Promise<unknown>;
|
||||
}
|
||||
|
||||
export function queueIssueAssignmentWakeup(input: {
|
||||
heartbeat: IssueAssignmentWakeupDeps;
|
||||
issue: { id: string; assigneeAgentId: string | null; status: string };
|
||||
reason: string;
|
||||
mutation: string;
|
||||
contextSource: string;
|
||||
requestedByActorType?: "user" | "agent" | "system";
|
||||
requestedByActorId?: string | null;
|
||||
}) {
|
||||
if (!input.issue.assigneeAgentId || input.issue.status === "backlog") return;
|
||||
|
||||
void input.heartbeat
|
||||
.wakeup(input.issue.assigneeAgentId, {
|
||||
source: "assignment",
|
||||
triggerDetail: "system",
|
||||
reason: input.reason,
|
||||
payload: { issueId: input.issue.id, mutation: input.mutation },
|
||||
requestedByActorType: input.requestedByActorType,
|
||||
requestedByActorId: input.requestedByActorId ?? null,
|
||||
contextSnapshot: { issueId: input.issue.id, source: input.contextSource },
|
||||
})
|
||||
.catch((err) => logger.warn({ err, issueId: input.issue.id }, "failed to wake assignee on issue assignment"));
|
||||
}
|
||||
|
|
@ -29,6 +29,8 @@ import { conflict, forbidden, notFound, unauthorized, unprocessable } from "../e
|
|||
import { issueService } from "./issues.js";
|
||||
import { secretService } from "./secrets.js";
|
||||
import { parseCron, validateCron } from "./cron.js";
|
||||
import { heartbeatService } from "./heartbeat.js";
|
||||
import { queueIssueAssignmentWakeup, type IssueAssignmentWakeupDeps } from "./issue-assignment-wakeup.js";
|
||||
|
||||
const OPEN_ISSUE_STATUSES = ["backlog", "todo", "in_progress", "in_review", "blocked"];
|
||||
const LIVE_HEARTBEAT_RUN_STATUSES = ["queued", "running"];
|
||||
|
|
@ -128,9 +130,10 @@ function nextResultText(status: string, issueId?: string | null) {
|
|||
return status;
|
||||
}
|
||||
|
||||
export function routineService(db: Db) {
|
||||
export function routineService(db: Db, deps: { heartbeat?: IssueAssignmentWakeupDeps } = {}) {
|
||||
const issueSvc = issueService(db);
|
||||
const secretsSvc = secretService(db);
|
||||
const heartbeat = deps.heartbeat ?? heartbeatService(db);
|
||||
|
||||
async function getRoutineById(id: string) {
|
||||
return db
|
||||
|
|
@ -616,6 +619,14 @@ export function routineService(db: Db) {
|
|||
status: "issue_created",
|
||||
linkedIssueId: createdIssue.id,
|
||||
});
|
||||
queueIssueAssignmentWakeup({
|
||||
heartbeat,
|
||||
issue: createdIssue,
|
||||
reason: "issue_assigned",
|
||||
mutation: "create",
|
||||
contextSource: "routine.dispatch",
|
||||
requestedByActorType: input.source === "schedule" ? "system" : undefined,
|
||||
});
|
||||
await updateRoutineTouchedState({
|
||||
routineId: input.routine.id,
|
||||
triggerId: input.trigger?.id ?? null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue