Files
metona-ai-desktop/electron/preload.ts
T
thzxx 3c5aea8fb7 feat: 升级至 v0.2.1 — 流式渲染修复、安全增强、工具自动执行
流式渲染修复:
- runId 机制防止 abort 后旧流事件污染新 run
- run lock 防止并发 run 污染引擎状态
- abort race 提前退出工具执行等待
- TERMINATED 状态通过 stateChange 发射
- tool_call_delta 流式参数拼接 + pending 占位替换
- 首轮卡片创建路径统一,traceStep 按 ID 精确匹配
- compressed 事件转发为 toast 通知

安全增强:
- ConfirmationHook 支持持久化自动执行(跨会话)
- 设置面板新增自动执行工具管理 UI
- SandboxManager 双重安全校验 fail-closed
- 审计日志链式哈希防篡改
- PromptInjectionDefender 中文注入标记清理
- scanCode 28 模式 + base64/$() 检测
- validatePath realpathSync 防符号链接逃逸
- code-search 使用 execFile 防命令注入

新增工具:
- file_editor、code_search、task_manager、diff_viewer

其他:
- Agent Loop 加 PARSING/REFLECTING 状态 + 指数退避重试
- MemoryManager TF-IDF 语义检索
- run_command Windows 中文编码修复(chcp 65001)
- 版本号 0.2.0 → 0.2.1
2026-07-12 12:54:52 +08:00

163 lines
7.2 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.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),
},
// ===== 应用工具 =====
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),
},
// ===== 工具管理 =====
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;
}