import { dialog, nativeTheme, shell } from "electron"; import { join } from "node:path"; import { z } from "zod"; import { IPC_CHANNELS, connectorInputSchema, entityGetterSchema, metricsQuerySchema, settingsSchema, wakeupQuerySchema, } from "@shared/contracts"; import { fail, ok, type RegisterCoreHandlersDependencies } from "./ipc-core-common"; export function registerCoreReadHandlers({ registerHandle, db, ensureCompanyExists, publishDomainChanged, checkForUpdates, resetAfterRestore, getApiPort, listConnectorModels, }: Pick< RegisterCoreHandlersDependencies, | "registerHandle" | "db" | "ensureCompanyExists" | "publishDomainChanged" | "checkForUpdates" | "resetAfterRestore" | "getApiPort" | "listConnectorModels" >) { registerHandle(IPC_CHANNELS.loadSnapshot, () => ok(db.listRendererSnapshot())); registerHandle(IPC_CHANNELS.pickDirectory, async () => { const result = await dialog.showOpenDialog({ properties: ["openDirectory", "createDirectory"], }); return ok(result.canceled ? null : (result.filePaths[0] ?? null)); }); registerHandle(IPC_CHANNELS.openPath, async (_event, targetPath) => { if (typeof targetPath !== "string" || targetPath.length === 0) { return fail("INVALID_PATH", "A valid path is required."); } if (!db.canOpenPath(targetPath)) { return fail("OPEN_PATH_FORBIDDEN", "The requested path is outside the managed desktop workspace."); } const error = await shell.openPath(targetPath); return error ? fail("OPEN_PATH_FAILED", error) : ok(true); }); registerHandle(IPC_CHANNELS.backupProfile, async () => { const result = await dialog.showOpenDialog({ properties: ["openDirectory", "createDirectory"], }); if (result.canceled || result.filePaths.length === 0) { return ok(null); } const backupPath = join(result.filePaths[0]!, `agentcompany-backup-${Date.now()}`); await db.exportBackup(backupPath); return ok(backupPath); }); registerHandle(IPC_CHANNELS.restoreProfile, async () => { const hasActiveRuns = db.listSnapshot().runs.some((run: { status: string }) => run.status === "queued" || run.status === "running"); if (hasActiveRuns) { return fail("RESTORE_BLOCKED", "Stop all active runs before restoring a profile backup."); } const result = await dialog.showOpenDialog({ properties: ["openDirectory"], }); if (result.canceled || result.filePaths.length === 0) { return ok(false); } await db.restoreFromBackup(result.filePaths[0]!); resetAfterRestore(); publishDomainChanged(); return ok(true); }); registerHandle(IPC_CHANNELS.updateSettings, async (_event, payload) => { const parsed = settingsSchema.parse(payload); db.updateSettings(parsed); if (parsed.theme) { nativeTheme.themeSource = parsed.theme; } publishDomainChanged(); return ok(true); }); registerHandle(IPC_CHANNELS.checkForUpdates, async () => { return ok(await checkForUpdates()); }); registerHandle(IPC_CHANNELS.getSidebarBadges, async (_event, companyId) => { if (typeof companyId !== "string" || !companyId) { return fail("INVALID_COMPANY_ID", "A valid company id is required."); } return ok(db.getSidebarBadges(companyId)); }); registerHandle(IPC_CHANNELS.getApiPort, () => ok(getApiPort())); registerHandle(IPC_CHANNELS.listConnectorModels, async (_event, connectorId) => { try { const parsed = connectorInputSchema.shape.id.parse(connectorId); return ok(await listConnectorModels(parsed)); } catch { return ok([]); } }); registerHandle(IPC_CHANNELS.getConnectorDisabledModels, (_event, connectorId) => { try { const parsed = connectorInputSchema.shape.id.parse(connectorId); return ok(db.getDisabledModels(parsed)); } catch { return ok([]); } }); registerHandle(IPC_CHANNELS.getInbox, (_event, payload) => { const companyId = z.string().uuid().parse(payload); const err = ensureCompanyExists(companyId); if (err) return err; return ok(db.getInbox(companyId)); }); registerHandle(IPC_CHANNELS.getCompanyMetrics, (_event, payload) => { const input = metricsQuerySchema.parse(payload); const err = ensureCompanyExists(input.companyId); if (err) return err; return ok(db.getCompanyMetrics(input.companyId, input.days)); }); registerHandle(IPC_CHANNELS.getAgentMetrics, (_event, payload) => { const input = metricsQuerySchema.parse(payload); const err = ensureCompanyExists(input.companyId); if (err) return err; if (!input.agentId) { return fail("MISSING_AGENT_ID", "agentId is required"); } const metrics = db.getCompanyMetrics(input.companyId, input.days); const agentMetric = metrics.agentMetrics.find((metric: { agentId: string }) => metric.agentId === input.agentId); if (!agentMetric) { return fail("AGENT_NOT_FOUND", "Agent metrics not found"); } return ok(agentMetric); }); registerHandle(IPC_CHANNELS.getStandupReport, (_event, payload) => { const companyId = z.string().uuid().parse(payload); const err = ensureCompanyExists(companyId); if (err) return err; return ok(db.getStandupReport(companyId)); }); registerHandle(IPC_CHANNELS.listWakeupRequests, (_event, input) => { const parsed = wakeupQuerySchema.parse(input); const err = ensureCompanyExists(parsed.companyId); if (err) return err; return ok(db.listWakeupRequests(parsed.companyId, { status: parsed.status, agentId: parsed.agentId, limit: parsed.limit, })); }); // --- Entity getters for granular IPC updates --- registerHandle(IPC_CHANNELS.getTask, (_event, payload) => { const parsed = entityGetterSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; try { const task = db.getTask(parsed.id); if (task.companyId !== parsed.companyId) return fail("TASK_NOT_FOUND", "Task not found in current company."); return ok(task); } catch { return fail("TASK_NOT_FOUND", "Task not found."); } }); registerHandle(IPC_CHANNELS.getAgent, (_event, payload) => { const parsed = entityGetterSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; try { const agent = db.getAgent(parsed.id); if (agent.companyId !== parsed.companyId) return fail("AGENT_NOT_FOUND", "Agent not found in current company."); return ok(agent); } catch { return fail("AGENT_NOT_FOUND", "Agent not found."); } }); registerHandle(IPC_CHANNELS.getRun, (_event, payload) => { const parsed = entityGetterSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; try { const run = db.getRun(parsed.id); if (run.companyId !== parsed.companyId) return fail("RUN_NOT_FOUND", "Run not found in current company."); return ok(run); } catch { return fail("RUN_NOT_FOUND", "Run not found."); } }); registerHandle(IPC_CHANNELS.getApproval, (_event, payload) => { const parsed = entityGetterSchema.parse(payload); const companyError = ensureCompanyExists(parsed.companyId); if (companyError) return companyError; try { const approval = db.getApprovalRecord(parsed.id); if (approval.companyId !== parsed.companyId) return fail("APPROVAL_NOT_FOUND", "Approval not found in current company."); return ok(approval); } catch { return fail("APPROVAL_NOT_FOUND", "Approval not found."); } }); }