返回文章列表

文章

Ramag 插件机制:借鉴 VS Code 与 Zed,构建可扩展的桌面工具平台

以 Ramag 当前的 Rust workspace、ToolRegistry 和 Shell 为基础,借鉴 VS Code 的 manifest、Extension Host 与按需激活,以及 Zed 的 Rust/WASM 与 capability 思路,设计内置插件、外部进程插件和 WASM 插件的分层演进路径。

目录
  1. 一、插件系统不只是动态加载几个模块
  2. 当一个桌面工具同时包含数据库客户端、Kafka 工具、版本管理、SSH、云存储和剪贴板等功能时,继续把所有功能直接编译进一个主程序,会让启动流程、依赖关系、发布节奏和故障边界越来越复杂。 插件化真正要解决的问题不是“能不能加载一个 DLL”,而是能否让功能以清晰的边界接入平台,同时拥有可发现的元数据、稳定的接口、独立的生命周期、受控的权限和可恢复的失败行为。 Ramag 当前已经具备插件平台的雏形:功能按 ramag-tool-* crate 拆分,ToolRegistry 负责注册工具,Shell 负责在统一窗口中切换工具视图。下一步不是推翻现有实现,而是把这些隐含约定逐步显式化。
  3. 二、VS Code:Manifest、Extension Host 与按需激活
  4. VS Code 最值得借鉴的是结构化贡献点。插件提交命令、菜单和视图模型,由宿主决定如何渲染和调度,而不是直接操作主程序内部组件。
  5. 三、Zed:Rust、WASM/WASI 与 capability
  6. Zed 的扩展机制更接近 Rust 桌面应用的现实约束。扩展可以使用 Rust 编写并编译为 WebAssembly,在 WASI 环境中运行;文件、网络和进程等能力通过明确的 capability 范围控制。 Zed 也根据扩展类型区分运行模型:语言服务、调试器、主题和语法高亮不必共享同一种接入方式。Ramag 应借鉴这种按能力选择运行时的思路,而不是为所有插件强行指定单一技术。
  7. 四、Ramag 推荐采用混合插件架构
  8. 1. 内置插件
  9. 2. 外部插件
  10. 3. WASM 插件
  11. WASM 插件适合格式解析、数据转换、脱敏、过滤和校验等不需要原生窗口的逻辑。它不应承担复杂的 GPUI 界面,也不应被当作所有插件的默认方案。
  12. 五、插件 UI 应该如何接入?
  13. 六、当前代码已经形成了哪些插件边界?
  14. ramag-domain/src/traits/tool.rs 定义 ToolMeta 和 Tool trait,提供工具身份与能力的基础抽象;ramag-app/src/tool_registry.rs 提供 ToolRegistry;ramag-ui/src/shell.rs 负责统一 Shell 和工具视图切换;ramag-bin/src/composition.rs 与 windows.rs 完成内置工具的组装和注册。 这说明插件边界已经分布在 Domain、Application、UI 和 Binary 四层,但目前注册过程仍然是编译期静态组装。后续可以增加 PluginManifest、PluginSource 和 PluginRuntime,把静态注册逐步迁移为“发现后注册”。 插件化工作只增加平台接入层、生命周期和通信协议,不把数据库、Kafka、SSH 等业务实现合并成新的大模块。各业务工具仍可独立演进、测试和发布。
  15. 七、一个可演进的插件 Manifest
  16. Manifest 不应直接声明任意系统命令或任意路径。宿主需要校验入口路径、签名、版本、权限和依赖,并在插件不可用时给出可诊断的错误,而不是静默跳过。
  17. 八、生命周期、权限与故障恢复
  18. 权限按能力拆分,而不是给插件一个无法审计的“全部权限”。例如 storage.plugin、network、filesystem.workspace、process.spawn、credential.access 和 ui.webview 应分别申请;默认拒绝高风险权限,授权结果按插件 ID 和版本保存。 插件数据使用独立目录,例如 data/plugins/<plugin-id>/。宿主只提供版本化存储 API,不允许插件直接读写 Ramag 的 SQLite 文件、配置文件或其他插件目录。 外部插件使用请求 ID、协议版本、消息大小上限和超时。崩溃后宿主记录退出原因,限制自动重启次数,清理残留 IPC 资源,并允许用户禁用插件后继续使用其他工具。
  19. 九、建议的分阶段落地计划
  20. 阶段一:稳定内置插件接口
  21. 阶段二:建立外部插件 IPC
  22. 阶段三:权限与存储隔离
  23. 阶段四:结构化 UI 扩展点
  24. 阶段五:WASM 运行时与插件分发
  25. 为纯计算型插件提供 WASM/WASI 运行时,建立签名、兼容性检查、升级、回滚和禁用流程。验收条件是插件可独立升级和回滚,损坏包、API 不兼容包和签名不匹配包均被拒绝。
  26. 十、不建议直接采用的方案
  27. 十一、结论
  28. 参考资料

一、插件系统不只是动态加载几个模块#

当一个桌面工具同时包含数据库客户端、Kafka 工具、版本管理、SSH、云存储和剪贴板等功能时,继续把所有功能直接编译进一个主程序,会让启动流程、依赖关系、发布节奏和故障边界越来越复杂。 插件化真正要解决的问题不是“能不能加载一个 DLL”,而是能否让功能以清晰的边界接入平台,同时拥有可发现的元数据、稳定的接口、独立的生命周期、受控的权限和可恢复的失败行为。 Ramag 当前已经具备插件平台的雏形:功能按 ramag-tool-* crate 拆分,ToolRegistry 负责注册工具,Shell 负责在统一窗口中切换工具视图。下一步不是推翻现有实现,而是把这些隐含约定逐步显式化。#

二、VS Code:Manifest、Extension Host 与按需激活#

VS Code 的插件通过 manifest 描述名称、版本、入口、命令、菜单、视图和激活条件。宿主可以在不执行插件代码的情况下完成发现和展示;只有用户执行命令、打开特定文件或进入相关工作区时,插件才启动。 插件运行在 Extension Host 中,而不是和主窗口渲染进程完全混在一起。插件崩溃时可以重启承载进程,主程序仍然有机会保留窗口、记录诊断并恢复其他功能。

plugin manifest
    |
    +-- identity: id, name, version
    +-- contribution: commands, menus, views
    +-- activation: when to start
    +-- runtime: host process and API version
    |
    v
Extension Host <----> Ramag Host

VS Code 最值得借鉴的是结构化贡献点。插件提交命令、菜单和视图模型,由宿主决定如何渲染和调度,而不是直接操作主程序内部组件。#

三、Zed:Rust、WASM/WASI 与 capability#

Zed 的扩展机制更接近 Rust 桌面应用的现实约束。扩展可以使用 Rust 编写并编译为 WebAssembly,在 WASI 环境中运行;文件、网络和进程等能力通过明确的 capability 范围控制。 Zed 也根据扩展类型区分运行模型:语言服务、调试器、主题和语法高亮不必共享同一种接入方式。Ramag 应借鉴这种按能力选择运行时的思路,而不是为所有插件强行指定单一技术。#

四、Ramag 推荐采用混合插件架构#

Ramag 的工具既需要访问数据库、SSH 或 Kafka 等系统能力,也需要在统一 Shell 中提供交互界面。推荐按权限、性能和 UI 需求分为三层。

Ramag Host
  |
  +-- 内置插件 Built-in Plugin
  |     Rust + GPUI,适合核心工具和高性能 UI
  |
  +-- 外部插件 External Plugin
  |     独立进程 + IPC,适合第三方工具和可选组件
  |
  +-- 受限插件 Sandboxed Plugin
        WASM/WASI,适合纯计算、解析和转换逻辑

1. 内置插件#

现有 ramag-tool-dbclient、ramag-tool-kafka、ramag-tool-vcs、ramag-tool-ssh、ramag-tool-object-storage、ramag-tool-clipboard 和 ramag-tool-system 可以继续作为内置插件。它们直接使用 Rust 类型和 GPUI,性能与用户体验最好,也最适合承担平台必须提供的基础能力。

2. 外部插件#

第三方插件通过独立进程运行,使用版本化 IPC 与 Ramag Host 通信。外部插件不能直接拿到 Shell、窗口或内部 Rust 对象,只能调用宿主公开的 API。插件异常退出时,宿主可以标记插件不可用、回收其视图并保留其他工具。

3. WASM 插件#

WASM 插件适合格式解析、数据转换、脱敏、过滤和校验等不需要原生窗口的逻辑。它不应承担复杂的 GPUI 界面,也不应被当作所有插件的默认方案。#

五、插件 UI 应该如何接入?#

当前 Shell 通过 register_tool_view 将具体的 GPUI 视图注册到工具 ID。这个方式适合内置插件,但如果把 GPUI 的 AnyView 或内部 Context 直接跨进程暴露,API 会被实现细节绑定。 外部插件应优先使用结构化 UI 描述,由宿主渲染表单、表格、树和日志;复杂界面再使用独立插件窗口或嵌入式 WebView,通过消息协议与宿主通信。插件只提交 UI 模型和事件,不直接操作宿主布局。

UI contribution
  plugin -> { view_id, title, icon, location, schema }
  host   -> render schema in Shell
  user   -> event
  host   -> { command, payload }
  plugin -> result / notification

六、当前代码已经形成了哪些插件边界?#

ramag-domain/src/traits/tool.rs 定义 ToolMeta 和 Tool trait,提供工具身份与能力的基础抽象;ramag-app/src/tool_registry.rs 提供 ToolRegistry;ramag-ui/src/shell.rs 负责统一 Shell 和工具视图切换;ramag-bin/src/composition.rs 与 windows.rs 完成内置工具的组装和注册。 这说明插件边界已经分布在 Domain、Application、UI 和 Binary 四层,但目前注册过程仍然是编译期静态组装。后续可以增加 PluginManifest、PluginSource 和 PluginRuntime,把静态注册逐步迁移为“发现后注册”。 插件化工作只增加平台接入层、生命周期和通信协议,不把数据库、Kafka、SSH 等业务实现合并成新的大模块。各业务工具仍可独立演进、测试和发布。#

七、一个可演进的插件 Manifest#

Manifest 是插件的静态描述文件。宿主先校验它,再决定是否展示、加载或启动插件。插件自身版本与 Ramag API 版本必须分开管理。

{
  "id": "com.example.tool",
  "name": "Example Tool",
  "version": "1.0.0",
  "api": "ramag.plugin.v1",
  "runtime": "process",
  "entry": "example-tool.exe",
  "activation": ["onCommand:example.open"],
  "contributes": {
    "commands": ["example.open"],
    "views": ["example.main"]
  },
  "permissions": ["storage.plugin"]
}

Manifest 不应直接声明任意系统命令或任意路径。宿主需要校验入口路径、签名、版本、权限和依赖,并在插件不可用时给出可诊断的错误,而不是静默跳过。#

八、生命周期、权限与故障恢复#

推荐生命周期是:发现、校验、注册、按需激活、就绪、停用、卸载。每一步都要有超时和错误状态,宿主不能因为一个插件启动失败而阻塞整个窗口。

discover -> validate -> register -> activate -> ready
                                  |
                         timeout / crash / protocol error
                                  v
                         disable -> diagnose -> restart

权限按能力拆分,而不是给插件一个无法审计的“全部权限”。例如 storage.plugin、network、filesystem.workspace、process.spawn、credential.access 和 ui.webview 应分别申请;默认拒绝高风险权限,授权结果按插件 ID 和版本保存。 插件数据使用独立目录,例如 data/plugins/<plugin-id>/。宿主只提供版本化存储 API,不允许插件直接读写 Ramag 的 SQLite 文件、配置文件或其他插件目录。 外部插件使用请求 ID、协议版本、消息大小上限和超时。崩溃后宿主记录退出原因,限制自动重启次数,清理残留 IPC 资源,并允许用户禁用插件后继续使用其他工具。#

九、建议的分阶段落地计划#

阶段一:稳定内置插件接口#

围绕现有 ToolMeta、Tool trait、ToolRegistry 和 Shell 增加 manifest-like 元数据、API 版本、贡献点和激活状态。验收条件是所有现有内置工具行为不变,工具顺序、设置和窗口恢复测试通过。

阶段二:建立外部插件 IPC#

实现独立进程插件的握手、能力查询、命令调用、通知、取消和关闭流程。验收条件是示例插件可以被发现、启动、展示健康状态,并在崩溃或超时后不影响主窗口。

阶段三:权限与存储隔离#

实现权限声明、用户授权、插件专属存储、日志范围和诊断信息。验收条件是未声明权限的请求被拒绝,插件无法访问宿主私有文件,重启后授权和数据行为符合预期。

阶段四:结构化 UI 扩展点#

先支持命令、菜单、设置页和简单表单,再评估表格、树和复杂视图。验收条件是 Shell 不依赖插件内部 UI 类型,窗口缩放、主题、键盘导航和错误状态都有覆盖。

阶段五:WASM 运行时与插件分发#

为纯计算型插件提供 WASM/WASI 运行时,建立签名、兼容性检查、升级、回滚和禁用流程。验收条件是插件可独立升级和回滚,损坏包、API 不兼容包和签名不匹配包均被拒绝。#

十、不建议直接采用的方案#

  • 不建议让第三方插件直接链接 ramag-ui 并调用 Shell 内部对象,这会把每次 UI 重构都变成插件兼容性问题。
  • 不建议一开始就把所有内置工具改成动态库,跨平台 ABI、GPUI 生命周期和调试成本会明显增加,而当前内置工具没有获得足够的隔离收益。
  • 不建议让插件拥有任意 Shell、stdin、文件系统和网络权限。桌面工具通常会接触连接配置和凭据,权限边界必须先于生态扩张建立。
  • 不建议把 WebView 当作唯一 UI 技术。它适合复杂第三方界面,但简单扩展应优先使用结构化 UI。

十一、结论#

VS Code 最值得 Ramag 借鉴的是 manifest、贡献点、按需激活和独立 Extension Host;Zed 最值得借鉴的是 Rust/WASM 运行方式和 capability 权限模型。结合 Ramag 当前的 Rust workspace、ToolRegistry 和 Shell,最稳妥的路径是:内置工具保留 Rust 原生能力,外部插件优先使用独立进程 IPC,纯逻辑插件再引入 WASM。 这样的平台化不会改变数据库、Kafka、SSH 等业务工具的边界,而是给它们提供统一的发现、导航、生命周期和发布基础。等插件 API、权限和故障恢复稳定后,Ramag 才真正具备向更大工具生态扩展的基础。

参考资料#