流式渲染修复: - 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
163 lines
7.2 KiB
TypeScript
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;
|
|
}
|