Skip to content

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 范围(插件系统的第一次迭代)

  1. 命令插件:注册命令面板操作
  2. 面板插件:打开插件UI面板(iframe / webview沙盒页面)
  3. AgentTool插件:为代理提供新的工具
  4. 技能插件:提供可加载的技能documents/flows
  5. 主题插件:提供覆盖设计令牌的 CSS 文件

超出 MVP 范围(已实现)

  1. MCP 服务器插件:声明 stdio 或远程 HTTP MCP 服务器,其工具 加入代理工具集
  2. 后台服务插件:让受监督的驻地工人保持活力
  3. 插件间消息总线:声明主题上的 publish/subscribe

稍后

  • 计费/签名插件
  • 企业私有插件源

当前实现: Plugins页面可以浏览和安装包 来自官方市场提供商。每个插件自动更新是可选的并且 拒绝静默权限扩展。这不是功能沙盒运行时: 插件主进程保留原始 Node 内置程序,因此市场包是 在实施计划的沙箱之前,不受限制的用户特权代码。

4. 插件形状

每个插件都是一个目录:

text
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

安装位置

text
~/.pi-desktop/plugins/
 ├── installed/
 │ └── <plugin-id>/
 ├── disabled/
 └── cache/

开发者模式可以直接加载本地路径,无需将其复制到 installed 中。

5. manifest.json(核心合约)

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 遵循 semver
  • permissions 必须显式声明
  • 未声明的权限默认为无
  • 验证失败的清单将被拒绝

6. 插件运行时模型

使用三层隔离

text
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 插件面板用户界面

  • 使用 iframewebview 加载插件页面
  • 只能调用插件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.publish
  • pi.bus.subscribe(pattern, handler)unsubscribe() // bus.subscribe
  • pi.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() 返回尽力而为的原生 权限状态(granteddeniedunknownunsupported)之后 执行简短的本机探测。这些通知不是持久任务 收件箱记录,单击时不会激活会话。

明确不直接提供

  • 任意主机内部 Electron 对象
  • 通过代理的 pi.fs API 进行任意绝对路径访问

该代理不直接提供 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. 命令面板

全局命令面板支持:

  • 搜索插件命令
  • 关键词触发
  • 最近使用过
  • 按类别分组

交互流程:

text
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. 插件生命周期

text
discover → validate → install → enable → load → running
 ↘ disable → unload
 ↘ uninstall → purge

挂钩:

  • onInstall
  • onLoad
  • onEnable
  • onDisable
  • onUnload
  • onUninstall

**今天实现:**运行时(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. 插件示例:

  • 你好面板
  • 工作区欢迎工具
  • 剪贴板笔记

本地开发流程:

bash
# develop the plugin
cd plugins/hello
pnpm dev

# in PI-Desktop
Plugins Load Development Plugin choose directory

14. 与 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. 验收(最小可用插件系统)

  1. 用户可以从本地目录加载插件
  2. 插件命令出现在命令面板中 3.插件可以打开自己的面板页面 4.插件可以注册低风险代理工具并成功调用
  3. 禁用插件会立即停用其命令和工具 6.插件崩溃不会导致主机退出

19. 示例

存储库中的示例插件:

  • examples/plugins/hello

为本地优先开发而构建。