数据截至 (上游 commit a9c304f343f1)
OpenWork — 架构与原理
30 秒导读: OpenWork 是一款开源桌面 App(macOS / Windows / Linux),让非程序员也能在自己电脑上、对着自己的文件用 AI agent 干活——它是 Claude Cowork / Codex 的开源替代。底层的"agent 大脑"直接复用开源引擎 OpenCode;OpenWork 在它外面套了一层外壳 + 中间层:Electron 窗口负责界面并在主进程内直接起
openwork-server(不 spawn 子进程),服务器以托管方式拉起带随机凭据的opencode serve子进程,并用带权限作用域的反向代理在 OpenCode 之上补齐会话管理、权限审批、工作区挂载、技能 / MCP 装配。默认只绑127.0.0.1本地跑,想协作时再显式打开远程;组织侧由 Den 控制面把能力发布成一个 OpenWork MCP,任何兼容 agent 加一个 URL 就能按需取用。
1. 这是什么(零基础也能懂)
一句话定义。 OpenWork = OpenCode(agent 引擎)+ 一层安全、好用、可分享的产品外壳。
它解决谁的什么问题。 现有的 opencode CLI/GUI 是给开发者用的:满屏 file diff、工具名、要靠命令行才能扩展。OpenWork 把这套能力产品化给普通人:
- 假设你不是工程师,但想让 AI 帮你整理一个装满合同/表格/笔记的文件夹——OpenWork 让你在一个桌面 App 里选中那个文件夹、发一句话,就能安全地让 agent 动手,而且每一步危险操作都会弹出审批让你点"允许 / 拒绝"。
- 假设你是团队管理员,想把"谁能用哪些技能、连哪些 MCP、用什么模型"集中管理——Den 控制面把这些发布成组织能力,成员在任意 agent(Codex、Claude Code、Cursor…)里加一个 OpenWork MCP 即可(README "Use OpenWork from any agent")。
它能做什么(功能)。 对应到代码:
| 功能 | 白话 |
|---|---|
| 桌面工作区 | 选一个本地文件夹,一键让 agent 在里面干活 |
| 会话(Sessions) | 创建 / 切换会话、发提示词,SSE 实时刷进展 |
| 权限(Permissions) | 把权限请求弹给人:允许一次 / 总是允许 / 拒绝 |
| 技能 / MCP / 插件 | 在 UI 里点几下装能力(第 4 章) |
| Connect(实验) | Google Workspace / Microsoft 365 能力接入 |
| OpenWork MCP | 云端组织能力面:search_capabilities / execute_capability |
| OpenWork Den(EE) | 组织控制面:成员/团队、市场、托管推理、桌面策略 |
用起来什么样(最小示例)。 桌面 App 是主入口;不装桌面,也能直接跑服务器 CLI(apps/server/src/cli.ts):
# 起一个 openwork-server,并让它托管一个 OpenCode 引擎
OPENWORK_MANAGE_OPENCODE=1 openwork-server --workspace /path/to/workspace
# 日志: OpenWork server listening on http://127.0.0.1:8787
# Managed OpenCode listening on http://127.0.0.1:<随机口>
桌面 App 做的事本质一样,只是把服务器塞进 Electron 主进程内起,并把这套编排藏在了一键按钮后面。
一句话直觉 / 类比。 把 OpenWork 想成"给 OpenCode 引擎装的一台整车":OpenCode 是发动机(会推理、会调工具、会改文件),但发动机不能直接给人开。OpenWork 加了车身(Electron 界面)、电控(进程内 openwork-server + 托管引擎)、安全带(权限审批、默认只绑本地),让不懂机械的人也能安全上路。
本节不谈底层代码。记住一件事:OpenWork 自己不实现 agent 循环,它包装并治理 OpenCode。
2. 顶层全景(它大概怎么转)
2.1 一张图看懂"谁拉起谁、谁挡在谁前面"
怎么读这张图: 从上到下是"外壳 → 服务 → 引擎"的层次;实线是进程拉起 / 模块加载,openwork-server 是所有请求进 OpenCode 的唯一闸门。
┌─────────────────────────────────────────────┐
│ Electron 桌面外壳 apps/desktop │
│ · 主进程开窗 + IPC 桥 │
│ · 内嵌单一 React UI (apps/app) │
└───────────────┬─────────────────────────────┘
│ engineStart → import() 进程内加载
▼
┌─────────────────────────────────────────────┐
│ openwork-server (apps/server) │
│ · 作用域鉴权反向代理 + 审批 + 文件系统 API │
│ · 托管引擎:spawn opencode serve │
│ (随机凭据 + stdout 就绪 + 注册表/蓝绿池) │
└───────────────┬─────────────────────────────┘
│ spawn(受管子进程)
▼
┌─────────────────────────────────────────────┐
│ opencode (agent 引擎, 外部二进制) │
└─────────────────────────────────────────────┘
云侧: Den 控制面(ee/apps/den-*) ──发布──▶ OpenWork MCP(/mcp/agent)
任何 MCP 客户端 ◀──search_capabilities / execute_capability──
第二张图放大请求如何穿过闸门——因为这是整个项目工程含量最高的一环:
UI / 远程客户端
│ 带 token: Bearer <client> 或 X-OpenWork-Host-Token
▼
openwork-server
│ 1. 认出 Actor + 作用域 (owner / collaborator / viewer)
│ 2. 匹配路由 (registry) 或识别 /w/:id/opencode/* 挂载
│ 3. assertOpencodeProxyAllowed: viewer 只能 GET/HEAD、
│ 不能自批权限;越权 → 403
▼
proxyOpencodeRequest ──▶ opencode serve (真正干活的 agent)
2.2 部件一句话职责
以下是主要部件及其所在文件(基于 pnpm workspace apps/* + packages/* + ee/apps/*):
| 部件 | 干什么 | 在哪 |
|---|---|---|
| Electron 外壳 | 开窗、菜单、IPC 桥、引导运行时、自动更新 | apps/desktop/electron/main.mjs |
| 运行时管理器 | 串行化启停、token/端口持久化、进程内起服务器 | apps/desktop/electron/runtime.mjs(createRuntimeManager) |
| openwork-server | OpenCode 之上的作用域鉴权反向代理 + 审批 + 工作区注册表 + 文件系统 API + 技能/MCP/插件装配 + 引擎托管 | apps/server/src/server.ts |
| 托管引擎机制 | spawn opencode serve、随机凭据、stdout 就绪、注册表、蓝绿池 | apps/server/src/managed-opencode.ts、engine-registry.ts、engine-pool.ts |
| opencode(外部) | 真正的 agent 引擎(推理、工具调用、改文件) | 外部二进制,非本仓 库 |
| React UI | 单一前端,桌面与 Web 共用;SSE 实时渲染会话 | apps/app/src/index.react.tsx |
| OpenWork Cloud(den, EE) | 控制面、组织能力市场、OpenWork MCP、托管推理 | ee/apps/den-* |
历史注记: 旧架构里有一个独立的
openwork-orchestratorCLI(监管 opencode / openwork-server / opencode-router 三个 sidecar)和 Slack/Telegram 消息桥opencode-router——两者均已被上游移除;本文所有章节按当前源码记述,退场痕迹见第 2 章 §4 与第 6 章 §7。
2.3 主线走一遍(不进代码)
- 你在桌面 App 里选一个项目文件夹;主进程调用
runtimeManager.engineStart(workspaceRoot, …)(main.mjs:1339的bootRuntimeForSelectedWorkspace→runtime.mjs:2066的engineStart)。 engineStart把一个打包好的 embedded 服务器模块import()进 Electron 主进程,起 HTTP 服务(runtime.mjs:1849的startOpenworkServer),并带着manageOpencode: true——由服务器去 spawn 并托管 OpenCode 引擎(第 2 章);它会为这个工作区签发/复用作用域 token。- UI(内嵌在 Electron 窗口里)通过
@opencode-ai/sdkv2 客户端连到openwork-server, 列会话、发提示词、订阅 SSE 事件流。 - agent 每要做一件敏感事(改文件、跑命令),
openwork-server的审批服务把请求挂起、弹给你点允许/拒绝(apps/server/src/approvals.ts)。 - 想让同事也能用?打开远程访问,
openwork-server从127.0.0.1改绑0.0.0.0并打印 LAN / mDNS 连接 URL(runtime.mjs:1880的绑定选择、buildConnectUrls);或从云端 Den 深链接一键连接远程 worker(第 6 章)。
3. 阅读地图(建议顺序)
OpenWork 是个大型 monorepo,单文件讲不完。按"外→内、本地→云"的顺序分成 6 章,建议顺序阅读:
- 桌面外壳与启动流程 — Electron 主进程怎么开窗、通过
contextBridge暴露invokeDesktopIPC 桥(apps/desktop/electron/preload.mjs:62)、以及"选文件夹 → 引导运行时"的完整启动链。先读这章建立入口直觉。 - 主机运行时:openwork-server 如何托管引擎 — 托管 spawn(
createManagedOpencodeServer)、随机凭据与 stdout 就绪信号、引擎注册表与孤儿回收、蓝绿池(EnginePool)、worker 心跳,以及旧 orchestrator 的退场考古。 - openwork-server:OpenCode 之上的鉴权代理与文件系统 API — 本项目的 核心。作用域鉴权(owner / collaborator / viewer)、代理闸门(
assertOpencodeProxyAllowed)、审批服务、工作区注册表、文件会话与批量读写。 - 可扩展性:技能、MCP、插件与内建扩展 —
.opencode/skills技能扫描、runtime 配置里的 MCP 与 opencode 插件装配、云插件市场与 Claude 插件兼容、内建扩展(affordance 元工具)。 - 前端:单一 React UI 与实时会话渲染 — 一套
apps/app如何同时服务桌面(HashRouter,跑在 Electron 窗口里)与 Web(BrowserRouter)、通过 SSE 实时刷会话、kernel/domains 分层。 - 远程与云:远程工作区、OpenWork MCP 与 Den — Connect remote(远程工作区)、签名的 connect-link、Den 的 agent MCP(
search_capabilities/execute_capability)、桌面↔云资源同步、托管推理与 worker 心跳。
4. 巧妙之处(值得带走的设计)
这一节挑几处不显然但很聪明的决策,先白话点出妙在哪,再给源码锚点。