import { randomUUID } from "node:crypto"; import type { Db } from "@paperclipai/db"; import { activityLog } from "@paperclipai/db"; import { PLUGIN_EVENT_TYPES, type PluginEventType } from "@paperclipai/shared"; import type { PluginEvent } from "@paperclipai/plugin-sdk"; import { publishLiveEvent } from "./live-events.js"; import { redactCurrentUserValue } from "../log-redaction.js"; import { sanitizeRecord } from "../redaction.js"; import { logger } from "../middleware/logger.js"; import type { PluginEventBus } from "./plugin-event-bus.js"; const PLUGIN_EVENT_SET: ReadonlySet = new Set(PLUGIN_EVENT_TYPES); let _pluginEventBus: PluginEventBus | null = null; /** Wire the plugin event bus so domain events are forwarded to plugins. */ export function setPluginEventBus(bus: PluginEventBus): void { if (_pluginEventBus) { logger.warn("setPluginEventBus called more than once, replacing existing bus"); } _pluginEventBus = bus; } export interface LogActivityInput { companyId: string; actorType: "agent" | "user" | "system"; actorId: string; action: string; entityType: string; entityId: string; agentId?: string | null; runId?: string | null; details?: Record | null; } export async function logActivity(db: Db, input: LogActivityInput) { const sanitizedDetails = input.details ? sanitizeRecord(input.details) : null; const redactedDetails = sanitizedDetails ? redactCurrentUserValue(sanitizedDetails) : null; await db.insert(activityLog).values({ companyId: input.companyId, actorType: input.actorType, actorId: input.actorId, action: input.action, entityType: input.entityType, entityId: input.entityId, agentId: input.agentId ?? null, runId: input.runId ?? null, details: redactedDetails, }); publishLiveEvent({ companyId: input.companyId, type: "activity.logged", payload: { actorType: input.actorType, actorId: input.actorId, action: input.action, entityType: input.entityType, entityId: input.entityId, agentId: input.agentId ?? null, runId: input.runId ?? null, details: redactedDetails, }, }); if (_pluginEventBus && PLUGIN_EVENT_SET.has(input.action)) { const event: PluginEvent = { eventId: randomUUID(), eventType: input.action as PluginEventType, occurredAt: new Date().toISOString(), actorId: input.actorId, actorType: input.actorType, entityId: input.entityId, entityType: input.entityType, companyId: input.companyId, payload: { ...redactedDetails, agentId: input.agentId ?? null, runId: input.runId ?? null, }, }; void _pluginEventBus.emit(event).then(({ errors }) => { for (const { pluginId, error } of errors) { logger.warn({ pluginId, eventType: event.eventType, err: error }, "plugin event handler failed"); } }).catch(() => {}); } }