import { IPC_CHANNELS, approvalDecisionSchema, approvalInputSchema, cancelRunSchema, commentInputSchema, getAgentPermissionsSchema, getConfigRevisionSchema, heartbeatPolicySchema, heartbeatSettingsSchema, listCommentsSchema, listConfigRevisionsSchema, listRunEventsSchema, rollbackConfigRevisionSchema, runtimeStateSchema, runLogChunkSchema, runLogSchema, setAgentPermissionSchema, setHeartbeatPolicySchema, setRuntimeStateSchema, startRunSchema, triggerHeartbeatSchema, } from "@shared/contracts"; import { resolveApprovalDecision, shouldAutoApproveApproval } from "./approval-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, }: Pick< RegisterCoreHandlersDependencies, | "registerHandle" | "db" | "ensureCompanyExists" | "publishDomainChanged" | "emitEvent" | "wakeAgentIfPossible" | "handleApprovalCreated" | "handleApprovalResolved" | "dispatchAutomation" | "queueTaskRun" | "getWorkerProcess" | "notify" >) { 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.belongsToCompany("tasks", parsed.relatedTaskId, parsed.companyId); const invalidAgent = parsed.requestedByAgentId && !db.belongsToCompany("agents", parsed.requestedByAgentId, parsed.companyId); const invalidRelatedAgent = parsed.relatedAgentId && !db.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.requestApproval(parsed); } catch (error) { return fail("APPROVAL_INVALID", error instanceof Error ? error.message : "Approval could not be created."); } db.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 (shouldAutoApproveApproval(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.belongsToCompany("approvals", parsed.approvalId, parsed.companyId)) { return fail("APPROVAL_NOT_FOUND", "Approval not found in the current company."); } const approval = db.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.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.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.belongsToCompany("runs", parsed.runId, parsed.companyId)) { return fail("RUN_NOT_FOUND", "Run not found in the current company."); } return ok(db.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.belongsToCompany("runs", parsed.runId, parsed.companyId)) { return fail("RUN_NOT_FOUND", "Run not found in the current company."); } return ok(db.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.addComment({ companyId: parsed.companyId, taskId: parsed.taskId, authorAgentId: parsed.authorAgentId ?? null, authorName: parsed.authorName, body: parsed.body, }); db.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.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 { // Task may have been deleted between comment creation and wake evaluation. } return ok(comment.id); }); registerHandle(IPC_CHANNELS.listComments, async (_event, payload) => { const parsed = listCommentsSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.belongsToCompany("tasks", parsed.taskId, parsed.companyId)) { return fail("TASK_NOT_FOUND", "Task not found in the current company."); } return ok(db.listComments(parsed.taskId)); }); registerHandle(IPC_CHANNELS.setHeartbeat, async (_event, payload) => { const parsed = heartbeatSettingsSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } db.setHeartbeat(parsed.agentId, parsed.enabled, parsed.intervalSec); db.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.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); }); registerHandle(IPC_CHANNELS.listConfigRevisions, async (_event, payload) => { const parsed = listConfigRevisionsSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } return ok(db.listConfigRevisions(parsed.agentId, parsed.companyId)); }); registerHandle(IPC_CHANNELS.getConfigRevision, async (_event, payload) => { const parsed = getConfigRevisionSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; return ok(db.getConfigRevision(parsed.revisionId, parsed.companyId)); }); registerHandle(IPC_CHANNELS.rollbackConfigRevision, async (_event, payload) => { const parsed = rollbackConfigRevisionSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } const newRevisionId = db.rollbackConfigRevision(parsed.agentId, parsed.revisionId, parsed.companyId); publishDomainChanged(); return ok({ revisionId: newRevisionId }); }); registerHandle(IPC_CHANNELS.listRunEvents, async (_event, payload) => { const parsed = listRunEventsSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.belongsToCompany("runs", parsed.runId, parsed.companyId)) { return fail("RUN_NOT_FOUND", "Run not found in the current company."); } return ok(db.listRunEvents(parsed.runId, parsed.companyId, { offset: parsed.offset, limit: parsed.limit })); }); registerHandle(IPC_CHANNELS.getAgentPermissions, async (_event, payload) => { const parsed = getAgentPermissionsSchema.parse(payload); return ok(db.getAgentPermissions(parsed.agentId)); }); registerHandle(IPC_CHANNELS.setAgentPermission, async (_event, payload) => { const parsed = setAgentPermissionSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } db.setAgentPermission(parsed.agentId, parsed.companyId, parsed.permissionKey, parsed.enabled); publishDomainChanged(); return ok(null); }); registerHandle(IPC_CHANNELS.getHeartbeatPolicy, async (_event, payload) => { const parsed = heartbeatPolicySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } return ok(db.getHeartbeatPolicy(parsed.agentId)); }); registerHandle(IPC_CHANNELS.setHeartbeatPolicy, async (_event, payload) => { const parsed = setHeartbeatPolicySchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } const { companyId, agentId, ...policy } = parsed; db.setHeartbeatPolicy(agentId, companyId, policy); publishDomainChanged(); return ok(null); }); registerHandle(IPC_CHANNELS.getRuntimeState, async (_event, payload) => { const parsed = runtimeStateSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } return ok(db.getRuntimeState(parsed.agentId)); }); registerHandle(IPC_CHANNELS.setRuntimeState, async (_event, payload) => { const parsed = setRuntimeStateSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; if (!db.belongsToCompany("agents", parsed.agentId, parsed.companyId)) { return fail("AGENT_NOT_FOUND", "Agent not found in the current company."); } db.setRuntimeState(parsed.agentId, parsed.companyId, parsed.state); return ok(null); }); }