深度拆解 OpenCoWork一个本地多智能体桌面平台的架构设计与实现OpenCoWork 是一个开源桌面多智能体 AI 协作平台。它的定位不是再做一个聊天窗口而是把大模型、文件系统、Shell、SSH、MCP、定时任务、办公 IM 插件和多 Agent 编排放进一个本地桌面运行时里让 Agent 真正进入开发者的工作环境。这篇文章不做功能清单罗列重点拆它的技术实现Electron 如何分层、Agent runtime 如何落地、工具心技术栈包括 Electron 36、React 19、TypeScript、Zustand、better-sqlite3、node-cron、MCP SDK、xterm.js、Monaco Editor 等。1. OpenCoWork 解决的不是“聊天”而是“本地执行”传统 LLM 产品最明显的问题是环境割裂代码在 IDE日志在终端需求在聊天软件文件在本地目录而 Agent 只能在浏览器里给建议。OpenCoWork 的架构目标很直接让 Agent 可以在用户授权下访问本地上下文并执行真实动作。它可以读取代码、搜索文件、修改文件、跑命令、开 SSH、处理文档、调用 MCP 工具、给飞书/钉钉/微信/Telegram 等渠道发送结果。所以它的核心不是“模型对话 UI”而是一个本地 Agent 操作系统雏形用户意图 - 会话模式 - Agent Runtime - 工具系统 - 本地/远程/插件能力 - 结果回流 UI 或消息渠道这也是它选择 Electron 的原因既要有桌面 UI又要拿到 Node.js 层的系统能力。2. 四层 Electron 架构把高权限能力关进主进程OpenCoWork 采用典型但并不简单的四层 Electron 架构Renderer React UI ↓ ipcRenderer.invoke Preload contextBridge ↓ ipcMain.handle Main Process IPC / DB / FS / Shell / SSH / Channels / Cron ↓ Main-process Agent Runtime / MCP / Provider Adapter对应代码非常清晰src/main/index.tsElectron 主进程入口负责窗口生命周期、IPC handler 注册、渠道插件注册、MCP、Cron、SSH、数据库关闭等。src/preload/index.ts通过contextBridge.exposeInMainWorld暴露极少量 API。src/renderer/src/App.tsxReact 入口初始化 provider、viewer、工具系统、插件监听、Agent stream 等。src/main/ipc/js-agent-runtime.ts与src/main/cron/cron-agent-background.ts主进程侧 Agent runtime 与后台 Agent loop 的核心实现。2.1 Main Process所有危险能力都在这里src/main/index.ts里可以看到大量能力注册registerFsHandlers() registerShellHandlers() registerSettingsHandlers() registerSkillsHandlers() registerSshHandlers() registerChannelHandlers(channelManager) registerMcpHandlers(mcpManager) registerCronHandlers() registerBrowserHandlers() registerGitHandlers() registerTeamRuntimeHandlers() registerTeamWorkerHandlers()这意味着文件系统、Shell、SSH、数据库、MCP、Cron、消息插件等高权限能力都集中在主进程通过 IPC 对渲染进程开放。这样做的好处是边界明确Renderer 不直接拿 Node 权限用户界面和系统能力之间隔着 preload IPC。风险也明显main/index.ts容易膨胀成“超级入口”。OpenCoWork 当前已经聚合了很多模块后续如果继续增长最好进一步拆成 domain service例如runtime-service、channel-service、mcp-service、job-service。2.2 Preload窄桥接不把 Node 能力裸露出去src/preload/index.ts的核心就是contextBridge.exposeInMainWorld(electron, electronAPI) contextBridge.exposeInMainWorld(api, api)它暴露的自定义 API 主要包括图片下载/读取/剪贴板、team runtime 创建/删除/快照/消息追加、isolated team worker 的启动/停止。这种做法符合 Electron 安全设计Renderer 不应直接访问fs、child_process而是通过受控 IPC 进入主进程。3. 渲染器不是简单 UI它负责工具目录和会话编排OpenCoWork 的 Renderer 层不只是页面。src/renderer/src/App.tsx在启动阶段会做几件关键事registerAllProviders() registerAllViewers() initProviderStore() initAppPluginStore() attachRendererToolBridge() attachRendererProviderBridge() agentStream.attach() registerAllTools()也就是说前端层承担了模型 Provider 注册、预览器注册、工具系统注册、Agent stream 事件接收、运行时同步事件处理以及 SubAgent / Team / Task / Plan 状态映射。App.tsx中对 runtime sync event 的处理也很关键比如task_add/task_update同步任务面板team_event/team_snapshot同步团队运行态subagent_event同步子 Agent 状态resolve_approval处理工具审批结果。这说明 OpenCoWork 的前端不是“薄 UI”而是 Agent 工作台的状态中枢。4. 工具系统从统一注册到运行时上下文工具系统入口在src/renderer/src/lib/tools/index.tsregisterAllTools()的注册顺序很有代表性registerTaskTools() registerFsTools() registerSearchTools() registerBashTools() registerWidgetTools() registerAskUserTools() registerPlanTools() registerCronTools() registerNotifyTool() registerGoalTools() registerMemoryTools() await refreshDynamicToolCatalog() registerCodeCompatibleTools() registerTeamTools()这里可以看到 OpenCoWork 的工具分为几类基础工具包括文件读写、搜索、Shell协作工具包括任务、提问、计划、目标、记忆自动化工具包括 Cron、Notify动态工具包括 Skill、SubAgent、Wiki、WebSearch兼容层工具面向 code-agent 风格 aliasTeam 工具负责多 Agent 团队编排。工具的统一接口在src/renderer/src/lib/tools/tool-types.tsexport interface ToolContext { sessionId?: string workingFolder?: string sshConnectionId?: string signal: AbortSignal ipc: IPCClient readFileHistory?: Mapstring, FileReadSnapshot inlineToolHandlers?: Recordstring, ToolHandler agentRunId?: string pluginId?: string pluginChatId?: string sharedState?: { deliveryUsed?: boolean; bashCwd?: string } } export interface ToolHandler { definition: ToolDefinition execute: (input, ctx) PromiseToolResultContent requiresApproval?: (input, ctx) boolean }这个设计抓住了 Agent 工具系统的关键工具不是孤立函数而是带上下文执行。上下文里包含当前会话、工作目录、SSH 连接、IPC 客户端、本轮已读文件快照、当前 Agent run id、插件消息来源和可变共享状态。所以同一个Read、Bash、Grep工具在本地会话、SSH 会话、Cron 后台任务、微信自动回复上下文里可以有不同执行语义。5. Agent Runtime主进程里的统一执行循环src/main/ipc/js-agent-runtime.ts定义了JsAgentRuntimeManager。它对外暴露类似 RPC 的方法case initialize case ping case shutdown case capabilities/check case agent/run case agent/append-messages case agent/cancel其中agent/run最终进入startRun()构造runId、AbortController、RuntimeMessageQueue、ToolContext、renderer fallback tool executor 和 renderer approval probe。关键点在这里主进程 Runtime 可以自己执行后台能力但遇到需要渲染器参与的工具、审批或 UI 状态时会通过桥接回到 Renderer。这是一种混合架构Main Runtime 负责 Agent loop 和系统执行 Renderer 负责工具目录、审批、UI 状态和部分工具 fallback优点是灵活兼容现有前端工具生态缺点是边界更复杂需要严格治理同名工具、审批逻辑和上下文同步。6. Plan Mode不是提示词约束而是工具层硬约束很多 Agent 产品所谓“计划模式”本质只是提示词告诉模型“先别改代码”。这不可靠。OpenCoWork 的 Plan Mode 做得更硬它在工具层限制写入能力。核心文件是src/renderer/src/lib/tools/plan-tool.ts关键逻辑是createGuardedPlanFileHandler()const currentPlanFilePath getCurrentPlanFilePath(ctx) const resolvedPath resolveToolPath(input.file_path, ctx.workingFolder) if (normalizeComparablePath(resolvedPath) ! normalizeComparablePath(currentPlanFilePath)) { return encodeToolError( In plan mode, ${toolName} is restricted to the current plan file ) }也就是说在 Plan Mode 下Write和Edit被替换成 guarded handler只允许改当前.plan/planId.md文件。流程如下EnterPlanMode - 创建或恢复 plan - 在工作目录生成 .plan/planId.md - 开启 UI plan mode - 注入 inline tool handlers Write/Edit - 检查目标路径是否等于当前 plan file - 不等则拒绝 ExitPlanMode - 读取 plan file - 提取标题 - 状态改为 awaiting_review - 要求等待用户审核这比单纯 prompt 约束可靠得多。因为限制落在工具执行层而不是模型自觉层。7. Cron Agent定时任务不是提醒而是后台 Agent 执行器OpenCoWork 的 Cron 能力不只是“到点发通知”。它真正调度的是 Agent。核心文件src/main/cron/cron-scheduler.ts src/main/cron/cron-agent-background.ts src/main/ipc/cron-handlers.tscron-scheduler.ts中定义了三种 scheduleschedule_kind: at | every | cron对应一次性定时、固定间隔和标准 cron 表达式。它还实现了并发保护let maxConcurrentRuns 2 const activeRunJobIds new Setstring() if (activeRunJobIds.has(jobId)) return false if (activeRunJobIds.size maxConcurrentRuns) return false这点很重要。后台 Agent 不是普通函数可能跑 Shell、读文件、请求模型、发消息。如果没有并发控制很容易把本地机器或模型额度打爆。当任务触发时核心链路是node-cron / setTimeout / setInterval - onJobFired(job) - 更新 last_fired_at 和 fire_count - sendToRenderer(cron:fired) 更新 UI - runCronAgentInBackground(...) - markFinished(job.id)cron-agent-background.ts里还能看到后台 Agent 的工具白名单Read, Write, Edit, LS, Glob, Grep, Bash, Notify, PluginSendMessage, PluginReplyMessage, SubmitReport这说明 Cron Agent 被设计成真正的自动执行单元它可以巡检日志、跑构建、生成报告再通过桌面通知或 IM 插件交付。8. Team Runtime用文件系统实现轻量多 Agent 协作状态多 Agent 团队运行时核心在src/main/ipc/team-runtime-handlers.ts src/main/ipc/team-worker-handlers.tsteam-runtime-handlers.ts把团队状态放在~/.open-cowork/teams/team/team.json ~/.open-cowork/teams/team/messages.jsonteam.json保存 team name、leadAgentId、leadSessionId、backend 类型、permissionMode、teamAllowedPaths、members 和 tasks。messages.json保存团队消息。它还实现了简单文件锁const LOCK_RETRY_DELAYS_MS [25, 50, 100, 200, 400] fs.promises.open(lockPath, wx)这个方案非常工程化不引入复杂基础设施用 JSON lock 就能支撑桌面单机多 Agent 协作。代价也明确它不适合高并发不适合复杂查询锁文件异常残留需要兜底多机器共享目录场景风险较高。但对一个本地桌面应用来说它的取舍是合理的简单、可调试、易迁移。9. MCP 接入多传输协议 能力缓存 自动降级OpenCoWork 的 MCP 客户端在src/main/mcp/mcp-client.ts src/main/mcp/mcp-manager.ts src/main/ipc/mcp-handlers.tsMcpClientWrapper支持三类 transportstdio、sse、streamable-http。实现里有一个很实用的可靠性设计if (this.config.transport streamable-http this.config.autoFallback ! false) { await this.tryConnect(sse) this._usedFallback true }也就是说如果 Streamable HTTP 连接失败可以自动回退到 SSE。连接成功后它会缓存三类能力_tools _resources _prompts并通过分页拉取fetchAllTools() fetchAllResources() fetchAllPrompts()这让 OpenCoWork 可以把外部 MCP Server 暴露的工具、资源、Prompt 纳入本地 Agent 工具链。换句话说OpenCoWork 自己是一个桌面 Agent 容器MCP 则是它连接外部工具生态的协议层。10. 办公消息插件把 Agent 输出送到真实协作场景OpenCoWork 支持飞书、钉钉、Telegram、Discord、WhatsApp、企业微信、QQ、微信公众号等渠道。在src/main/index.ts中可以看到插件注册channelManager.registerFactory(feishu-bot, createFeishuService) channelManager.registerFactory(dingtalk-bot, createDingTalkService) channelManager.registerFactory(telegram-bot, createTelegramService) channelManager.registerFactory(discord-bot, createDiscordService) channelManager.registerFactory(whatsapp-bot, createWhatsAppService) channelManager.registerFactory(wecom-bot, createWeComService) channelManager.registerFactory(qq-bot, createQQService) channelManager.registerFactory(weixin-official, createWeixinService)技术意义在于Agent 的入口和出口不再局限于桌面窗口。典型链路可以是微信群/飞书群收到消息 - Channel Plugin 解析消息 - 自动回复会话构造 Agent 上下文 - Agent 调用本地工具或 MCP - PluginSendMessage / ReplyMessage 返回结果这让 OpenCoWork 更像“本地 Agent 网关”它既连接本地环境也连接团队沟通环境。11. 持久化SQLite 管业务数据文件系统管用户可编辑资产OpenCoWork 的本地数据目录是~/.open-cowork/里面包括 SQLite 数据库data.db、agents、commands、prompts、skills、teams、MCP 配置和运行时数据。项目使用better-sqlite3做本地持久化适合桌面应用同步 API 简单、性能足够、部署成本低。另一个设计点是用户可编辑资产很多不是硬编码在应用里而是放到文件系统比如 agents、prompts、skills。这样用户可直接编辑便于备份迁移便于动态加载也不必每次扩展都改代码。这也是 OpenCoWork 能支持 Markdown skill、动态 SubAgent catalog 的基础。12. 我认为最值得借鉴的 5 个实现点第一Plan Mode 应该约束工具而不是只约束提示词OpenCoWork 通过 inline guarded tool handler 限制Write/Edit只能改计划文件这是非常正确的方向。Agent 产品要做安全能力不能只靠 system prompt。第二Cron Agent 要有并发保护和交付通道后台 Agent 不是普通定时器。它需要并发限制、运行记录、失败状态、交付模式。OpenCoWork 的maxConcurrentRuns、fire_count、delivery_mode、plugin_chat_id都是工程上必须考虑的字段。第三MCP 客户端要做能力缓存和传输降级MCP Server 的稳定性不可控。OpenCoWork 的 Streamable HTTP - SSE fallback 是很实用的可靠性设计。第四多 Agent 状态不一定一上来就上数据库Team Runtime 使用 JSON lock 文件虽然不是长期最强方案但对本地桌面应用足够直接。早期追求可观察、可调试比过早引入复杂分布式存储更重要。第五Renderer 和 Main 的边界要清楚但可以保留桥接弹性OpenCoWork 把危险能力放在 Main把交互和审批放在 Renderer。中间通过 IPC 和 runtime sync 通信。这个方向是对的但也需要持续治理工具执行边界避免 Renderer tool 和 Main runtime tool 形成双轨复杂度。13. 当前架构的风险与改进方向OpenCoWork 已经具备完整平台形态但从技术演进看有几个风险需要提前压住。13.1 主进程入口膨胀src/main/index.ts注册了大量模块。短期没问题长期建议进一步抽 domain service形成更清晰的启动编排层。13.2 IPC 面过大本地 Agent 产品天然需要很多 IPC但 IPC surface 越大安全审计成本越高。建议维护一份 IPC 权限清单对高风险 channel 做参数校验、来源限制和审批策略。13.3 工具系统存在双运行上下文工具定义主要在 RendererAgent Runtime 又在 Main。当前通过 bridge/fallback 解决但长期最好抽象统一 tool contract明确哪些工具可在 Main 原生执行哪些必须回 Renderer。13.4 Team Runtime 的 JSON 持久化需要演进边界JSON lock 很适合本地单机但当团队消息和任务增多时需要索引、分页、压缩或迁移到 SQLite。13.5 自动化能力越强权限模型越关键OpenCoWork 已经支持文件写入、Shell、SSH、消息发送、Cron 后台执行。越接近“本地自动执行平台”越需要细粒度权限、审计日志和回滚机制。结语OpenCoWork 的价值不在于又做了一个 AI Chat而在于它把 Agent 放进了真实工作环境本地文件、终端、SSH、MCP、定时任务、消息渠道、多 Agent 编排这些能力组合起来才像一个真正可执行的 AI 协作平台。从架构上看它最核心的判断是UI 在 Renderer系统能力在 Main安全边界在 Preload/IPCAgent Runtime 尽量靠近系统能力运行。这个判断是成立的。如果后续继续演进我会优先关注三件事主进程服务拆分降低入口复杂度IPC 与工具权限模型系统化Agent Runtime、Cron、Team、MCP 之间形成更统一的运行时契约。