# 12.1 插件开发体系：自定义扩展的工程机制

扩展能力的工程化关键不是“能不能加功能”，而是“扩展是否可控、可审计、可回滚”。OpenClaw 当前仍以插件体系作为扩展主入口：原生插件可以引入新的工具、渠道、模型供应商或运行时组件，并通过显式启用与白名单机制收敛风险；同时系统也能发现并导入兼容的 Codex、Claude、Cursor bundle。需要特别区分的是：用户侧配置与 CLI 仍统一使用 `plugins.*` / `openclaw plugins ...`，而源码与发行产物中常会看到 `extensions/` 目录这一实现形态。本节基于最新官方文档与源码说明原生插件的核心结构、兼容 bundle 的边界，以及一组可用于验收的命令。

## 12.1.1 插件是什么：长驻进程内的 TypeScript/JavaScript 模块

与仅提供文本提示思路的技能（Skills）截然不同，**原生插件本质是运行在 Gateway 进程内的 TypeScript/JavaScript 扩展模块**。它们通过运行时加载器动态装入，可直接挂载自定义 Agent tools、注册聊天渠道、实现后台轮询服务，甚至接管特定 Hook 或 HTTP/RPC 能力。

官方文档对原生插件的定位、能力范围与加载方式给出了完整说明。其中最核心的安全与工程设计在于引入了 **`openclaw.plugin.json`** 清单文件。它声明插件 `id`、`configSchema`、可选 `uiHints` 等元数据，使系统在渲染 Control UI 表单或做首轮配置校验时，**无需先执行第三方插件代码**。同时也要注意，最新版本并不只有这一种插件格式；对兼容 bundle，OpenClaw 会识别 `.codex-plugin/plugin.json`、`.claude-plugin/plugin.json`、`.cursor-plugin/plugin.json` 等布局，并按兼容层规则导入可承接的能力。

如果插件通过 npm 包、压缩包等形式分发，当前版本还要求在 `package.json` 的 `openclaw.extensions` 中声明运行时入口列表。换句话说，`openclaw.plugin.json` 负责“这是什么插件、怎么配”，`package.json#openclaw.extensions` 负责“运行时代码入口在哪里”。

工程上建议把插件当作严肃的代码级系统依赖对待：

1. 插件版本进入版本控制与发布流程。
2. 插件的 `openclaw.plugin.json` 严格约束参数，不合规清单在 Gateway 启动期直接拒载。
3. 打包分发时显式维护 `package.json` 中的 `openclaw.extensions`，避免安装后找不到入口。
4. 插件的工具与副作用被工具策略与白名单彻底约束绑定。

## 12.1.2 配置与启用：entries、enabled、config 与 load.paths

插件的启用通过 `plugins.entries` 声明。这里应优先把 **manifest 中的插件 ID** 视为权威键，而不是想当然地把 npm 包名当作最终配置键。即便源码目录常命名为 `extensions/<id>/`，对用户稳定暴露的治理接口仍是 `plugins.entries.<id>`。每个条目包含启用开关与插件自定义配置；CLI 层的 `openclaw plugins enable <id>` / `disable <id>` 本质上也是切换 `plugins.entries.<id>.enabled`。官方文档还提供了 `allow`、`deny`、`load.paths` 等字段的完整写法：<https://docs.openclaw.ai/tools/plugin>。需要补充的是：workspace 来源插件默认关闭，而 bundled plugin 遵循内建 default-on 集合，不应把“所有插件默认禁用”写成绝对规则。

下面示例展示了完整骨架：白名单准入 + 本地路径加载 + 启用插件并传入配置。

```jsonc
{
  plugins: {
    enabled: true,
    allow: ['my-plugin'],           // 白名单：仅允许列出的插件加载
    deny: ['untrusted-plugin'],     // 黑名单：deny 优先于 allow
    load: {
      paths: ['~/Projects/my-plugin'], // 本地开发中的插件目录
    },
    entries: {
      'my-plugin': {
        enabled: true,
        config: {
          // 插件自定义配置
        },
      },
    },
  },
}
```

为了降低供应链风险，建议配合白名单机制只允许经过审计的插件生效。

## 12.1.3 安全边界：插件白名单与工具策略协同

插件引入的是能力，能力的使用边界仍应由确定性的策略系统兜底。官方插件文档提供了 `plugins.allow` 与 `plugins.deny` 的白名单机制；官方工具文档提供了工具的允许、拒绝与分层策略机制，并强调拒绝规则优先：

* 插件白名单：<https://docs.openclaw.ai/tools/plugin>
* 工具治理：<https://docs.openclaw.ai/tools>

实践中建议采用“双层收敛”：

1. 先用 `plugins.allow` 收敛可加载插件集合。
2. 再用工具策略把插件提供的工具按风险分组，并在默认档案中拒绝高风险组。

进而可以将”扩展可用性”与”扩展可执行性”拆开管理，避免某个插件被安装后立即具备不受控副作用。

**具体例子：Jira 插件的灰度上线流程**

假设团队开发了一个 Jira 集成插件 `jira-sync`（npm 包名 `@yourteam/jira-sync`），能让智能体查询和创建工单。安全的上线路径如下：

1. **阶段一：白名单准入** ——只在 `plugins.allow` 中添加该插件，但在 `tools.deny` 中拒绝其写入工具，先只允许查询功能：

```jsonc
{
  plugins: {
    allow: ['jira-sync'],
    entries: { 'jira-sync': { enabled: true } },
  },
  tools: {
    deny: ['jira.create*', 'jira.update*', 'jira.delete*'],
  },
}
```

2. **阶段二：小范围放开写入** ——在内部 Telegram 群中，通过 `toolsBySender` 只允许管理员触发创建工单：

```jsonc
{
  channels: {
    telegram: {
      groups: {
        '*': {
          toolsBySender: {
            'admin_id_001': { alsoAllow: ['jira.create*'] },
          },
        },
      },
    },
  },
}
```

3. **阶段三：全量放开** ——在内部验证 2 周无异常后，移除 `tools.deny` 中的 Jira 写入限制，但保留审计日志与幂等键。

## 12.1.4 Hook 架构：插件的核心运行机制

插件的真正力量不在于“注册工具”，而在于 **Hook（钩子）系统**——插件通过在执行管线的关键节点注册钩子函数来拦截、修改或观测系统行为。书中这一节讨论的是**原生 OpenClaw 插件的 Hook 运行时**；兼容 bundle 中的 hooks 只会映射到 OpenClaw 可以安全承接的那一部分，不应把 bundle 文档里的任意钩子语义直接等同为 OpenClaw 原生运行时。

**钩子分类总览**：

### 智能体生命周期钩子

| 钩子名称                   | 执行模式 | 典型用途         |
| ---------------------- | ---- | ------------ |
| `before_model_resolve` | 修改型  | 覆盖模型/供应商选择   |
| `before_prompt_build`  | 修改型  | 注入上下文与系统提示词  |
| `llm_input`            | 观测型  | 审计 LLM 精确输入  |
| `llm_output`           | 观测型  | 审计 LLM 精确输出  |
| `agent_end`            | 观测型  | 分析已完成会话      |
| `before_compaction`    | 修改型  | 干预消息压缩过程（前置） |
| `after_compaction`     | 修改型  | 干预消息压缩过程（后置） |

### 消息处理钩子

| 钩子名称                   | 执行模式 | 典型用途             |
| ---------------------- | ---- | ---------------- |
| `message_received`     | 观测型  | 消息到达后的旁路处理（即发即忘） |
| `message_sending`      | 修改型  | 修改或取消待发消息        |
| `message_sent`         | 观测型  | 消息发送后的确认         |
| `before_message_write` | 同步型  | JSONL 写入前的同步阻断   |

### 工具调用钩子

| 钩子名称                  | 执行模式 | 典型用途      |
| --------------------- | ---- | --------- |
| `before_tool_call`    | 修改型  | 拦截或修改工具参数 |
| `after_tool_call`     | 观测型  | 观测工具执行结果  |
| `tool_result_persist` | 同步型  | 链式修改持久化消息 |

### 子智能体钩子

| 钩子名称                       | 执行模式 | 典型用途   |
| -------------------------- | ---- | ------ |
| `subagent_spawning`        | 顺序型  | 配置线程绑定 |
| `subagent_delivery_target` | 顺序型  | 解析路由目标 |

### 网关生命周期钩子

| 钩子名称            | 执行模式 | 典型用途    |
| --------------- | ---- | ------- |
| `gateway_start` | 观测型  | 网关启动时触发 |
| `gateway_stop`  | 观测型  | 网关停止时触发 |

**四种执行模式**：

* **观测型**（Void Hooks）：**并行执行**，不能修改状态，用于审计与旁路处理（如 `llm_input`、`message_sent`）。
* **修改型**（Modifying Hooks）：**按优先级顺序执行**（高优先级先执行），每个钩子的修改结果流入下一个钩子，最终合并应用（如 `before_tool_call`、`message_sending`）。
* **顺序型**：与修改型类似按优先级链式执行，但专用于子智能体路由场景——如 `subagent_spawning`（配置线程绑定）和 `subagent_delivery_target`（解析路由目标），每个钩子可以补充或覆盖路由决策。
* **同步型**：在热路径上同步执行，系统会检测并拒绝意外的异步处理器。包括 `tool_result_persist`（链式修改持久化消息）和 `before_message_write`（JSONL 写入前的同步阻断）。

**全局 Hook Runner**：Gateway 启动时创建单例 Hook Runner，所有执行路径通过它统一调度。单个插件的钩子崩溃会被捕获并记录日志，不会中断系统运行。

**插件注册钩子的方式**：

```typescript
// 推荐：类型化 API
api.on("before_tool_call", (event, ctx) => {
  if (event.toolName === "dangerous_tool") {
    return { block: true, blockReason: "该工具在此会话中被禁止" };
  }
  return { params: { ...event.params, audited: true } };
}, { priority: 10 });

// 遗留：无类型 API
api.registerHook("hook_name", handler, { entry, register: true });
```

**结果合并策略**因钩子类型而异：`before_model_resolve` 采用首个非空结果获胜；`before_prompt_build` 拼接上下文与系统提示词；`before_tool_call` 合并参数与阻断状态；`message_sending` 首个取消操作获胜。

**渠道插件**是一种特殊插件类型，通过 `api.registerChannel()` 注册，可提供 15+ 种适配器接口（配置、网关、出站、消息处理等）。渠道插件深度集成子智能体路由——通过 `subagent_spawning` 钩子配置每会话的线程绑定，通过 `subagent_delivery_target` 钩子解析消息路由目标。

**插件加载的完整生命周期**：

1. **发现**：从 bundled、global、workspace、config 等来源扫描候选插件。
2. **校验**：读取 `openclaw.plugin.json` 清单，验证配置是否符合 JSON Schema。
3. **入口解析**：按插件目录或 `package.json#openclaw.extensions` 解析运行时入口，并执行边界检查。
4. **注册**：同步调用插件的 `register(api)` 函数，插件通过 API 注册工具、钩子、渠道、服务、HTTP 路由等。
5. **激活**：所有插件注册完成后，构建 `PluginRegistry`，初始化全局 Hook Runner。
6. **运行**：在每个钩子点，系统通过 Hook Runner 调度已注册的处理器。

对应的生命周期可以简化为下面这张图：

{% @mermaid/diagram content="flowchart LR
D\["发现候选插件"] --> V\["读取 Manifest<br/>Schema 校验"]
V --> L\["jiti 加载入口"]
L --> R\["register(api) 注册工具/钩子/渠道"]
R --> P\["构建 PluginRegistry"]
P --> H\["初始化 Hook Runner"]
H --> X\["运行期调度"]" %}

图 12-1：插件从发现到运行期调度的生命周期

## 12.1.5 插件市场与 Bundle 兼容

除了手动在 `load.paths` 中指定本地路径外，OpenClaw 还支持从\*\*插件市场（Marketplace）\*\*一键安装，也支持直接安装本地路径、压缩包与 npm 规格。对原生插件来说，最核心的识别文件仍是 `openclaw.plugin.json`；对兼容 bundle 来说，OpenClaw 会额外识别 `.codex-plugin/plugin.json`、`.claude-plugin/plugin.json`、`.cursor-plugin/plugin.json` 等入口。

当前官方 CLI 支持多种市场来源：Claude 已知 marketplace 名称、本地 marketplace 根目录或 `marketplace.json`、GitHub 仓库简写，以及 git URL。写书时不应把某一种来源写成唯一官方形态。

安装方式：

```bash
# 从市场安装指定插件
openclaw plugins install <插件名>@<市场名>

# 列出市场中所有可用插件
openclaw plugins marketplace list <市场名>

# 检查已安装插件的更新
openclaw plugins update
```

此外，OpenClaw 可以发现并安装兼容的 **Codex、Claude 和 Cursor bundle**。系统会尝试把 bundle 中的技能、命令、settings 以及部分 hooks/agents 信息映射到 OpenClaw 可承接的能力面，但这种映射是“尽力兼容”而不是“逐字段等价”。根据当前官方插件 CLI 文档，已经明确说明会在 `inspect` / `doctor` 输出里展示被识别出的 bundle 能力，其中并非所有能力都会自动接入运行时执行。

> \[!NOTE] Bundle 兼容性是选择性的：技能文件（如 `SKILL.md`）通常最容易映射；但涉及特定框架 API、运行时命令模型或平台专属 hooks 的部分，可能只会被部分加载，甚至被安全策略直接跳过。建议安装后通过 `openclaw plugins doctor` 与 `openclaw plugins list` 验证映射结果，确认哪些能力已成功加载、哪些被跳过。

## 12.1.6 验收与排障：用插件诊断与状态深查做成完整流程

插件相关问题不应靠“对话试出来”。官方 CLI 提供了插件列表与诊断命令，可用于上线前验收与线上排障：<https://docs.openclaw.ai/cli/plugins>。

建议把以下命令固定为插件变更后的最小验收集：

```bash
openclaw plugins list
openclaw plugins inspect my-plugin
openclaw plugins status
openclaw plugins doctor
openclaw status --deep
```

当插件加载失败时，优先看 `plugins doctor` 与 `status --deep` 输出；当插件加载成功但行为异常时，再结合结构化日志按 `traceId` 回放单次链路。

```bash
openclaw logs --follow --json
```
