Plugin 开发手册
本文面向 Open Nua Plugin 作者,说明如何把一个领域能力交付为可安装、可审计、可治理的
Plugin。当前契约以 Agent Plugins v1 作为可移植核心,以
com.opennua.desktop 扩展承载 Open Nua 专有能力。
Plugin 包是可版本化的交付单元:Hub 校验包和 manifest,Gateway 只代理已批准的工具面, Desktop 只加载已安装且启用的 Adapter、Skill 和受控 App。Plugin 不通过 manifest 自行 指定任意 upstream URL,也不获得未声明的网络、文件或账户权限。
发布参数和 Hub 认证见Hub 发布手册。官方包可参考 workspace/plugins/packages。
1. Plugin 与 Skill 的边界
- Plugin 拥有可执行 Adapter、Capability 声明、App/Agent 贡献、版本、安装和启用状态。
- Skill 是给 Agent 阅读的操作知识,说明适用场景、工具选择、步骤和边界。
- Plugin 可以贡献一个或多个标准 Skill,但 Skill 本身不获得执行权限。
安装成功后 Plugin 默认停用;只有 installed + enabled 的 Plugin 才能向新一轮会话贡献
Skill、Agent context 或 Capability。禁用不删除本地状态,卸载也不会自动清除状态文档。
如果场景只需要提示词和已有工具,优先发布 Skill;需要新增受控能力、领域 App 或持久化 本地状态时,才开发 Plugin。
2. 选择 Adapter
1.2.0 的 Open Nua Plugin 有两类 Adapter,另允许没有 Adapter 的纯 Skill Plugin:
| Adapter | 适用场景 | 能力提供方 | 状态与部署 |
|---|---|---|---|
gateway_mcp | 集中部署、工具调用、需要统一数据和服务治理 | Gateway 受控 MCP 代理 | 领域服务由组织部署,Desktop 不携带服务依赖 |
local_state | 离线、本地优先的领域 App | Desktop Host 的本地状态和只读领域上下文 | 每个用户与 Plugin 隔离,单一 JSON 文档,最大 64 KiB |
gateway_mcp 和 local_state 在 1.2.0 中不能同时声明。App、声明式 UI、Agent Definition
和标准 Skill 是独立贡献,可以与任一 Adapter 组合。
远程服务仍由部署管理员配置。manifest 只声明 Gateway 路径、工具白名单和 timeout;客户端 不能提交或覆盖 upstream URL。生产环境由 Backend 的已审核 runtime policy 连接私网服务, 未发布、未启用或未映射的服务必须返回 unavailable。
3. 包目录
每个归档必须只有一个顶层目录,并至少包含 PLUGIN.md、根 plugin.json 和一个标准 Skill:
my-plugin/
├── PLUGIN.md
├── USER_GUIDE.zh-CN.md # 推荐
├── plugin.json
├── skills/
│ └── my-plugin/
│ └── SKILL.md
└── com.opennua.desktop/ # 仅当使用 Open Nua 扩展时
├── agents/
│ ├── my-plugin.json
│ └── my-plugin.md
└── ui/
└── my-plugin.html
Open Nua 专有文件只能位于 com.opennua.desktop/。不要把 .env、.git、.venv、
__pycache__、node_modules 或运行时生成的数据放入归档;归档不能包含符号链接、硬链接、
特殊文件、路径穿越或多个顶层目录。
4. 根 plugin.json
根 manifest 使用 Agent Plugins v1 核心字段。核心身份字段不包含 Open Nua runtime:
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
"name": "my-market-research",
"version": "1.0.0",
"description": "Research public market data without trading.",
"license": "UNLICENSED",
"extensions": {
"com.opennua.desktop": {
"schema_version": 2,
"runtime": {
"gateway_mcp": {
"name": "market-research",
"gateway_path": "/api/plugins/market-research/mcp",
"tools": ["get_market_data"],
"timeout_seconds": 60,
"tool_name_prefix": true
}
}
}
}
}
name 使用小写字母、数字、连字符和点,长度为 1–64;不能以分隔符结尾,也不能包含
连续的连字符或点。version 使用 SemVer。未知的 extension namespace 可以被通用客户端
忽略,Open Nua 字段不得泄漏到核心身份字段。
4.1 gateway_mcp
{
"runtime": {
"gateway_mcp": {
"name": "market-research",
"gateway_path": "/api/plugins/market-research/mcp",
"tools": ["get_market_data", "get_company_profile"],
"timeout_seconds": 60,
"tool_name_prefix": true
}
}
}
工具白名单数量为 1–64,工具名只使用字母、数字、下划线和连字符,长度不超过 128;
timeout_seconds 为 1–300。可选的 model_delegation.tools 只能引用同一白名单中的
工具,不会授予 Plugin 长期模型凭据。
4.2 local_state
{
"runtime": {
"local_state": {
"state_schema_version": 1,
"state_max_bytes": 65536
}
}
}
Host 以用户和 Plugin 为边界存储一个 schema-versioned JSON 文档,使用临时文件加原子替换
写入。state_schema_version 只描述当前 Plugin 的状态契约;Host 不执行任意 Plugin 代码,也不
会静默迁移或清空旧文档。版本不匹配时返回 LOCAL_STATE_MIGRATION_REQUIRED,并保留旧 state、
revision 与 expected version;Plugin App 必须显式完成迁移后再调用 setLocalState。损坏文档
返回 LOCAL_STATE_RECOVERY_REQUIRED,由 Plugin 提供用户确认的恢复路径。
本地 App 不能直接访问文件系统、浏览器存储、Node API 或网络。
4.3 Capability
contributes.capabilities 必须显式声明 provider、audience、operation、risk、确认和幂等策略:
{
"id": "my-market-research.get_market_data",
"provider": "gateway_mcp",
"target": "get_market_data",
"audience": "both",
"operation": "query",
"risk": "low",
"requiresConfirmation": false,
"requiresHitl": false,
"idempotency": "none"
}
gateway_mcp capability 的 target 必须出现在 Adapter 工具白名单中。desktop_local_context
只能用于本地只读 query projection,并且必须声明 contextType。App 使用的 capability
必须对 app 或 both audience 开放;Agent、App 和声明式 UI 共用同一套 Capability 校验。
4.4 Agent Definition、声明式 UI 和 App
Agent Definition 位于 com.opennua.desktop/agents/,manifest 只索引相对路径:
{
"agents": [
{
"agentId": "my-market-research-assistant",
"default": true,
"definition": "com.opennua.desktop/agents/my-market-research-assistant.json"
}
]
}
contributes.ui 是受控声明式 Host UI;contributes.app 是经过 SHA-256 校验的领域 App:
{
"app": {
"protocolVersion": "1",
"resourceUri": "ui://my-market-research/market-research.html",
"resourcePath": "com.opennua.desktop/ui/market-research.html",
"mimeType": "text/html;profile=mcp-app",
"sha256": "<64 lowercase hex characters>",
"capabilities": ["my-market-research.get_market_data"]
}
}
resourceUri 必须属于当前 Plugin,resourcePath 必须位于
com.opennua.desktop/ui/。App 只能通过 Host Bridge 调用已声明且已授权的 Capability,
不能直接调用 IPC、模型、MCP、网络或文件系统。
5. 标准 Skill 与可选 MCP
标准 Skill 固定放在 skills/<name>/SKILL.md,frontmatter 至少包含非空的 name 和
description:
---
name: My Market Research
description: Research public market data without trading or account access.
---
# My Market Research
Use only the declared read-only capabilities and state the source and limitations.
只有确实需要向通用 Agent Plugins 客户端暴露标准 MCP endpoint 时,才提供根 mcp.json:
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
"mcpServers": {
"my-market-research": {
"command": "my-market-research-mcp",
"args": ["serve"]
}
}
}
没有根 mcp.json 的包仍然是有效的标准 Skill Plugin,但普通客户端不会获得 Open Nua 的
Gateway、App 或 local-state 能力。三个当前官方包明确属于 Open Nua extension-only MCP:
Vibe Trading 和 DeepTutor 使用受控 Gateway Adapter,抓娃娃使用 local-state Adapter,均不
提供根 mcp.json。
6. 本地开发与 plugins:dev
官方 Plugin 的 runtime 和 App 源码由 plugins 仓维护。workspace 根运行:
npm run plugins:dev
该命令从每个 packages/*/plugin.json 生成短租约 development registry,启动 loopback-only
runtime 和 Vite App server;它不读取旧的隐藏 manifest 路径。默认 runtime、upstream 和 UI
端口分别由脚本输出,也可通过脚本支持的环境变量覆盖。开发 registry 只供本地 Gateway 和
Electron 使用,退出命令后会失效;packaged Desktop 和 prod-sim 不读取它。
官方 App 源码保存于各 Plugin 自己的目录,构建产物写入
packages/<plugin>/com.opennua.desktop/ui/。构建和测试:
npm run plugins:ui:typecheck
npm run plugins:ui:build
npm run plugins:test
本地 Desktop 中选择“资产库 → 插件 → 加载本地插件”,选择包含 PLUGIN.md、plugin.json
和 skills/ 的源码目录。Desktop 会创建指向源码的本地链接;校验失败不会替换原安装。
源码修改后可以用 CDP reload:
npm run plugin -- reload-local my-market-research
npm run plugin -- reload-local plugins/packages/my-market-research \
--cdp-url http://127.0.0.1:9225 --json
reload 会重新校验根 manifest、Skill、Agent、App digest 和 Adapter,并保留启用状态。修改 Capability、工具集合或 Skill 后必须使用新会话验收;旧会话保留的是当时的能力快照。
7. 测试与验收
包级测试至少覆盖:
PLUGIN.md、所有SKILL.mdfrontmatter 和根plugin.json可解析;- Agent Definition、App resource path 和每个 asset digest 均存在且一致;
- 可选
mcp.json的 schema、mcpServers和 transport 声明合法; - 归档没有链接、路径穿越、环境文件或生成数据;
- Plugin 未启用时 Skill、Capability 和 App 均不可用;
- Gateway target 白名单、Capability provider/audience/operation/context 一致;
- local-state 的用户隔离、64 KiB 上限、迁移、原子写和损坏恢复;
- 最终用户任务、会话复用、错误提示和恢复路径形成闭环。
workspace 根的官方包与 Hub 兼容性检查:
npm run plugins:test
UV_CACHE_DIR=/tmp/openneo-uv-cache uv run --directory services/backend pytest -q tests/test_plugin_archive.py
Desktop 还应运行 npm run typecheck、focused Node/Renderer tests、uv run ruff check src/agent_bridge,并用 Electron CDP 验收远程 App、离线 local-state App、能力 unavailable、
损坏状态和会话恢复。不要只验证进程没有报错,要检查用户可见结果。
8. 打包与发布
归档示例:
COPYFILE_DISABLE=1 tar -czf /tmp/my-plugin-1.0.0.tgz \
--exclude='.DS_Store' --exclude='.env' --exclude='.git' \
--exclude='.venv' --exclude='__pycache__' --exclude='node_modules' \
-C plugins/packages my-plugin
tar -tzf /tmp/my-plugin-1.0.0.tgz
首次发布和后续版本使用 Hub 发布器:
npm run publish:plugin -- \
--archive /tmp/my-plugin-1.0.0.tgz \
--slug my-plugin --version 1.0.0 \
--visibility authenticated --tags research
npm run publish:plugin -- \
--archive /tmp/my-plugin-1.1.0.tgz \
--asset-id <plugin-uuid> --version 1.1.0
Plugin 版本不可覆盖。Backend 是 package、manifest、Skill、App digest、MCP 和版本合法性 的最终校验方;CLI 不绕过服务端授权。
9. 常见问题
Plugin 列表有包但 Workbench 无法打开
先确认 Plugin 为 installed + enabled,再检查 App 的 resourcePath 是否位于
com.opennua.desktop/ui/、文件和 SHA-256 是否匹配。若是本地 Plugin,执行 reload;修改
源码后重启 Desktop Main 进程,避免继续使用旧的 Main bundle。
远程 Adapter unavailable
manifest 不会启动远程服务。检查已发布版本、Gateway policy、gateway_path、scope、工具
白名单和受控 upstream 是否一致。不要在 Plugin 中加入专用 URL 配置。
会话列表无法加载
Workbench 会话仍然复用 Desktop 的标准 Thread;Plugin 不维护私有聊天存储。检查当前 Plugin 和 context 是否已启用、Capability 是否对 App/Agent 开放,以及 Gateway 或 local context 返回的错误是否被 Host 显示为可恢复状态。切换领域对象不会静默改绑旧会话。
10. 发布前清单
- 根
plugin.json使用 Agent Plugins v1 schema,Open Nua 字段全部在扩展 namespace。 - 所有 Skill 位于
skills/<name>/SKILL.md,frontmatter 可解析。 - 如提供标准 MCP,根
mcp.jsonschema 和 transport 校验通过;否则记录 extension-only 边界。 - Adapter、Capability provider/audience/operation/context 和工具白名单一致。
- App、Agent Definition、asset path 和 digest 通过归档校验。
- local-state 通过隔离、上限、迁移、原子写和损坏恢复测试。
-
plugins:dev、plugins:test、Desktop focused tests、typecheck、ruff 和 Backend pytest 通过。 - 完成远程 App、local-state App、unavailable、损坏状态和会话恢复的 Electron CDP 走查。