import { app, dialog, ipcMain, shell, type BrowserWindow } from "electron"; import type { DatabaseConnection } from "../database/connection"; import { IPC_CHANNELS } from "../../shared/ipc"; import { fail, ok, type DesktopResult } from "../../shared/result"; import type { AppSnapshot } from "../../shared/domain"; import { SnapshotService } from "../services/snapshot-service"; import { OrganizationService } from "../services/organization-service"; import { ApprovalService } from "../services/approval-service"; import { ChatService } from "../services/chat-service"; import { ProviderService } from "../services/provider-service"; import { ProviderOAuthService } from "../services/provider-oauth-service"; import { SkillService } from "../services/skill-service"; import { SocialService } from "../services/social-service"; import { FileService } from "../services/file-service"; import { MigrationService } from "../services/migration-service"; import { OnboardingService } from "../services/onboarding-service"; import { ComplianceService } from "../services/compliance-service"; import { SearchService } from "../services/search-service"; import { IdentityAccessService } from "../services/identity-access-service"; import { OperationsService } from "../services/operations-service"; import { SdlcService } from "../services/sdlc-service"; import { AuditService } from "../services/audit-service"; import { RollbackService } from "../services/rollback-service"; import { TaskExecutionService } from "../services/task-execution-service"; import { DocumentService } from "../services/document-service"; import { KnowledgeService } from "../services/knowledge-service"; import { AutomationService } from "../services/automation-service"; import { MeetingService } from "../services/meeting-service"; import { SprintService } from "../services/sprint-service"; import { ComposioConnectionService } from "../services/composio/composio-connection-service"; import { IntegrationKeyStore } from "../services/integration-key-store"; import { importStagedSkills, stageBuiltinSkillsLibrary } from "../services/builtin-skills"; import { coalesce } from "./coalesce"; import { normalizeExternalBrowserUrl } from "./external-url"; export interface IpcDeps { connection: DatabaseConnection; mainWindow: BrowserWindow; artifactRoot: string; /** Bundled built-in skills source (repo folder in dev, resources when packaged). */ builtinSkillsSource: string; /** Normalized on-disk skill library under the profile directory. */ builtinSkillsLibrary: string; } export function registerIpcHandlers(deps: IpcDeps): void { const snapshot = new SnapshotService(deps.connection.db); const organization = new OrganizationService(deps.connection.db); const approvals = new ApprovalService(deps.connection.db); const chat = new ChatService(deps.connection.db); const providers = new ProviderService(deps.connection.db); const integrationKeys = new IntegrationKeyStore(deps.connection.db); const providerOAuth = new ProviderOAuthService(integrationKeys); const skills = new SkillService(deps.connection.db, deps.artifactRoot); const notifyDataChanged = coalesce(() => { try { if (!deps.mainWindow.isDestroyed() && !deps.mainWindow.webContents.isDestroyed()) { deps.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "snapshot-changed" }); } } catch { /* renderer may have been disposed during agent run */ } }, 200); const taskExecution = new TaskExecutionService(deps.connection.db, providers, skills, notifyDataChanged); const social = new SocialService(deps.connection.db); const files = new FileService(deps.connection.db, deps.artifactRoot); const migrations = new MigrationService(deps.connection.db); const compliance = new ComplianceService(deps.connection.db); const search = new SearchService(deps.connection.db); const identityAccess = new IdentityAccessService(deps.connection.db); const operations = new OperationsService(deps.connection.db); const sdlc = new SdlcService(deps.connection.db); const audit = new AuditService(deps.connection.db); const rollback = new RollbackService(deps.connection.db); const documents = new DocumentService(deps.connection.db); const knowledge = new KnowledgeService(deps.connection.db); const automation = new AutomationService(deps.connection.db); const meetingService = new MeetingService(deps.connection.db); const sprintService = new SprintService(deps.connection.db); const onboarding = new OnboardingService({ db: deps.connection.db, saveProviderCredential: (input) => providers.saveCredential(input), }); const mutate = (operation: () => T): DesktopResult => { try { deps.connection.transaction(operation); notifyDataChanged(); return ok(snapshot.load()); } catch (error) { return fail("operation_failed", error instanceof Error ? error.message : String(error)); } }; const query = (code: string, read: () => T): DesktopResult => { try { return ok(read()); } catch (error) { return fail(code, error instanceof Error ? error.message : String(error)); } }; ipcMain.handle(IPC_CHANNELS.loadSnapshot, () => { try { return ok(snapshot.load()); } catch (error) { return fail("snapshot_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.bootstrapOnboarding, (_event, input) => { try { let summary: { orgId: string } | undefined; // Staging happens before the transaction (file mirroring), the import // inside it: the CEO's kickoff run can be claimed within seconds of // commit and must already find the forced humanizer skill. const staged = stageBuiltinSkillsLibrary(deps.builtinSkillsSource, deps.builtinSkillsLibrary); deps.connection.transaction(() => { summary = onboarding.bootstrap(input); const report = importStagedSkills(skills, summary.orgId, staged); if (report.failures.length > 0) { console.warn(`[skills] onboarding import failures (${report.failures.length}): ${report.failures.slice(0, 3).join("; ")}`); } }); notifyDataChanged(); return ok(summary); } catch (error) { return fail("onboarding_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.createOrganization, (_event, input) => mutate(() => organization.createOrganization(input))); ipcMain.handle(IPC_CHANNELS.setActiveOrganization, (_event, input) => mutate(() => organization.setActiveOrganization(input.orgId))); ipcMain.handle(IPC_CHANNELS.setUiPreferences, (_event, input) => { const before = snapshot.load().uiPreferences.language; const result = mutate(() => organization.updateUiPreferences(input)); if (result.ok && result.data.uiPreferences.language !== before) { // Chromium's locale is fixed at launch; a language change needs a // fresh process to take effect. setTimeout(() => { app.relaunch(); app.quit(); }, 150); } return result; }); ipcMain.handle(IPC_CHANNELS.deleteOrganization, (_event, input) => mutate(() => organization.deleteOrganization(input.orgId))); ipcMain.handle(IPC_CHANNELS.createAgent, (_event, input) => mutate(() => organization.createAgent(input))); ipcMain.handle(IPC_CHANNELS.updateAgent, (_event, input) => mutate(() => organization.updateAgent(input))); ipcMain.handle(IPC_CHANNELS.updateEmployeeProfile, (_event, input) => mutate(() => organization.updateEmployeeProfile(input))); ipcMain.handle(IPC_CHANNELS.setAutonomousPaused, (_event, input) => mutate(() => organization.setAutonomousPaused(Boolean(input.paused)))); ipcMain.handle(IPC_CHANNELS.setIntegrationKey, (_event, input) => mutate(() => integrationKeys.write(input.name, input.value))); ipcMain.handle(IPC_CHANNELS.clearIntegrationKey, (_event, input) => mutate(() => integrationKeys.clear(input.name))); ipcMain.handle(IPC_CHANNELS.createTask, (_event, input) => mutate(() => organization.createTask(input))); ipcMain.handle(IPC_CHANNELS.updateTask, (_event, input) => mutate(() => organization.updateTask(input))); ipcMain.handle(IPC_CHANNELS.deleteTask, (_event, input) => mutate(() => organization.deleteTask(input))); ipcMain.handle(IPC_CHANNELS.updateTaskStatus, (_event, input) => mutate(() => organization.updateTaskStatus(input.orgId, input.taskId, input.status))); ipcMain.handle(IPC_CHANNELS.runTask, async (_event, input) => { try { await taskExecution.runTask(input); notifyDataChanged(); return ok(snapshot.load()); } catch (error) { notifyDataChanged(); return fail("task_run_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.createGoal, (_event, input) => mutate(() => organization.createGoal(input))); ipcMain.handle(IPC_CHANNELS.createProject, (_event, input) => mutate(() => organization.createProject(input))); ipcMain.handle(IPC_CHANNELS.createWorkspace, (_event, input) => mutate(() => organization.createWorkspace(input))); ipcMain.handle(IPC_CHANNELS.createDocument, (_event, input) => mutate(() => documents.create(input))); ipcMain.handle(IPC_CHANNELS.updateDocument, (_event, input) => mutate(() => documents.update(input))); ipcMain.handle(IPC_CHANNELS.deleteDocument, (_event, input) => mutate(() => documents.delete(input))); ipcMain.handle(IPC_CHANNELS.createKnowledgeEntry, (_event, input) => mutate(() => knowledge.create(input))); ipcMain.handle(IPC_CHANNELS.deleteKnowledgeEntry, (_event, input) => mutate(() => knowledge.delete(input))); ipcMain.handle(IPC_CHANNELS.updateKnowledgeEntry, (_event, input) => mutate(() => knowledge.update(input))); ipcMain.handle(IPC_CHANNELS.listDocumentVersions, (_event, input) => query("document_versions_failed", () => documents.versions(input.orgId, input.documentId).map((v) => ({ version: v.version, title: v.title, content: v.content, status: v.status, changedByType: v.changed_by_type, changedById: v.changed_by_id, reason: v.reason, createdAt: v.created_at, })))); ipcMain.handle(IPC_CHANNELS.listKnowledgeRevisions, (_event, input) => query("knowledge_revisions_failed", () => knowledge.revisions(input.orgId, input.entryId).map((r) => ({ revision: r.revision, topic: r.topic, category: r.category, content: r.content, importance: r.importance, changedByType: r.changed_by_type, changedById: r.changed_by_id, reason: r.reason, createdAt: r.created_at, })))); ipcMain.handle(IPC_CHANNELS.createAutomationRule, (_event, input) => mutate(() => automation.create(input))); ipcMain.handle(IPC_CHANNELS.updateAutomationRule, (_event, input) => mutate(() => automation.update(input))); ipcMain.handle(IPC_CHANNELS.deleteAutomationRule, (_event, input) => mutate(() => automation.delete(input))); ipcMain.handle(IPC_CHANNELS.createMeeting, (_event, input) => mutate(() => meetingService.create(input))); ipcMain.handle(IPC_CHANNELS.deleteMeeting, (_event, input) => mutate(() => meetingService.delete(input))); ipcMain.handle(IPC_CHANNELS.createSprint, (_event, input) => mutate(() => sprintService.create(input))); ipcMain.handle(IPC_CHANNELS.deleteSprint, (_event, input) => mutate(() => sprintService.delete(input))); ipcMain.handle(IPC_CHANNELS.createApproval, (_event, input) => mutate(() => approvals.create(input))); ipcMain.handle(IPC_CHANNELS.decideApproval, (_event, input) => mutate(() => approvals.decide(input))); ipcMain.handle(IPC_CHANNELS.syncEmployeeIdentity, (_event, input) => mutate(() => identityAccess.syncEmployeeIdentity(input))); ipcMain.handle(IPC_CHANNELS.grantRole, (_event, input) => mutate(() => identityAccess.grantRole(input))); ipcMain.handle(IPC_CHANNELS.createChatSpace, (_event, input) => mutate(() => chat.createSpace(input))); ipcMain.handle(IPC_CHANNELS.sendMessage, (_event, input) => mutate(() => chat.sendMessage(input))); ipcMain.handle(IPC_CHANNELS.editMessage, (_event, input) => mutate(() => chat.editMessage(input))); ipcMain.handle(IPC_CHANNELS.deleteMessage, (_event, input) => mutate(() => chat.deleteMessage(input))); ipcMain.handle(IPC_CHANNELS.markChatSpaceRead, (_event, input) => mutate(() => chat.markSpaceRead(input))); ipcMain.handle(IPC_CHANNELS.saveProviderCredential, (_event, input) => mutate(() => providers.saveCredential(input))); ipcMain.handle(IPC_CHANNELS.revokeProviderCredential, (_event, input) => mutate(() => providers.revokeCredential(input))); ipcMain.handle(IPC_CHANNELS.updateProviderPolicy, (_event, input) => mutate(() => providers.updatePolicy(input))); ipcMain.handle(IPC_CHANNELS.checkProviderHealth, async (_event, input) => { try { await providers.checkHealth(input.orgId, input.providerId); notifyDataChanged(); return ok(snapshot.load()); } catch (error) { return fail("provider_health_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.listProviderModels, async (_event, input) => { try { return ok(await providers.listModels(input)); } catch (error) { return fail("provider_models_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.launchProviderSignIn, (_event, input) => { try { providers.launchSignInWindow(input); return ok(null); } catch (error) { return fail("provider_sign_in_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.startProviderOAuth, async (_event, input) => { const emit = (event: Record) => deps.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "provider-oauth", providerKey: input.providerKey, ...event }); try { const secret = await providerOAuth.run( { providerKey: input.providerKey, enterpriseDomain: input.enterpriseDomain ?? null }, { onAuth: ({ url, instructions }) => { const browserUrl = normalizeExternalBrowserUrl(url); void shell.openExternal(browserUrl); emit({ phase: "authorize", url: browserUrl, instructions }); }, onProgress: (message) => emit({ phase: "progress", message }), }, ); if (input.orgId && input.providerId && input.employeeId) { providers.saveCredential({ orgId: input.orgId, providerId: input.providerId, secret, model: input.model ?? null, employeeId: input.employeeId, }); emit({ phase: "active" }); notifyDataChanged(); return ok({ secret: null }); } emit({ phase: "active" }); return ok({ secret }); } catch (error) { const message = error instanceof Error ? error.message : String(error); emit({ phase: message === "Login cancelled" ? "cancelled" : "error", message }); return fail("provider_oauth_failed", message); } }); ipcMain.handle(IPC_CHANNELS.cancelProviderOAuth, (_event, input) => { providerOAuth.cancel(input.providerKey); return ok(null); }); ipcMain.handle(IPC_CHANNELS.openExternalUrl, async (_event, url: string) => { try { await shell.openExternal(normalizeExternalBrowserUrl(url)); return ok(null); } catch (error) { return fail("external_url_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.createProviderResponse, async (_event, input) => { try { await providers.createResponse(input); notifyDataChanged(); return ok(snapshot.load()); } catch (error) { return fail("provider_response_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.streamProviderResponse, async (_event, input) => { try { await providers.streamResponse(input, (streamEvent) => { deps.mainWindow.webContents.send(IPC_CHANNELS.rendererEvent, { type: "provider-response-stream", event: streamEvent }); }); notifyDataChanged(); return ok(snapshot.load()); } catch (error) { return fail("provider_stream_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.cancelProviderResponse, async (_event, input) => { try { await providers.cancelResponse(input); notifyDataChanged(); return ok(snapshot.load()); } catch (error) { return fail("provider_cancel_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.createSocialAccount, (_event, input) => mutate(() => social.createAccount(input))); ipcMain.handle(IPC_CHANNELS.createScheduledPost, (_event, input) => mutate(() => social.createScheduledPost(input))); ipcMain.handle(IPC_CHANNELS.setScheduledPostState, (_event, input) => mutate(() => social.setScheduledPostState(input))); ipcMain.handle(IPC_CHANNELS.setFilePinned, (_event, input) => mutate(() => files.setPinned(input.orgId, input.fileId, input.pinned))); ipcMain.handle(IPC_CHANNELS.launchSocialLogin, (_event, input) => { try { social.launchLoginWindow(input); return ok(null); } catch (error) { return fail("social_login_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.importFile, async (_event, input) => { try { const fileId = await files.importFile(input); if (fileId) notifyDataChanged(); return ok(fileId); } catch (error) { return fail("file_import_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.openFile, async (_event, input) => { try { await files.openFile(input); return ok(null); } catch (error) { return fail("file_open_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.pickDirectory, async () => { try { const result = await dialog.showOpenDialog(deps.mainWindow, { properties: ["openDirectory", "createDirectory"] }); if (result.canceled || result.filePaths.length === 0) return ok(null); return ok(result.filePaths[0]); } catch (error) { return fail("pick_directory_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.createMigrationReport, (_event, input) => mutate(() => migrations.createReport(input))); ipcMain.handle(IPC_CHANNELS.createRollbackSnapshot, (_event, input) => mutate(() => rollback.createSnapshot(input))); ipcMain.handle(IPC_CHANNELS.setRollbackSnapshotActive, (_event, input) => mutate(() => rollback.setActive(input))); ipcMain.handle(IPC_CHANNELS.resolveMigrationReconciliation, (_event, input) => mutate(() => rollback.resolveReconciliationItem(input))); ipcMain.handle(IPC_CHANNELS.setFeatureFlag, (_event, input) => mutate(() => operations.setFeatureFlag(input))); ipcMain.handle(IPC_CHANNELS.runChaosDrill, (_event, input) => mutate(() => operations.runChaosDrill(input))); ipcMain.handle(IPC_CHANNELS.runSdlcReview, (_event, input) => mutate(() => sdlc.runReview(input))); ipcMain.handle(IPC_CHANNELS.runEvalSuite, (_event, input) => mutate(() => sdlc.runEvalSuite(input))); ipcMain.handle(IPC_CHANNELS.setPrBotGate, (_event, input) => mutate(() => sdlc.setPrBotGate(input))); ipcMain.handle(IPC_CHANNELS.createComplianceExport, (_event, input) => mutate(() => compliance.createExport(input))); ipcMain.handle(IPC_CHANNELS.createAuditReplay, (_event, input) => mutate(() => audit.createThreadReplay(input))); ipcMain.handle(IPC_CHANNELS.createLegalHold, (_event, input) => mutate(() => compliance.createLegalHold(input))); ipcMain.handle(IPC_CHANNELS.setLegalHoldState, (_event, input) => mutate(() => compliance.setLegalHoldState(input))); ipcMain.handle(IPC_CHANNELS.createRetentionFreeze, (_event, input) => mutate(() => compliance.createRetentionFreeze(input))); ipcMain.handle(IPC_CHANNELS.setRetentionFreezeState, (_event, input) => mutate(() => compliance.setRetentionFreezeState(input))); ipcMain.handle(IPC_CHANNELS.search, (_event, input) => { try { return ok(search.search(input)); } catch (error) { return fail("search_failed", error instanceof Error ? error.message : String(error)); } }); const composioConnections = new ComposioConnectionService(deps.connection.db, integrationKeys); ipcMain.handle(IPC_CHANNELS.listConnectorCatalog, async () => { try { return ok(await composioConnections.listCatalog()); } catch (error) { return fail("composio_catalog_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.connectComposioToolkit, async (_event, input) => { try { const result = await composioConnections.connectToolkit(input.orgId, input.toolkitSlug); notifyDataChanged(); return ok(result); } catch (error) { return fail("composio_connect_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.disconnectComposioToolkit, async (_event, input) => { try { await composioConnections.disconnectToolkit(input.orgId, input.toolkitSlug); notifyDataChanged(); return ok(snapshot.load()); } catch (error) { return fail("composio_disconnect_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.refreshComposioAccounts, async (_event, input) => { try { await composioConnections.refreshAccounts(input.orgId); notifyDataChanged(); return ok(snapshot.load()); } catch (error) { return fail("composio_refresh_failed", error instanceof Error ? error.message : String(error)); } }); ipcMain.handle(IPC_CHANNELS.windowMinimize, () => deps.mainWindow.minimize()); ipcMain.handle(IPC_CHANNELS.windowMaximize, () => deps.mainWindow.isMaximized() ? deps.mainWindow.unmaximize() : deps.mainWindow.maximize()); ipcMain.handle(IPC_CHANNELS.windowClose, () => deps.mainWindow.close()); }