数据截至 (上游 commit a9c304f343f1)
可扩展性:技能、MCP、插件与内 建扩展
30 秒导读: OpenWork 是一个跑在你自己文件上的 AI agent 桌面应用。裸模型只会说话——它不知道你的公司规范、连不上你的 Notion、也没有画图的手。本章讲 OpenWork 最核心的产品价值:用四条互补的路给 agent 装能力(技能 / MCP / 插件 / 内建扩展),以及它们如何全部落到
opencode.json、.opencode/目录、和服务端 runtime 配置这三处载体上。
本章聚焦「能力从哪来、装到哪去、agent 运行时怎么看见」。想先了解 OpenWork 的整体三 sidecar 架构,看 02-orchestrator;想了解承载这些读写 API 的服务端本身,看 03-openwork-server。
1. 这是什么(零基础也能懂)
一句话定义: OpenWork 底层跑的是 OpenCode 引擎,它天生支持技能、MCP、插件三种扩展点;OpenWork 在其上再加一层「内建扩展」和「一键安装」的 UI,把「给 agent 装能力」变成点几下按钮的事。
为什么需要它。 一个只有基础工具(读文件、跑命令、搜网页)的 agent,遇到下面这些请求会卡住:
- "按我们团队的 PR 规范来改" —— 它不知道你的规范。
- "查一下我 Google 日历上周五有没有空" —— 它连不上 Google。
- "把这个功能画成一张架构图" —— 它没有生成图片的能力。
四条路,各补一块。 OpenWork 把「装能力」这件事拆成四种手段,每种补的东西不一样:
| 扩展路径 | 白话:它给 agent 补什么 | 典型例子 |
|---|---|---|
| 技能 Skills | 一段领域知识 / 操作手册,按需注入 | "我们的发布流程"、"如何写符合规范的 commit" |
| MCP | 一条通往外部系统的连线(带工具) | 连 GitHub、Linear、Notion、自建服务 |
| 插件 Plugins | 一段改造引擎本身的代码 | 拦截请求、加自定义工具、改 prompt |
| 内建扩展 Extensions | OpenWork 随包带的托管能力 | Google Workspace、OpenAI 生图 |
一句话直觉: 把 agent 想成一个新来的实习生。技能是给他的《员工手册》;MCP 是给他公司各系统的门禁卡;插件是直接改造他的工位设备;内建扩展是公司已经配好、开箱即用的那几台机器。
用起来什么样。 用户在 OpenWork 的「Extensions」设置面板里操作,底层每一步都归结为对三处载体的读写:添加一个技能 = 往 .opencode/skills/<name>/SKILL.md 写一个文件;连一个 MCP = 往 runtime 配置里加一条 mcp 记录;装一个插件 = 往 opencode.json 的 plugin 数组里加一项。UI 分区见 ExtensionsSection(apps/app/src/react-app/domains/settings/pages/extensions-view.tsx:10-20,已扩展到 apps / connections / commands / agents / 签到态等 11 个分区)。
2. 顶层全景(它大概怎么转)
2.1 三处载体
理解本章的关键,是先记住能力最终落到哪三处——所有扩展路径都在往这三处之一写:
| 载体 | 位置 | 谁在读 | 承载什么 |
|---|---|---|---|
.opencode/ 目录文件 | 工作区内(受版本控制) | OpenCode 引擎按目录约定加载 | 技能、命令、agent、目录级插件文件 |
opencode.json | 工作区 / 全局 | 引擎启动时读 | plugin 数组、mcp 映射(用户手写的) |
| 服务端 runtime 配置 | 服务端专属 SQLite(runtime DB) | 服务端构建后注入给引擎 | OpenWork 通过 UI 添加的 MCP / 插件 |
前两处是 OpenCode 原生的、用户可手写、可提交进 git;第三处是 OpenWork 私有的——UI 加的 MCP 不直接写用户的 opencode.json,而是存进 runtime DB,再在构建引擎配置时合并注入(见 openwork-runtime-config.ts:135 的 mcp: runtimeMcpMap(runtimeConfig))。这样「用户手写的配置」和「OpenWork 帮你加的」互不污染。
2.2 一张图:四条路如何汇入引擎
怎么读这张图:左边是四条扩展路径(用户视角),中间是它们各自落地的载体,右边是最终消费者——OpenCode 引擎。从左到右是「安装 → 落地 → 生效」。
用户操作(UI/手写) 落地载体 消费者
┌─────────────┐
│ ① 技能 │──写文件──▶ .opencode/skills/<n>/SKILL.md ─┐
└─────────────┘ │
┌─────────────┐ │
│ ② MCP │──写 DB──▶ runtime 配置(mcp 映射) ────────┤
└─────────────┘ │ ▼
┌── ───────────┐ │(构建时合并) ┌──────────────────┐
│ ③ 插件 │──写 DB/json─┴──▶ plugin 数组─▶│ OpenCode 引擎 │
└─────────────┘ │ (buildOpenwork- │
┌─────────────┐ │ RuntimeConfig) │
│ ④ 内建扩展 │──随包内建──▶ openwork_context/query/execute ──┘
└─────────────┘ (服务端 action 注册表)
MCP 那条线还有一个「热同步」旁路:UI 加 MCP 后,除了写 runtime DB,服务端还会立刻把这一条推给正在运行的引擎(syncRuntimeMcpToOpencodeEngine,apps/server/src/server.ts:3201),不必等引擎重启就能连接。见 §3.2。
2.3 部件一句话职责
| 部件 | 干什么 | 文件 |
|---|---|---|
skills.ts | 扫描/读写工作区与全局技能 | apps/server/src/skills.ts |
mcp.ts | 列出/增删/开关 MCP(写 runtime DB) | apps/server/src/mcp.ts |
plugins.ts | 列出/增删 OpenCode 插件 | apps/server/src/plugins.ts |
cloud-plugins.ts | 安装 marketplace 云插件包 | apps/server/src/cloud-plugins.ts |
claude-plugin-bundle.ts | 把 Claude Code 插件仓库转成云插件包 | apps/server/src/claude-plugin-bundle.ts |
extensions/index.ts | 内建扩展 action 的注册表与派发 | apps/server/src/extensions/index.ts |
openwork-runtime-config.ts | 构建引擎可见的最终配置(注入插件+MCP) | apps/server/src/openwork-runtime-config.ts |
3. 核心机制(逐个机制,由浅入深)
3.0 基线:扩展之前 agent 免费拿到什么
在讲四条扩展路之前,先明确基线:agent 不装任何扩展时,已经有一整套内建工具。前端在 apps/app/src/lib/build-in-tools.ts 里为这些工具逐个定义了类型和识别函数,用来渲染每种工具调用的 UI。
这份清单就是「裸 agent 的能力边界」:bash、read、write、edit、grep、glob、webfetch、websearch、task(子 agent)、lsp、apply_patch、todowrite,以及一个特殊的 skill(见 build-in-tools.ts:351 的 SkillToolPart)。
注意 skill 也在这份内建清单里——这说明「调用技能」本身是引擎的一等工具,而技能内容才是可扩展的部分。四条扩展路做的,都是在这条基线之上追加能力。
3.1 技能 Skills —— 给 agent 的操作手册
要解决的小问题: 怎么把「一段领域知识/操作步骤」按需喂给 agent,而不是塞满上下文。
思路: 一个技能就是一个文件夹,里面有一个 SKILL.md,frontmatter 写 name 和 description,正文写操作手册。引擎按 description 判断相关性,需要时才用 skill 工具把正文读进来。
技能长什么样(示意,非源码):
---
name: release-checklist
description: Use when cutting a new release — bumps version, updates changelog.
---
# 发布流程
1. 跑 `pnpm test`
2. 更新 CHANGELOG
3. 打 tag …
发现:多来源、多约定的扫描。 OpenWork 不只认自己的目录,还要兼容 Claude Code、通用 agent 等约定。listSkills 会沿工作区向上直到 .git 边界逐层找项目技能,再可选地找全局技能(apps/server/src/skills.ts:188 的 listSkills):
| 作用域 | 扫描的目录 |
|---|---|
| 项目 | <root>/.opencode/skills、<root>/.claude/skills |
| 全局 | ~/.config/opencode/skills、~/.claude/skills、~/.agents/skills、~/.agent/skills |
每个目录还支持两种布局:扁平的 skills/<name>/SKILL.md,以及领域分组的 skills/<domain>/<name>/SKILL.md——listSkillsInDir 找不到直接的 SKILL.md 时会再往下钻一层(skills.ts:145 起)。同名技能按扫描顺序去重取先见者(skills.ts:212-218)。
写入。 用户导入/新建技能时,upsertSkill 一律写到项目的 projectSkillsDir,即 .opencode/skills/<name>/SKILL.md(skills.ts:261;路径定义见 workspace-files.ts 的 projectSkillsDir)。写入前 buildSkillContent 会校验名字 、补齐 frontmatter,并强制 frontmatter 的 name 与 payload 一致(skills.ts:226)。
从远程安装(已改道)。 旧版这里的"从 GitHub Hub 仓库(skill-hub.ts)一键安装技能"机制已在上游移除——apps/server/src/skill-hub.ts 不复存在,DEFAULT_HUB_REPO、installHubSkill、resolveSafeChild 等符号均不可再 grep 到。技能的远程分发并入了两条新通道:云插件市场(§3.4,来自 Den 的 marketplace 分发,技能作为包成员落盘)与 Claude 插件仓库导入(§3.4,claude-plugin-bundle.ts,GitHub 仓库里 skills/ 目录下的技能会被逐个装入)。
3.2 MCP —— 通往外部系统的连线
要解决的小问题: 怎么让 agent 用上 GitHub、Notion、或你自建服务的工具,而这些系统各有各的鉴权。
思路: MCP(Model Context Protocol,模型上下文协议)是一套标准,一个 MCP server 对外暴露一组工具;引擎作为 client 连上去就能用这些工具。OpenWork 负责「配置这条连线 + 走完鉴权」。
三层来源、就近覆盖。 listMcp(mcp.ts:477)把 MCP 分三个来源合并,优先级从低到高:
来源(source) | 来自哪 | 谁写的 |
|---|---|---|
config.global | ~/.config/opencode/opencode.json 的 mcp | 用户手写 |
config.project | 工作区 opencode.json 的 mcp | 用户手写 |
config.remote | 服务端 runtime DB 的 mcp 映射 | OpenWork UI 加的 |
同名时项目覆盖全局、runtime 覆盖项目(mcp.ts:552-575:config.global→config.project→config.remote)。还有一个巧妙细节:isMcpDisabledByTools(mcp.ts:472)会检查 tools.deny 里有没有 mcp.<name>、mcp.* 等 glob——用户可以不删 MCP、只在 deny 列表里把它「静音」,UI 会把这类标成 disabledByTools。
UI 加的 MCP 写哪。 addMcp / removeMcp / setMcpEnabled 全部只动 runtime DB(mcp.ts:662/678/694,底层是 writeRuntimeOpencodeConfig),不碰用户的 opencode.json。setMcpEnabled 特意用「按路径更新」而非整块覆盖,以保留条目内的行内注释(mcp.ts:693 的注释,提到 #1444 回归)。
OAuth 流程(最有工程含量的一支)。 远程 MCP 常需要 OAuth。这条流程由引擎驱动,OpenWork 桌面端弹一个模态框(apps/app/.../connections/mcp-auth-modal.tsx)扮演「浏览器」。以 mcp.oauth-flow.e2e.test.ts 佐证的完整链路:
怎么读:每一步命中即进入下一步;虚线是「远程工作区 loopback 打不通」时的手动兜底。
引擎 OAuth 提供方(MCP server)
│ POST /mcp/<name>/auth
├───── 发现 + 动态客户端注册(DCR)──────▶ POST /register
│◀──── 返回 authorizationUrl(带 PKCE S256 challenge)
│
用户打开 authorizationUrl ─────────────────▶ GET /authorize
│◀──── 302 重定向到引擎 loopback callback(带 code)
│
├───── 用 code 换 token(PKCE 验证)───────▶ POST /token
│◀──── access token(持久化到 mcp-auth.json,跨重启复用)
│
└───── 已鉴权的 MCP 握手 ──────────────────▶ POST /mcp ✔ connected
┆ (loopback 不可达时) POST /mcp/<name>/auth/callback ← 手动兜底
测试断言了几处关键行为:授权 URL 必带 code_challenge_method=S256(PKCE 防截获,mcp.oauth-flow.e2e.test.ts:163);token 落盘到 mcp-auth.json 供跨重启复用(:210);登出 DELETE /mcp/<name>/auth 会删除存储的 token 并断连(:228)。无 OAuth 的普通远程 MCP 则简单得多,mcp.remote-connect.e2e.test.ts 佐证:addMcp → listMcp → removeMcp 一条龙,只动 runtime 配置。
引擎热同步(engine sync)。 runtime DB 是「真相源」,但正在跑的引擎实例未必立刻知道。syncRuntimeMcpToOpencodeEngine(apps/server/src/server.ts:4296)负责把 runtime MCP 推进引擎:
- 逐条
POST /mcp,单条失败不阻断后续——一个坏 MCP 不能拖垮其余条目的注册(server.ts:4350-4353的注释与循环)。 - 对 5xx/网络错误重试一次(引擎常在 dispose 后短暂重建中),4xx 不重试(
postMcpEntryWithRetry,server.ts:4433,循环上限attempt < 2于:4441)。 - 把最后一次同步结果按工作区记下来(
recordEngineMcpSyncResult,server.ts:5033),GET /workspace/:id/mcp会带上engineSync字段(server.ts:3033),让 UI 能解释「为什么这个明明启用的 MCP 却显示未连接」。 - 启动时
syncAllWorkspacesRuntimeMcpToEngine(server.ts:5314)补推所有工作区——因为注入的 runtime 配置文件只覆盖workspaces[0],其余工作区的 MCP 靠它才对引擎可见。
mcp.engine-sync.e2e.test.ts 用一个 mock opencode server 佐证了这些重试/失败上报路径。
3.3 插件 Plugins —— 改造引擎本身
要解决的小问题: 技能只是知识、MCP 只是外连,想改引擎行为(拦请求、加工具、改 prompt)怎么办?
思路: 插件是 OpenCode 原生的扩展方式(README 原话:插件是扩展 OpenCode 的 native 方式)。在 opencode.json 的 plugin 数组里列一个 npm 包名或文件路径,引擎加载它,插件导出的钩子就生效了。
最小配置(README opencode.json 示例):
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["opencode-wakatime"]
}
四种来源、有加载序。 listPlugins(plugins.ts:53)聚合插件并给出 loadOrder:
来源(source/scope) | 来自哪 |
|---|---|
config / project | 工作区 opencode.json 的 plugin 数组 + runtime DB 的插件列表 |
dir.project | <root>/.opencode/plugins/*.js|.ts |
dir.global | ~/.config/opencode/plugins/*.js|.ts |
加载序固定为 ["config.global", "config.project", "dir.global", "dir.project"](plugins.ts:85)。
规格归一化去重。 插件规格五花八门(@scope/[email protected]、file:...、/abs/path、裸包名),normalizePluginSpec(plugins.ts:11)把版本号/协议前缀剥掉再比,避免同一插件写两遍。addPlugin 也用它判重后才追加进 runtime 插件列表(plugins.ts:89)。
OpenWork 自己注入的内建插件。 构建引擎配置时,plugin 数组头部会硬塞几个 OpenWork 私有插件,再接上用户的 runtime 插件(openwork-runtime-config.ts:125-133):
plugin: [
"opencode-chrome-devtools", // 浏览器自动化
openworkExtensionsPreviewPluginPath(), // 暴露 openwork_context/query/execute 元工具
openworkCapabilitiesKnowledgePluginPath(),
openworkOfficeAttachmentsPluginPath(), // 邮件附件(新增)
openworkAnthropicAdaptiveThinkingPluginPath(),
openworkAnthropicToolSchemaPluginPath(),
...runtimePluginList(runtimeConfig), // 用户装的
]
其中 openworkExtensionsPreviewPluginPath() 指向的那个插件,正是把「内建扩展」接进 agent 的桥梁——见 §3.5。这几个内建插件的解析路径要兼顾开发态(.ts)和 Electron 打包态(app.asar 外的 opencode-plugins/*.js),逻辑在 openwork-extensions-plugin-path.ts 的 openworkPluginPath(:17 起,office-attachments 的路径常量在 :38)。
3.4 云插件包与 Claude 插件兼容 —— 一键装一整包
要解决的小问题: 技能、MCP、命令、agent 往往成套出现(一个团队的整套配置)。逐个装太累,能不能一个链接装一整包?
思路: 云插件(marketplace package)把「若干配置对象」打成一个 CloudPluginResolved,installCloudPlugin(cloud-plugins.ts:536)遍历其成员,按类型分别落地;每个插件还带 marketplaceId,安装时同时登记进 marketplaces 索引(cloud-plugins.ts:623-634),供桌面云同步对账(见第 6 章)。
命名空间隔离。 一整包的东西全部装到 <plugin-namespace>/ 子目录下,避免和你已有的技能/命令撞名。落地路径由 getPluginObjectInstallPath(cloud-plugins.ts:226)按对象类型决定:
| 对象类型 | 落地路径 |
|---|---|
| skill | .opencode/skills/<ns>/<name>/SKILL.md |
| agent | .opencode/agents/<ns>/<name>.md |
| command | .opencode/commands/<ns>/<name>.md |
| mcp | 走 addMcp 写 runtime 配置(不落文件) |
注意 MCP 成员不写文件,而是复用 §3.2 的 addMcp(cloud-plugins.ts:562-565)——所以云插件里的 MCP 也自动走 runtime DB + 引擎热同步那套。卸载 removeCloudPlugin(cloud-plugins.ts:649)按安装时记下的文件清单精确回滚:文件删文件,MCP 调 removeMcp;重装同一插件时,上次装过、这次清单里消失的 MCP 名也会被算出来一并清掉(removedMcpNames,:601-606)。
兼容 Claude Code 插件仓库。 很多现成插件是 Claude Code 格式(.claude-plugin/plugin.json + .mcp.json + skills//commands//agents/)。claude-plugin-bundle.ts 的巧思是:不新写一套安装逻辑,而是把 GitHub 仓库解析成同样的 CloudPluginResolved 形状,从而白嫖 installCloudPlugin 的命名空间、frontmatter 翻译、MCP 注册、卸载全套(见文件顶部注释,claude-plugin-bundle.ts:1-12)。
resolveClaudePluginBundle(claude-plugin-bundle.ts:268)处理了不少现实脏活:
- URL 解析容忍
github.com/owner/repo、owner/repo、.git后缀、/tree/<ref>/<subdir>,并只允许 github.com(parseClaudePluginSource:72)。 - 分支名可含斜杠(
release/v1),/tree/<...>里 ref 和子目录的边界因此有歧义——resolveRefAndTree逐个候选去撞 trees API,取第一个能解析的(claude-plugin-bundle.ts:160)。 - 定位插件根:给定子目录、仓库根、或最浅的含
plugin.json的目录;同深度出现多个则报「歧义,请在 URL 里指定目录」(locatePluginRoot:202)。 - 不支持的部分明确降级并告警而非静默:声明了
hooks、用了${CLAUDE_PLUGIN_ROOT}的 MCP 、技能带SKILL.md以外的额外文件——都推进warnings让用户知情(claude-plugin-bundle.ts:292-293、:346、:334)。
UI 侧对应 apps/app/.../connections/modals/claude-plugin-import-modal.tsx 与 add-mcp-modal.tsx。
3.5 内建扩展 Extensions —— 随包带的托管能力
要解决的小问题: Google Workspace、生图这类能力,鉴权和 API 调用都复杂,不该让每个用户自己配 MCP。OpenWork 想开箱即用。
思路(两段桥): 内建扩展的能力不作为普通工具直接给 agent,而是经由一个「应用上下文 + affordance(带效果声明的动作面)」桥接。agent 的工具列表保持三个精简元工具,扩展能力按需发现:
agent 想干活 → openwork_context (读语义快照:当前界面/可用 affordance)
→ openwork_query (side-effect-free 的 affordance)
→ openwork_execute(带副 作用的 affordance, expectedRevision 防陈旧写)
│ affordance id: extension.actions / extension.call
▼ HTTP /experimental/extensions/*
openwork-server 的扩展注册表
│ 按 extensionId 派发
┌─────────┼──────────────┐
▼ ▼ ▼
google-workspace openai-image- openwork-cloud-uploads
(日历/Gmail/Drive) generation (传 Drive / Gmail 草稿附件)
(gpt-image-2)
第一段桥:引擎内的三个元工具。 openwork-extensions-preview.ts 是一个 OpenCode 插件(就是 §3.3 里被硬注入的那个),它:
- 通过
experimental.chat.system.transform往系统提示注入「OpenWork app context」指令段(openwork-extensions-preview.ts:929,指令常量OPENWORK_AGENT_SURFACE_INSTRUCTION在:138)——告诉 agent"依赖当前界面/标签页/设置面板的请求先读openwork_context,每个 affordance 都声明了效 果与执行者"。 - 暴露
openwork_context(:960)、openwork_query(:971)、openwork_execute(:978)三个工具:前两个只读,后者执行命令。 - 扩展发现与调用被建模成两个 affordance:
extension.actions(查,:449)与extension.call(调,:492),底层打服务端的/experimental/extensions/*。 - UI 控制走一个带 token 的本地 UI bridge(
discoverUiBridge,:318,2 秒缓存、5 秒超时);connect.*前缀的 affordance 会被明确指回"专用 Connect 执行者工具"(:458-462)。
第二段桥:服务端的 action 注册表。 extensions/index.ts 是派发中枢:OPENWORK_EXPERIMENTAL_EXTENSION_ACTIONS 汇总所有内建扩展的 action(extensions/index.ts:26-30);callExperimentalExtensionAction(:51)按 extensionId 找到对应模块调用;找不到实现则回 501「已注册但未实现」(:95)。HTTP 入口在 routes/core.ts:318(list)与 :328(call)。
三个现成扩展:
扩展(extensionId) | 提供的 action(节选) | 鉴权 |
|---|---|---|
google-workspace | calendar_list_events、gmail_create_draft、drive_search_files、chat_send_message… | Google OAuth |
openai-image-generation | status、image_generate(gpt-image-2 出 PNG) | OpenAI API key |
openwork-cloud-uploads | 上传工作区文件到 Google Drive、带附件建 Gmail 草稿(extensions/cloud-uploads.ts:25-42) | 复用 Google 连接 |
生图扩展的落地细节值得一看(openai-image-generation.ts:155 的 generateOpenAiImageArtifact):key 依次从环境变量服务和进程环境找(OPENWORK_OPENAI_IMAGE_API_KEY → OPENAI_API_KEY,:58-60);生成的 PNG 只写到工作区的 artifacts/ 下,且过 resolveSafeChildPath 防路径穿越(:102);把 base64 解码落盘后回一个相对路径(:166-172)。
4. 巧妙之处(可借鉴的技术)
-
一份安装逻辑,喂三种来源。 云插件、Claude 插件仓库最终都被规整成
CloudPluginResolved,复用同一个installCloudPlugin(cloud-plugins.ts:536)。新增一种「插件源」= 写一个解析器,而非再抄一套安装/卸载/命名空间/回滚。这是「归一化到一个内部表示」的典范。 -
用户配置与工具配置分家。 UI 加的 MCP/插件进 runtime DB,不污染用户手写的
opencode.json;构建时才合并注入(openwork-runtime-config.ts:135)。用户随时能把自己的配置提交进 git,而不会混入 OpenWork 的私货。 -
单条失败不连坐 + 有限重试。 引擎 MCP 热同步逐条推、坏条目跳过、5xx 才重试一次(
server.ts:4350-4353、postMcpEntryWithRetry:4433)。一个失效的第三方 MCP 不会让整批注册瘫痪。 -
降级要吵、要留痕。 Claude 插件遇到不支持的
hooks/${CLAUDE_PLUGIN_ROOT}/多余文件,一律进warnings而非静默丢弃(claude-plugin-bundle.ts:292);MCP 同步失败也记录并经engineSync字段回给 UI(server.ts:5033)。宁可让用户看见「装了但有东西没生效」。 -
元工具做能力发现。 内建扩展不塞满工具列表,而是
openwork_context/openwork_query/openwork_execute三个元工具 + 声明式 affordance 按需发现(openwork-extensions-preview.ts:960-978),配指令段引导(:929)。加新扩展不用改 agent 的工具 schema。
5. 边界与局限(诚实)
- Claude 插件的 hooks 不支持。 声明了 hooks 会被跳过并告警(
claude-plugin-bundle.ts:292-293)。 - 插件本地 MCP 命令不支持。 用
${CLAUDE_PLUGIN_ROOT}指向插件内命令的 MCP 会被跳过(claude-plugin-bundle.ts:346)——OpenWork 只接远程/独立 MCP。 - 技能只装
SKILL.md(bundle 路径)。 Claude 插件里带额外文件的技能,通过 bundle 路径导入时只装SKILL.md(claude-plugin-bundle.ts:334);旧的 Hub 安装路径(会装全整个技能目录)已随skill-hub.ts一并移除。 - 内建扩展是实验性的。 走的是
/experimental/extensions/*,注册了但没实现的 action 会明确回 501(extensions/index.ts:95)。目前是 Google Workspace、OpenAI 生图与云上传三个。 - 只接受 github.com 的插件源。 非 github.com 的 URL 直接拒(
claude-plugin-bundle.ts:72的parseClaudePluginSource)。
6. 横向对比
本章讲的是 OpenWork「装能力」这一面;它和同组其它章共同拼出完整系统:
- 承载这些读写 API、鉴权、审计的服务端本身 → 03-openwork-server。
- 引擎、服务端、路由三个 sidecar 由谁监管、如何启停 → 02-orchestrator。
- 这些扩展在 UI 里长什么样、会话如何实时渲染工具调用 → 05-frontend。
- 远程/云工作区下 MCP 的 loopback 兜底与托管 worker → 06-remote-cloud。
与 shelf 内其它 coding agent 的取舍:OpenWork 不自造扩展协议,而是站在 OpenCode 的原生扩展点上(技能/插件/MCP 都是 OpenCode 的概念),只在其上加「一键安装 + 托管扩展 + UI」。它的差异化不在「发明新扩展模型」,而在「把已有扩展模型的安装体验做到点几下」。
7. 代码地图(导航索引)
| 主题 | 文件 | 关键符号 |
|---|---|---|
| 技能扫描(多来源/多布 局) | apps/server/src/skills.ts | listSkills、listSkillsInDir、parseSkillEntry |
| 技能写入 | apps/server/src/skills.ts | upsertSkill、buildSkillContent、deleteSkill |
| MCP 列举/增删/开关 | apps/server/src/mcp.ts | listMcp、addMcp、removeMcp、setMcpEnabled、isMcpDisabledByTools |
| MCP 引擎热同步 | apps/server/src/server.ts | syncRuntimeMcpToOpencodeEngine、postMcpEntryWithRetry、syncAllWorkspacesRuntimeMcpToEngine |
| 插件列举/增删/归一化 | apps/server/src/plugins.ts | listPlugins、addPlugin、normalizePluginSpec |
| 云插件安装/卸载 | apps/server/src/cloud-plugins.ts | installCloudPlugin、removeCloudPlugin、getPluginObjectInstallPath |
| Claude 插件包解析 | apps/server/src/claude-plugin-bundle.ts | resolveClaudePluginBundle、parseClaudePluginSource、locatePluginRoot |
| 内建扩展派发 | apps/server/src/extensions/index.ts | callExperimentalExtensionAction、listExperimentalExtensionActions、OPENWORK_EXPERIMENTAL_EXTENSION_ACTIONS |
| Google Workspace 扩展 | apps/server/src/extensions/google-workspace.ts | GOOGLE_WORKSPACE_EXTENSION_ACTIONS、callGoogleWorkspaceExtensionAction |
| OpenAI 生图扩展 | apps/server/src/extensions/openai-image-generation.ts | OPENAI_IMAGE_GENERATION_EXTENSION_ACTIONS、generateOpenAiImageArtifact |
| 引擎内元工具桥 | apps/server/src/opencode-plugins/openwork-extensions-preview.ts | openwork_context、openwork_query、openwork_execute、queryOpenworkAffordance、executeOpenworkAffordance |
| 内建插件路径解析 | apps/server/src/openwork-extensions-plugin-path.ts | openworkPluginPath、openworkExtensionsPreviewPluginPath |
| 最终引擎配置构建 | apps/server/src/openwork-runtime-config.ts | buildOpenworkRuntimeConfigObject、注入 plugin 数组 + runtimeMcpMap |
| runtime 配置存取 | apps/server/src/runtime-opencode-config-store.ts | runtimeMcpMap、runtimePluginList、writeRuntimeOpencodeConfig |
| 路径约定 | apps/server/src/workspace-files.ts | projectSkillsDir、projectPluginsDir、opencodeConfigPath |
| 前端内建工具类型 | apps/app/src/lib/build-in-tools.ts | SkillToolPart、isSkillToolPart 等识别函数 |
| 扩展设置面板 | apps/app/src/react-app/domains/settings/pages/extensions-view.tsx | ExtensionsView、ExtensionsSection |
| MCP OAuth 模态 | apps/app/src/react-app/domains/connections/mcp-auth-modal.tsx | OAuth 授权 URL / callback 处理 |