OpenWork 开源桌面应用仓库导读:改一处功能该从哪个目录进去

2026-08-04

本文基于 openwork 仓库 commit 3b41381(2026-08-03)梳理,该项目仍在高频迭代,具体行为以仓库 https://github.com/different-ai/openwork 最新代码与文档为准。

OpenWork 这个仓库不是”一个应用加几个工具包”,它是四类各自独立运行的东西被装进同一个 pnpm workspace:一个 Electron 桌面壳、一个能单独发布安装的本地服务端、一份被桌面与网页两种宿主复用的前端,以及一块许可证跟其余部分不同的企业控制面。 大多数人第一次进来找不到入口,是因为默认它只有一层。全仓 3490 个受版本控制的文件,你真正要改的那一处,九成落在 apps/packages/ee/ 这三块里的某一块,剩下的是打包、文档和验收。

同样是仓库结构导读,站内的 ecc 仓库结构Hermes 仓库结构pi 仓库结构 三篇拆的是各自项目的代码组织方式,本篇只谈 OpenWork——它的特殊之处在于同一个仓库里同时供养着桌面进程、可独立分发的服务进程、多宿主复用的前端,以及一块单独授权的团队控制面,这四者的边界正是本篇要说清的事。

一、先看工作区声明:目录切分是被 pnpm 配置钉死的

不要凭目录名猜,直接看 pnpm-workspace.yaml。它声明的 packages 字段只有四条:apps/*packages/*ee/apps/*ee/packages/*。这四条就是全仓的一级切分,其余顶层目录(docs/evals/packaging/scripts/patches/)都不是工作区成员。

对应地数一下:apps/ 下 4 个,packages/ 下 12 个,ee/apps/ 下 10 个,ee/packages/ 下 3 个。你要新增一个包,就是往这四个位置之一放;放在别处不会被 pnpm 认出来。

同一份文件里还藏着三个会咬人的机制。一是 minimumReleaseAge,给外部依赖设了一段冷却期,只有 minimumReleaseAgeExclude 列出的少数几个(比如 @opencode-ai/sdk)能绕过,所以你想装一个刚发布的新版本很可能装不上,这不是网络问题。二是 patchedDependencies,仓库对 @modelcontextprotocol/sdk 以及 Better Auth 的两个包(@better-auth/drizzle-adapter@better-auth/oauth-provider)打了本地补丁,补丁文件在 patches/ 目录下。三是 allowBuilds 白名单,electronbetter-sqlite3node-ptysharp 这类需要本地编译的包被显式允许执行安装脚本,不在名单上的不会跑。

这三条合起来解释了 AGENTS.md 里那条硬规定:只用 pnpm,永远不要用 npm 或 yarn。换包管理器不是风格问题,是补丁和原生构建会静默失效。

package.json 里锁了 packageManager,工作区根包名是 @different-ai/openwork-workspace,本身是 private,不发布。

下面这张表是全仓的落点索引,后面几节按这个顺序展开:

组成部分它负责什么对应仓库位置你什么时候会碰到它
桌面壳Electron 主进程、窗口与菜单、外部链接与媒体权限、加载本地服务端apps/desktop/electron/改窗口行为、系统集成、打包分发
前端应用React + Vite 单页应用,被 Electron 与纯网页两种宿主复用apps/app/src/改任何界面、交互与路由
本地服务端以文件系统为底的 HTTP API:工作区、技能、MCP、审批、文件会话apps/server/src/加接口、改后端行为、调审批策略
共享包跨进程类型契约、共享 UI、路径解析、连接链接、产品文档packages/改跨进程数据结构、加共享组件
企业控制面Den 控制平面、网关、推理、诊断、数据库与落地页ee/apps/ee/packages/改组织、成员、策略、市场
验收端到端规格与证据带evals/specs/evals/packages/testkit/改动能被运行时观察到时
分发三种打包与自托管方式packaging/aurpackaging/dockerpackaging/helm改安装方式或部署形态

二、apps/:四个进程,四种改法

这一层是主战场。四个包的名字和职责分得很干净。

apps/desktop(包名 @openwork/desktop)是 Electron 壳。入口写在 package.json 的 main 字段:electron/main.mjs。这个目录下都是 .mjs,按关注点拆得很细——browser-panel.mjscomputer-use.mjsopen-external.mjsmedia-permissions.mjssystem-ca.mjslinux-desktop-integration.mjsdev-profile.mjs,其中不少配了同名 .test.mjs 摆在旁边(open-externallinux-desktop-integrationdev-profileruntimeconnect-link 都有,media-permissionscomputer-use 这类偏系统能力的则没有)。打包配置是一组 electron-builder.*.yml,除了基础的还有 cloud、enterprise、demo-a、demo-b 几套。

最值得记住的是 electron/runtime.mjs 这个文件。它做的事是在运行时动态 import 服务端产物 apps/server/dist/embedded.js,然后调用其中的 startEmbeddedServer。这就是桌面壳与服务端之间唯一的缝:桌面应用不是通过网络去连一个外部服务,而是把服务端当模块加载进自己的进程。它在源码路径、打包路径和 process.resourcesPath 三处候选里找这个产物,找不到就直接抛错。

另外 apps/desktop/scripts/prepare-sidecar.mjs 负责准备 OpenCode 的可执行文件作为 sidecar,版本从仓库根的 constants.json 读。AGENTS.md 里那句 “Ejectable: OpenWork is powered by OpenCode” 落到代码上就是这一步——它自己不实现推理引擎与 agent 循环,而是把 OpenCode 装进来。

apps/app(包名 @openwork/app)是前端。这个包自带一份 src/react-app/ARCHITECTURE.md,是全仓性价比最高的一份文档,第一段就写明:apps/app 是 React + Vite 应用,是每一种 OpenWork 部署形态的界面,Electron 壳加载它、纯网页也能提供它,它通过 HTTP 跟 openwork-server、opencode、Den 说话,src/index.react.tsx 是唯一入口。

内部分两大层。src/app/ 是框架无关层,文档里写的约束是这一层不出现 React import,里面放的是各类客户端与桥接(opencode、openwork-server、den、桌面 IPC)、共享类型、常量。src/react-app/ 才是 React 那一半,再分成 shell/kernel/infra/design-system/domains/ 五块。依赖方向是单向的:app/i18n/ 不许反向 import react-app/kernel/infra/ 不许 import domains/shell/ 在最上层可以 import 一切。文档还提到这套规则是拿 madge --circular 验证到零环的。

功能代码按域走 domains/,一个产品域一个目录:session/workspace/settings/connections/cloud/onboarding/。你要改聊天界面就进 domains/session/,要改 MCP 与模型鉴权的界面就进 domains/connections/

路由是工作区作用域的,规范路径形如 /workspace/:workspaceId/session/:sessionId/workspace/:workspaceId/settings/:tab。文档明确要求用 react-app/shell/workspace-routes.ts 生成这些路径,别手拼,也要求活动工作区与会话从 URL 参数读而不是从全局可变状态读。

apps/server(包名 openwork-server)是本地服务端,README 第一句自我定位是给 OpenWork 远程客户端用的、以文件系统为底的 API,并强调它刻意独立于桌面应用。src/ 下顶层就有 138 个 .ts 文件,测试与实现同名并排放,只有三个子目录:routes/extensions/opencode-plugins/routes/ 里是 core.tssessions.tsworkspaces.tsfiles.tsoperations.tsregistry.tscloud-mcp.ts 七个文件。

它的配置默认落在 ~/.config/openwork/server.json,可以用 OPENWORK_SERVER_CONFIG--config 覆盖。环境变量一长串,关键的几个是 OPENWORK_TOKEN(客户端 bearer token)、OPENWORK_HOST_TOKEN(宿主审批 token)、OPENWORK_APPROVAL_MODEmanualauto)。接口面 README 列得很全,从 /health/status/capabilities/workspace/:id/skills/workspace/:id/mcp/workspace/:id/commands/workspace/:id/audit,再到收发件相关的 /workspace/:id/inbox/workspace/:id/artifacts。它还反向代理 OpenCode,路径是 /opencode/*/w/:id/opencode/*

apps/ui-demo(包名 @openwork/ui-demo)是第四个,根脚本 dev:ui-demo 单独起它。

三、packages/:跨进程的契约都压在这一层

12 个包里有几个必须认识。

@openwork/types 是跨进程线上契约的家。前端架构文档说得直白:跟其它进程共享的 wire 契约放 packages/types,比如 WorkspaceWire,生产方的类型要断言可赋值到它。桌面策略文档也指向同一处——策略目录 desktopPolicyDefinitions 定义在 packages/types/src/den/desktop-policies.ts,并要求 API、Den 网页端、桌面端都从这里 import,不要各自复制一份 ID 列表。

@openwork/paths 很小,只对外暴露一个入口,被桌面包直接依赖,负责路径解析这类最底层的事。@openwork/ui 是共享 React 组件。@openwork/connect-link 对应桌面端那套连接链接机制(apps/desktop/electron/connect-link.mjs 是它的消费方)。@openwork/enterprise-mcp-client@openwork/enterprise-mcp-mock-server 一对,一个是客户端一个是本地假服务端。openwork-bootstrapopenwork-ui-mcp 两个包不带 scope,从名字与位置看是要独立分发的。

packages/docs 是个例外:它没有 package.json,不是真正的工作区包,而是一份文档站的内容目录。里面有 57 份 mdx,其中 model-context-protocol/ 一个目录就占 10 份,一份对应一个客户端的接入指南(chatgptclaude-codeclaude-desktopcodexcursorgemini-cliopencodevs-codewindsurfzed)。另有 docs.jsonopenapi.jsonroadmap.mdxchangelog.mdx,以及 start-here/cloud/ 两个面向不同读者的分区。

要区分清楚:packages/docs/ 是给用户看的产品文档,仓库根的 docs/(20 份 md)是给开发者看的架构与设计说明,两者不是一回事。

四、ee/:团队控制面单独一块,许可证也单独一块

ee/ 是这个仓库最容易被误读的部分,先把授权讲清楚。

LICENSE 开头写的是 Copyright (c) 2026-present Different AI, Inc.,然后明确分层:所有位于 /ee 目录下的内容按 ee/LICENSE 定义的许可证(根 LICENSE 称其为 Fair Source License);第三方组件按各自原始许可证;上述范围之外的内容才是 MIT,Copyright 2026 Different AI。打开 ee/LICENSE 看,正文标题是 Functional Source License, Version 1.1, MIT Future License,缩写 FSL-1.1-MIT,Notice 段落写 Copyright 2026 Different AI Inc。

所以把 OpenWork 笼统说成”MIT 开源项目”是不准确的。凡是涉及团队控制面、组织管理、推理编排这些能力,你碰到的代码在 ee/ 下面,适用的是另一份许可证。能不能商用、能不能改、改完能不能分发,一律以许可证原文为准,本文不提供法律意见。

结构上,ee/apps/ 10 个:den-apiden-controllerden-gatewayden-webden-worker-proxyden-worker-runtimediagnosticsenterprise-mock-labinferencelanding。其中 den-controller 目录下目前只剩一份 README,den-worker-runtime 只有 README 加脚本与一份 Dockerfile——ee/apps/den-api/README.md 第一句就写明 den-api 是 Hono 实现的 Den 控制平面,前身叫 den-controller,是当前活跃实现。这类”名字还在但内容已迁走”的目录,不看 README 会白翻半天。

ee/packages/ 3 个:den-admin-mcpden-dbutils。数据库这层用 Drizzle,ee/packages/den-db 下有 drizzle/drizzle.config.ts

README 里对 Den 的定位是团队与组织层面的控制平面,能力包括按规模开通推理并控制哪些成员与团队可用哪个模型供应商、邀请成员建团队管权限、下发桌面策略与限制可用的应用版本、通过市场发布技能与插件并按组织/团队/个人分配。这些是仓库 README 自己的说法,不是本文的评价。

五、这套结构放弃了什么

先说它明确不管的事。

它不实现推理引擎与 agent 执行循环。AGENTS.md 的表述是 OpenWork 由 OpenCode 驱动,OpenCode 能做的事在 OpenWork 里都能用,哪怕还没有专门的界面。桌面包在打包前用脚本去取 OpenCode 二进制,版本记在 constants.json。所以你想改”模型怎么调工具、上下文怎么裁”这类问题,改点大概率不在这个仓库里。要建立这层判断,可以先看 MCP 协议是什么 把协议边界与实现边界分开。

它也不是能拆着用的组件库。工作区内的包大量用 workspace:* 互相引用,加上依赖补丁与原生构建白名单,单独 clone 某个子目录基本跑不起来。apps/server 是唯一被刻意设计成可独立分发的(README 明说它独立于桌面应用,可以全局安装后直接跑),其余都绑在工作区里。

再说代价,这部分跟你的机器与数据直接相关。

第一,桌面壳把服务端加载进自己的进程,而这个服务端是以文件系统为底的——它对你指定的工作区目录有读写能力。README 里的缓解手段是所有写操作都经过宿主审批,接口是 GET /approvalsPOST /approvals/:id,鉴权要么用 X-OpenWork-Host-Token,要么用 scope 为 owner 的 bearer token。同时它给了 OPENWORK_APPROVAL_MODE=auto 这个开关用于本地开发,开了就是全自动放行。这个开关放在环境变量里,很容易设了忘关。

第二,凭据是集中保管的。docs/external-mcp-oauth.md 写得很明白:OpenWork 发现并授权由 Den 托管的外部 MCP 连接时,access token、refresh token、client secret、PKCE verifier 以及待处理的授权事务都加密留在 Den,不进入 agent 引擎。这个设计把凭据挡在引擎之外是对的,但它同时意味着暴露面集中到了 Den 这一处——自托管的话,这处就是你的责任。

第三,装了组织版之后,功能开关未必在你手里。docs/desktop-app-policies.md 说桌面策略由云端通过 GET /v1/me/desktop-config 下发,在应用内通过 DesktopConfigProvider 暴露;布尔型策略键取 false 表示该功能被限制;响应里还带 allowedDesktopVersions,用来约束组织内允许运行的应用版本。组织侧同时能配置引导语提示。换句话说,这是一台你在用、但策略由组织决定的机器。相关的权限扩张问题,可以对照 权限逐步扩大 那篇的思路自查。

第四,能力发现走的是一条统一的通道而不是不断新增工具。docs/marketplace-capabilities-architecture.md 里反复强调 MCP 工具面不扩张,始终只有两个工具,README 也给出了这两个名字:search_capabilities 负责找到你能用的能力,execute_capability 负责执行。好处是接入端稳定,代价是你在客户端那侧看不到细粒度的工具清单,能用什么取决于服务端的目录与你的授权。

六、上手与避坑清单

别用 npm 或 yarn 装依赖。 会踩是因为多数人进新仓库先敲习惯的命令。后果不是报错而是静默失真:patchedDependencies 里的补丁不会打上,allowBuilds 白名单里的原生模块不会编译,workspace:* 的互链解析也不一样。避法是先看 AGENTS.md 的 Package Managers 一节,全程 pnpm。

别拿 pnpm dev 去调纯界面改动。 会踩是因为根 package.jsondev 看起来最像”启动开发”。它实际做的是设 OPENWORK_DEV_MODE=1 并把请求转给 @openwork/desktop 的 dev,会拉起 Electron 与嵌入式服务端,反馈慢。只改 UI 就用 dev:ui,它只起 @openwork/app。要跑团队控制面那套则是另一组命令(dev:den 系列),它们会带起数据库与多个服务。

同时开多个 git worktree 时用 dev:worktree,不要用 dev 会踩是因为两个 checkout 各起一份桌面应用,profile 锁只有一个能拿到。README 写了 dev:worktree 做的三件事:设 OPENWORK_DEV_PROFILE=auto 从 worktree 路径推导稳定的 profile 名、让 Electron 自选空闲的 CDP 端口、让 Vite 自选空闲端口。它还默认打开 OPENWORK_ELECTRON_USE_MOCK_KEYCHAIN=1,原因 README 也说了:全新 profile 没有存过凭据,在 macOS 上 Chromium 一旦持久化已认证 cookie 就会弹系统钥匙串,那个模态框会卡住 Electron 主循环。

别在 src/app/ 里 import React。 会踩是因为写着写着发现某个逻辑需要一点组件状态,顺手就引了。这一层是被当作框架无关层维护的,架构文档给的做法是反转依赖(回调注册)或者把原语下沉,而不是把 React 引上来。同理,跨进程共享的类型别就地定义,放 packages/types

别手写 /session/... 这种路径。 会踩是因为这些兼容入口现在还能访问。架构文档的立场是它们只是兼容入口,能解析出工作区时应当重定向到工作区作用域的 URL;正经做法是用 shell/workspace-routes.ts 构造路径,活动工作区与会话从 URL 参数读。

别把 ee/ 下的代码当 MIT 复制走。 会踩是因为仓库根有 MIT 字样,扫一眼就下结论了。根 LICENSE 是分层声明,/eeee/LICENSE。判断以许可证原文为准。

改了运行时能观察到的行为,就得配一条验收。 会踩是因为很多人以为跑通了本地就算完。AGENTS.md 的要求是新增的可执行端到端覆盖只有一条路径:evals/specs/**/*.test.tstest@openwork/testkit 引入,驱动应用的规格用 .slow.test.ts 后缀。提 PR 要报告跑了什么命令、结果如何,并附上 testkit 的证据带;纯文档、纯类型、惰性配置可以跳过,但要说明。规格文件集中放在 evals/specs/ 下,evals/ 顶层还有 26 份流程说明 md,写新规格前先翻同名流程文档。注意 evals/ 自带 pnpm-workspace.yaml,是个嵌套的独立工作区,所以根脚本里对它的调用形如 pnpm --dir evals run test

别在本地随手开自动审批然后忘了关。 会踩是因为手动审批在调试时确实烦。OPENWORK_APPROVAL_MODE=auto 意味着服务端对工作区的写操作不再问你。开了就记在待办里,收工前关掉。

七、收束

真要动手,按这个顺序读四份文件基本不会走弯路:pnpm-workspace.yaml 确认目录切分与依赖机制,AGENTS.md 确认工程纪律与验收路径,apps/app/src/react-app/ARCHITECTURE.md 确认前端分层与路由规范,apps/server/README.md 确认服务端的配置项、环境变量与接口面。四份读完,再打开你要改的那个域目录。

改动落地前给自己过一遍:这个需求到底是界面问题、服务端问题,还是控制面问题?改点落在 MIT 那部分还是 ee/ 那部分?有没有跨进程的数据结构要同步进 packages/types?运行时行为变了吗,变了就去 evals/specs/ 补一条。分发方式受影响吗,受影响就去 packaging/ 看那三种方式(AUR、Docker、Helm)哪一种要跟着改。

最后提醒一句和代码无关的事:这类工具会在你的机器上装桌面应用、代管模型凭据与第三方服务授权,还可能连上办公套件与组织控制面。上手之前,把”它能读写哪些目录、凭据存在谁那里、组织侧能看到与限制什么”这三个问题弄清楚,比先跑起来重要。

本篇属于一个把开源AI 工作流桌面应用 OpenWork逐层拆开讲的系列,整体地图见 OpenWork 是什么:把技能与 MCP 打包成能力的开源桌面应用;沿着这条线往下,还可以看 OpenWork 桌面应用与 OpenCode 内核:能力分发层的边界开源桌面应用 OpenWork 的许可证分层:MIT 与 /ee 目录边界如何影响自建与商用

想系统学会用 AI?报名体系课或加入会员,照着学、照着用。