Skip to main content
有关公开能力模型、插件形态以及所有权/执行约定,请参见 插件架构。本页介绍内部机制:加载流水线、注册表、运行时钩子、Gateway HTTP 路由、导入路径和 schema 表。

加载流水线

在启动时,OpenClaw 大致会执行以下步骤:
  1. 发现候选插件根目录
  2. 读取原生或兼容捆绑清单以及包元数据
  3. 拒绝不安全的候选项
  4. 规范化插件配置(plugins.enabledallowdenyentriesslotsload.paths
  5. 为每个候选项决定是否启用
  6. 加载已启用的原生模块:已构建的捆绑模块使用原生加载器; 第三方本地源码 TypeScript 使用紧急 Jiti 回退
  7. 调用原生 register(api) 钩子,并将注册内容收集到插件注册表中
  8. 将注册表暴露给命令/运行时界面
安全门会在运行时执行之前运行。发现阶段会在以下情况下阻止候选项:
  • 其解析后的入口逃逸出了插件根目录
  • 其路径(或其根目录)是全局可写的
  • 对于非捆绑插件,其路径所有权与当前 uid(或 root)不匹配
全局可写的捆绑目录会先尝试就地 chmod 修复(npm/全局安装可能会以 0777 提供包目录),然后安全门才会重新检查;所有权检查对捆绑来源会完全跳过。 当已知插件 id 时,被阻止的候选项在发出的诊断信息中仍会携带该 id(包括从一个在其他方面被拒绝的目录中的清单解析出的 id),因此引用该 id 的配置会看到一个与路径安全警告绑定的被阻止插件,而不是无关的“未知插件”错误。

清单优先行为

清单是控制面的事实来源。OpenClaw 使用它来:
  • 识别插件
  • 发现声明的通道/技能/配置模式或 bundle 能力
  • 验证 plugins.entries.<id>.config
  • 增强 Control UI 标签/占位符
  • 展示安装/目录元数据
  • 在不加载插件运行时的情况下保留廉价的激活和设置描述符
对于原生插件,运行时模块是数据面部分。它负责注册实际行为,例如钩子、工具、命令或提供者流程。 可选的清单 activationsetup 块仍然留在控制面。它们是用于激活规划和设置发现的纯元数据描述符;它们不会替代运行时注册、register(...)setupEntry。实时激活消费者会使用清单中的命令、通道和提供者提示,在更大范围的注册表物化之前缩小插件加载范围:
  • CLI 加载会缩小到拥有所请求主命令的插件
  • 通道设置/插件解析会缩小到拥有所请求通道 id 的插件
  • 显式提供者设置/运行时解析会缩小到拥有所请求提供者 id 的插件
  • Gateway 启动规划会对显式启动导入使用 activation.onStartup;没有启动元数据的插件只会通过更窄的激活触发器加载
激活规划器同时为现有调用方提供仅 ids 的 API,以及用于诊断的 plan API。计划条目会报告插件被选中的原因,将显式的 activation.* 提示与清单所有权回退区分开来: 这种原因拆分就是兼容边界:现有插件元数据继续可用,而新代码可以在不改变运行时加载语义的情况下检测更宽泛的提示或回退行为。 请求时的运行时预加载如果请求的是宽泛的 all 范围,仍会从配置、启动规划、已配置通道、slots 和自动启用规则中推导出一个显式的有效插件 id 集合(src/plugins/effective-plugin-ids.ts 中的 resolveEffectivePluginIds)。如果推导出的集合为空,OpenClaw 会保持该范围为空,而不是扩大到所有可发现的插件。 设置发现会优先使用描述符拥有的 ids,例如 setup.providerssetup.cliBackends,先缩小候选插件范围,然后才回退到 setup-api,以处理那些仍然需要设置时运行时钩子的插件。提供者设置列表会使用清单 providerAuthChoices、描述符派生的设置选项以及安装目录元数据,而无需加载提供者运行时。显式的 setup.requiresRuntime: false 是一个仅描述符级别的截止条件;省略 requiresRuntime 会保留旧版的 setup-api 回退,以兼容旧行为。如果有多个发现的插件声称拥有同一个规范化后的设置提供者或 CLI 后端 id,设置查找会拒绝这个有歧义的所有者,而不是依赖发现顺序。当设置运行时确实执行时,注册表诊断会报告 setup.providers / setup.cliBackends 与实际由 setup-api 注册的 providers 或 CLI backends 之间的漂移,但不会阻止旧插件。

插件缓存边界

OpenClaw 不会在基于墙钟时间的窗口内缓存插件发现结果或直接的清单注册表数据。安装、清单编辑和加载路径变更必须在下一次显式的元数据读取或快照重建时可见。清单文件解析器维护一个有界的文件签名缓存,该缓存以已打开的清单路径以及设备/inode、大小和 mtime/ctime 为键;该缓存只用于避免对未变更字节的重复解析,绝不能缓存发现、注册表、所有者或策略答案。 安全的元数据快路径是显式对象所有权,而不是隐藏缓存。Gateway 启动的热路径应在调用链中传递当前的 PluginMetadataSnapshot、派生的 PluginLookUpTable 或显式清单注册表。配置验证、启动自动启用、插件引导和提供者选择可以在这些对象代表当前配置和插件库存时复用它们。设置查找仍会按需重建清单元数据,除非特定设置路径接收到了显式的清单注册表;请将其保留为冷路径回退,而不是添加隐藏的查找缓存。当输入变化时,应重建并替换快照,而不是变异它或保留历史副本。活动插件注册表以及捆绑通道引导辅助视图应根据当前注册表/根目录重新计算。在一次调用内用于去重工作或防止重入的短生命周期 map 是可以的;它们不能变成进程级元数据缓存。 对于插件加载,持久缓存层是运行时加载。它可以在代码或已安装工件实际被加载时重用加载器状态,例如:
  • PluginLoaderCacheState 和兼容的活动运行时注册表
  • jiti/module 缓存和公共表面加载器缓存,用于避免重复导入 相同的运行时表面
  • 用于已安装插件工件的文件系统缓存
  • 用于路径规范化或重复项解析的短生命周期、按调用创建的 map
这些缓存是数据面实现细节。除非调用方明确请求运行时加载,否则它们不能回答控制面问题,例如“哪个插件拥有这个提供者?”。 不要为以下内容添加持久化或基于墙钟时间的缓存:
  • 发现结果
  • 直接的清单注册表
  • 从已安装插件索引重建的清单注册表
  • 提供者所有者查找、模型抑制、提供者策略或公共工件元数据
  • 任何其他派生自清单的答案,只要清单变更、已安装索引变化或加载路径变化,应在下一次元数据读取时可见
从持久化的已安装插件索引重建清单元数据的调用方,会按需重建该注册表。已安装索引是持久化的源平面状态;它不是隐藏的进程内元数据缓存。

注册表模型

已加载的插件不会直接修改随机的核心全局变量。它们会注册到一个 中心插件注册表(src/plugins/registry-types.ts 中的 PluginRegistry), 该注册表会跟踪插件记录(身份、来源、出处、状态、诊断信息) 以及每种能力对应的数组:工具、旧式 hooks 和类型化 hooks、 通道、提供者、网关 RPC 处理器、HTTP 路由、CLI 注册器、 后台服务、插件拥有的命令,以及数十种更多的类型化提供者 家族(语音、嵌入、图像/视频/音乐生成、Web 抓取/搜索、代理控制器、会话操作,等等)。 核心功能随后会从该注册表中读取,而不是直接与插件 模块交互。这使得加载方向保持单向:
  • 插件模块 -> 注册表注册
  • 核心运行时 -> 注册表消费
这种分离对可维护性很重要。它意味着大多数核心表面只需要 一个集成点:“读取注册表”,而不是“为每个插件模块做特殊处理”。

会话绑定回调

绑定会话的插件可以在审批结果确定时进行响应。 使用 api.onConversationBindingResolved(...) 可以在绑定请求被批准或拒绝后接收回调:
回调载荷字段:
  • status"approved""denied"
  • decision"allow-once""allow-always""deny”`
  • binding:批准请求的已解析绑定
  • request:原始请求摘要、解除绑定提示、发送者 ID 和会话元数据
此回调仅用于通知。它不会改变谁可以绑定会话,并且会在核心审批处理完成后运行。

提供方运行时钩子

提供方插件有三层:
  • Manifest 元数据,用于运行时前的低成本查找: setup.providers[].envVarsproviderAuthAliasesproviderAuthChoiceschannelConfigs
  • 配置时钩子catalog 以及 applyConfigDefaults
  • 运行时钩子:40 多个可选钩子,涵盖身份验证、模型解析、 流包装、思考级别、重放策略和用量端点。请参阅 钩子顺序和用法
OpenClaw 仍然负责通用的代理循环、故障切换、转录处理和工具策略。 这些钩子是面向提供方特定行为的扩展接口,而不需要完全自定义的推理传输。 当提供方具有基于环境变量的凭据,且通用的身份验证/状态/模型选择器路径需要在不加载插件运行时的情况下访问这些凭据时,请使用 Manifest 中的 setup.providers[].envVars。当一个提供方 ID 应复用另一个提供方 ID 的环境变量、身份验证配置文件、基于配置的身份验证以及 API 密钥引导选项时,请使用 Manifest 中的 providerAuthAliases。当引导/身份验证选项 CLI 界面需要在不加载提供方运行时的情况下了解提供方的选项 ID、分组标签和简单的单标志身份验证连接方式时,请使用 Manifest 中的 providerAuthChoices。将提供方运行时的 envVars 保留用于面向操作员的提示,例如引导标签或 OAuth 客户端 ID/客户端密钥设置变量。 通过所属的 channelConfigs.<id>.schema 和设置描述符,描述由环境变量驱动的频道设置和身份验证。

钩子顺序与使用

对于模型/提供者插件,OpenClaw 按以下大致顺序调用钩子。 “何时使用”列是快速决策指南。 OpenClaw 不再调用的仅兼容性提供者字段,例如 ProviderPlugin.capabilitiessuppressBuiltInModel,故意不列在此处。 normalizeModelIdnormalizeTransportnormalizeConfig 会先检查 匹配到的提供者插件,然后继续回退到其他具备钩子能力的提供者插件,直到有某个插件真正改变模型 ID 或传输/配置为止。这样可以让别名/兼容性提供者 shim 继续工作,而无需调用方知道哪个捆绑插件负责该重写。如果没有任何提供者钩子重写受支持的 Google 家族配置条目,捆绑的 Google 配置规范化器仍然会应用那种兼容性清理。 如果提供者需要完全自定义的线协议或自定义请求执行器,那就是另一类扩展。这些钩子面向仍然运行在 OpenClaw 正常推理循环上的提供者行为。 resolveUsageAuth 决定 OpenClaw 是否应调用 fetchUsageSnapshot,还是在用量/状态界面上回退到通用凭据解析。若提供者具有用量凭据,则返回 { token, accountId?, subscriptionType?, rateLimitTier? }(可选的计划元数据会流入 fetchUsageSnapshot);当提供者拥有的用量认证已处理该请求且必须禁止通用 API key/OAuth 回退时,返回 { handled: true };当提供者未处理用量认证时,返回 nullundefined 在清单 providerUsageAuthEnvVars 中声明组织或计费凭据。这样通用发现和秘密清理界面就能识别它们,而不会把它们当作推理认证候选项。

提供商示例

内置示例

捆绑的提供者插件会结合上面的钩子,以适配每个供应商的目录、认证、thinking、回放和使用量需求。权威的钩子集合位于各插件在 extensions/ 下的实现中;本页展示的是形态,而不是逐项复刻列表。
OpenRouter、Kilocode、Z.AI、xAI 会注册 catalog 以及 resolveDynamicModel / prepareDynamicModel,以便在 OpenClaw 的静态目录之前暴露上游 模型 id。
GitHub Copilot、Gemini CLI、ChatGPT Codex、MiniMax、小米、z.ai 会将 prepareRuntimeAuthformatApiKeyresolveUsageAuth + fetchUsageSnapshot 配对,以负责令牌交换和 /usage 集成。
共享的命名家族(google-geminipassthrough-geminianthropic-by-modelhybrid-anthropic-openai)允许提供者通过 buildReplayPolicy 采用转录策略,而不是由每个插件各自重新实现清理。
bytepluscloudflare-ai-gatewayhuggingfacekimi-codingnvidiaqianfansynthetictogethervenicevercel-ai-gatewayvolcengine 只注册 catalog 并依赖共享推理循环。
Beta 头、/fast / serviceTiercontext1m 位于 Anthropic 插件的公共 api.ts / contract-api.ts 接缝中 (wrapAnthropicProviderStreamresolveAnthropicBetasresolveAnthropicFastModeresolveAnthropicServiceTier),而不是在通用 SDK 中。

运行时辅助工具

插件可以通过 api.runtime 访问选定的核心辅助工具。对于 TTS:
注释:
  • textToSpeech 为文件/语音消息表面返回正常的核心 TTS 输出载荷。
  • 使用核心 tts 配置和提供方选择。
  • 返回 PCM 音频缓冲区及采样率。插件必须针对提供方进行重采样/编码。
  • listVoices 对每个提供方来说都是可选的。将其用于供应商自有的语音选择器或设置流程。
  • 核心会向提供方的 listVoices 钩子传递解析后的请求截止时间;提供方特定的超时设置可能会覆盖该时间。
  • 语音列表可以包含更丰富的元数据,例如区域设置、性别和个性标签,以便支持提供方感知的选择器。
  • 目前 OpenAI 和 ElevenLabs 支持电话场景。Microsoft 不支持。
插件也可以通过 api.registerSpeechProvider(...) 注册语音提供方。
注释:
  • 将 TTS 策略、回退和回复投递保留在核心中。
  • 对于供应商自有的合成行为,请使用语音提供方。
  • 旧版 Microsoft edge 输入会被规范化为 microsoft 提供方 id。
  • 推荐的所有权模型是公司导向的:一个供应商插件可以拥有文本、语音、图像,以及 OpenClaw 增加这些能力合同时的未来媒体提供方。
对于图像/音频/视频理解,插件应注册一个带类型的媒体理解提供方,而不是通用的键/值袋:
注释:
  • 将编排、回退、配置和通道接线保留在核心中。
  • 将供应商行为保留在提供方插件中。
  • 增量扩展应保持类型化:新增可选方法、新增可选结果字段、新增可选能力。
  • 视频生成已经遵循相同模式:
    • 核心负责能力合约和运行时辅助工具
    • 供应商插件注册 api.registerVideoGenerationProvider(...)
    • 功能/通道插件消费 api.runtime.videoGeneration.*
对于媒体理解运行时辅助工具,插件可以调用:
对于音频转写,插件可以使用媒体理解运行时,或者旧的 STT 别名:
注释:
  • api.runtime.mediaUnderstanding.* 是图像/音频/视频理解的首选共享接口。
  • extractStructuredWithModel(...) 是面向插件的、用于有界供应商自有图像优先提取的衔接点。至少应包含一个图像输入;文本输入作为补充上下文。产品插件负责其路由和架构,而 OpenClaw 负责提供方/运行时边界。
  • 使用核心媒体理解音频配置(tools.media.audio)和提供方回退顺序。
  • 当未生成转写输出时(例如输入被跳过或不受支持),返回 { text: undefined }
插件还可以通过 api.runtime.subagent 启动后台子代理运行:
注释:
  • providermodel 是每次运行可选的覆盖项,不是持久的会话更改。
  • toolsAlsoAllow 接受由调用插件注册的、精确且唯一拥有的工具名称。核心和含糊不清的名称会被拒绝。它会在正常配置文件的基础上进行叠加,但操作员的允许列表和拒绝列表仍然具有最终权威。
  • OpenClaw 仅对受信任的调用方认可这些覆盖字段。
  • 对于插件拥有的回退运行,操作员必须显式启用 plugins.entries.<id>.subagent.allowModelOverride: true
  • 使用 plugins.entries.<id>.subagent.allowedModels 将受信任插件限制到特定的规范化 provider/model 目标,或者使用 "*" 显式允许任何目标。
  • 不受信任的插件子代理运行仍然可以工作,但覆盖请求会被拒绝,而不是静默回退。
  • 由插件创建的子代理会话会标记创建它的插件 id。兼容的 api.runtime.subagent.deleteSession(...) 只能删除这些所属会话;任意会话删除仍然需要具有管理员范围的 Gateway 请求。
对于网络搜索,插件可以消费共享运行时辅助工具,而不是直接进入代理工具接线:
插件也可以通过 api.registerWebSearchProvider(...) 注册网络搜索提供方。 注释:
  • 将提供方选择、凭据解析和共享请求语义保留在核心中。
  • 对于供应商特定的搜索传输,请使用网络搜索提供方。
  • api.runtime.webSearch.* 是需要搜索行为、但不依赖代理工具包装器的功能/通道插件的首选共享接口。

api.runtime.imageGeneration

  • generate(...):使用已配置的图像生成提供方链生成图像。
  • listProviders(...):列出可用的图像生成提供方及其能力。

Gateway HTTP 路由

插件可以使用 api.registerHttpRoute(...) 暴露 HTTP 端点。
路由字段:
  • path: Gateway HTTP 服务器下的路由路径。
  • auth: 必填,"gateway""plugin"。使用 "gateway" 要求常规 Gateway 身份验证,使用 "plugin" 则由插件管理身份验证或 Webhook 验证。
  • match: 可选。"exact"(默认)或 "prefix"
  • handleUpgrade: 可选,用于处理同一路由上的 WebSocket 升级请求。
  • replaceExisting: 可选。仅动态生命周期注册要替换自身现有路由时必需。
  • handler: 路由处理请求时返回 true
注释:
  • api.registerHttpHandler(...) 已被移除,使用它会导致插件加载错误。请改用 api.registerHttpRoute(...)
  • 插件路由必须显式声明 auth
  • 具有相同 match 模式的规范等价路径共用一个路由。同一插件中的静态 api.registerHttpRoute(...) 调用会替换该路由;其他插件无法替换它。
  • 不同 auth 级别的重叠路由会被拒绝。仅在相同的身份验证级别上保留 exact/prefix 回退链。
  • 使用 openclaw/plugin-sdk/webhook-ingress 中的 registerPluginHttpRoute(...) 的动态生命周期代码必须设置 replaceExisting: true,以刷新自身的规范路由。命名注册只能替换具有相同非空 pluginId 的注册;当任一方设置了路由 source 时,双方都必须设置相同的非空 source。对于已发布 SDK 调用方,同一插件的无 source 到无 source 刷新以及匿名到匿名刷新仍受支持,但命名路由和匿名路由不能相互替换。
  • 将路由 source 视为稳定的同插件子所有者标识,而不是诊断标签。现有的无 source 调用方可以继续省略它;使用 source 的调用方必须在刷新期间保持其不变。
  • 动态生命周期注册在被拒绝时默认记录日志并返回一个空操作注销回调。当就绪状态依赖该路由时,请设置 throwOnFailure: true;必需的内置 Webhook 传输使用严格注册,因此不会在没有有效入口的情况下报告就绪。
  • auth: "plugin" 路由不会自动获得操作员运行时作用域。它们用于插件管理的 Webhook 或签名验证,而不是特权 Gateway 辅助调用。
  • auth: "gateway" 路由在 Gateway 请求运行时作用域中运行。默认界面(gatewayRuntimeScopeSurface: "write-default")有意保持保守:
    • 共享密钥 bearer 身份验证(gateway.auth.mode = "token" / "password")以及任何非可信代理身份验证方法,即使调用方发送了 x-openclaw-scopes,也只获得单个 operator.write 作用域
    • 未显式提供 x-openclaw-scopes 标头的 trusted-proxy 调用方同样仅保留传统的 operator.write 作用域界面
    • 提供了 x-openclaw-scopestrusted-proxy 调用方则获得所声明的作用域
    • 路由可以选择 gatewayRuntimeScopeSurface: "trusted-operator",以便对携带身份的身份验证模式始终遵循 x-openclaw-scopes(如果未提供该标头,则回退到完整的 CLI 默认作用域集合)
  • auth: "gateway" 路由支持的沙盒化外部 Control UI 标签页使用仅由经过身份验证的引导流程签发的短期签名 Cookie 授权;插件身份验证标签页保留其直接 iframe 路径。在挂载之前,父页面会在同一个不透明沙盒内运行路由专属探测;当浏览器隐私策略阻止 Cookie 时,探测会安全失败。该授权绑定到所属插件、匹配的路由根路径以及当前身份验证代次;其进程随机生成的 Cookie 名称可防止受信任的同主机 Gateway 相互覆盖,但 Cookie 无法隔离 TCP 端口。因此,Gateway 主机名构成一个凭据边界:不要在该主机名下托管相互不信任的服务,包括其他端口。路由分发会拒绝针对另一个插件所拥有嵌套路由的重用。由于沙盒后代在 Cookie 语义上属于跨站点内容,该授权仅接受带有 operator.readGETHEAD 请求;变更操作和 WebSocket 升级仍必须使用显式的 Gateway 身份验证界面。该 Cookie 有意不能使用 CHIPS:当前浏览器会在分区密钥中加入跨站祖先标记,因此嵌套的不透明沙盒框架将无法访问同一路由的资源。该 Cookie 要求安全上下文以及浏览器对跨站 Cookie 的许可,因此在普通 HTTP 局域网来源或完全阻止第三方 Cookie 的环境下,Gateway 身份验证的外部标签页不可用;请使用 HTTPS/Tailscale Serve,或使用兼容 Cookie 策略且受浏览器信任的回环地址。
  • 该授权可防止 Gateway bearer 令牌泄露以及路由/作用域被意外复用;但它不会在原生插件之间建立安全边界。原生插件代码及其提供的 UI 内容仍属于同一受信任的进程内插件边界。
  • 实际规则:不要假设 Gateway 身份验证的插件路由隐含具备管理员界面。如果你的路由需要仅限管理员的行为,请选择 trusted-operator 作用域界面,要求使用携带身份的身份验证模式,并记录明确的 x-openclaw-scopes 标头约定。
  • 启动插件会在 Gateway 开始监听后,使用其完整运行时注册 HTTP 路由。在启动侧车就绪之前,未被其他路由声明的 HTTP 请求会返回 503 以及 Retry-After: 1;核心路由仍会正常分发。此通用回退机制覆盖了运行时注册表尚无法识别其所有者之前的插件路由。
  • 路由匹配和身份验证后,普通处理程序会参与 Gateway 根级工作准入。Gateway 已准备就绪或正在重启时,会在调用处理程序之前返回 503。唯一的狭义例外是:一个拥有清单授权的 auth: "gateway" 路由,同时选择了路由专属的 trusted-operator 界面;该路由仍可访问,从而避免暂停控制分发被阻塞,而同一插件的普通兄弟路由仍处于准入边界之后。WebSocket handleUpgrade 的所有权使用相同的原子准入边界;一旦处理程序接受套接字,该套接字后续的生命周期便由插件所有,并不受此边界跟踪。

插件 SDK 导入路径

在编写新插件时,请使用更窄的 SDK 子路径,而不是单体的 openclaw/plugin-sdk 根聚合入口。核心子路径: 通道插件会从一组更窄的接入点中选择——channel-setupsetup-runtimesetup-toolschannel-pairingchannel-contractchannel-feedbackchannel-inboundchannel-outboundcommand-authsecret-inputwebhook-ingresschannel-targetschannel-actions。审批行为应当收敛到单一的 approvalCapability 契约上,而不是分散在互不相关的插件字段中。请参见 通道插件 Runtime 和配置辅助工具位于对应的聚焦 *-runtime 子路径下 (approval-runtimeagent-runtimelazy-runtimedirectory-runtimetext-utility-runtimeruntime-storesystem-event-runtimeheartbeat-runtimechannel-activity-runtime 等)。请优先使用 config-contractsplugin-config-runtimeruntime-config-snapshotconfig-mutation, 而不是宽泛的 config-runtime 兼容性聚合入口。
openclaw/plugin-sdk/channel-lifecycle、小型通道辅助工具门面、 openclaw/plugin-sdk/config-runtimeopenclaw/plugin-sdk/infra-runtime 是面向旧版插件的已弃用兼容性垫片。新代码应改为导入更窄的通用原语。
仓库内部入口点(按打包插件包根目录):
  • index.js — 打包后的插件入口
  • api.js — 辅助工具/类型聚合入口
  • runtime-api.js — 仅运行时聚合入口
  • setup-entry.js — 设置插件入口
外部插件应仅导入 openclaw/plugin-sdk/* 子路径。切勿从核心或其他插件中导入另一个插件包的 src/*。门面加载的入口点优先使用活动运行时配置快照(如果存在),然后回退到磁盘上的已解析配置文件。 image-generationmedia-understandingspeech 这样的能力特定子路径之所以存在,是因为打包插件今天就在使用它们。它们并不是自动长期冻结的外部契约——在依赖它们时,请查看相关的 SDK 参考页面。

消息工具架构

插件应拥有渠道特定的 describeMessageTool(...) 架构贡献,用于非消息原语,例如反应、已读和投票。共享发送展示应使用通用的 MessagePresentation 合约,而不是 provider 原生的 button、component、block 或 card 字段。有关该合约、降级规则、provider 映射以及插件作者检查清单,请参见 消息展示 具备发送能力的插件通过消息能力声明它们可以渲染的内容:
  • presentation 用于语义展示块(textcontextdividercharttablebuttonsselect
  • delivery-pin 用于置顶发送请求
Core 决定是原生渲染该展示,还是将其降级为文本。不要通过通用消息工具暴露 provider 原生 UI 的逃生通道。面向旧版原生架构的已弃用 SDK 辅助函数仍会导出,以兼容现有第三方插件,但新插件不应使用它们。

渠道目标解析

渠道插件应拥有渠道特定的目标语义。保持共享的出站主机通用化,并使用消息适配器接口来处理提供商规则:
  • messaging.inferTargetChatType({ to }) 决定在目录查找之前,是否应将规范化目标视为 directgroupchannel。 隐式所有者心跳传递要求进行此直接分类;否则,Gateway 状态报告将显示 waiting for route
  • messaging.targetResolver.looksLikeId(raw, normalized) 告知核心,某个输入是否应跳过目录搜索,直接进行类似 ID 的解析。
  • messaging.targetResolver.reservedLiterals 列出对于该提供商而言属于渠道/会话引用的裸词。解析会在拒绝保留字面量之前保留已配置的目录条目,然后在目录未命中时安全失败。
  • messaging.targetResolver.resolveTarget(...) 是核心在规范化之后或目录未命中之后需要提供商负责的最终解析时,由插件提供的回退方案。
  • messaging.resolveOutboundSessionRoute(...) 在目标解析完成后,负责构建提供商特定的会话路由。
推荐拆分方式:
  • inferTargetChatType 用于应在搜索对等方/群组之前发生的分类决策。
  • looksLikeId 用于“将其视为显式/原生目标 ID”的检查。
  • resolveTarget 用于提供商特定的归一化回退,而不是用于广泛目录搜索。
  • 将聊天 ID、线程 ID、JID、句柄和房间 ID 等提供商原生 ID 保留在 target 值或提供商特定参数中,而不是放在通用 SDK 字段里。

基于配置的目录

从配置派生目录条目的插件,应将该逻辑保留在插件内部,并复用来自 openclaw/plugin-sdk/directory-runtime 的共享辅助函数。 当某个渠道需要基于配置的 peers/groups 时使用此方式,例如:
  • 基于 allowlist 的 DM peers
  • 已配置的 channel/group 映射
  • 账户作用域的静态目录回退
directory-runtime 中的共享辅助函数只处理通用操作:
  • 查询过滤
  • limit 应用
  • 去重/归一化辅助
  • 构建 ChannelDirectoryEntry[]
渠道特定的账户检查和 id 归一化应保留在插件实现中。

provider 目录

provider 插件可以通过 registerProvider({ catalog: { run(...) { ... } } }) 为推理定义模型目录。 catalog.run(...) 返回与 OpenClaw 写入 models.providers 的相同结构:
  • { provider } 表示单个 provider 条目
  • { providers } 表示多个 provider 条目
当插件拥有 provider 特定的模型 id、base URL 默认值,或受认证门控的模型元数据时,使用 catalog catalog.order 控制插件的目录与 OpenClaw 内置隐式 provider 的合并顺序:
  • simple:普通 API key 或 env 驱动的 provider
  • profile:在存在认证 profile 时出现的 provider
  • paired:合成多个相关 provider 条目的 provider
  • late:最后一轮,在其他隐式 provider 之后
后面的 provider 在键冲突时获胜,因此插件可以有意用相同的 provider id 覆盖内置 provider 条目。 插件还可以通过 api.registerModelCatalogProvider({ provider, kinds, staticCatalog, liveCatalog }) 发布只读模型行。这是用于列表/帮助/选择器界面的前进路径,并支持 textvoiceimage_generationvideo_generationmusic_generation 行。provider 插件仍然负责实时端点调用、令牌交换以及 厂商响应映射;核心负责通用行结构、来源标签以及 媒体工具帮助格式。媒体生成 provider 注册会自动根据 defaultModelmodelscapabilities 合成静态目录行。 兼容性:
  • discovery 仍可作为旧别名使用,但会发出弃用警告
  • 如果同时注册了 catalogdiscovery,OpenClaw 会使用 catalog 并发出警告
  • augmentModelCatalog 已弃用;内置 provider 应通过 registerModelCatalogProvider 发布补充行。

只读渠道检查

如果你的插件注册了一个渠道,优先在 resolveAccount(...) 旁边实现 plugin.config.inspectAccount(cfg, accountId) 原因:
  • resolveAccount(...) 是运行时路径。它可以假设凭据已经完全实例化,并且在所需密钥缺失时快速失败。
  • 诸如 openclaw statusopenclaw status --allopenclaw channels statusopenclaw channels resolve 以及 doctor/config 修复流程等只读命令路径,不应仅为了描述配置而去实例化运行时凭据。
推荐的 inspectAccount(...) 行为:
  • 只返回具描述性的账户状态。
  • 保留 enabledconfigured
  • 在相关时包含凭据来源/状态字段,例如:
    • tokenSourcetokenStatus
    • botTokenSourcebotTokenStatus
    • appTokenSourceappTokenStatus
    • signingSecretSourcesigningSecretStatus
  • 仅为了报告只读可用性,不需要返回原始 token 值。返回 tokenStatus: "available"(以及匹配的 source 字段)对状态类命令已经足够。
  • 当凭据通过 SecretRef 配置,但在当前命令路径中不可用时,使用 configured_unavailable
这使得只读命令可以报告“已配置但在此命令路径中不可用”,而不是崩溃或把账户误报为未配置。

包集合

插件目录可以包含带有 openclaw.extensionspackage.json 文件:
每个条目都会成为一个插件。如果一个包列出了多个扩展,插件 id 会变为 <manifestOrPackageName>/<fileBase>(如果存在,则优先使用 manifest id;否则使用未带作用域的 package.json 名称)。 如果你的插件导入了 npm 依赖,请将它们安装在该目录中,以便 node_modules 可用(npm installpnpm install)。 安全防护:在符号链接解析之后,每个 openclaw.extensions 条目仍必须保留在插件目录内。逃逸出包目录的条目将被拒绝。 安全提示:openclaw.plugins install 使用项目本地的 npm install --omit=dev --ignore-scripts 来安装插件依赖(运行时不执行生命周期脚本,不包含 dev 依赖),并忽略继承而来的全局 npm 安装设置。请保持插件依赖树为“纯 JS/TS”,并避免使用需要 postinstall 构建的包。 可选项:openclaw.setupEntry 可以指向一个轻量级的仅用于设置的模块。当 OpenClaw 需要为被禁用的频道插件显示设置界面,或者当频道插件已启用但尚未配置时,它会加载 setupEntry,而不是完整的插件入口。这使启动和设置更轻量,同时仍允许主插件入口连接工具、钩子或其他仅运行时需要的代码。 捆绑的频道还可以发布仅用于设置的契约表面辅助函数,核心可以在加载完整频道运行时之前查询这些辅助函数。当前的设置提升表面包括:
  • singleAccountKeysToMove
  • namedAccountPromotionKeys
  • resolveSingleAccountPromotionTarget(...)
当核心需要将旧的单账户频道配置提升到 channels.<id>.accounts.* 时,会使用此表面,而无需加载完整的插件入口。Matrix 是当前的内置示例:当已存在命名账户时,它只会将 auth/bootstrap 键移动到命名的提升账户中,并且可以保留已配置的非默认账户键,而不是总是创建 accounts.default 这些设置补丁适配器保留了对内置契约表面功能的惰性发现。请保持导入轻量;提升表面只会在首次使用时加载,而不会在模块导入期间重新进入内置频道启动流程。 当设置表面包含网关 RPC 方法时,请将其置于插件专用的前缀下。核心管理命名空间(config.*exec.approvals.*wizard.*update.*)仍然保留,并始终解析为 operator.admin,即使插件请求了更窄的作用域。

频道目录元数据

频道插件可以通过 openclaw.channel 声明设置/发现元数据,并通过 openclaw.install 提供安装指导。这使核心目录不再承载数据。 示例:
除了最小示例之外,openclaw.channel 还有几个有用的字段:
  • detailLabel:用于更丰富的目录/状态界面的次要标签
  • docsLabel:覆盖文档链接的文本
  • preferOver:此目录条目应优先于的低优先级插件/频道 id
  • selectionDocsPrefixselectionDocsOmitLabelselectionExtras:选择界面的文案控制项
  • markdownCapable:将频道标记为支持 Markdown,以便进行出站格式化决策
  • exposure.configured:设为 false 时,将频道从已配置频道列表界面中隐藏
  • exposure.setup:设为 false 时,将频道从交互式设置/配置选择器中隐藏
  • exposure.docs:将频道标记为内部/私有频道,用于文档导航界面
  • quickstartAllowFrom:将频道加入标准快速入门 allowFrom 流程
  • forceAccountBinding:即使只有一个账户,也要求显式账户绑定
  • preferSessionLookupForAnnounceTarget:解析公告目标时优先使用会话查找
OpenClaw 还可以合并外部频道目录(例如 MPM 注册表导出)。把 JSON 文件放在以下任一位置:
  • ~/.openclaw/mpm/plugins.json
  • ~/.openclaw/mpm/catalog.json
  • ~/.openclaw/plugins/catalog.json
或者将 OPENCLAW_PLUGIN_CATALOG_PATHS(或 OPENCLAW_MPM_CATALOG_PATHS)指向一个或多个 JSON 文件,使用逗号、分号或 PATH 分隔。每个文件应包含 { "entries": [ { "name": "@scope/pkg", "openclaw": { "channel": {...}, "install": {...} } } ] }。解析器也接受 "packages""plugins" 作为 "entries" 键的旧别名。 生成的频道目录条目和提供者安装目录条目会在原始 openclaw.install 块旁公开规范化的安装源事实。规范化事实会识别 npm spec 是精确版本还是浮动选择器、是否存在预期的完整性元数据,以及是否也可用本地源路径。当已知目录/包身份时,如果解析出的 npm 包名与该身份不匹配,规范化事实会发出警告。它们还会在 defaultChoice 无效或指向不可用源时发出警告,并且在 npm 完整性元数据存在但没有有效 npm 源时发出警告。消费者应将 installSource 视为一个额外的可选字段,这样手工构建的条目和目录适配器就不需要自行生成它。 这使得 onboarding 和诊断能够解释源平面状态,而无需导入插件运行时。 官方外部 npm 条目应优先使用精确的 npmSpec 加上 expectedIntegrity。为了兼容性,仍然允许裸包名和 dist-tag, 但它们会暴露源平面的警告,以便目录能够在不破坏现有插件的情况下,逐步转向固定并经过完整性验证的安装。 当从本地目录路径进行 onboarding 时,它会记录一个托管插件 插件索引条目,使用 source: "path",并在可能时记录一个 相对于工作区的 sourcePath。绝对运行时加载路径仍保留在 plugins.load.paths 中;安装记录避免将本地工作站路径复制到长期配置中。 这使本地开发安装在源平面诊断中可见,同时不会增加第二个原始文件系统路径泄漏面。 持久化的 installed_plugin_index SQLite 表是安装 来源的事实来源,并且可以在不加载插件运行时模块的情况下刷新。 即使插件清单缺失或无效,其 installRecords 映射仍然是持久化的;其 plugins 载荷是可重建的清单视图。

上下文引擎插件

上下文引擎插件负责会话上下文的编排,包括摄取、组装和压缩。通过你的插件使用 api.registerContextEngine(id, factory) 注册它们,然后通过 plugins.slots.contextEngine 选择当前启用的引擎。 当你的插件需要替换或扩展默认的上下文流水线,而不仅仅是增加记忆搜索或钩子时,请使用此功能。
工厂函数 ctx 提供可选的 configagentDirworkspaceDir 值,用于构造时初始化。 主机会在调用非旧版引擎的 assemble() 之前,完成已注册的异步记忆提示准备。buildMemorySystemPromptAddition(...) 保持同步,并在 assemble() 执行期间读取该不可变的运行快照。请原样传递所提供的工具和引用上下文,以确保快照不会跨越运行边界。 当活动的处理框架具有持久化后端线程时,assemble() 可以返回 contextProjection。对于旧版的逐轮投影,请省略它。当组装后的上下文应注入后端线程一次,并在 epoch 发生变化之前重复使用时,请返回 { mode: "thread_bootstrap", epoch }。在引擎自有的压缩过程之后等引擎语义上下文发生变化时,请更改 epoch。主机可以在引导线程投影中保留工具调用元数据、输入形状和经过脱敏的工具结果,从而使新的后端线程在不复制包含原始机密的载荷的情况下,保留工具连续性。 如果你的引擎负责压缩算法,请保留 compact() 的实现,并显式委托它:

添加新能力

当插件需要当前 API 无法满足的行为时,不要通过私有方式绕过插件系统。应当补齐缺失的能力。 推荐顺序:
  1. 定义核心契约。 决定 core 应当拥有哪些共享行为: 策略、回退、配置合并、生命周期、面向通道的语义,以及 运行时辅助函数的形态。
  2. 添加带类型的插件注册/运行时表面。 扩展 OpenClawPluginApi 和/或 api.runtime,提供最小且有用的带类型 能力表面。
  3. 连接 core + 通道/功能消费者。 通道和功能插件 应当通过 core 消费新能力,而不是直接导入某个厂商实现。
  4. 注册厂商实现。 然后由厂商插件围绕该能力注册它们的后端。
  5. 添加契约覆盖。 添加测试,使所有权和注册形态在长期内保持明确。
这就是 OpenClaw 在保持明确立场的同时,又不会变成对某个提供商世界观的硬编码的方式。参见 能力食谱,其中包含具体的文件清单和完整示例。

能力检查清单

当你添加一种新能力时,实现通常应该同时涉及这些表面:
  • src/<capability>/types.ts 中的 core 契约类型
  • src/<capability>/runtime.ts 中的 core 运行器/运行时辅助函数
  • src/plugins/types.ts 中的插件 API 注册表面
  • src/plugins/registry.ts 中的插件注册表连接线
  • 当功能/通道插件需要消费它时,在 src/plugins/runtime/* 中暴露插件运行时
  • src/test-utils/plugin-registration.ts 中的捕获/测试辅助函数
  • src/plugins/contracts/registry.ts 中的所有权/契约断言
  • docs/ 中的运维/插件文档
如果其中某个表面缺失,通常意味着该能力还没有完全集成。

能力模板

最小模式:
契约测试模式(src/plugins/contracts/registry.ts 暴露诸如 providerContractPluginIds 之类的所有权查找;测试断言某个插件的 contracts.videoGenerationProviders 列表与其实际注册内容一致):
这样可以让规则保持简单:
  • core 负责能力契约 + 编排
  • 厂商插件负责厂商实现
  • 功能/通道插件消费运行时辅助函数
  • 契约测试让所有权保持明确。

相关内容