import { IPC_CHANNELS, approvalDecisionSchema, approvalInputSchema, cancelRunSchema, commentInputSchema, heartbeatSettingsSchema, runLogChunkSchema, runLogSchema, startRunSchema, triggerHeartbeatSchema, } from "@shared/contracts"; import { resolveApprovalDecision } from "./approval-policy"; import { shouldAutoApprove } from "./execution-policy"; import { fail, ok, type RegisterCoreHandlersDependencies } from "./ipc-core-common"; export function registerCoreOperationHandlers({ registerHandle, db, ensureCompanyExists, publishDomainChanged, emitEvent, wakeAgentIfPossible, handleApprovalCreated, handleApprovalResolved, dispatchAutomation, queueTaskRun, getWorkerProcess, notify, logger, }: Pick< RegisterCoreHandlersDependencies, | "registerHandle" | "db" | "ensureCompanyExists" | "publishDomainChanged" | "emitEvent" | "wakeAgentIfPossible" | "handleApprovalCreated" | "handleApprovalResolved" | "dispatchAutomation" | "queueTaskRun" | "getWorkerProcess" | "notify" | "logger" >) { registerHandle(IPC_CHANNELS.requestApproval, async (_event, payload) => { const parsed = approvalInputSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; const invalidTask = parsed.relatedTaskId && !db.settings.belongsToCompany("tasks", parsed.relatedTaskId, parsed.companyId); const invalidAgent = parsed.requestedByAgentId && !db.settings.belongsToCompany("agents", parsed.requestedByAgentId, parsed.companyId); const invalidRelatedAgent = parsed.relatedAgentId && !db.settings.belongsToCompany("agents", parsed.relatedAgentId, parsed.companyId); if (invalidTask || invalidAgent || invalidRelatedAgent) { return fail("INVALID_COMPANY_REFERENCE", "The approval references data outside the current company."); } let id: string; try { id = db.approvalsMod.requestApproval(parsed); } catch (error) { return fail("APPROVAL_INVALID", error instanceof Error ? error.message : "Approval could not be created."); } db.activityMod.addActivity({ companyId: parsed.companyId, actor: parsed.requestedByAgentId ?? "board", action: "approval.created", entityType: "approval", entityId: id, detail: parsed.payloadSummary, }); // Notify thread integration about the new approval handleApprovalCreated(id, parsed.companyId, parsed.relatedTaskId ?? null, parsed.type, parsed.payloadSummary ?? ""); if (shouldAutoApprove(db, parsed.companyId, parsed.type)) { resolveApprovalDecision({ db, handleApprovalResolved, companyId: parsed.companyId, approvalId: id, state: "approved", decisionNote: "Auto-approved by company policy", detail: "Auto-approved by company policy", }); publishDomainChanged(); return ok(id); } // Approval was NOT auto-approved — wake decision-makers to review it dispatchAutomation("approval_created", { companyId: parsed.companyId, agentId: parsed.requestedByAgentId ?? undefined, taskId: parsed.relatedTaskId ?? undefined, }); notify({ title: "Approval Required", body: parsed.payloadSummary ?? `${parsed.type} approval pending`, urgency: "critical", navigation: { section: "approvals", entityId: id }, batchKey: "pending_approval", }); publishDomainChanged(); return ok(id); }); registerHandle(IPC_CHANNELS.decideApproval, async (_event, payload) => { const parsed = approvalDecisionSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.settings.belongsToCompany("approvals", parsed.approvalId, parsed.companyId)) { return fail("APPROVAL_NOT_FOUND", "Approval not found in the current company."); } const approval = db.approvalsMod.getApprovalRecord(parsed.approvalId); if (!approval || approval.companyId !== parsed.companyId) { return fail("APPROVAL_NOT_FOUND", "Approval not found in the current company."); } resolveApprovalDecision({ db, handleApprovalResolved, companyId: approval.companyId, approvalId: approval.id, state: parsed.state, decisionNote: parsed.decisionNote, detail: parsed.decisionNote, }); emitEvent({ type: "approval-updated", approvalId: approval.id, action: "decided" }); return ok(true); }); registerHandle(IPC_CHANNELS.startTaskRun, async (_event, payload) => { const parsed = startRunSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.settings.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) { return fail("TASK_NOT_FOUND", "Task not found in the current company."); } return queueTaskRun(parsed.taskId, "manual", "board"); }); registerHandle(IPC_CHANNELS.cancelRun, (_event, payload) => { const parsed = cancelRunSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) { return fail("RUN_NOT_FOUND", "Run not found in the current company."); } getWorkerProcess()?.postMessage({ type: "cancel-run", runId: parsed.runId }); return ok(true); }); registerHandle(IPC_CHANNELS.getRunLog, (_event, payload) => { const parsed = runLogSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) { return fail("RUN_NOT_FOUND", "Run not found in the current company."); } return ok(db.runs.readRunLog(parsed.runId)); }); registerHandle(IPC_CHANNELS.getRunLogChunk, (_event, payload) => { const parsed = runLogChunkSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.settings.belongsToCompany("runs", parsed.runId, parsed.companyId)) { return fail("RUN_NOT_FOUND", "Run not found in the current company."); } return ok(db.runs.readRunLogChunk(parsed.runId, parsed.offset, parsed.limit)); }); registerHandle(IPC_CHANNELS.addComment, async (_event, payload) => { const parsed = commentInputSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; const comment = db.activityMod.addComment({ companyId: parsed.companyId, taskId: parsed.taskId, authorAgentId: parsed.authorAgentId ?? null, authorName: parsed.authorName, body: parsed.body, }); db.activityMod.addActivity({ companyId: parsed.companyId, actor: parsed.authorAgentId ?? "board", action: "comment.created", entityType: "task", entityId: parsed.taskId, detail: parsed.body.slice(0, 100), }); emitEvent({ type: "agent-message", taskId: parsed.taskId, comment }); emitEvent({ type: "task-updated", taskId: parsed.taskId, action: "updated" }); try { const task = db.entityCrud.getTask(parsed.taskId); if (task.assigneeAgentId) { wakeAgentIfPossible(task.assigneeAgentId, task.companyId, "comment"); } dispatchAutomation("comment_posted", { companyId: parsed.companyId, taskId: parsed.taskId, agentId: parsed.authorAgentId ?? undefined, }); } catch (err) { logger.warn(`[ipc/core-operations] Post-comment wake/dispatch failed for task ${parsed.taskId}:`, err instanceof Error ? err.message : String(err)); } return ok(comment.id); }); registerHandle(IPC_CHANNELS.setHeartbeat, async (_event, payload) => { const parsed = heartbeatSettingsSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.settings.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } db.agents.setHeartbeat(parsed.agentId, parsed.enabled, parsed.intervalSec); db.activityMod.addActivity({ companyId: parsed.companyId, actor: "board", action: parsed.enabled ? "heartbeat.enabled" : "heartbeat.disabled", entityType: "agent", entityId: parsed.agentId, detail: `Interval: ${parsed.intervalSec}s`, }); emitEvent({ type: "heartbeat-status", agentId: parsed.agentId, enabled: parsed.enabled, nextWakeAt: null }); publishDomainChanged(); return ok(true); }); registerHandle(IPC_CHANNELS.triggerHeartbeat, async (_event, payload) => { const parsed = triggerHeartbeatSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.settings.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } const runId = wakeAgentIfPossible(parsed.agentId, parsed.companyId, parsed.trigger); if (!runId) { return fail("HEARTBEAT_SKIPPED", "Agent cannot be woken right now (check connector status, budget, or active runs)."); } return ok(runId); }); }