mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-13 07:12:00 +01:00
Add eleven sparse incoming and outgoing restrictions, human CLI/API administration, shared mutation enforcement, and pending-delivery checks. Preserve existing defaults, agent settings, and existing authorization boundaries. Co-Authored-By: Paperclip <noreply@paperclip.ing>
94 lines
3.5 KiB
TypeScript
94 lines
3.5 KiB
TypeScript
import { logger } from "../middleware/logger.js";
|
|
import type { DurableChatWakeupRequest } from "./durable-chat-wakeup.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;
|
|
idempotencyKey?: string | null;
|
|
allowRunCoalescing?: boolean;
|
|
requestedByActorType?: "user" | "agent" | "system";
|
|
requestedByActorId?: string | null;
|
|
requestedByActorCompanyId?: string;
|
|
contextSnapshot?: Record<string, unknown>;
|
|
durableChatRequest?: DurableChatWakeupRequest;
|
|
},
|
|
) => 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;
|
|
requestedByActorCompanyId?: string;
|
|
taskKey?: string | null;
|
|
/** Latest issue comment that caused this wakeup. Included in both payload
|
|
* and context so the heartbeat can build the exact turn that was requested. */
|
|
wakeCommentId?: string | null;
|
|
/** Closed, server-derived omission counts for provider attachments on the
|
|
* exact wake comment. These are prompt diagnostics, never authorization. */
|
|
attachmentOmissionReasons?: Record<string, number> | null;
|
|
/** External chat conversation the wake answers. The wake queue keeps each
|
|
* conversation in its own batch, because a reviewed chat run attests one. */
|
|
chatConversationId?: string | null;
|
|
rethrowOnError?: boolean;
|
|
durableChatRequest?: DurableChatWakeupRequest;
|
|
}) {
|
|
if (!input.issue.assigneeAgentId || input.issue.status === "backlog") return;
|
|
|
|
return input.heartbeat
|
|
.wakeup(input.issue.assigneeAgentId, {
|
|
source: "assignment",
|
|
triggerDetail: "system",
|
|
reason: input.reason,
|
|
payload: {
|
|
issueId: input.issue.id,
|
|
mutation: input.mutation,
|
|
...(input.taskKey ? { taskKey: input.taskKey } : {}),
|
|
...(input.wakeCommentId ? { wakeCommentId: input.wakeCommentId } : {}),
|
|
},
|
|
requestedByActorType: input.requestedByActorType,
|
|
requestedByActorId: input.requestedByActorId ?? null,
|
|
...(input.requestedByActorCompanyId ? { requestedByActorCompanyId: input.requestedByActorCompanyId } : {}),
|
|
...(input.durableChatRequest
|
|
? { durableChatRequest: input.durableChatRequest }
|
|
: {}),
|
|
contextSnapshot: {
|
|
issueId: input.issue.id,
|
|
source: input.contextSource,
|
|
...(input.taskKey ? { taskKey: input.taskKey } : {}),
|
|
...(input.wakeCommentId ? { wakeCommentId: input.wakeCommentId } : {}),
|
|
...(input.chatConversationId ? { chatConversationId: input.chatConversationId } : {}),
|
|
...(input.wakeCommentId && input.attachmentOmissionReasons
|
|
? {
|
|
externalAttachmentOmissions: [
|
|
{
|
|
commentId: input.wakeCommentId,
|
|
reasons: input.attachmentOmissionReasons,
|
|
},
|
|
],
|
|
}
|
|
: {}),
|
|
},
|
|
})
|
|
.catch((err) => {
|
|
logger.warn(
|
|
{ err, issueId: input.issue.id },
|
|
"failed to wake assignee on issue assignment",
|
|
);
|
|
if (input.rethrowOnError) throw err;
|
|
return null;
|
|
});
|
|
}
|