- 新增 config:setBatch IPC:批量写入 8 个 LLM 字段后统一 reloadAdapter, 解决设置页串行 config:set 在中间态触发"LLM 配置不完整"错误的问题 - SettingsModal.handleSave 和 OnboardingWizard.handleNext 改用 setBatch - workspace.path 写入独立文件失败时返回 success:false(config:set 和 setBatch 一致), 避免用户误以为保存成功但下次启动仍使用旧路径 - setBatch 将 provider 切换清空 apiKey 的逻辑移到循环前执行, 消除对 entries 中 llm.provider 必须出现在 llm.apiKey 之前的隐含顺序依赖
191 lines
8.6 KiB
TypeScript
191 lines
8.6 KiB
TypeScript
/**
|
|
* 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 }) =>
|
|
ipcRenderer.invoke('tasks:update', id, updates),
|
|
delete: (id: string) => ipcRenderer.invoke('tasks:delete', id),
|
|
},
|
|
|
|
// ===== 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;
|
|
}
|