跳转至

功能插件

A feature plugin 可以拥有自己的后端操作和 Control UI。它可以添加页面、导航、会话操作、面板、仪表盘组件和头部附件,或提供工作区、会话列表、composer、transcript 和工具结果的替换。

所有插件 API 均为实验性,包括本页上的后端和浏览器契约。请固定并测试你的 OpenClaw 宿主版本。

原生 UI 会在 Control UI 源中运行受信任的 JavaScript。仅从你信任的作者处安装。原生模块共享已登录操作者的 Gateway 权限:host.request 可以调用该连接范围允许的任何方法,包括管理员可用的管理方法。当内容应与宿主应用隔离时,请使用现有的沙盒仪表盘组件或 MCP App 界面。

打开由已连接 Gateway 提供的 Control UI。原生插件资源需要同一源;连接到另一个 Gateway 的独立托管 UI 无法加载这些资源,并会说明应打开哪个 Control UI。

已认证的原生 UI 还要求 HTTPS 或浏览器信任的环回 URL,例如 http://127.0.0.1:18789/。局域网地址上的普通 HTTP 可以配对并使用仪表盘,但无法发送用于原生资源的安全 Cookie。插件页面会说明如何切换到 HTTPS/Tailscale Serve 或 localhost;后端操作仍然可用。

启用自定义插件 UI

在 Control UI 中,打开 设置 → Labs → 自定义插件 UI。该设置默认关闭,用于控制用户安装插件(包括本地开发插件)的原生浏览器代码。等效配置为:

{
  gateway: {
    controlUi: {
      experimental: { customPlugins: true },
    },
  },
}

更改无需重启 Gateway 即可生效,已连接的 Control UI 页面会自动刷新其插件视图。禁用它会阻止自定义原生 UI 加载,并移除其视图。重新加载浏览器标签页以清除已经运行的插件 JavaScript。这不会卸载插件,也不会禁用其后端操作、工具或服务。普通插件 API、沙盒仪表盘组件和 MCP Apps 不受影响。

随 OpenClaw 提供的原生 UI 对已启用的捆绑插件仍然可用,包括 Workboard。OpenClaw 根据已加载插件的来源判断其捆绑状态,而不是根据其名称或清单声明。单独安装的副本使用自定义插件设置。

创建功能插件

在打开脚手架的浏览器视图之前,启用 自定义插件 UI 实验室。

openclaw plugins init draft-review --name "Draft Review" --type feature
cd draft-review
npm install
npm run build
npm run validate
openclaw plugins install .

脚手架包含一个 draft-analysis 操作、一个代理工具、一个原生页面和一个 composer 替换。从 Control UI 侧边栏打开 Draft Review,或打开 插件 → 高级 → 自定义 UI 并选择 Draft composer。选择内置以恢复某个视图。替换选择属于当前浏览器运行时;它不是一个持久化配置设置。

自定义控件位于 插件 → 高级 的第一部分。如果工作区替换隐藏了导航,请在你的 Control UI 基础 URL 下打开 /settings/plugins?tab=advanced 以选择内置;插件设置始终使用内置工作区。

该项目有三个公共 SDK 导入:

导入 用途
openclaw/plugin-sdk/feature-contract 共享操作模式、类型化客户端和事件订阅;浏览器安全。
openclaw/plugin-sdk/feature-plugin 将后端实现注册为会话操作、可选代理工具和命令适配器。
openclaw/plugin-sdk/control-ui 原生浏览器激活、宿主能力、贡献类型和组件挂载。

浏览器代码拥有自己的 DOM,并打包其框架依赖。它不会导入 Control UI 内部实现,也不会返回宿主框架的模板。

一次性定义操作

defineFeatureContract 使用 TypeBox 输入和输出模式声明命名查询和操作。defineFeaturePlugin 通过 setup(api, events) 实现它们,并将每个操作注册到现有的插件会话操作传输上。查询需要 operator.read;操作需要 operator.write。

带有 tool 声明的操作还会注册一个代理工具。可选命令适配器会解析实际的命令上下文并格式化结果。处理程序接收一个可区分的调用上下文:source 为 session-action、tool 或 command,并带有该界面的原始上下文。工具策略、已认证命令处理和 Gateway 范围检查仍由其现有执行路径负责。

在浏览器视图中使用 createFeatureClient(contract, context.host):

const feature = createFeatureClient(contract, context.host);
const report = await feature.invoke("analyze", { text: "A draft to inspect" });
context.signal.throwIfAborted();
output.textContent = `${report.words} words`;

后端输入、输出和事件会作为有界 JSON 进行验证。同样,请在模式中也定义有意义的限制。使用插件现有的运行时和存储 API 来管理持久状态和服务。

对于变化的数据,请在契约中声明事件,并在插件服务启动后通过 events 参数发出它们。客户端可以使用 feature.on(...) 订阅,或使用 feature.watch(query, input, options) 在初始时、命名事件后以及重连后请求最新快照。watch 需要 onChange 和 onError,返回一个清理函数,并在更新的刷新或视图清理后拒绝过期结果。它不会轮询。

贡献并替换视图

从浏览器入口导出 defineControlUiPlugin({ id, activate(host) })。 其 id 必须与插件清单匹配。通过 host.ui 注册贡献;每个注册都会返回一个 disposer。

注册 宿主位置
registerPage 和 registerNavigation 插件拥有的路由和侧边栏目标。
registerAction Composer、页眉或会话菜单操作。可选的 resolve 函数提供当前标签、隐藏状态和禁用状态。
registerPanel 会话面板。
registerAccessory 会话页眉内容。
registerWidget 原生仪表板小部件视图。
registerReplacement workspace、session-list、composer、transcript 或 tool-result。

对于仪表板小部件,还需注册一个后端 api.session.controls.registerControlUiDescriptor,其中 surface: "widget"、 相同的小部件 id 及其 requiredScopes。Gateway 会为当前连接的 scopes 通告小部件 类型;只有当匹配的后端描述符被通告时,原生视图才会渲染。

当插件拥有的状态改变某个操作或其他贡献的呈现时,使用 host.ui.invalidate()。使用插件 id 作为自定义元素和 CSS 的命名空间,以便独立打包的插件可以共存。

视图挂载到 HTMLElement 中,并接收 context.host、props、 signal、presented 和 mountDefault。根据需要返回包含 update、 focus 和 dispose 的对象。Surface 属性和呈现变更通过 update 到达;使用 context.host.subscribe(...) 观察宿主快照 变更。当 presented 为 false 时,保持挂载的视图会保留其宿主生命周期;用它来暂停视觉工作同时保留状态。移除或销毁 视图会中止其 signal 并使其宿主句柄退役。在异步 工作之后检查 signal,并在 dispose 中释放插件资源。

与会话绑定的视图和操作会接收 sessionKey 和 agentId。发起会话请求时请同时携带两者: 应用程序当前选择的 agent 可能与视图的 agent 不同。更改该身份的任何一部分都会使旧视图的 句柄退役,包括保留的 composer 操作。当页眉和 composer 操作所在的窗格停止呈现时,这些操作也会 退役。之后重新返回该窗格会允许新的操作;它不会恢复先前调用中的句柄。

替换项可以通过调用 context.mountDefault(container) 来组合内置视图。工作区替换项如果希望保留内置聊天状态和会话所有者,应使用此方法。SDK 不会为完全独立的工作区暴露单独的无头聊天服务。

Composer 替换项会接收当前草稿、准入状态、禁用 原因,以及规范的 setDraft、send 和可选的 abort 操作。请使用 这些操作,而不是发出原始聊天 RPC。当被允许时,send() 解析为 true; 被拒绝时解析为 false;对于本地命令或未提交则解析为 undefined。应显示被拒绝的提交,而不是清除草稿。Composer 操作在视图停止呈现时会退役,即使其 DOM 和 宿主生命周期仍然存在。当视图再次呈现时,使用 update 提供的新操作;先前捕获的操作仍保持退役状态。

宿主还暴露会话和 agent 快照及操作、插件页面 导航、经过身份验证的请求以及订阅。会话和 agent 的 refresh() 操作会获取新快照,并在失败时 reject,因此插件 可以显示错误并提供重试。host.sessions.rows 是当前 经过筛选、分页的会话列表。host.sessions.refresh() 会保留该 列表的筛选条件。使用 host.sessions.observe(query, onChange) 来维护一个 独立的会话查询,而不替换它。查询接受 agentId、 search、archived(true、false 或 "all")、limit、configuredAgentsOnly、 includeGlobal、includeUnknown、includeDerivedTitles 和 includeLastMessage。回调接收 { result, loading, error }, 从当前快照开始;在数据可用之前,result 为 null。 结果包含 sessions 以及 Gateway 的 hasMore、nextOffset 和 totalCount 分页元数据。

宿主会获取查询,并通过会话事件、 观察者恢复及其常规删除处理保持其最新。observe 返回 { refresh, dispose }:调用 refresh() 进行显式重试,当不再需要查询时调用 dispose()。 错误会出现在快照中,显式刷新在失败时会 reject。视图销毁也会释放其查询; 保留的句柄和未完成的刷新在该生命周期结束后会 reject。

在筛选或不完整的列表中缺少某一行并不能证明该会话 已被删除。待处理的删除也可能在分页元数据 更改之前隐藏行。将 { sessionKey: row.key, agentId: row.agentId } 传递给 host.sessions.open、host.sessions.patch 以及仪表板的 session prop。 携带返回行中的身份:原始 global key 属于其 查询的 agent,而不是当前选择或卡片分配的 agent。将未 解析或模糊的搜索视为未知。当名册更改时,会话操作的呈现会更新,并且操作在被调用时会再次检查当前行。 操作可以检查 session.hasActiveRun;缺失值表示活动状态尚未知。 host.components 从普通 props 和 DOM 内容挂载宿主拥有的对话框、agent 选择器、可搜索的选择器以及会话 仪表板。每个组件返回 update 和 dispose 方法; 宿主保留权限检查、焦点处理和仪表板 provider 所有权。使用 mountSelectPicker 处理 { value, label, description? } 选项列表、选中的 value、accessibleLabel 和 onSelect 回调。 当 searchable: true 时,超过八个选项的列表会显示搜索字段。 选择器会匹配选项标签、值和描述。

构建并重载

package.json 指定浏览器 源:

{
  "openclaw": {
    "extensions": ["./dist/index.js"],
    "controlUi": "./src/control-ui.ts"
  }
}

openclaw plugins build 会使用插件的 esbuild 开发依赖,将该源及其浏览器依赖捆绑在一起。它会在 dist/control-ui/<content-hash>/ 下写入不可变的 JavaScript 和 CSS,然后在 openclaw.plugin.json.controlUi 中发布其路径。构建失败时,之前的清单和资源仍保持可用。plugins validate 和 plugins build --check 会检测过时的源、资源或生成的元数据。

构建会输出一个自包含的 JavaScript 入口和可选的 CSS。将其他静态资源嵌入到 bundle 中;任意文件和拆分的懒加载 chunk 不在此构建契约范围内。导入必须可被 esbuild 分析:字面量路径和受支持的 glob 导入可用;未解析的动态导入、间接 require 调用和 require.resolve 会被拒绝。每个资源限制为 4 MiB,整个插件浏览器构建的限制为 8 MiB。

具有预构建浏览器 bundle 的插件可以省略 package.json.openclaw.controlUi,并在 openclaw.plugin.json.controlUi 中声明构建后的入口和样式。打包和提供服务会包含该入口目录下的 JavaScript 和 CSS 依赖,包括嵌套 chunk 和导入的样式表,并受相同的字节限制。TypeScript 源、source map 和隐藏文件会被排除。将所有浏览器依赖保留在该目录内;遍历限制为八层嵌套目录和 128 个条目,文件和目录均计入。

仅进行浏览器端编辑后,重新构建已安装的插件,并以管理员身份打开 插件 → 高级 → 自定义 UI → 重新加载插件 UI。Gateway 会捕获新的资源修订版本,并通知已连接的浏览器。资源加载或激活失败会在 UI 自定义控件中报告;在可能的情况下,会保留之前正常工作的激活状态。修正插件后重试,或选择 Built-in 以恢复被替换的视图。

已通告的资源修订版本在其后端插件的生命周期内保持可用,包括旧标签页或保留的工作视图所需的导入。共享缓存最多保存 256 个修订版本和 64 MiB,覆盖所有原生插件。当缓存填满时,重新加载会拒绝新构建并保留已通告的资源。重启 Gateway,然后重试重新加载。

首次激活时,在目录、资源授权和初始化器完成期间,插件页面和仪表板小部件会显示加载状态。每个插件独立于其他仍在加载的插件变为可用。断开连接的视图会等待 Gateway 连接后再检查可用性。小部件会显示初始化失败,并提供重试操作。

激活期间创建的注册和替换选择会在初始化成功后一起发布。在该时间点之前释放注册也会取消其待处理的选择。无效选择会拒绝新的激活,而不会停用之前正常工作的 UI。

当贡献在该界面上仍然存在时,所选替换会在成功的修订后保留。注销贡献、移除其插件或重新连接会清除该选择;重用 ID 不会恢复它。

自定义元素定义属于浏览器文档。如果插件修改了现有自定义元素类,也请重新加载浏览器标签页,或使用新的带版本号的标签名。

后端更改使用 插件更新或重新加载 来替换正在运行的插件,而无需重启 Gateway。浏览器重新加载不会替换后端服务,也不会更改已在运行的 agent 的工具目录。

批准 agent 构建的工件

构建并验证后,生成导入归档:

openclaw plugins pack --root . --out ./draft-review.tgz --json

回执包含归档的绝对路径、SHA-256 摘要、插件 id 和 plugin_activate_artifact 请求。打包会捆绑后端依赖,保持宿主 openclaw 导入为外部依赖,并包含清单和编译后的 UI。归档不包含安装脚本或运行时包依赖。它必须有一个后端入口;需要单独运行时文件的功能需要使用正常的已审核包安装流程。打包会拒绝后端对 import.meta、__dirname、__filename 和 require.resolve 的引用,包括捆绑依赖中的引用,因为它们的原始文件和位置不会随工件一起传输。改为在后端构建中导入 JSON 或嵌入其他资源。

后端导入也必须可被 esbuild 分析。直接 CommonJS 导入可用,包括 Node 内置模块和宿主 SDK。未解析的动态导入、间接 require 调用以及带有不透明运行时加载器的预捆绑会被拒绝。提供一个未使用这些加载器编译的入口,以便打包可以捆绑其依赖,或使用正常的包安装流程。

系统代理可以使用该路径和摘要提议激活。在批准之前,OpenClaw 会验证并保留确切的归档,并在不执行插件的情况下检查其声明的能力和原生 UI 存在性。批准后的应用会通过受管理的插件安装程序使用这些保留的字节。在批准待定期间更改源文件不会改变所安装的内容。现有的安装策略和能力检查仍然适用。

工件批准不会启用 Custom 插件 UI 实验室。已安装的后端可以在该设置关闭时运行;其原生浏览器 UI 仍受门控。

待处理导入在一小时后过期。OpenClaw 最多保留八个待处理归档,每个最大 32 MiB,并在准备另一个提议时清理过期或最旧的导入。过期或被驱逐的审查需要新的提议。已批准的归档会单独保留作为安装源,包括当安装程序错误导致最终安装结果不确定时。

工件激活目前要求根配置文件中包含插件配置,且没有根级 $include。对于仅包含 $include: "plugins.json5" 的 plugins 部分(配置目录下的单个文件,没有嵌套 include),请从受信任的 shell 中使用 openclaw plugins install <archive>。常规安装程序也会拒绝根级、嵌套和外部 include 布局;安装前请调整这些布局。

工件激活也会拒绝替换支撑 OpenClaw 活动推理路由的插件。请停止 OpenClaw,并从受信任的 shell 安装该工件。

由 Gateway 托管的工件激活会等待后端运行时应用。终端或其他没有实时 Gateway 生命周期回调的主机会保存安装并报告写入方的后续操作;该结果不是运行时应用回执。一旦 Gateway 已应用该插件,请检查 plugins.controlUi.status,以查看当前已连接的 Control UI 客户端的激活报告。报告会指明插件修订版本以及 activated 或 failed;它是浏览器激活回执,并非证明所有功能操作都已被执行。没有已连接的浏览器,就意味着尚未有浏览器激活回执。

有关底层清单字段,请参阅 插件清单。 有关安装、更新和移除,请参阅 管理插件。

本页原文 Markdown:在 AtomGit 查看·内容源自开源项目 cl/openclaw