Files
metona-ollama-desktop/src/main/preload.ts
T
thzxx 933ce7a082 v0.11.1: Agent Loop稳定性增强 + 6个系统工具 + 搜索引擎替换
【Agent Loop 稳定性】
- P0-1: 工具消息硬限制40条,超出自动删旧
- P0-2: 截断周期从5轮缩短为3轮
- P1-1: 增量记忆提取改为fire-and-forget
- P1-2: TOOLS_WITH_DATA_DEPS精简为仅web_fetch
- P2: 重复检测改为注入警告而非强制终止
- Final Answer检测增强: >300字自动放行 + 收紧反过早停止

【新增工具】(40→44)
- datetime: 系统精确时间(中文日期+时段+人性化)
- calculator: 安全数学计算(递归下降解析器)
- random: 随机数/随机选择(int/float/pick/string)
- uuid: UUID v4生成(crypto.randomUUID)
- json_format: JSON格式化+验证+键排序
- hash: MD5/SHA1/SHA256/SHA384/SHA512

【搜索引擎替换】
- Google+DuckDuckGo → 搜狗+360搜索
- 四引擎变为: Bing+百度+搜狗+360搜索

【删除】
- 联网搜索代理全部代码(search-proxy/ + 7文件代理逻辑)
- https-proxy-agent依赖

【UI】
- 模型栏: 上下文总长(蓝色)+剩余上下文(绿色)实时显示
- 设置面板上下文长度移至模型栏
- SOUL.md/AGENT.md精简为纯抽象定义

【系统提示词】
- OS环境信息改为从preload同步获取真实值(os.homedir/os.arch/os.userInfo)
2026-06-11 22:07:46 +08:00

129 lines
7.3 KiB
TypeScript

/**
* Metona Ollama Desktop - Preload 脚本
*/
import { contextBridge, ipcRenderer } from 'electron';
import * as os from 'os';
contextBridge.exposeInMainWorld('metonaDesktop', {
isDesktop: true,
info: () => ipcRenderer.invoke('app:info'),
sys: {
homeDir: os.homedir(),
tmpDir: os.tmpdir(),
shell: process.env.SHELL || process.env.ComSpec || (process.platform === 'win32' ? 'cmd.exe' : 'bash'),
arch: os.arch(),
platform: os.platform(),
hostname: os.hostname(),
username: os.userInfo().username,
},
dialog: {
openFile: (options?: unknown) => ipcRenderer.invoke('dialog:openFile', options),
saveFile: (options?: unknown) => ipcRenderer.invoke('dialog:saveFile', options)
},
fs: {
readFile: (filePath: string) => ipcRenderer.invoke('fs:readFile', filePath),
readFileBase64: (filePath: string) => ipcRenderer.invoke('fs:readFileBase64', filePath),
writeFile: (filePath: string, content: string, encoding?: string) => ipcRenderer.invoke('fs:writeFile', filePath, content, encoding)
},
tool: {
execute: (toolName: string, args: Record<string, unknown>) => ipcRenderer.invoke('tool:execute', toolName, args),
getConfig: () => ipcRenderer.invoke('tool:getConfig'),
setAllowedDirs: (dirs: string[]) => ipcRenderer.invoke('tool:setAllowedDirs', dirs),
setTimeouts: (timeouts: { http?: number; mcp?: number }) => ipcRenderer.invoke('tool:setTimeouts', timeouts)
},
notify: (title: string, body: string) => ipcRenderer.invoke('notify', title, body),
window: {
minimize: () => ipcRenderer.invoke('window:minimize'),
maximize: () => ipcRenderer.invoke('window:maximize'),
close: () => ipcRenderer.invoke('window:close')
},
openExternal: (url: string) => ipcRenderer.invoke('shell:openExternal', url),
onMenuAction: (callback: (action: string) => void) => {
ipcRenderer.on('menu-action', (_: unknown, action: string) => callback(action));
},
onTrayAction: (callback: (action: string) => void) => {
ipcRenderer.on('tray-action', (_: unknown, action: string) => callback(action));
},
onAppQuit: (callback: () => void) => {
ipcRenderer.on('app-quit', () => callback());
},
removeAllListeners: (channel: string) => {
ipcRenderer.removeAllListeners(channel);
},
onMainLog: (callback: (data: { level: string; message: string; detail?: string }) => void) => {
ipcRenderer.on('main:log', (_: unknown, data: { level: string; message: string; detail?: string }) => callback(data));
},
db: {
saveSession: (session: unknown) => ipcRenderer.invoke('db:saveSession', session),
getSession: (id: string) => ipcRenderer.invoke('db:getSession', id),
getAllSessions: () => ipcRenderer.invoke('db:getAllSessions'),
deleteSession: (id: string) => ipcRenderer.invoke('db:deleteSession', id),
clearAllSessions: () => ipcRenderer.invoke('db:clearAllSessions'),
saveMessage: (msg: unknown) => ipcRenderer.invoke('db:saveMessage', msg),
getMessages: (sessionId: string) => ipcRenderer.invoke('db:getMessages', sessionId),
saveMemory: (entry: unknown) => ipcRenderer.invoke('db:saveMemory', entry),
getMemory: (id: string) => ipcRenderer.invoke('db:getMemory', id),
getAllMemories: () => ipcRenderer.invoke('db:getAllMemories'),
getMemoriesByType: (type: string) => ipcRenderer.invoke('db:getMemoriesByType', type),
deleteMemory: (id: string) => ipcRenderer.invoke('db:deleteMemory', id),
clearAllMemories: () => ipcRenderer.invoke('db:clearAllMemories'),
searchMemories: (query: string, limit?: number) => ipcRenderer.invoke('db:searchMemories', query, limit),
saveSetting: (key: string, value: unknown) => ipcRenderer.invoke('db:saveSetting', key, value),
getSetting: (key: string, defaultValue?: unknown) => ipcRenderer.invoke('db:getSetting', key, defaultValue),
saveToolCall: (tc: unknown) => ipcRenderer.invoke('db:saveToolCall', tc),
getToolCalls: (sessionId: string) => ipcRenderer.invoke('db:getToolCalls', sessionId),
saveTrace: (trace: unknown) => ipcRenderer.invoke('db:saveTrace', trace),
getTraces: (sessionId: string) => ipcRenderer.invoke('db:getTraces', sessionId),
exportSessions: () => ipcRenderer.invoke('db:exportSessions'),
importSessions: (data: unknown) => ipcRenderer.invoke('db:importSessions', data),
// v4.2 Skills
saveSkill: (skill: unknown) => ipcRenderer.invoke('db:saveSkill', skill),
getAllSkills: () => ipcRenderer.invoke('db:getAllSkills'),
deleteSkill: (id: string) => ipcRenderer.invoke('db:deleteSkill', id),
clearAllSkills: () => ipcRenderer.invoke('db:clearAllSkills'),
searchSkills: (query: string, limit?: number) => ipcRenderer.invoke('db:searchSkills', query, limit),
incrementSkillUsage: (id: string, success: boolean, durationMs: number) => ipcRenderer.invoke('db:incrementSkillUsage', id, success, durationMs),
getAllTokenStats: () => ipcRenderer.invoke('db:getAllTokenStats'),
},
workspace: {
getDir: () => ipcRenderer.invoke('workspace:getDir'),
setDir: (dir: string) => ipcRenderer.invoke('workspace:setDir', dir),
listDir: (dirPath?: string) => ipcRenderer.invoke('workspace:listDir', dirPath),
readFile: (filePath: string) => ipcRenderer.invoke('workspace:readFile', filePath),
exec: (params: { id: string; command: string; cwd?: string }) => {
ipcRenderer.send('workspace:exec', params);
},
kill: (id: string) => {
ipcRenderer.send('workspace:kill', id);
},
onOutput: (callback: (data: { id: string; type: 'stdout' | 'stderr'; data: string }) => void) => {
ipcRenderer.on('workspace:output', (_: unknown, data: { id: string; type: 'stdout' | 'stderr'; data: string }) => callback(data));
},
onExit: (callback: (data: { id: string; code: number | null }) => void) => {
ipcRenderer.on('workspace:exit', (_: unknown, data: { id: string; code: number | null }) => callback(data));
},
/** 无超时工具命令执行,用于 AI Tool Calling */
execTool: (command: string, cwd?: string) => ipcRenderer.invoke('tool:execute', 'run_command', { command, cwd }),
/** AI 命令实时输出推送到工作空间终端 */
onCmdOutput: (callback: (data: { command: string; type: 'stdout' | 'stderr'; data: string }) => void) => {
ipcRenderer.on('cmd:output', (_: unknown, data: { command: string; type: 'stdout' | 'stderr'; data: string }) => callback(data));
},
/** AI 命令执行完毕 */
onCmdDone: (callback: (data: { command: string; exitCode: number | null }) => void) => {
ipcRenderer.on('cmd:done', (_: unknown, data: { command: string; exitCode: number | null }) => callback(data));
},
/** 终止当前 AI 命令 */
cmdKill: () => ipcRenderer.invoke('cmd:kill')
},
mcp: {
startServer: (config: { name: string; command: string; args: string[]; enabled: boolean; description?: string }) => ipcRenderer.invoke('mcp:startServer', config),
stopServer: (name: string) => ipcRenderer.invoke('mcp:stopServer', name),
stopAll: () => ipcRenderer.invoke('mcp:stopAll'),
callTool: (serverName: string, toolName: string, args: Record<string, unknown>) => ipcRenderer.invoke('mcp:callTool', serverName, toolName, args),
getTools: () => ipcRenderer.invoke('mcp:getTools'),
getStatuses: () => ipcRenderer.invoke('mcp:getStatuses'),
refreshTools: (name: string) => ipcRenderer.invoke('mcp:refreshTools', name)
}
});