01. 插件系统
翻译说明: 本页是与 英文源规格 一一对应的机器辅助翻译。代码、协议字段和标识符保持原文;如翻译与英文源事实有歧义,以英文版本为准。
0. 冻结实现默认值
- 插件工具公开名称使用强制前缀
plugin_<pluginIdSafe>_<toolName>(D015) - 启用→加载失败自动禁用插件(D017)
- 卸载默认删除插件数据(D016)
- MVP (D018) 中不允许使用插件设置机密
- 运行时目标仍然是单独的进程; M4 可以使用主机管理的沙盒运行时 (D009)
- 每个扩展都带有
enabled以及激活范围;两个插件和 用户自己的 MCP 服务器和技能是全局的或仅限于指定项目 (D192) - 用户 MCP 工具使用前缀
mcp_<serverId>_<toolName>,与 D015 不相交 插件命名空间 (D193)
Plan 策略:插件代理工具、注册工具的插件技能以及任何 未知的插件贡献在 Plan 中不可见或可执行。这否认 在通用插件权限评估之前由 host-core 强制执行,并且不能 可以被低风险清单、会话授权或 auto 绕过。插件工具 在批准的 Plan → Agent 转换后,对同一 Agent 仍然可用。
1. 目标
为 PI-Desktop 提供类似于已建立的桌面插件生态系统的可扩展性(例如 VS Code 扩展):
- 用户可以安装/启用/禁用/卸载插件
- 开发人员可以构建自定义插件
- 插件可以扩展命令、面板、工具和 Agent 功能
- 平台保持安全边界,不会将完整的系统权限直接交给任意第三方代码
用一句话来说:
PI-Desktop 是主持人;插件是功能包。
2. 设计目标
从已建立的插件生态系统中采用的模式
- 基于目录的插件安装
- 在清单文件中声明的功能
- 功能关键字/命令触发器
- 专门的插件管理页面
- 用于加载本地插件的开发者模式
差异(因为我们是 Agent 桌面)
- 插件不仅仅是小型实用面板;他们还可以扩展:
- Agent 工具
- 技能
- MCP 桥
- 会话命令
- 设置
- 高风险能力必须经过权限框架
- 插件默认无法直接获取任意Node/Electron权限
3. 插件可以做什么
MVP-Plugin 范围(插件系统的第一次迭代)
- 命令插件:注册命令面板操作
- 面板插件:打开插件UI面板(iframe / webview沙盒页面)
- AgentTool插件:为代理提供新的工具
- 技能插件:提供可加载的技能documents/flows
- 主题插件:提供覆盖设计令牌的 CSS 文件
超出 MVP 范围(已实现)
- MCP 服务器插件:声明 stdio 或远程 HTTP MCP 服务器,其工具 加入代理工具集
- 后台服务插件:让受监督的驻地工人保持活力
- 插件间消息总线:声明主题上的 publish/subscribe
稍后
- 计费/签名插件
- 企业私有插件源
当前实现: Plugins页面可以浏览和安装包 来自官方市场提供商。每个插件自动更新是可选的并且 拒绝静默权限扩展。这不是功能沙盒运行时: 插件主进程保留原始 Node 内置程序,因此市场包是 在实施计划的沙箱之前,不受限制的用户特权代码。
4. 插件形状
每个插件都是一个目录:
my-plugin/
├── manifest.json # required
├── package.json # optional (if it carries build artifacts / dependency metadata)
├── main.js # plugin runtime extension entry (restricted API)
├── preload.js # optional, plugin panel bridge
├── renderer/ # plugin UI (static assets)
│ ├── index.html
│ └── assets/
├── skills/ # optional
├── themes/ # optional (`.css` files declared in contributes.themes)
├── tools/ # optional (declarative tool schema)
├── icon.png
└── README.md安装位置
~/.pi-desktop/plugins/
├── installed/
│ └── <plugin-id>/
├── disabled/
└── cache/开发者模式可以直接加载本地路径,无需将其复制到 installed 中。
5. manifest.json(核心合约)
{
"schemaVersion": 1,
"id": "demo.hello",
"name": "Hello Plugin",
"version": "0.1.0",
"description": "Example plugin",
"author": "you",
"main": "main.js",
"ui": {
"panel": "renderer/index.html",
"width": 480,
"height": 360
},
"contributes": {
"commands": [
{
"id": "hello.say",
"title": "Hello: Say",
"keywords": ["hello", "hi"],
"category": "Demo"
}
],
"agentTools": [
{
"name": "hello_echo",
"description": "Echo text back",
"risk": "low",
"schema": {
"type": "object",
"properties": {
"text": { "type": "string" }
},
"required": ["text"]
}
}
],
"skills": ["./skills/hello.md"],
"settings": [
{
"key": "greeting",
"type": "string",
"default": "Hello",
"title": "Greeting"
}
]
},
"permissions": [
"clipboard.read",
"clipboard.write",
"notify",
"fs.read.workspace",
"fs.delete.workspace",
"agent.tool.register"
],
"engines": {
"piDesktop": ">=0.1.0"
},
"entrypoints": {
"onLoad": "main.js#onLoad",
"onUnload": "main.js#onUnload"
}
}字段限制
schemaVersion为必填项,且必须为1; Rust 主机 (crates/host-core/src/plugins.rs) 会拒绝没有它的清单id全球独一无二;建议使用反向域名命名version遵循 semverpermissions必须显式声明- 未声明的权限默认为无
- 验证失败的清单将被拒绝
6. 插件运行时模型
使用三层隔离:
Host Main (PI-Desktop)
├─ PluginManager
├─ PluginPermissionGateway
├─ Plugin Sandbox / Worker
└─ Plugin Panel (Renderer iframe/webview)6. 1 主机主
- 安装/卸载/启用/禁用
- 验证清单
- 权限管理
- 路由命令和工具调用
6. 2 插件运行时(受限)
插件逻辑在受限环境中运行;它不等同于完整的 Electron 主要权限。
**今天实施(ADR 0008):**每个插件的主模块都在自己的中运行 utilityProcess (electron/main/plugin-host-process.mjs) 并到达主机 仅通过 JSON RPC 到 electron/main/plugin-runtime.ts 中的经纪商,其中 强制执行 API 白名单和权限网关。插件代码永远不会得到 主机对象并且不能 require 主机模块。
仍然开放:插件进程内的功能沙箱(原始 Node 内置 可以到达)和 CPU/memory 限制。
6. 3 插件面板用户界面
- 使用
iframe或webview加载插件页面 - 只能调用插件preload公开的安全API
- 默认情况下无法访问主机 DOM/主机存储
7. 托管 API(可通过插件调用)
命名空间:pi.plugin.*
基础知识
pi.app.getVersion()pi.plugin.getManifest()pi.plugin.getSettings()pi.plugin.setSettings(partial)pi.commands.register(command)pi.ui.openPanel(options?)pi.ui.showToast(message)pi.ui.notify(title, body)pi.ui.getNotificationPermission()pi.ui.requestNotificationPermission()pi.ui.showNativeNotification({ title, body? })
工作区(需要许可)
pi.workspace.get()pi.fs.readText(path)pi.fs.writeText(path, content)// 高风险pi.fs.glob(pattern)
Agent(需要许可)
pi.agent.registerTool(tool)pi.agent.unregisterTool(name)
技能以声明方式贡献(contributes.skills + agent.prompt.inject), 不由插件调用:主机将目录放在系统提示符中,并且 模型通过内置的 Skill 工具 (D174) 加载主体。有计划,没有 目前曝光:pi.agent.appendSystemHint(text)。
后台服务(需要 background.service)
pi.services.register({ id, start, stop? })pi.services.unregister(id)
登记为本地记账;代理仅在以下情况下启动服务 清单声明了它并授予了权限,并监督重新启动。
消息总线(需要许可)
pi.bus.publish(topic, payload)//bus.publishpi.bus.subscribe(pattern, handler)→unsubscribe()//bus.subscribepi.events.on(event, handler)/pi.events.off(event, handler)// 主机推送, 包括原始bus.message流
剪贴板/系统(需要许可)
pi.clipboard.readText()pi.clipboard.writeText(text)pi.shell.openExternal(url)// 默认确认pi.net.fetch(input)
pi.ui.notify 是一个应用内 Toast。本机插件通知使用 Electron 主进程通知 API 并共享清单 notify 许可。 requestNotificationPermission() 返回尽力而为的原生 权限状态(granted、denied、unknown 或 unsupported)之后 执行简短的本机探测。这些通知不是持久任务 收件箱记录,单击时不会激活会话。
明确不直接提供
- 任意主机内部 Electron 对象
- 通过代理的
pi.fsAPI 进行任意绝对路径访问
该代理不直接提供 Node 功能。然而,当前 实用程序进程插件运行时不是 Node 功能沙箱:插件代码可以 独立于 pi.* 到达原始 Node 内置函数。下面的权限模型 因此,在提供运行时沙箱之前,仅适用于代理 API。
8. 权限模型
权限列表
| 许可 | 风险 | 描述 |
|---|---|---|
ui.panel | 低 | 显示面板 |
ui.theme | 低 | 贡献主题 CSS 文件 |
clipboard.read | 中等 | 读取剪贴板 |
clipboard.write | 中等 | 写入剪贴板 |
notify | 低 | 系统通知 |
fs.read.workspace | 中等 | 读取工作区 |
fs.write.workspace | 高 | 写入工作区 |
fs.delete.workspace | 高 | 删除工作区文件 |
agent.tool.register | 高 | 注册代理工具 |
agent.prompt.inject | 高 | 注入提示;激活 contributes.skills |
net.fetch | 高 | 网络请求 |
shell.openExternal | 中等 | 打开外部链接 |
mcp.server.local | 高 | 生成 stdio MCP 服务器 |
mcp.server.remote | 高 | 连接远程 HTTP MCP 服务器 |
background.service | 中等 | 保持常驻服务运行 |
bus.publish | 中等 | 发布到已声明的总线主题 |
bus.subscribe | 中等 | 订阅已声明的总线模式 |
主题、MCP 服务器、服务和总线主题均在清单中声明,因此 他们的权限在验证时和运行时都会受到检查 - 请参阅 13-插件权限-matrix.md。
授权时机
1.安装或升级审核时显示声明的权限列表 2. 只有授予的权限才会传递给代理运行时;缺少补助金 相应的 pi.* 调用失败 3、用户可以在插件管理页面撤销权限;重新加载是 正在运行的插件需要观察更改的授权集
未实施每次调用确认和直接 Node 访问的任何策略。
9. 命令面板
全局命令面板支持:
- 搜索插件命令
- 关键词触发
- 最近使用过
- 按类别分组
交互流程:
User opens the command palette
→ types a keyword
→ matches a plugin command
→ executes the command handler
→ opens a panel or triggers an agent/tool快捷方式(推荐):
- macOS:
Command+Shift+P或自定义 - 支持稍后调用快速启动器
10. AgentTool插件机制
插件注册工具后:
1.PluginManager验证schema和权限 2. ToolHost对工具进行包装 3、每次调用都先经过权限和审核 4. 实际执行落在插件运行时 5. 结果在返回给代理之前进行归一化
包裹层必须添加:
- 超时
- 参数验证
- 误差标准化
- 审计日志记录
- 禁用开关
11. 插件生命周期
discover → validate → install → enable → load → running
↘ disable → unload
↘ uninstall → purge挂钩:
onInstallonLoadonEnableonDisableonUnloadonUninstall
**今天实现:**运行时(apps/desktop/electron/main/plugin-runtime.ts)调用onLoad(当load/enable上加载插件时)和onUnload(在插件进程中,停止之前,5s预算);卸载会删除插件注册的命令和工具。其他钩子在 API 中声明,但尚未触发。
**计划:**一旦完整生命周期落地,钩子将按以下顺序触发:安装→启用→加载→(运行)→卸载→禁用→卸载。有关详细序列,请参阅 05-plugin-lifecycle.md。
失败政策:
- 加载失败:标记错误,不影响主机启动
- 工具执行失败:返回工具错误,不要使主进程崩溃
12. 扩展 UI
应用程序外壳的专用扩展目标拥有用户添加的所有内容 到应用程序。不要在“设置”中重复任何内容。
四个选项卡,因为这三种创建方式完全不同—— 安装、配置、编写——并且一个合并列表必须隐藏这一点 在最低公分母行后面:
| 选项卡 | 内容 |
|---|---|
| 已安装 | 插件,分组:需要注意、更新、活动、禁用 |
| MCP | 用户自己的 MCP 服务器 (D193) |
| 技能 | 用户自己的技能文档(D194) |
| 市场 | 浏览并安装 |
插件功能:
- 本地安装(选择目录/zip)
- 开发人员负载(路径)
- 激活范围(关闭/本项目/各处)
- 卸载
- 查看权限
- 查看日志
- 打开插件目录
状态指示灯:
- 启用
- 禁用
- 错误
- 开发加载
12. 1 激活范围控制
一个控制器可用于所有三种类型 (D192)。这是一个三段轨道有序 通过扩大影响范围 - 关闭 → 这些项目 → 无处不在 - 如此扩大和 缩小是在相反方向上相同的手势,加上一个摘要芯片 当中间段处于活动状态时打开项目选择器。
控制编码规则:
- 选择“这些项目”,没有任何选择,但种子当前开放 项目,所以常见情况是一键点击。
- 切换到“无处不在”或“关闭”会保留项目列表,因此可以返回 恢复它。
- 已确定范围但不再出现在最近列表中的项目仍会出现在 选择器,否则范围将永远无法撤消。
- 具有空列表的项目范围扩展会发出警告,而不是默默执行 什么也没有。
12. 2 MCP 选项卡
- 从 JSON 导入是主要操作:粘贴的
mcpServers块是什么 用户手头有。列出不良条目时会附有原因,但并非致命的 (D193)。 - 每行的前导字形兼作连接灯 - 空闲、连接、 准备好了,失败了——所以“这可行吗”是第一句话。
- 编辑器表具有 stdio 和 HTTP 传输卡、用于 env 的 key/value 行或 标题,以及一个测试连接按钮,用于报告它找到的工具名称。
12. 3 技能选项卡
- 创建写入
SKILL.md; import 将现有文档复制到。 - 描述是必需的,位于正文上方,因为它是唯一的 进入提示符的部分(D174、D194)。
- 针对 128KB 文档上限的实时字节计数器。
13. 开发者经验
提供:
1.插件模板:npm create pi-desktop-plugin 2.清单模式验证器 3.开发者热重载(watch目录) 4. 插件示例:
- 你好面板
- 工作区欢迎工具
- 剪贴板笔记
本地开发流程:
# develop the plugin
cd plugins/hello
pnpm dev
# in PI-Desktop
Plugins → Load Development Plugin → choose directory14. 与 pi 生态系统的关系
| 生态系统对象 | 关系 |
|---|---|
| 圆周率技能 | 可以通过技能插件分发/管理 |
| 圆周率扩展 | 不直接等同;需要一个适配器层 |
| MCP | 插件在 contributes.mcpServers 中声明 MCP 服务器;他们的工具加入代理的工具集中 |
| Agent 工具 | 最重要的插件扩展界面之一 |
原则:
- 不排除 pi 原生功能
- 但在用户方面,将一切称为“插件”
15. 安全基线(不可协商)
1.插件默认没有权限 2.插件无法直接访问主机渲染器状态 3.插件默认无法读写工作区之外的文件 4.插件网络功能默认关闭 5.插件更新/安装需要完整性验证(稍后签名) 6.主机核心进程不执行插件注入的任意Electron主代码
16. 分阶段推出
P0(先设计,可与M2/M3并行准备)
- 清单规格
- PluginManager 骨架
- 本地加载/启用-禁用
- 命令注册
- 1 个示例插件
P1
- 插件面板用户界面
- 权限授予UX
- AgentTool注册和调用
- 插件设置存储
P2
- 压缩安装
- 插件日志中心
- 开发人员热重载
- 更多官方示例
P3
- 插件市场
- 签名和自动更新
- MCP 插件类型
- 后台服务插件
17. MVP产品策略调整
最初的 MVP 可以推迟开放“完整插件市场”,但应保留:
- 插件目录
- 清单
- 插件管理器界面
- 至少一个内置/示例插件路径
即:
首先有插件架构,然后是插件生态系统。
18. 验收(最小可用插件系统)
- 用户可以从本地目录加载插件
- 插件命令出现在命令面板中 3.插件可以打开自己的面板页面 4.插件可以注册低风险代理工具并成功调用
- 禁用插件会立即停用其命令和工具 6.插件崩溃不会导致主机退出
19. 示例
存储库中的示例插件:
examples/plugins/hello