# Conflicts: # apps/desktop/electron/electron-env.d.ts # apps/desktop/electron/ipc/index.ts # apps/desktop/electron/preload.ts # apps/desktop/src/App.tsx # apps/desktop/src/pages/layout.tsx # src/agent/async-agent.ts # src/agent/runner.ts # src/hub/hub.ts
294 lines
12 KiB
TypeScript
294 lines
12 KiB
TypeScript
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<HubStatus> => ipcRenderer.invoke('hub:getStatus'),
|
|
getAgentInfo: (): Promise<AgentInfo | null> => 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<ToolInfo[]> => 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<SkillInfo[]> => 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<ProfileData> => 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<ProviderStatus[]> => ipcRenderer.invoke('provider:list'),
|
|
/** List only available (configured) providers */
|
|
listAvailable: (): Promise<ProviderStatus[]> => ipcRenderer.invoke('provider:listAvailable'),
|
|
/** Get current provider and model from the active agent */
|
|
current: (): Promise<CurrentProviderInfo> => 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<boolean> => 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<typeof ipcRenderer.on>) {
|
|
const [channel, listener] = args
|
|
return ipcRenderer.on(channel, (event, ...args) => listener(event, ...args))
|
|
},
|
|
off(...args: Parameters<typeof ipcRenderer.off>) {
|
|
const [channel, ...omit] = args
|
|
return ipcRenderer.off(channel, ...omit)
|
|
},
|
|
send(...args: Parameters<typeof ipcRenderer.send>) {
|
|
const [channel, ...omit] = args
|
|
return ipcRenderer.send(channel, ...omit)
|
|
},
|
|
invoke(...args: Parameters<typeof ipcRenderer.invoke>) {
|
|
const [channel, ...omit] = args
|
|
return ipcRenderer.invoke(channel, ...omit)
|
|
},
|
|
})
|
|
|
|
// Type declaration for window object
|
|
export type ElectronAPI = typeof electronAPI
|