Files

197 lines
9.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* MetonaAI Desktop — Preload 脚本
*
* 通过 contextBridge 安全地将 IPC 通道暴露给渲染进程。
* 渲染进程无 Node.js 访问权限,只能通过此桥接层通信。
*/
import { contextBridge, ipcRenderer } from 'electron';
import { electronAPI } from '@electron-toolkit/preload';
/**
* Metona API — 暴露给渲染进程的安全接口
*/
const metonaAPI = {
// ===== Agent 交互 =====
agent: {
/** 发送用户消息 */
sendMessage: (message: unknown, sessionId: string) => ipcRenderer.invoke('agent:sendMessage', message, sessionId),
/** 监听流式事件 */
onStreamEvent: (callback: (event: unknown) => void) => {
const listener = (_event: Electron.IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on('agent:streamEvent', listener);
return () => ipcRenderer.removeListener('agent:streamEvent', listener);
},
/** 监听状态变化 */
onStateChange: (callback: (state: unknown) => void) => {
const listener = (_event: Electron.IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on('agent:stateChange', listener);
return () => ipcRenderer.removeListener('agent:stateChange', listener);
},
/** 中断会话 */
abortSession: (sessionId: string) => ipcRenderer.invoke('agent:abortSession', sessionId),
/** 监听 Provider 切换通知 */
onProviderSwitched: (callback: (data: unknown) => void) => {
const listener = (_event: Electron.IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on('agent:providerSwitched', listener);
return () => ipcRenderer.removeListener('agent:providerSwitched', listener);
},
},
// ===== 会话管理 =====
sessions: {
list: () => ipcRenderer.invoke('sessions:list'),
create: (title?: string) => ipcRenderer.invoke('sessions:create', title),
rename: (sessionId: string, title: string) => ipcRenderer.invoke('sessions:rename', sessionId, title),
delete: (sessionId: string) => ipcRenderer.invoke('sessions:delete', sessionId),
getMessages: (sessionId: string) => ipcRenderer.invoke('sessions:getMessages', sessionId),
pin: (sessionId: string, pinned: boolean) => ipcRenderer.invoke('sessions:pin', sessionId, pinned),
archive: (sessionId: string, archived: boolean) => ipcRenderer.invoke('sessions:archive', sessionId, archived),
deleteMessage: (messageId: string) => ipcRenderer.invoke('sessions:deleteMessage', messageId),
clearMessages: (sessionId: string) => ipcRenderer.invoke('sessions:clearMessages', sessionId),
saveTrace: (sessionId: string, data: unknown) => ipcRenderer.invoke('sessions:saveTrace', sessionId, data),
getTrace: (sessionId: string) => ipcRenderer.invoke('sessions:getTrace', sessionId),
},
// ===== MCP 管理 =====
mcp: {
listServers: () => ipcRenderer.invoke('mcp:listServers'),
addServer: (config: unknown) => ipcRenderer.invoke('mcp:addServer', config),
removeServer: (name: string) => ipcRenderer.invoke('mcp:removeServer', name),
toggleServer: (name: string, enabled: boolean) => ipcRenderer.invoke('mcp:toggleServer', name, enabled),
},
// ===== 记忆系统 =====
memory: {
search: (query: string, options?: unknown) => ipcRenderer.invoke('db:searchMemories', query, options),
listAll: (options?: { type?: string; limit?: number }) =>
ipcRenderer.invoke('memory:listAll', options),
delete: (type: string, id: string) => ipcRenderer.invoke('memory:delete', type, id),
},
// ===== v0.2.0: 任务管理 =====
tasks: {
list: (sessionId?: string) => ipcRenderer.invoke('tasks:list', sessionId),
create: (data: { sessionId: string; title: string; description?: string; priority?: string; parentId?: string }) =>
ipcRenderer.invoke('tasks:create', data),
update: (id: string, updates: { title?: string; description?: string; status?: string; priority?: string; assignedTo?: string }, sessionId: string) =>
ipcRenderer.invoke('tasks:update', id, updates, sessionId),
delete: (id: string, sessionId: string) => ipcRenderer.invoke('tasks:delete', id, sessionId),
// P2v0.3.13: 订阅任务变更事件(Agent 通过 task_manager 写入后触发)
onTaskChanged: (callback: (sessionId: string) => void) => {
const listener = (_event: unknown, sessionId: string): void => callback(sessionId);
ipcRenderer.on('task:changed', listener);
return () => ipcRenderer.removeListener('task:changed', listener);
},
},
// ===== v0.2.0: 审计日志 =====
audit: {
verifyChain: () => ipcRenderer.invoke('audit:verifyChain'),
query: (filters?: { sessionId?: string; eventType?: string; limit?: number }) =>
ipcRenderer.invoke('audit:query', filters),
},
// ===== v0.2.0: 工具确认请求 =====
tool: {
onConfirmationRequest: (callback: (request: unknown) => void) => {
const listener = (_event: Electron.IpcRendererEvent, data: unknown) => callback(data);
ipcRenderer.on('tool:confirmationRequest', listener);
return () => ipcRenderer.removeListener('tool:confirmationRequest', listener);
},
sendConfirmationResponse: (response: { toolCallId: string; approved: boolean; remember: boolean; autoExecute?: boolean }) =>
ipcRenderer.send('tool:confirmationResponse', response),
// v0.3.2: 批量确认响应(并行工具一次性审批)
sendConfirmationResponseBatch: (response: { toolCallIds: string[]; approved: boolean; remember: boolean; autoExecute?: boolean }) =>
ipcRenderer.send('tool:confirmationResponseBatch', response),
// v0.3.2: 拉取当前所有 pending 确认(前端弹框打开时调用,防止 state 覆盖丢失)
getPendingConfirmations: () =>
ipcRenderer.invoke('tool:getPendingConfirmations') as Promise<{
success: boolean;
data: Array<{
toolCallId: string;
toolName: string;
args: Record<string, unknown>;
riskLevel: string;
reason: string;
expiresAt?: number;
}>;
}>,
// v0.2.0: 持久化自动执行设置
setAutoExecute: (toolName: string, enabled: boolean) =>
ipcRenderer.invoke('tool:setAutoExecute', toolName, enabled),
getAutoExecuteList: () => ipcRenderer.invoke('tool:getAutoExecuteList'),
},
// ===== 配置 =====
config: {
get: (key: string) => ipcRenderer.invoke('config:get', key),
set: (key: string, value: unknown) => ipcRenderer.invoke('config:set', key, value),
// v0.3.9: 批量保存配置,避免串行保存中间态触发 reloadAdapter 失败
setBatch: (entries: Array<{ key: string; value: unknown }>) =>
ipcRenderer.invoke('config:setBatch', entries),
},
// ===== 应用工具 =====
app: {
getVersion: () => ipcRenderer.invoke('app:getVersion'),
getAppDataPath: () => ipcRenderer.invoke('app:getAppDataPath'),
openExternal: (url: string) => ipcRenderer.invoke('app:openExternal', url),
showItemInFolder: (path: string) => ipcRenderer.invoke('app:showItemInFolder', path),
selectFolder: (defaultPath?: string) => ipcRenderer.invoke('app:selectFolder', defaultPath),
restart: () => ipcRenderer.invoke('app:restart'),
},
// ===== 工作空间管理 =====
workspace: {
check: (targetPath: string) => ipcRenderer.invoke('workspace:check', targetPath),
inheritFiles: (params: { targetPath: string; sourcePath: string; files: string[] }) =>
ipcRenderer.invoke('workspace:inheritFiles', params),
getInfo: () => ipcRenderer.invoke('workspace:getInfo'),
},
// ===== 工具管理 =====
tools: {
list: () => ipcRenderer.invoke('tools:list'),
toggle: (toolName: string, enabled: boolean) => ipcRenderer.invoke('tools:toggle', toolName, enabled),
},
// ===== 数据管理 =====
data: {
exportData: (sessionId?: string) => ipcRenderer.invoke('data:export', sessionId),
clearSessions: () => ipcRenderer.invoke('data:clearSessions'),
clearMemories: () => ipcRenderer.invoke('data:clearMemories'),
clearAuditLogs: () => ipcRenderer.invoke('data:clearAuditLogs'),
},
// ===== SearXNG =====
searxng: {
testConnection: (url: string, authKey: string, authType: string) =>
ipcRenderer.invoke('searxng:testConnection', url, authKey, authType),
},
// ===== Toast 通知桥接 =====
toast: {
onShow: (callback: (data: { type: string; message: string; options?: unknown }) => void) => {
const listener = (_event: Electron.IpcRendererEvent, data: { type: string; message: string; options?: unknown }) =>
callback(data);
ipcRenderer.on('toast:show', listener);
return () => ipcRenderer.removeListener('toast:show', listener);
},
},
};
// ===== 安全暴露到渲染进程 =====
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI);
contextBridge.exposeInMainWorld('metona', metonaAPI);
} catch (error) {
console.error('Failed to expose APIs:', error);
}
} else {
// 降级方案(不推荐)
(globalThis as unknown as Record<string, unknown>).electron = electronAPI;
(globalThis as unknown as Record<string, unknown>).metona = metonaAPI;
}