依赖
@tavily/core
:Tavily 的 JavaScript SDK 允许与 Tavily API 轻松交互,直接在 JavaScript 和 TypeScript 程序中使用 Tavily 的全部搜索和提取功能。利用强大的 Tavily Search 和 Tavily Extract API,轻松将智能搜索和内容提取功能集成到应用程序中。@computer-use/node-mac-permissions
:用来管理 macOS 权限的 nodejs 包。@agent-infra/mcp-server-commands
:执行任意命令的 MCP 服务器。@agent-infra/mcp-server-filesystem
:访问文件系统的 MCP 服务器。@agent-infra/mcp-server-browser
:操作浏览器的 MCP 服务器。jotai
:轻量状态管理库
架构
Monorepo
- pnpm workspace
Electron
agent-tars 是一个基于 Electron 的桌面应用,Electron 的主进程(Main Process)和渲染进程(Renderer Process)是其双进程架构的核心设计,两者的职责、运行环境和交互方式决定了 Electron 应用的性能与安全性。
主进程(Main Process)
- 入口与生命周期管理:作为应用的入口(
src/main/index.ts
),负责启动/关闭应用、管理窗口生命周期(如创建/销毁BrowserWindow
实例)。 - 系统级交互:直接访问 Node.js API 和操作系统资源(如文件系统、网络请求、系统通知),执行敏感操作(如加密数据处理)
- 全局状态控制:管理跨窗口的全局数据(如用户配置、缓存),协调多渲染进程的通信。
渲染进程(Renderer Process)
- UI 渲染与交互:每个窗口对应一个独立的渲染进程,基于 Chromium 实现网页渲染,处理 HTML、CSS、JavaScript 的执行及用户交互(如点击事件)。
- 部分 Node.js能力:默认不启用 Node.js 集成,但可通过
preload
脚本和contentBridge
暴露有限 API(如fs
模块的部分功能)。 - 沙箱环境:出于安全考虑,渲染进程默认隔离,无法直接访问系统资源或执行危险操作。
进程间通信(IPC)机制
主进程与渲染进程通过 IPC(Inter-Process Communication)实现安全通信:
1、异步通信:
- 渲染进程通过
ipcRenderer.send()
发送消息,主进程通过ipcMain.on()
监听并响应。 - 示例:渲染进程请求打开文件 → 主进程调用
fs
模块读取 → 结果返回渲染进程。
2、同步通信:
- 渲染进程通过
ipcRenderer.sendSync()
发送同步请求,主进程处理后返回结果(可能阻塞 UI 线程,慎用)
3、预加载脚本(Preload Script)
- 在渲染进程加载前注入,通过
contextBridge.exposeInMainWorld()
安全暴露主进程 API(如sendMessageToMain
) - 示例:预加载脚本桥接
ipcRenderer
,避免直接暴露 Node.js 功能。
渲染进程(src/renderer)
main.tsx
渲染进程的入口在 src/main.tsx
,主要作用:
- 执行
initMonacoWorkers()
方法初始化 MonacoWorkers,具体分析看 initMonacoWorkers - 引入
./components/App.tsx
,具体分析看 App.tsx
App.tsx
main.tsx
一般都是执行一些和 UI 无关的全局初始化操作,App.tsx
则是处理一些 UI 相关的全局初始化操作:
- 调用
useMainProcessErrorHandler()
注册主进程错误处理程序,这不是我们关注的重点,请查看源码 - 引入
./components/AgentApp
进行展示
AgentApp.tsx
<LeftSidebar />
:放置主题切换、会话管理和设置,这不是本文关注的重点。<OpenAgentChatUI />
:负责指令输入和 Agent 执行链展示<CanvasPanel>
:负责展示 Agent 执行细节的展示
OpenAgentChatUI
1 | import { ChatUI as BaseChatUI } from '@vendor/chat-ui'; |
以上是去掉和 ChatUI 交互后的简化的核心代码,@vender/chat-ui
由于内部原因,暂未公开,可以关注 issues#417。核心逻辑如下:
- 用户输入完成后,触发
sendMessage
,将用户输入发送给launchAgentFlow
launchAgentFlow
触发 Agent 执行,详细代码请查看 useAgentFlow
slots 插槽的配置分别是:
beforeMessageList
,MessageList 前面的槽位,这里放了菜单栏和欢迎页beforeInputContainer
,InputContainer 前面的槽位,这里放了<BeforeInputContainer />
,customFeatures
,自定义功能槽位,这里放了<AgentStatusTip />
,用来展示 Agent 的状态
BeforeInputContainer
1 | import { PlanTaskStatus } from './PlanTaskStatus'; |
<PlanTaskStatus />
,计划任务状态,展示计划执行列表的进度<UserInterruptArea />
,用户中断区域,消息发送或执行中,允许用户发送新的消息或中断
CanvasPanel
hooks
useAgentFlow
1 | import { useCallback } from "react"; |
useAgentFow
hook 接收用户输入后,创建一个AgentFlow
实例,并调用agentFlow.run()
触发 Agent 执行
agent
AgentFlow.ts
1 | import { ToolCallType } from '@renderer/type/agent'; |
Aware
1 | export class Aware { |
src/main/ipcRoutes/llm.ts
1 | export const llmRoute = t.route({ |
src/main/llmProvider/index.ts
1 | export class LLM { |
src/main/llmProvider/providers/OpenAIProvider.ts
:
1 | export class OpenAIProvider extends BaseProvider { |
Executor
1 | export class Executor { |
构建出的 executor 结构,我们主要关注
EventManager
EventManager
类主要是负责和 chat-ui 交互的,由于 chat-ui 没有开源,这块是黑盒的。后续开源之后详细讲解。
1 | export class EventManager { |
src/renderer/src/type/event.ts
:
1 | export enum EventType { |
api
ipcClient
1 | import { createClient } from '@ui-tars/electron-ipc/renderer'; |
createClient 代码如下:
1 | import { IpcRenderer } from 'electron'; |
utils
initMonacoWorkers
initMonacoWorkers
方法位于 src/utils/monacoConfig
:
1 | import * as monaco from 'monaco-editor'; |
主进程(src/main)
ipcRoutes
1 | import { initIpc, createServer } from '@ui-tars/electron-ipc/main'; |
actionRoute
1 | import { MCPServerName } from '@agent-infra/shared'; |
mcp client
1 | import { MCPServerName } from '@agent-infra/shared'; |
@agent-infra/shared
TODO
@agent-infra/mcp-client
基于 @modelcontextprotocol/sdk/client/index.js
和 @modelcontextprotocol/sdk/client/sse.js
实现的 MCP 客户端。负责管理 MCP 服务器和工具调用。
@agent-infra/mcp-server-browser
模块位于
packages/agent-infra/mcp-servers/browser
server.ts
1 | import { |