> ## Documentation Index
> Fetch the complete documentation index at: https://docs2.openclaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 插件架构内部机制

对于公开的能力模型、插件形态和所有权/执行契约，请参阅 [插件架构](/zh-CN/plugins/architecture)。本页介绍内部机制：加载流水线、注册表、运行时钩子、Gateway 网关 HTTP 路由、导入路径和 schema 表。

## 加载流水线

启动时，OpenClaw 大致会执行以下步骤：

1. 发现候选插件根目录
2. 读取原生或兼容 bundle 清单和包元数据
3. 拒绝不安全的候选项
4. 规范化插件配置（`plugins.enabled`、`allow`、`deny`、`entries`、
   `slots`、`load.paths`）
5. 决定每个候选项是否启用
6. 加载已启用的原生模块：构建好的内置模块使用原生加载器；
   第三方本地源码 TypeScript 使用应急 Jiti fallback
7. 调用原生 `register(api)` 钩子，并将注册项收集到插件注册表中
8. 将注册表暴露给命令/运行时表面

<Note>
  `activate` 是 `register` 的旧版别名——加载器会解析存在的那个（`def.register ?? def.activate`），并在同一时机调用它。所有内置插件都使用 `register`；新插件请优先使用 `register`。
</Note>

安全门禁会在运行时执行**之前**运行。发现流程会在以下情况下阻止候选项：

* 其解析后的入口逃逸出插件根目录
* 其路径（或其根目录）是全局可写的
* 对于非内置插件，路径所有权与当前 uid（或 root）不匹配

全局可写的内置目录会先尝试原地 `chmod` 修复（npm/全局安装可能会以 `0777` 发布包目录），然后门禁会重新检查；内置来源会完全跳过所有权检查。

被阻止的候选项在已知插件 id 时仍会在发出的诊断中携带该 id（包括从某个本应被拒绝的目录内清单解析出的 id），因此引用该 id 的配置会看到一个与路径安全警告绑定的被阻止插件，而不是无关的“未知插件”错误。

### 清单优先行为

清单是控制平面的事实来源。OpenClaw 用它来：

* 识别插件
* 发现声明的渠道/Skills/配置 schema 或 bundle 能力
* 验证 `plugins.entries.<id>.config`
* 补充 Control UI 标签/占位符
* 显示安装/目录元数据
* 在不加载插件运行时的情况下保留低成本激活和设置描述符

对于原生插件，运行时模块是数据平面部分。它会注册实际行为，例如钩子、工具、命令或提供商流程。

可选清单 `activation` 和 `setup` 块保留在控制平面中。它们是用于激活规划和设置发现的纯元数据描述符；它们不会替代运行时注册、`register(...)` 或 `setupEntry`。实时激活消费者会使用清单命令、渠道和提供商提示，在更广泛的注册表物化之前缩小插件加载范围：

* CLI 加载会缩小到拥有所请求主命令的插件
* 渠道设置/插件解析会缩小到拥有所请求渠道 id 的插件
* 显式提供商设置/运行时解析会缩小到拥有所请求提供商 id 的插件
* Gateway 网关启动规划会使用 `activation.onStartup` 进行显式启动导入；没有启动元数据的插件只会通过更窄的激活触发器加载

激活规划器同时为现有调用方暴露仅 ids 的 API，并为诊断暴露计划 API。计划条目会报告插件被选中的原因，并将显式 `activation.*` 提示与清单所有权 fallback 区分开来：

| 原因（来自 `activation.*` 提示）        | 原因（来自清单所有权）                                                                                  |
| ------------------------------- | -------------------------------------------------------------------------------------------- |
| `activation-agent-harness-hint` | —                                                                                            |
| `activation-capability-hint`    | —                                                                                            |
| `activation-channel-hint`       | `manifest-channel-owner` (`channels`)                                                        |
| `activation-command-hint`       | `manifest-command-alias` (`commandAliases`)                                                  |
| `activation-provider-hint`      | `manifest-provider-owner` (`providers`), `manifest-setup-provider-owner` (`setup.providers`) |
| `activation-route-hint`         | —                                                                                            |
| —（钩子触发器没有提示变体）                  | `manifest-hook-owner` (`hooks`), `manifest-tool-contract` (`contracts.tools`)                |

该原因拆分是兼容性边界：现有插件元数据会继续工作，同时新代码可以检测宽泛提示或 fallback 行为，而无需改变运行时加载语义。

请求时运行时预加载如果请求宽泛的 `all` 作用域，仍会从配置、启动规划、已配置渠道、slots 和自动启用规则中派生出显式的有效插件 id 集合（`src/plugins/effective-plugin-ids.ts` 中的 `resolveEffectivePluginIds`）。如果派生出的集合为空，OpenClaw 会保持作用域为空，而不是扩大到每个可发现插件。

设置发现会优先使用描述符拥有的 id（如 `setup.providers` 和 `setup.cliBackends`）来缩小候选插件范围，然后再 fallback 到仍需要设置时运行时钩子的插件的 `setup-api`。提供商设置列表会使用清单 `providerAuthChoices`、从描述符派生的设置选项和安装目录元数据，而不会加载提供商运行时。显式 `setup.requiresRuntime: false` 是仅描述符的截止点；省略 `requiresRuntime` 会保留旧版 setup-api fallback 以维持兼容性。如果多个已发现插件声明了同一个规范化后的设置提供商或 CLI 后端 id，设置查找会拒绝这个有歧义的所有者，而不是依赖发现顺序。当设置运行时确实执行时，注册表诊断会报告 `setup.providers` / `setup.cliBackends` 与 setup-api 实际注册的提供商或 CLI 后端之间的漂移，但不会阻止旧版插件。

### 插件缓存边界

OpenClaw 不会把插件发现结果或直接清单注册表数据缓存在基于挂钟时间的窗口后面。安装、清单编辑和加载路径变更必须在下一次显式元数据读取或快照重建时可见。清单文件解析器会保留一个有界的文件签名缓存，该缓存以已打开的清单路径以及 device/inode、size 和 mtime/ctime 为键；该缓存只用于避免重新解析未变更的字节，并且不得缓存发现、注册表、所有者或策略答案。

安全的元数据快速路径是显式对象所有权，而不是隐藏缓存。Gateway 网关启动热路径应沿调用链传递当前的 `PluginMetadataSnapshot`、派生的 `PluginLookUpTable` 或显式清单注册表。只要这些对象代表当前配置和插件清单，配置验证、启动自动启用、插件引导和提供商选择都可以复用它们。设置查找仍会按需重建清单元数据，除非具体设置路径收到显式清单注册表；应将其保留为冷路径 fallback，而不是添加隐藏查找缓存。当输入变更时，重建并替换快照，而不是修改它或保留历史副本。应从当前注册表/根目录重新计算活动插件注册表上的视图和内置渠道引导辅助项。短生命周期映射可以在一次调用内用于去重工作或防止重入；它们不得成为进程元数据缓存。

对于插件加载，持久缓存层是运行时加载。它可以在代码或已安装产物实际加载时复用加载器状态，例如：

* `PluginLoaderCacheState` 和兼容的活动运行时注册表
* jiti/模块缓存，以及用于避免重复导入同一运行时表面的公共表面加载器缓存
* 已安装插件产物的文件系统缓存
* 用于路径规范化或重复项解析的短生命周期单次调用映射

这些缓存是数据平面实现细节。它们不得回答控制平面问题，例如“哪个插件拥有此提供商？”，除非调用方明确请求了运行时加载。

不要为以下内容添加持久缓存或挂钟时间缓存：

* 发现结果
* 直接清单注册表
* 从已安装插件索引重建的清单注册表
* 提供商所有者查找、模型抑制、提供商策略或公共产物元数据
* 任何其他从清单派生的答案，其中变更后的清单、已安装索引或加载路径应在下一次元数据读取时可见

从持久化已安装插件索引重建清单元数据的调用方会按需重建该注册表。已安装索引是持久来源平面状态；它不是隐藏的进程内元数据缓存。

## 注册表模型

已加载插件不会直接修改随机核心全局状态。它们会注册到中央插件注册表（`src/plugins/registry-types.ts` 中的 `PluginRegistry`）中，该注册表跟踪插件记录（身份、来源、origin、状态、诊断），以及每种能力的数组：工具、旧版钩子和类型化钩子、渠道、提供商、Gateway 网关 RPC 处理器、HTTP 路由、CLI 注册器、后台服务、插件拥有的命令，以及数十种更多类型化提供商家族（语音、嵌入、图像/视频/音乐生成、Web 获取/搜索、Agent harnesses、会话操作等）。

随后核心功能会读取该注册表，而不是直接与插件模块通信。这让加载保持单向：

* 插件模块 -> 注册表注册
* 核心运行时 -> 注册表消费

这种分离对可维护性很重要。它意味着大多数核心表面只需要一个集成点：“读取注册表”，而不是“为每个插件模块做特殊处理”。

## 会话绑定回调

绑定会话的插件可以在审批得到解析时作出反应。

使用 `api.onConversationBindingResolved(...)` 在绑定请求被批准或拒绝后接收回调：

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
export default {
  id: "my-plugin",
  register(api) {
    api.onConversationBindingResolved(async (event) => {
      if (event.status === "approved") {
        // A binding now exists for this plugin + conversation.
        console.log(event.binding?.conversationId);
        return;
      }

      // The request was denied; clear any local pending state.
      console.log(event.request.conversation.conversationId);
    });
  },
};
```

回调载荷字段：

* `status`：`"approved"` 或 `"denied"`
* `decision`：`"allow-once"`、`"allow-always"` 或 `"deny"`
* `binding`：已批准请求的已解析绑定
* `request`：原始请求摘要、分离提示、发送者 id 和会话元数据

此回调仅用于通知。它不会改变谁被允许绑定会话，并且会在核心审批处理完成后运行。

## 提供商运行时钩子

提供商插件有三层：

* **清单元数据**，用于低成本的运行时前查找：
  `setup.providers[].envVars`、已弃用的兼容性 `providerAuthEnvVars`、
  `providerAuthAliases`、`providerAuthChoices` 和 `channelEnvVars`。
* **配置时钩子**：`catalog`（旧版 `discovery`）加
  `applyConfigDefaults`。
* **运行时钩子**：40 多个可选钩子，覆盖凭证、模型解析、
  流包装、思考级别、重放策略和用量端点。请参阅
  [钩子顺序和用法](#hook-order-and-usage)。

OpenClaw 仍然拥有通用 Agent loop、故障转移、转录处理和工具策略。这些钩子是用于提供商特定行为的扩展表面，不需要完整的自定义推理传输。

使用清单 `setup.providers[].envVars`，当提供商具有基于环境变量的凭证，并且通用 auth/status/model-picker 路径应在不加载插件运行时的情况下看到这些凭证时使用。已弃用的 `providerAuthEnvVars` 在弃用窗口期内仍由兼容性适配器读取，使用它的非内置插件会收到一条清单诊断。使用清单 `providerAuthAliases`，当一个提供商 id 应复用另一个提供商 id 的环境变量、认证配置文件、基于配置的认证以及 API key 新手引导选择时使用。使用清单 `providerAuthChoices`，当 onboarding/auth-choice CLI 表面应在不加载提供商运行时的情况下知道该提供商的选择 id、分组标签以及简单的单标志认证接线时使用。将提供商运行时的 `envVars` 保留给面向操作员的提示，例如新手引导标签或 OAuth client-id/client-secret 设置变量。

使用清单 `channelEnvVars`，当渠道具有由环境变量驱动的认证或设置，并且通用 shell 环境回退、配置/状态检查或设置提示应在不加载渠道运行时的情况下看到它时使用。

### 钩子顺序和用法

对于模型/提供商插件，OpenClaw 大致按以下顺序调用钩子。
“When to use” 列是快速决策指南。
OpenClaw 不再调用的仅兼容性提供商字段，例如 `ProviderPlugin.capabilities` 和 `suppressBuiltInModel`，这里有意未列出。

| 钩子                                | 作用                                                            | 使用场景                                                            |
| --------------------------------- | ------------------------------------------------------------- | --------------------------------------------------------------- |
| `catalog`                         | 在生成 `models.json` 期间，将提供商配置发布到 `models.providers`             | 提供商拥有目录或基础 URL 默认值                                              |
| `applyConfigDefaults`             | 在配置物化期间应用提供商拥有的全局配置默认值                                        | 默认值取决于凭证模式、环境变量或提供商模型系列语义                                       |
| *(内置模型查找)*                        | OpenClaw 会先尝试普通的注册表/目录路径                                      | *(不是插件钩子)*                                                      |
| `normalizeModelId`                | 在查找前规范化旧版或预览版模型 ID 别名                                         | 提供商在规范模型解析前负责别名清理                                               |
| `normalizeTransport`              | 在通用模型组装前规范化提供商系列的 `api` / `baseUrl`                           | 提供商负责同一传输系列中自定义提供商 ID 的传输清理                                     |
| `normalizeConfig`                 | 在运行时/提供商解析前规范化 `models.providers.<id>`                        | 提供商需要由插件负责的配置清理；内置 Google 系列辅助工具也会兜底支持的 Google 配置项              |
| `applyNativeStreamingUsageCompat` | 对配置提供商应用原生流式用量兼容性重写                                           | 提供商需要由端点驱动的原生流式用量元数据修复                                          |
| `resolveConfigApiKey`             | 在运行时凭证加载前解析配置提供商的环境变量标记凭证                                     | 提供商暴露自己的环境变量标记 API key 解析钩子                                     |
| `resolveSyntheticAuth`            | 暴露本地/自托管或配置支持的凭证，同时不持久化明文                                     | 提供商可以使用合成/本地凭证标记运行                                              |
| `resolveExternalAuthProfiles`     | 叠加提供商拥有的外部凭证配置文件；CLI/应用拥有的凭据默认 `persistence` 为 `runtime-only` | 提供商复用外部凭证，而不持久化复制的刷新令牌；在清单中声明 `contracts.externalAuthProviders` |
| `shouldDeferSyntheticProfileAuth` | 降低存储的合成配置文件占位符优先级，使其排在环境变量/配置支持的凭证之后                          | 提供商存储的合成占位符配置文件不应赢得优先级                                          |
| `resolveDynamicModel`             | 为本地注册表中尚不存在的提供商拥有模型 ID 提供同步回退                                 | 提供商接受任意上游模型 ID                                                  |
| `prepareDynamicModel`             | 异步预热，然后再次运行 `resolveDynamicModel`                             | 提供商在解析未知 ID 前需要网络元数据                                            |
| `normalizeResolvedModel`          | 在嵌入式运行器使用已解析模型前进行最终重写                                         | 提供商需要传输重写，但仍使用核心传输                                              |
| `normalizeToolSchemas`            | 在嵌入式运行器看到工具 schema 前规范化它们                                     | 提供商需要传输系列 schema 清理                                             |
| `inspectToolSchemas`              | 在规范化后暴露提供商拥有的 schema 诊断                                       | 提供商希望提供关键字警告，而不让核心学习提供商特定规则                                     |
| `resolveReasoningOutputMode`      | 选择原生或带标签的推理输出契约                                               | 提供商需要带标签的推理/最终输出，而不是原生字段                                        |
| `prepareExtraParams`              | 在通用流选项包装器前进行请求参数规范化                                           | 提供商需要默认请求参数或按提供商清理参数                                            |
| `createStreamFn`                  | 用自定义传输完全替换普通流路径                                               | 提供商需要自定义线协议，而不只是包装器                                             |
| `wrapStreamFn`                    | 在应用通用包装器后进行流包装                                                | 提供商需要请求头/正文/模型兼容性包装器，而不需要自定义传输                                  |
| `resolveTransportTurnState`       | 附加原生逐轮传输头或元数据                                                 | 提供商希望通用传输发送提供商原生的轮次身份                                           |
| `resolveWebSocketSessionPolicy`   | 附加原生 WebSocket 头或会话冷却策略                                       | 提供商希望通用 WS 传输调整会话头或回退策略                                         |
| `formatApiKey`                    | 凭证配置文件格式化器：存储的配置文件会变成运行时 `apiKey` 字符串                         | 提供商存储额外凭证元数据，并需要自定义运行时令牌形态                                      |
| `refreshOAuth`                    | 针对自定义刷新端点或刷新失败策略的 OAuth 刷新覆盖                                  | 提供商不适合共享的 OpenClaw 刷新器                                          |
| `buildAuthDoctorHint`             | OAuth 刷新失败时附加的修复提示                                            | 提供商需要在刷新失败后提供由提供商拥有的凭证修复指引                                      |
| `matchesContextOverflowError`     | 提供商拥有的上下文窗口溢出匹配器                                              | 提供商存在通用启发式会漏掉的原始溢出错误                                            |
| `classifyFailoverReason`          | 提供商拥有的故障转移原因分类                                                | 提供商可以将原始 API/传输错误映射为速率限制/过载等                                    |
| `isCacheTtlEligible`              | 代理/回传提供商的提示缓存策略                                               | 提供商需要代理特定的缓存 TTL 门控                                             |
| `buildMissingAuthMessage`         | 替换通用缺失凭证恢复消息                                                  | 提供商需要提供商特定的缺失凭证恢复提示                                             |
| `augmentModelCatalog`             | 在设备发现后追加的合成/最终目录行（已弃用，见下文）                                    | 提供商需要在 `models list` 和选择器中提供合成的前向兼容行                            |
| `resolveThinkingProfile`          | 模型特定的 `/think` 级别集、显示标签和默认值                                   | 提供商为选定模型暴露自定义 thinking 阶梯或二元标签                                  |
| `isBinaryThinking`                | 开/关推理切换兼容性钩子                                                  | 提供商只暴露二元 thinking 开/关                                           |
| `supportsXHighThinking`           | `xhigh` 推理支持兼容性钩子                                             | 提供商只希望部分模型启用 `xhigh`                                            |
| `resolveDefaultThinkingLevel`     | 默认 `/think` 级别兼容性钩子                                           | 提供商拥有模型系列的默认 `/think` 策略                                        |
| `isModernModelRef`                | 用于实时配置文件过滤和 smoke 选择的现代模型匹配器                                  | 提供商拥有实时/smoke 首选模型匹配                                            |
| `prepareRuntimeAuth`              | 在推理前将已配置凭据交换为实际运行时令牌/key                                      | 提供商需要令牌交换或短期请求凭据                                                |
| `resolveUsageAuth`                | 为 `/usage` 和相关状态界面解析用量/计费凭据                                   | 提供商需要自定义用量/配额令牌解析，或使用不同的用量凭据                                    |
| `fetchUsageSnapshot`              | 凭证解析后获取并规范化提供商特定的用量/配额快照                                      | 提供商需要提供商特定的用量端点或载荷解析器                                           |
| `createEmbeddingProvider`         | 为记忆/搜索构建由提供商拥有的嵌入适配器                                          | 记忆嵌入行为归属提供商插件                                                   |
| `buildReplayPolicy`               | 返回用于控制提供商转录记录处理的重放策略                                          | 提供商需要自定义转录记录策略（例如，移除思考块）                                        |
| `sanitizeReplayHistory`           | 在通用转录记录清理后重写重放历史                                              | 提供商需要超出共享压缩辅助函数范围的提供商特定重放重写                                     |
| `validateReplayTurns`             | 在嵌入式运行器之前执行最终重放轮次验证或重塑                                        | 提供商传输需要在通用清理后进行更严格的轮次验证                                         |
| `onModelSelected`                 | 运行由提供商拥有的选择后副作用                                               | 当模型变为活动状态时，提供商需要遥测或由提供商拥有的状态                                    |

`normalizeModelId`、`normalizeTransport` 和 `normalizeConfig` 会先检查匹配的提供商插件，然后继续回退到其他支持钩子的提供商插件，直到其中一个实际更改模型 ID 或传输/配置。这样可以让别名/兼容性提供商 shim 继续工作，而无需调用方知道哪个内置插件拥有该重写。如果没有提供商钩子重写受支持的 Google 系列配置项，内置 Google 配置规范化器仍会应用该兼容性清理。

如果提供商需要完全自定义的线路协议或自定义请求执行器，那属于另一类扩展。这些钩子适用于仍运行在 OpenClaw 常规推理循环上的提供商行为。

`resolveUsageAuth` 决定 OpenClaw 应该调用 `fetchUsageSnapshot`，还是针对用量/状态界面回退到通用凭据解析。当提供商有用量凭据时返回 `{ token, accountId? }`；当提供商拥有的用量认证已处理请求且必须抑制通用 API 密钥/OAuth 回退时，返回 `{ handled: true }`；当提供商未处理用量认证时，返回 `null` 或 `undefined`。

在清单 `providerUsageAuthEnvVars` 中声明组织或计费凭据。这样通用发现和密钥清理界面就能识别它们，同时不会把它们作为推理认证候选项。

### 提供商示例

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
api.registerProvider({
  id: "example-proxy",
  label: "Example Proxy",
  auth: [],
  catalog: {
    order: "simple",
    run: async (ctx) => {
      const apiKey = ctx.resolveProviderApiKey("example-proxy").apiKey;
      if (!apiKey) {
        return null;
      }
      return {
        provider: {
          baseUrl: "https://proxy.example.com/v1",
          apiKey,
          api: "openai-completions",
          models: [{ id: "auto", name: "Auto" }],
        },
      };
    },
  },
  resolveDynamicModel: (ctx) => ({
    id: ctx.modelId,
    name: ctx.modelId,
    provider: "example-proxy",
    api: "openai-completions",
    baseUrl: "https://proxy.example.com/v1",
    reasoning: false,
    input: ["text"],
    cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
    contextWindow: 128000,
    maxTokens: 8192,
  }),
  prepareRuntimeAuth: async (ctx) => {
    const exchanged = await exchangeToken(ctx.apiKey);
    return {
      apiKey: exchanged.token,
      baseUrl: exchanged.baseUrl,
      expiresAt: exchanged.expiresAt,
    };
  },
  resolveUsageAuth: async (ctx) => {
    const auth = await ctx.resolveOAuthToken();
    return auth ? { token: auth.token } : null;
  },
  fetchUsageSnapshot: async (ctx) => {
    return await fetchExampleProxyUsage(ctx.token, ctx.timeoutMs, ctx.fetchFn);
  },
});
```

### 内置示例

内置提供商插件会组合上面的钩子，以适配每个供应商的目录、认证、思考、重放和用量需求。权威钩子集合位于 `extensions/` 下的各个插件中；本页展示的是形态，而不是镜像完整列表。

<AccordionGroup>
  <Accordion title="透传目录提供商">
    OpenRouter、Kilocode、Z.AI、xAI 注册 `catalog` 加
    `resolveDynamicModel` / `prepareDynamicModel`，以便它们能在 OpenClaw 的静态目录之前呈现上游模型 ID。
  </Accordion>

  <Accordion title="OAuth 和用量端点提供商">
    GitHub Copilot、Gemini CLI、ChatGPT Codex、MiniMax、Xiaomi、z.ai 将
    `prepareRuntimeAuth` 或 `formatApiKey` 与 `resolveUsageAuth` +
    `fetchUsageSnapshot` 配对，用于拥有令牌交换和 `/usage` 集成。
  </Accordion>

  <Accordion title="重放和转录清理系列">
    共享的命名系列（`google-gemini`、`passthrough-gemini`、
    `anthropic-by-model`、`hybrid-anthropic-openai`）让提供商可以通过
    `buildReplayPolicy` 选择加入转录策略，而不是让每个插件都重新实现清理。
  </Accordion>

  <Accordion title="仅目录提供商">
    `byteplus`、`cloudflare-ai-gateway`、`huggingface`、`kimi-coding`、`nvidia`、
    `qianfan`、`synthetic`、`together`、`venice`、`vercel-ai-gateway` 和
    `volcengine` 只注册 `catalog`，并沿用共享推理循环。
  </Accordion>

  <Accordion title="Anthropic 专用流式辅助工具">
    Beta 头、`/fast` / `serviceTier` 和 `context1m` 位于 Anthropic 插件的公开
    `api.ts` / `contract-api.ts` 边界内
    （`wrapAnthropicProviderStream`、`resolveAnthropicBetas`、
    `resolveAnthropicFastMode`、`resolveAnthropicServiceTier`），而不是通用 SDK 中。
  </Accordion>
</AccordionGroup>

## 运行时辅助工具

插件可以通过 `api.runtime` 访问选定的核心辅助工具。对于 TTS：

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
const clip = await api.runtime.tts.textToSpeech({
  text: "Hello from OpenClaw",
  cfg: api.config,
});

const result = await api.runtime.tts.textToSpeechTelephony({
  text: "Hello from OpenClaw",
  cfg: api.config,
});

const voices = await api.runtime.tts.listVoices({
  provider: "elevenlabs",
  cfg: api.config,
});
```

说明：

* `textToSpeech` 返回用于文件/语音备注界面的常规核心 TTS 输出载荷。
* 使用核心 `messages.tts` 配置和提供商选择。
* 返回 PCM 音频缓冲区 + 采样率。插件必须为提供商重新采样/编码。
* `listVoices` 对每个提供商都是可选的。将其用于供应商拥有的语音选择器或设置流程。
* 语音列表可以包含更丰富的元数据，例如区域设置、性别和个性标签，供感知提供商的选择器使用。
* OpenAI 和 ElevenLabs 目前支持电话语音。Microsoft 不支持。

插件也可以通过 `api.registerSpeechProvider(...)` 注册语音提供商。

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
api.registerSpeechProvider({
  id: "acme-speech",
  label: "Acme Speech",
  isConfigured: ({ config }) => Boolean(config.messages?.tts),
  synthesize: async (req) => {
    return {
      audioBuffer: Buffer.from([]),
      outputFormat: "mp3",
      fileExtension: ".mp3",
      voiceCompatible: false,
    };
  },
});
```

说明：

* 将 TTS 策略、回退和回复投递保留在核心中。
* 将语音提供商用于供应商拥有的合成行为。
* 旧版 Microsoft `edge` 输入会规范化为 `microsoft` 提供商 ID。
* 首选所有权模型以公司为导向：随着 OpenClaw 添加这些能力契约，一个供应商插件可以拥有文本、语音、图像和未来媒体提供商。

对于图像/音频/视频理解，插件注册一个类型化的媒体理解提供商，而不是通用键/值包：

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
api.registerMediaUnderstandingProvider({
  id: "google",
  capabilities: ["image", "audio", "video"],
  describeImage: async (req) => ({ text: "..." }),
  transcribeAudio: async (req) => ({ text: "..." }),
  describeVideo: async (req) => ({ text: "..." }),
});
```

说明：

* 将编排、回退、配置和渠道接线保留在核心中。
* 将供应商行为保留在提供商插件中。
* 增量扩展应保持类型化：新的可选方法、新的可选结果字段、新的可选能力。
* 视频生成已经遵循相同模式：
  * 核心拥有能力契约和运行时辅助工具
  * 供应商插件注册 `api.registerVideoGenerationProvider(...)`
  * 功能/渠道插件使用 `api.runtime.videoGeneration.*`

对于媒体理解运行时辅助工具，插件可以调用：

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
const image = await api.runtime.mediaUnderstanding.describeImageFile({
  filePath: "/tmp/inbound-photo.jpg",
  cfg: api.config,
  agentDir: "/tmp/agent",
});

const video = await api.runtime.mediaUnderstanding.describeVideoFile({
  filePath: "/tmp/inbound-video.mp4",
  cfg: api.config,
});

const extraction = await api.runtime.mediaUnderstanding.extractStructuredWithModel({
  provider: "codex",
  model: "gpt-5.5",
  input: [
    {
      type: "image",
      buffer: receiptImageBuffer,
      fileName: "receipt.png",
      mime: "image/png",
    },
    { type: "text", text: "Use the printed fields as the source of truth." },
  ],
  instructions: "Return entities and searchable tags.",
  schemaName: "example.evidence",
  jsonSchema: {
    type: "object",
    properties: {
      entities: { type: "array", items: { type: "string" } },
      tags: { type: "array", items: { type: "string" } },
    },
  },
  cfg: api.config,
});
```

对于音频转录，插件可以使用媒体理解运行时，或较旧的 STT 别名：

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
const { text } = await api.runtime.mediaUnderstanding.transcribeAudioFile({
  filePath: "/tmp/inbound-audio.ogg",
  cfg: api.config,
  // Optional when MIME cannot be inferred reliably:
  mime: "audio/ogg",
});
```

说明：

* `api.runtime.mediaUnderstanding.*` 是图像/音频/视频理解的首选共享界面。
* `extractStructuredWithModel(...)` 是面向插件的边界，用于有界的、提供商拥有的图像优先提取。至少包含一个图像输入；文本输入是补充上下文。产品插件拥有自己的路由和 schema，而 OpenClaw 拥有提供商/运行时边界。
* 使用核心媒体理解音频配置（`tools.media.audio`）和提供商回退顺序。
* 当没有生成转录输出时返回 `{ text: undefined }`（例如跳过/不支持的输入）。
* `api.runtime.stt.transcribeAudioFile(...)` 仍作为兼容性别名保留。

插件还可以通过 `api.runtime.subagent` 启动后台子智能体运行：

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
const result = await api.runtime.subagent.run({
  sessionKey: "agent:main:subagent:search-helper",
  message: "Expand this query into focused follow-up searches.",
  provider: "openai",
  model: "gpt-4.1-mini",
  deliver: false,
});
```

说明：

* `provider` 和 `model` 是每次运行的可选覆盖项，不是持久会话更改。
* OpenClaw 只会为受信任调用方遵守这些覆盖字段。
* 对于插件拥有的回退运行，操作员必须使用 `plugins.entries.<id>.subagent.allowModelOverride: true` 选择加入。
* 使用 `plugins.entries.<id>.subagent.allowedModels` 将受信任插件限制为特定规范 `provider/model` 目标，或使用 `"*"` 显式允许任意目标。
* 不受信任的插件子智能体运行仍会工作，但覆盖请求会被拒绝，而不是静默回退。
* 插件创建的子智能体会话会用创建插件 ID 标记。回退 `api.runtime.subagent.deleteSession(...)` 只能删除这些归属会话；任意会话删除仍需要管理员范围的 Gateway 网关请求。

对于 Web 搜索，插件可以使用共享运行时辅助工具，而不是触及智能体工具接线：

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
const providers = api.runtime.webSearch.listProviders({
  config: api.config,
});

const result = await api.runtime.webSearch.search({
  config: api.config,
  args: {
    query: "OpenClaw plugin runtime helpers",
    count: 5,
  },
});
```

插件也可以通过 `api.registerWebSearchProvider(...)` 注册 Web 搜索提供商。

说明：

* 将提供商选择、凭据解析和共享请求语义保留在核心中。
* 将 Web 搜索提供商用于供应商特定的搜索传输。
* `api.runtime.webSearch.*` 是需要搜索行为但不依赖智能体工具包装器的功能/渠道插件的首选共享界面。

### `api.runtime.imageGeneration`

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
const result = await api.runtime.imageGeneration.generate({
  config: api.config,
  args: { prompt: "A friendly lobster mascot", size: "1024x1024" },
});

const providers = api.runtime.imageGeneration.listProviders({
  config: api.config,
});
```

* `generate(...)`：使用配置的图像生成提供商链生成图像。
* `listProviders(...)`：列出可用的图像生成提供商及其能力。

## Gateway 网关 HTTP 路由

插件可以通过 `api.registerHttpRoute(...)` 暴露 HTTP 端点。

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
api.registerHttpRoute({
  path: "/acme/webhook",
  auth: "plugin",
  match: "exact",
  handler: async (_req, res) => {
    res.statusCode = 200;
    res.end("ok");
    return true;
  },
});
```

路由字段：

* `path`：Gateway 网关 HTTP 服务器下的路由路径。
* `auth`：必填，`"gateway"` 或 `"plugin"`。使用 `"gateway"` 要求普通 Gateway 网关认证，或使用 `"plugin"` 进行插件管理的认证/webhook 验证。
* `match`：可选。`"exact"`（默认）或 `"prefix"`。
* `handleUpgrade`：可选，用于同一路由上的 WebSocket 升级请求的处理程序。
* `replaceExisting`：可选。允许同一个插件替换自己已有的路由注册。
* `handler`：当该路由已处理请求时返回 `true`。

说明：

* `api.registerHttpHandler(...)` 已移除，并会导致插件加载错误。请改用 `api.registerHttpRoute(...)`。
* 插件路由必须显式声明 `auth`。
* 精确的 `path + match` 冲突会被拒绝，除非设置 `replaceExisting: true`，并且一个插件不能替换另一个插件的路由。
* 不同 `auth` 级别的重叠路由会被拒绝。仅在同一认证级别上保留 `exact`/`prefix` 回退链。
* `auth: "plugin"` 路由**不会**自动接收操作员运行时权限范围。它们用于插件管理的 webhook/签名验证，而不是特权 Gateway 网关辅助调用。
* `auth: "gateway"` 路由在 Gateway 网关请求运行时权限范围内运行。默认表面（`gatewayRuntimeScopeSurface: "write-default"`）有意保持保守：
  * 共享密钥 bearer 认证（`gateway.auth.mode = "token"` / `"password"`）和任何非可信代理认证方法只会获得单一 `operator.write` 权限范围，即使调用方发送了 `x-openclaw-scopes`
  * 没有显式 `x-openclaw-scopes` 标头的 `trusted-proxy` 调用方也会保留旧版仅 `operator.write` 的表面
  * 确实发送 `x-openclaw-scopes` 的 `trusted-proxy` 调用方会改为获得声明的权限范围
  * 路由可以选择加入 `gatewayRuntimeScopeSurface: "trusted-operator"`，以便始终对带身份的认证模式遵循 `x-openclaw-scopes`（当标头缺失时回退到完整的 CLI 默认权限范围集合）
* 实用规则：不要假设一个 Gateway 网关认证的插件路由就是隐式管理员表面。如果你的路由需要仅管理员可用的行为，请选择加入 `trusted-operator` 权限范围表面，要求带身份的认证模式，并记录显式 `x-openclaw-scopes` 标头契约。

## 插件 SDK 导入路径

编写新插件时，请使用窄范围 SDK 子路径，而不是单体的 `openclaw/plugin-sdk` 根
barrel。核心子路径：

| 子路径                                 | 用途                                             |
| ----------------------------------- | ---------------------------------------------- |
| `openclaw/plugin-sdk/plugin-entry`  | 插件注册基础能力                                       |
| `openclaw/plugin-sdk/channel-core`  | 渠道入口/构建辅助工具                                    |
| `openclaw/plugin-sdk/core`          | 通用共享辅助工具和总括契约                                  |
| `openclaw/plugin-sdk/config-schema` | 根 `openclaw.json` Zod schema（`OpenClawSchema`） |

渠道插件会从一组窄范围接缝中选择：`channel-setup`、
`setup-runtime`、`setup-tools`、`channel-pairing`、
`channel-contract`、`channel-feedback`、`channel-inbound`、`channel-outbound`、
`command-auth`、`secret-input`、`webhook-ingress`、
`channel-targets` 和 `channel-actions`。审批行为应统一到一个
`approvalCapability` 契约上，而不是混用互不相关的插件字段。
参见 [渠道插件](/zh-CN/plugins/sdk-channel-plugins)。

运行时和配置辅助工具位于匹配的聚焦 `*-runtime` 子路径下
（`approval-runtime`、`agent-runtime`、`lazy-runtime`、`directory-runtime`、
`text-runtime`、`runtime-store`、`system-event-runtime`、`heartbeat-runtime`、
`channel-activity-runtime` 等）。优先使用 `config-contracts`、
`plugin-config-runtime`、`runtime-config-snapshot` 和 `config-mutation`，
而不是宽泛的 `config-runtime` 兼容性 barrel。

<Info>
  `openclaw/plugin-sdk/channel-runtime`、`openclaw/plugin-sdk/channel-lifecycle`、
  小型渠道辅助 facade、`openclaw/plugin-sdk/outbound-runtime`、
  `openclaw/plugin-sdk/outbound-send-deps`、`openclaw/plugin-sdk/config-runtime`
  和 `openclaw/plugin-sdk/infra-runtime` 是面向旧插件的已弃用兼容性 shim。
  新代码应改为导入更窄的通用基础能力。
</Info>

仓库内部入口点（按每个内置插件包根目录）：

* `index.js` — 内置插件入口
* `api.js` — 辅助工具/类型 barrel
* `runtime-api.js` — 仅运行时 barrel
* `setup-entry.js` — 设置插件入口

外部插件应只导入 `openclaw/plugin-sdk/*` 子路径。切勿从核心或另一个插件导入另一个插件包的 `src/*`。
通过 facade 加载的入口点会优先使用当前运行时配置快照（如果存在），然后回退到磁盘上解析出的配置文件。

能力专用子路径（如 `image-generation`、`media-understanding`
和 `speech`）存在，是因为内置插件现在使用它们。它们不会自动成为长期冻结的外部契约；依赖它们时请查看相关 SDK 参考页面。

## 消息工具 schema

插件应拥有面向非消息基础能力（如表情回应、已读和投票）的渠道专用
`describeMessageTool(...)` schema 贡献。
共享发送呈现应使用通用 `MessagePresentation` 契约，
而不是提供商原生按钮、组件、分块或卡片字段。
参见 [消息呈现](/zh-CN/plugins/message-presentation)，了解契约、
回退规则、提供商映射和插件作者检查清单。

具备发送能力的插件通过消息能力声明它们能渲染的内容：

* `presentation` 用于语义呈现块（`text`、`context`、`divider`、`buttons`、`select`）
* `delivery-pin` 用于置顶投递请求

核心决定是以原生方式渲染呈现，还是降级为文本。
不要从通用消息工具暴露提供商原生 UI 逃生口。
面向旧版原生 schema 的已弃用 SDK 辅助工具仍会为现有第三方插件导出，
但新插件不应使用它们。

## 渠道目标解析

渠道插件应拥有渠道专用目标语义。保持共享出站宿主的通用性，并使用消息适配器表面处理提供商规则：

* `messaging.inferTargetChatType({ to })` 决定在目录查找前，规范化目标应被视为 `direct`、`group` 还是 `channel`。
* `messaging.targetResolver.looksLikeId(raw, normalized)` 告诉核心输入是否应跳过目录搜索，直接进入类似 id 的解析。
* `messaging.targetResolver.reservedLiterals` 列出该提供商的裸词渠道/会话引用。解析会先保留已配置的目录条目，再拒绝保留字面量，然后在目录未命中时失败关闭。
* `messaging.targetResolver.resolveTarget(...)` 是核心在规范化后或目录未命中后需要最终由提供商拥有的解析时使用的插件回退。
* `messaging.resolveOutboundSessionRoute(...)` 在目标解析完成后拥有提供商专用的会话路由构造。

推荐拆分：

* 使用 `inferTargetChatType` 处理应在搜索对端/群组前发生的类别决策。
* 使用 `looksLikeId` 进行“将其视为显式/原生目标 id”的检查。
* 使用 `resolveTarget` 作为提供商专用规范化回退，而不是用于宽泛目录搜索。
* 将聊天 id、线程 id、JID、handle 和房间 id 等提供商原生 id 保留在 `target` 值或提供商专用参数中，而不是放入通用 SDK 字段。

## 配置支持的目录

从配置派生目录条目的插件应将该逻辑保留在插件中，并复用来自
`openclaw/plugin-sdk/directory-runtime` 的共享辅助工具。

当渠道需要配置支持的对端/群组时使用它，例如：

* 由 allowlist 驱动的私信对端
* 已配置的渠道/群组映射
* 账号作用域的静态目录回退

`directory-runtime` 中的共享辅助工具只处理通用操作：

* 查询过滤
* 限制应用
* 去重/规范化辅助工具
* 构建 `ChannelDirectoryEntry[]`

渠道专用账号检查和 id 规范化应保留在插件实现中。

## 提供商目录

提供商插件可以使用
`registerProvider({ catalog: { run(...) { ... } } })`
定义用于推理的模型目录。

`catalog.run(...)` 返回与 OpenClaw 写入
`models.providers` 相同的形状：

* `{ provider }` 用于一个提供商条目
* `{ providers }` 用于多个提供商条目

当插件拥有提供商专用模型 id、base URL 默认值或由认证保护的模型元数据时，请使用 `catalog`。

`catalog.order` 控制插件目录相对于 OpenClaw 内置隐式提供商合并的时机：

* `simple`：普通 API key 或由环境驱动的提供商
* `profile`：存在认证配置档时出现的提供商
* `paired`：合成多个相关提供商条目的提供商
* `late`：最后一轮，在其他隐式提供商之后

后出现的提供商会在键冲突时获胜，因此插件可以有意用相同的提供商 id 覆盖内置提供商条目。

插件还可以通过
`api.registerModelCatalogProvider({ provider, kinds, staticCatalog, liveCatalog
})` 发布只读模型行。这是列表/帮助/选择器表面的前进路径，并支持
`text`、`voice`、`image_generation`、`video_generation` 和 `music_generation`
行。提供商插件仍然拥有实时端点调用、令牌交换和供应商响应映射；核心拥有通用行形状、来源标签和媒体工具帮助格式。媒体生成提供商注册会从 `defaultModel`、`models` 和
`capabilities` 自动合成静态目录行。

兼容性：

* `discovery` 仍可作为旧版别名使用，但会发出弃用警告
* 如果同时注册了 `catalog` 和 `discovery`，OpenClaw 会使用 `catalog`
  并发出警告
* `augmentModelCatalog` 已弃用；内置提供商应通过 `registerModelCatalogProvider` 发布补充行

## 只读渠道检查

如果你的插件注册渠道，建议在 `resolveAccount(...)` 旁实现
`plugin.config.inspectAccount(cfg, accountId)`。

原因：

* `resolveAccount(...)` 是运行时路径。它可以假设凭据已经完全物化，并且可以在缺少必需密钥时快速失败。
* `openclaw status`、`openclaw status --all`、
  `openclaw channels status`、`openclaw channels resolve` 以及 doctor/配置
  修复流程等只读命令路径，不应仅为了描述配置就需要物化运行时凭据。

推荐的 `inspectAccount(...)` 行为：

* 只返回描述性账号状态。
* 保留 `enabled` 和 `configured`。
* 在相关时包含凭据来源/状态字段，例如：
  * `tokenSource`、`tokenStatus`
  * `botTokenSource`、`botTokenStatus`
  * `appTokenSource`、`appTokenStatus`
  * `signingSecretSource`、`signingSecretStatus`
* 你不需要为了报告只读可用性而返回原始令牌值。返回 `tokenStatus: "available"`（以及匹配的来源字段）就足以用于状态类命令。
* 当凭据通过 SecretRef 配置，但在当前命令路径中不可用时，使用 `configured_unavailable`。

这让只读命令可以报告“已配置但在此命令路径中不可用”，而不是崩溃或错误地将账号报告为未配置。

## 包集合

插件目录可以包含带有 `openclaw.extensions` 的 `package.json`：

```json theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  "name": "my-pack",
  "openclaw": {
    "extensions": ["./src/safety.ts", "./src/tools.ts"],
    "setupEntry": "./src/setup-entry.ts"
  }
}
```

每个条目都会成为一个插件。如果包列出了多个扩展，插件
id 会变为 `<manifestOrPackageName>/<fileBase>`（存在 manifest id 时它优先；否则使用不带作用域的 `package.json` 名称）。

如果你的插件导入 npm 依赖，请在该目录中安装它们，以便
`node_modules` 可用（`npm install` / `pnpm install`）。

安全护栏：每个 `openclaw.extensions` 条目在解析符号链接后都必须留在插件
目录内。逃逸出包目录的条目会被拒绝。

安全说明：`openclaw plugins install` 会使用项目本地的
`npm install --omit=dev --ignore-scripts` 安装插件依赖（无生命周期脚本，
运行时无开发依赖），并忽略继承的全局 npm 安装设置。
保持插件依赖树为“纯 JS/TS”，并避免使用需要
`postinstall` 构建的包。

可选：`openclaw.setupEntry` 可以指向一个轻量的仅设置模块。
当 OpenClaw 需要为已禁用的渠道插件提供设置界面，或者
当渠道插件已启用但仍未配置时，它会加载 `setupEntry`，
而不是完整的插件入口。当你的主插件入口还会连线工具、Hooks 或其他仅运行时
代码时，这可以让启动和设置更轻量。

可选：`openclaw.startup.deferConfiguredChannelFullLoadUntilAfterListen`
可以让渠道插件在 Gateway 网关的监听前启动阶段选择使用同一个 `setupEntry` 路径，
即使该渠道已经配置完成。

仅当 `setupEntry` 完全覆盖 Gateway 网关开始监听前必须存在的启动界面时才使用此项。
实际中，这意味着设置入口必须注册启动所依赖的每个渠道所属能力，例如：

* 渠道注册本身
* Gateway 网关开始监听前必须可用的任何 HTTP 路由
* 同一时间窗口内必须存在的任何 Gateway 网关方法、工具或服务

如果你的完整入口仍然拥有任何必需的启动能力，请不要启用
此标志。保持插件使用默认行为，并让 OpenClaw 在启动期间加载
完整入口。

内置渠道也可以发布仅设置的契约界面辅助函数，供核心在完整渠道运行时加载前
查询。当前的设置提升界面是：

* `singleAccountKeysToMove`
* `namedAccountPromotionKeys`
* `resolveSingleAccountPromotionTarget(...)`

当核心需要在不加载完整插件入口的情况下，将旧版单账号渠道
配置提升到 `channels.<id>.accounts.*` 时，会使用该界面。
Matrix 是当前的内置示例：当命名账号已存在时，它只将身份验证/引导键移入
命名提升账号，并且它可以保留已配置的非规范默认账号键，而不是总是创建
`accounts.default`。

这些设置补丁适配器让内置契约界面发现保持惰性。导入
时间保持轻量；提升界面只会在首次使用时加载，而不是
在模块导入时重新进入内置渠道启动。

当这些启动界面包含 Gateway 网关 RPC 方法时，请将它们放在
插件专属前缀下。核心管理员命名空间（`config.*`、
`exec.approvals.*`、`wizard.*`、`update.*`）保持保留，并且始终解析为
`operator.admin`，即使插件请求更窄的权限范围也是如此。

示例：

```json theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  "name": "@scope/my-channel",
  "openclaw": {
    "extensions": ["./index.ts"],
    "setupEntry": "./setup-entry.ts",
    "startup": {
      "deferConfiguredChannelFullLoadUntilAfterListen": true
    }
  }
}
```

### 渠道目录元数据

渠道插件可以通过 `openclaw.channel` 宣告设置/发现元数据，并通过
`openclaw.install` 宣告安装提示。这让核心目录保持无数据。

示例：

```json theme={"theme":{"light":"min-light","dark":"min-dark"}}
{
  "name": "@openclaw/nextcloud-talk",
  "openclaw": {
    "extensions": ["./index.ts"],
    "channel": {
      "id": "nextcloud-talk",
      "label": "Nextcloud Talk",
      "selectionLabel": "Nextcloud Talk (self-hosted)",
      "docsPath": "/channels/nextcloud-talk",
      "docsLabel": "nextcloud-talk",
      "blurb": "Self-hosted chat via Nextcloud Talk webhook bots.",
      "order": 65,
      "aliases": ["nc-talk", "nc"]
    },
    "install": {
      "npmSpec": "@openclaw/nextcloud-talk",
      "localPath": "<bundled-plugin-local-path>",
      "defaultChoice": "npm"
    }
  }
}
```

除了最小示例之外，常用的 `openclaw.channel` 字段包括：

* `detailLabel`：用于更丰富目录/状态界面的次级标签
* `docsLabel`：覆盖文档链接的链接文本
* `preferOver`：此目录条目应优先于的低优先级插件/渠道 ID
* `selectionDocsPrefix`、`selectionDocsOmitLabel`、`selectionExtras`：选择界面文案控制
* `markdownCapable`：将渠道标记为支持 markdown，用于出站格式化决策
* `exposure.configured`：设为 `false` 时，从已配置渠道列表界面中隐藏该渠道
* `exposure.setup`：设为 `false` 时，从交互式设置/配置选择器中隐藏该渠道
* `exposure.docs`：将渠道标记为内部/私有，用于文档导航界面
* `showConfigured` / `showInSetup`：仍为兼容性接受的旧版别名；优先使用 `exposure`
* `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 规格是精确版本还是浮动
选择器、是否存在预期的完整性元数据，以及是否也有本地
源路径可用。当目录/包身份已知时，如果解析出的 npm 包名偏离该身份，
规范化事实会发出警告。
当 `defaultChoice` 无效或指向不可用的源，以及存在 npm 完整性元数据但没有有效 npm
源时，它们也会发出警告。消费者应将 `installSource` 视为一个附加的可选字段，
这样手写条目和目录垫片就不必合成它。
这让新手引导和诊断可以解释源平面状态，而无需
导入插件运行时。

官方外部 npm 条目应优先使用精确的 `npmSpec` 加
`expectedIntegrity`。裸包名和 dist-tag 仍可用于
兼容性，但它们会暴露源平面警告，使目录可以在不破坏现有插件的情况下
转向固定且带完整性校验的安装。
当新手引导从本地目录路径安装时，它会记录一个托管插件
插件索引条目，其中 `source: "path"`，并在可能时使用相对于工作区的
`sourcePath`。绝对的操作加载路径仍保留在
`plugins.load.paths` 中；安装记录避免将本地工作站
路径重复写入长期配置。这让本地开发安装对
源平面诊断可见，同时不会新增第二个原始文件系统路径披露
界面。持久化的 `installed_plugin_index` SQLite 表是安装
源的事实来源，并且可以在不加载插件运行时模块的情况下刷新。
它的 `installRecords` 映射即使在插件清单缺失或
无效时也会持久保留；它的 `plugins` 负载是可重建的清单视图。

## 上下文引擎插件

上下文引擎插件拥有会话上下文的摄取、组装
和压缩编排。使用 `api.registerContextEngine(id, factory)` 从你的插件注册它们，
然后使用 `plugins.slots.contextEngine` 选择活动引擎。

当你的插件需要替换或扩展默认上下文
流水线，而不只是添加记忆搜索或 Hooks 时，请使用此项。

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
import { buildMemorySystemPromptAddition } from "openclaw/plugin-sdk/core";

export default function (api) {
  api.registerContextEngine("lossless-claw", (ctx) => ({
    info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
    async ingest() {
      return { ingested: true };
    },
    async assemble({ messages, availableTools, citationsMode }) {
      return {
        messages,
        estimatedTokens: 0,
        systemPromptAddition: buildMemorySystemPromptAddition({
          availableTools: availableTools ?? new Set(),
          citationsMode,
        }),
      };
    },
    async compact() {
      return { ok: true, compacted: false };
    },
  }));
}
```

工厂 `ctx` 会暴露可选的 `config`、`agentDir` 和 `workspaceDir`
值，用于构造时初始化。

当活动 harness 拥有持久后端线程时，`assemble()` 可以返回
`contextProjection`。对于旧版逐轮投影，请省略它。当组装后的上下文应
一次性注入后端线程并复用直到 epoch 变化时，返回
`{ mode: "thread_bootstrap", epoch }`。在引擎的语义上下文发生变化后更改
epoch，例如在一次由引擎拥有的压缩过程之后。宿主可以在线程引导投影中保留工具调用元数据、输入
形状和已脱敏工具结果，使新的
后端线程无需复制包含原始机密的
负载即可保留工具连续性。

如果你的引擎**不**拥有压缩算法，请保留 `compact()`
实现并显式委托它：

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
import {
  buildMemorySystemPromptAddition,
  delegateCompactionToRuntime,
} from "openclaw/plugin-sdk/core";

export default function (api) {
  api.registerContextEngine("my-memory-engine", (ctx) => ({
    info: {
      id: "my-memory-engine",
      name: "My Memory Engine",
      ownsCompaction: false,
    },
    async ingest() {
      return { ingested: true };
    },
    async assemble({ messages, availableTools, citationsMode }) {
      return {
        messages,
        estimatedTokens: 0,
        systemPromptAddition: buildMemorySystemPromptAddition({
          availableTools: availableTools ?? new Set(),
          citationsMode,
        }),
      };
    },
    async compact(params) {
      return await delegateCompactionToRuntime(params);
    },
  }));
}
```

## 添加新能力

当插件需要的行为不适合当前 API 时，不要通过私有越界访问
绕过插件系统。添加缺失的能力。

推荐顺序：

1. **定义核心契约。** 决定核心应拥有哪些共享行为：
   策略、fallback、配置合并、生命周期、面向渠道的语义，以及
   运行时辅助函数形状。
2. **添加类型化插件注册/运行时界面。** 使用最小有用的类型化
   能力界面扩展 `OpenClawPluginApi` 和/或 `api.runtime`。
3. **连线核心 + 渠道/功能消费者。** 渠道和功能插件
   应通过核心消费新能力，而不是直接导入供应商
   实现。
4. **注册供应商实现。** 然后供应商插件针对该能力注册它们的
   后端。
5. **添加契约覆盖。** 添加测试，使所有权和注册形状
   随时间保持明确。

这就是 OpenClaw 在保持有主见的同时，不被硬编码到某个
提供商世界观中的方式。请参阅 [能力扩展手册](/zh-CN/plugins/adding-capabilities)，
了解具体文件清单和完整示例。

### 能力清单

添加新能力时，实现通常应同时触及这些
界面：

* `src/<capability>/types.ts` 中的核心合约类型
* `src/<capability>/runtime.ts` 中的核心 runner/运行时辅助函数
* `src/plugins/types.ts` 中的插件 API 注册接口面
* `src/plugins/registry.ts` 中的插件注册表接线
* 当功能/渠道插件需要消费它时，`src/plugins/runtime/*` 中的插件运行时暴露
* `src/test-utils/plugin-registration.ts` 中的捕获/测试辅助函数
* `src/plugins/contracts/registry.ts` 中的所有权/合约断言
* `docs/` 中的操作员/插件文档

如果缺少其中某个表面，通常说明该能力尚未完全集成。

### 能力模板

最小模式：

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
// core contract
export type VideoGenerationProviderPlugin = {
  id: string;
  label: string;
  generateVideo: (req: VideoGenerationRequest) => Promise<VideoGenerationResult>;
};

// plugin API
api.registerVideoGenerationProvider({
  id: "openai",
  label: "OpenAI",
  async generateVideo(req) {
    return await generateOpenAiVideo(req);
  },
});

// shared runtime helper for feature/channel plugins
const clip = await api.runtime.videoGeneration.generate({
  prompt: "Show the robot walking through the lab.",
  cfg,
});
```

合约测试模式（`src/plugins/contracts/registry.ts` 暴露所有权查找，例如 `providerContractPluginIds`；测试会断言插件的 `contracts.videoGenerationProviders` 列表与它实际注册的内容匹配）：

```ts theme={"theme":{"light":"min-light","dark":"min-dark"}}
expect(pluginManifest.contracts?.videoGenerationProviders).toEqual(["openai"]);
```

这让规则保持简单：

* 核心拥有能力合约 + 编排
* 供应商插件拥有供应商实现
* 功能/渠道插件消费运行时辅助函数
* 合约测试让所有权保持明确

## 相关

* [插件架构](/zh-CN/plugins/architecture) — 公共能力模型和结构
* [插件 SDK 子路径](/zh-CN/plugins/sdk-subpaths)
* [插件 SDK 设置](/zh-CN/plugins/sdk-setup)
* [构建插件](/zh-CN/plugins/building-plugins)
