import { ipcRenderer, contextBridge } from 'electron' // ============================================================================ // Type definitions for IPC API // ============================================================================ export interface HubStatus { hubId: string status: string agentCount: number gatewayConnected: boolean gatewayUrl?: string defaultAgent?: { agentId: string status: string } | null } export interface AgentInfo { agentId: string status: string } export interface ToolInfo { name: string group: string enabled: boolean } export interface SkillInfo { id: string name: string description: string version: string enabled: boolean source: 'bundled' | 'global' | 'profile' triggers: string[] } export interface ProfileData { profileId: string | undefined name: string | undefined style: string | undefined userContent: string | undefined } export interface ProviderStatus { id: string name: string authMethod: 'api-key' | 'oauth' available: boolean configured: boolean current: boolean defaultModel: string models: string[] loginUrl?: string loginCommand?: string loginInstructions?: string } export interface CurrentProviderInfo { provider: string model: string | undefined providerName: string | undefined available: boolean } // Local chat event types (for direct IPC communication without Gateway) export interface LocalChatEvent { agentId: string streamId?: string type?: 'error' content?: string event?: { type: 'message_start' | 'message_update' | 'message_end' | 'tool_execution_start' | 'tool_execution_end' | 'compaction_start' | 'compaction_end' id?: string message?: { role: string content?: Array<{ type: string; text?: string }> } [key: string]: unknown } } // Local chat approval request (mirrors ExecApprovalRequestPayload from @multica/sdk) export interface LocalChatApproval { approvalId: string agentId: string command: string cwd?: string riskLevel: 'safe' | 'needs-review' | 'dangerous' riskReasons: string[] expiresAtMs: number } // Available style options export const AGENT_STYLES = ['concise', 'warm', 'playful', 'professional'] as const export type AgentStyle = (typeof AGENT_STYLES)[number] // ============================================================================ // Expose typed API to Renderer process // ============================================================================ const electronAPI = { // Hub management hub: { init: () => ipcRenderer.invoke('hub:init'), getStatus: (): Promise => ipcRenderer.invoke('hub:getStatus'), getAgentInfo: (): Promise => ipcRenderer.invoke('hub:getAgentInfo'), info: () => ipcRenderer.invoke('hub:info'), reconnect: (url: string) => ipcRenderer.invoke('hub:reconnect', url), listAgents: () => ipcRenderer.invoke('hub:listAgents'), createAgent: (id?: string) => ipcRenderer.invoke('hub:createAgent', id), getAgent: (id: string) => ipcRenderer.invoke('hub:getAgent', id), closeAgent: (id: string) => ipcRenderer.invoke('hub:closeAgent', id), sendMessage: (agentId: string, content: string) => ipcRenderer.invoke('hub:sendMessage', agentId, content), registerToken: (token: string, agentId: string, expiresAt: number) => ipcRenderer.invoke('hub:registerToken', token, agentId, expiresAt), onDeviceConfirmRequest: (callback: (deviceId: string, meta?: { userAgent?: string; platform?: string; language?: string }) => void) => { ipcRenderer.on('hub:device-confirm-request', (_event, deviceId: string, meta?: { userAgent?: string; platform?: string; language?: string }) => callback(deviceId, meta)) }, offDeviceConfirmRequest: () => { ipcRenderer.removeAllListeners('hub:device-confirm-request') }, deviceConfirmResponse: (deviceId: string, allowed: boolean) => { ipcRenderer.send('hub:device-confirm-response', deviceId, allowed) }, listDevices: () => ipcRenderer.invoke('hub:listDevices'), revokeDevice: (deviceId: string) => ipcRenderer.invoke('hub:revokeDevice', deviceId), onConnectionStateChanged: (callback: (state: string) => void) => { ipcRenderer.on('hub:connection-state-changed', (_event, state: string) => callback(state)) }, offConnectionStateChanged: () => { ipcRenderer.removeAllListeners('hub:connection-state-changed') }, onDevicesChanged: (callback: () => void) => { ipcRenderer.on('hub:devices-changed', () => callback()) }, offDevicesChanged: () => { ipcRenderer.removeAllListeners('hub:devices-changed') }, }, // Tools management tools: { list: (): Promise => ipcRenderer.invoke('tools:list'), toggle: (name: string) => ipcRenderer.invoke('tools:toggle', name), setStatus: (name: string, enabled: boolean) => ipcRenderer.invoke('tools:setStatus', name, enabled), active: () => ipcRenderer.invoke('tools:active'), reload: () => ipcRenderer.invoke('tools:reload'), }, // Skills management skills: { list: (): Promise => ipcRenderer.invoke('skills:list'), get: (id: string) => ipcRenderer.invoke('skills:get', id), toggle: (id: string) => ipcRenderer.invoke('skills:toggle', id), setStatus: (id: string, enabled: boolean) => ipcRenderer.invoke('skills:setStatus', id, enabled), reload: () => ipcRenderer.invoke('skills:reload'), add: (source: string, options?: { name?: string; force?: boolean }) => ipcRenderer.invoke('skills:add', source, options), remove: (name: string) => ipcRenderer.invoke('skills:remove', name), }, // Agent management agent: { status: () => ipcRenderer.invoke('agent:status'), }, // Profile management profile: { get: (): Promise => ipcRenderer.invoke('profile:get'), updateName: (name: string) => ipcRenderer.invoke('profile:updateName', name), updateStyle: (style: string) => ipcRenderer.invoke('profile:updateStyle', style), updateUser: (content: string) => ipcRenderer.invoke('profile:updateUser', content), }, // Provider management provider: { /** List all providers with their status */ list: (): Promise => ipcRenderer.invoke('provider:list'), /** List only available (configured) providers */ listAvailable: (): Promise => ipcRenderer.invoke('provider:listAvailable'), /** Get current provider and model from the active agent */ current: (): Promise => ipcRenderer.invoke('provider:current'), /** Switch the agent to a different provider and/or model */ set: (providerId: string, modelId?: string): Promise<{ ok: boolean; provider?: string; model?: string; error?: string }> => ipcRenderer.invoke('provider:set', providerId, modelId), /** Get metadata for a specific provider */ getMeta: (providerId: string) => ipcRenderer.invoke('provider:getMeta', providerId), /** Check if a specific provider is available */ isAvailable: (providerId: string): Promise => ipcRenderer.invoke('provider:isAvailable', providerId), /** Save API key for a provider */ saveApiKey: (providerId: string, apiKey: string): Promise<{ ok: boolean; error?: string }> => ipcRenderer.invoke('provider:saveApiKey', providerId, apiKey), /** Import OAuth credentials from CLI tools (claude-code, codex) */ importOAuth: (providerId: string): Promise<{ ok: boolean; expiresAt?: number; error?: string }> => ipcRenderer.invoke('provider:importOAuth', providerId), }, // Channel management (Telegram, Discord, etc.) channels: { /** List all channel account states */ listStates: () => ipcRenderer.invoke('channels:listStates'), /** Get channels config from credentials.json5 */ getConfig: () => ipcRenderer.invoke('channels:getConfig'), /** Save a channel token and start the bot immediately */ saveToken: (channelId: string, accountId: string, token: string) => ipcRenderer.invoke('channels:saveToken', channelId, accountId, token), /** Remove a channel token and stop the bot */ removeToken: (channelId: string, accountId: string) => ipcRenderer.invoke('channels:removeToken', channelId, accountId), /** Stop a channel account */ stop: (channelId: string, accountId: string) => ipcRenderer.invoke('channels:stop', channelId, accountId), /** Start a channel account from saved config */ start: (channelId: string, accountId: string) => ipcRenderer.invoke('channels:start', channelId, accountId), }, // Cron jobs management cron: { list: () => ipcRenderer.invoke('cron:list'), toggle: (jobId: string) => ipcRenderer.invoke('cron:toggle', jobId), remove: (jobId: string) => ipcRenderer.invoke('cron:remove', jobId), }, heartbeat: { last: () => ipcRenderer.invoke('heartbeat:last'), setEnabled: (enabled: boolean) => ipcRenderer.invoke('heartbeat:setEnabled', enabled), wake: (reason?: string) => ipcRenderer.invoke('heartbeat:wake', reason), }, // Local chat (direct IPC, no Gateway required) localChat: { /** Subscribe to agent events for local direct chat */ subscribe: (agentId: string) => ipcRenderer.invoke('localChat:subscribe', agentId), /** Unsubscribe from agent events */ unsubscribe: (agentId: string) => ipcRenderer.invoke('localChat:unsubscribe', agentId), /** Get message history for local chat with pagination (returns raw AgentMessageItem[]) */ getHistory: (agentId: string, options?: { offset?: number; limit?: number }) => ipcRenderer.invoke('localChat:getHistory', agentId, options), /** Send message to agent via direct IPC (no Gateway) */ send: (agentId: string, content: string) => ipcRenderer.invoke('localChat:send', agentId, content), /** Resolve an exec approval request */ resolveExecApproval: (approvalId: string, decision: string) => ipcRenderer.invoke('localChat:resolveExecApproval', approvalId, decision), /** Listen for agent events */ onEvent: (callback: (event: LocalChatEvent) => void) => { ipcRenderer.on('localChat:event', (_event, data: LocalChatEvent) => callback(data)) }, /** Remove event listener */ offEvent: () => { ipcRenderer.removeAllListeners('localChat:event') }, /** Listen for exec approval requests */ onApproval: (callback: (approval: LocalChatApproval) => void) => { ipcRenderer.on('localChat:approval', (_event, data: LocalChatApproval) => callback(data)) }, /** Remove approval listener */ offApproval: () => { ipcRenderer.removeAllListeners('localChat:approval') }, }, } // Expose to renderer contextBridge.exposeInMainWorld('electronAPI', electronAPI) // Also expose ipcRenderer for backward compatibility contextBridge.exposeInMainWorld('ipcRenderer', { on(...args: Parameters) { const [channel, listener] = args return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args)) }, off(...args: Parameters) { const [channel, ...omit] = args return ipcRenderer.off(channel, ...omit) }, send(...args: Parameters) { const [channel, ...omit] = args return ipcRenderer.send(channel, ...omit) }, invoke(...args: Parameters) { const [channel, ...omit] = args return ipcRenderer.invoke(channel, ...omit) }, }) // Type declaration for window object export type ElectronAPI = typeof electronAPI