Spaces:
Paused
Paused
| import { and, eq } from "drizzle-orm"; | |
| import type { Db } from "@paperclipai/db"; | |
| import { agents } from "@paperclipai/db"; | |
| import type { HireApprovedPayload } from "@paperclipai/adapter-utils"; | |
| import { findActiveServerAdapter } from "../adapters/registry.js"; | |
| import { logger } from "../middleware/logger.js"; | |
| import { logActivity } from "./activity-log.js"; | |
| const HIRE_APPROVED_MESSAGE = | |
| "Tell your user that your hire was approved, now they should assign you a task in Paperclip or ask you to create issues."; | |
| export interface NotifyHireApprovedInput { | |
| companyId: string; | |
| agentId: string; | |
| source: "join_request" | "approval"; | |
| sourceId: string; | |
| approvedAt?: Date; | |
| } | |
| /** | |
| * Invokes the adapter's onHireApproved hook when an agent is approved (join-request or hire_agent approval). | |
| * Failures are non-fatal: we log and write to activity, never throw. | |
| */ | |
| export async function notifyHireApproved( | |
| db: Db, | |
| input: NotifyHireApprovedInput, | |
| ): Promise<void> { | |
| const { companyId, agentId, source, sourceId } = input; | |
| const approvedAt = input.approvedAt ?? new Date(); | |
| const row = await db | |
| .select() | |
| .from(agents) | |
| .where(and(eq(agents.id, agentId), eq(agents.companyId, companyId))) | |
| .then((rows) => rows[0] ?? null); | |
| if (!row) { | |
| logger.warn({ companyId, agentId, source, sourceId }, "hire hook: agent not found in company, skipping"); | |
| return; | |
| } | |
| const adapterType = row.adapterType ?? "process"; | |
| const adapter = findActiveServerAdapter(adapterType); | |
| const onHireApproved = adapter?.onHireApproved; | |
| if (!onHireApproved) { | |
| return; | |
| } | |
| const payload: HireApprovedPayload = { | |
| companyId, | |
| agentId, | |
| agentName: row.name, | |
| adapterType, | |
| source, | |
| sourceId, | |
| approvedAt: approvedAt.toISOString(), | |
| message: HIRE_APPROVED_MESSAGE, | |
| }; | |
| const adapterConfig = | |
| typeof row.adapterConfig === "object" && row.adapterConfig !== null && !Array.isArray(row.adapterConfig) | |
| ? (row.adapterConfig as Record<string, unknown>) | |
| : {}; | |
| try { | |
| const result = await onHireApproved(payload, adapterConfig); | |
| if (result.ok) { | |
| await logActivity(db, { | |
| companyId, | |
| actorType: "system", | |
| actorId: "hire_hook", | |
| action: "hire_hook.succeeded", | |
| entityType: "agent", | |
| entityId: agentId, | |
| details: { source, sourceId, adapterType }, | |
| }); | |
| return; | |
| } | |
| logger.warn( | |
| { companyId, agentId, adapterType, source, sourceId, error: result.error, detail: result.detail }, | |
| "hire hook: adapter returned failure", | |
| ); | |
| await logActivity(db, { | |
| companyId, | |
| actorType: "system", | |
| actorId: "hire_hook", | |
| action: "hire_hook.failed", | |
| entityType: "agent", | |
| entityId: agentId, | |
| details: { source, sourceId, adapterType, error: result.error, detail: result.detail }, | |
| }); | |
| } catch (err) { | |
| logger.error( | |
| { err, companyId, agentId, adapterType, source, sourceId }, | |
| "hire hook: adapter threw", | |
| ); | |
| await logActivity(db, { | |
| companyId, | |
| actorType: "system", | |
| actorId: "hire_hook", | |
| action: "hire_hook.error", | |
| entityType: "agent", | |
| entityId: agentId, | |
| details: { | |
| source, | |
| sourceId, | |
| adapterType, | |
| error: err instanceof Error ? err.message : String(err), | |
| }, | |
| }); | |
| } | |
| } | |