返回

OpenClaw 源码解析(四):扩展点——打造你的 OpenClaw

深入 OpenClaw 的扩展机制,学习如何开发 Channel Plugin、自定义工具和生命周期钩子,打造属于自己的 OpenClaw。

适用版本: OpenClaw v2026.3 阅读时间: 约 30 分钟 前置知识: 阅读前三篇文章,理解架构和消息流程 源码位置: src/plugins/, extensions/, skills/

开篇:为什么需要扩展?

前三篇文章,我们深入理解了 OpenClaw 的核心架构:

  1. Gateway——控制平面
  2. 消息流程——从接收到回复
  3. Agent——AI 的躯壳与灵魂

这些是 OpenClaw 的"骨架"。但要成为一个真正有用的助手,它需要"肌肉"——扩展

本文回答核心问题:

OpenClaw 的哪些部分可以定制?如何优雅地扩展而不修改核心代码?

扩展点概览

flowchart TB
    subgraph Core["核心系统"]
        GW[Gateway]
        SM[Session Manager]
        AR[Agent Runtime]
    end

    subgraph Extensions["扩展点"]
        CP[Channel Plugins<br/>渠道适配]
        TOOLS[Custom Tools<br/>自定义工具]
        HOOKS[Hooks<br/>生命周期钩子]
        SKILLS[Skills<br/>技能包]
    end

    subgraph External["外部集成"]
        WA[WhatsApp]
        TG[Telegram]
        API[Custom API]
        WEB[Webhook]
    end

    External --> CP
    CP --> GW
    GW --> HOOKS
    AR --> TOOLS
    HOOKS --> SKILLS

    style Extensions fill:#e8f5e9
扩展点 用途 开发难度
Channel Plugin 接入新的消息平台 中等
Custom Tool 扩展 Agent 能力 简单
Hook 介入生命周期事件 中等
Skill 封装复杂能力 中等

Channel Plugin:接入新渠道

问题:如何支持一个新的消息平台?

假设你要为 OpenClaw 添加 Signal 支持。

步骤 1:创建插件目录

~/.openclaw/plugins/signal/
├── openclaw.plugin.yaml    # 插件清单
├── index.ts                # 入口文件
├── channel.ts              # Channel 实现
└── types.ts                # 类型定义

步骤 2:编写插件清单

# ~/.openclaw/plugins/signal/openclaw.plugin.yaml
id: signal
name: Signal Channel
version: 1.0.0
description: Signal messaging channel support

# 入口文件
main: ./index.js

# 提供的能力
provides:
  - channel:signal

# 依赖
dependencies:
  - signal-cli  # 外部依赖

# 配置 Schema
config:
  type: object
  properties:
    phoneNumber:
      type: string
      description: Signal phone number
    dataDir:
      type: string
      description: Signal data directory
  required:
    - phoneNumber

步骤 3:实现 Channel 接口

// ~/.openclaw/plugins/signal/channel.ts
import {
  ChannelPlugin,
  ChannelId,
  ChannelContext,
  NormalizedMessage,
  SendMessageParams,
  MessageCallback
} from "@openclaw/channel-types";

export class SignalChannel implements ChannelPlugin {
  id: ChannelId = "signal";

  private config: SignalConfig;
  private messageCallbacks: MessageCallback[] = [];
  private signalCli: SignalCli;

  async start(context: ChannelContext): Promise<void> {
    this.config = context.config as SignalConfig;

    // 1. 初始化 signal-cli
    this.signalCli = new SignalCli({
      phoneNumber: this.config.phoneNumber,
      dataDir: this.config.dataDir,
    });

    // 2. 监听消息
    this.signalCli.on("message", async (rawMsg) => {
      const normalized = this.normalizeMessage(rawMsg);
      for (const cb of this.messageCallbacks) {
        await cb(normalized);
      }
    });

    // 3. 启动接收循环
    await this.signalCli.startReceive();
  }

  async stop(): Promise<void> {
    await this.signalCli.stop();
  }

  async sendMessage(params: SendMessageParams): Promise<void> {
    await this.signalCli.sendMessage({
      recipient: params.to,
      message: params.text,
      attachments: params.attachments?.map(a => a.path),
    });
  }

  onMessage(callback: MessageCallback): void {
    this.messageCallbacks.push(callback);
  }

  private normalizeMessage(rawMsg: SignalMessage): NormalizedMessage {
    return {
      id: rawMsg.timestamp.toString(),
      channelId: "signal",
      accountId: this.config.phoneNumber,
      senderId: rawMsg.sender,
      senderName: rawMsg.senderName,
      text: rawMsg.message,
      timestamp: rawMsg.timestamp,
      attachments: rawMsg.attachments?.map(a => ({
        type: a.contentType,
        data: a.data,
        filename: a.filename,
      })),
      isGroup: rawMsg.groupId !== undefined,
      groupId: rawMsg.groupId,
    };
  }
}

步骤 4:注册插件

// ~/.openclaw/plugins/signal/index.ts
export { SignalChannel } from "./channel";

// 插件导出
export default {
  channels: [SignalChannel],
};

步骤 5:配置启用

# ~/.openclaw/openclaw.yaml
channels:
  signal:
    enabled: true
    phoneNumber: "+1234567890"
    dataDir: ~/.signal

plugins:
  enabled:
    - signal

插件生命周期

sequenceDiagram
    autonumber
    participant Gateway as Gateway
    participant Loader as Plugin Loader
    participant Signal as Signal Channel

    Note over Gateway: 启动阶段
    Gateway->>Loader: loadPlugins()
    Loader->>Signal: new SignalChannel()
    Loader->>Signal: start(context)

    Note over Signal: 运行阶段
    Signal-->>Gateway: onMessage(msg)
    Gateway->>Signal: sendMessage(params)

    Note over Gateway: 关闭阶段
    Gateway->>Signal: stop()

Custom Tool:扩展 Agent 能力

问题:如何让 Agent 执行自定义操作?

假设你要让 Agent 能够查询公司内部的 Jira 系统。

方式一:通过配置定义工具

# ~/.openclaw/openclaw.yaml
tools:
  jira.search:
    description: Search Jira issues
    parameters:
      type: object
      properties:
        query:
          type: string
          description: JQL query string
        project:
          type: string
          description: Project key
      required:
        - query
    # 工具执行方式
    executor: http
    config:
      url: https://your-company.atlassian.net/rest/api/3/search
      method: POST
      headers:
        Authorization: Bearer ${JIRA_API_TOKEN}
        Content-Type: application/json
      body:
        jql: "${params.query}"
        project: "${params.project}"

方式二:编写 TypeScript 工具

// ~/.openclaw/tools/jira.ts
import { AgentTool, ToolContext, ToolResult } from "@openclaw/tool-types";

export const jiraSearchTool: AgentTool = {
  name: "jira.search",
  description: "Search Jira issues using JQL",
  inputSchema: {
    type: "object",
    properties: {
      query: {
        type: "string",
        description: "JQL query string, e.g. 'status=Open AND assignee=currentUser()'",
      },
      maxResults: {
        type: "number",
        description: "Maximum number of results to return",
        default: 10,
      },
    },
    required: ["query"],
  },

  handler: async (params, ctx: ToolContext): Promise<ToolResult> => {
    const { query, maxResults = 10 } = params;

    // 1. 获取配置
    const jiraConfig = await ctx.getConfig("jira");
    if (!jiraConfig) {
      return {
        content: "Jira is not configured. Please set up Jira integration first.",
        isError: true,
      };
    }

    // 2. 调用 Jira API
    const response = await fetch(`${jiraConfig.baseUrl}/rest/api/3/search`, {
      method: "POST",
      headers: {
        "Authorization": `Bearer ${jiraConfig.apiToken}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        jql: query,
        maxResults,
        fields: ["key", "summary", "status", "priority", "assignee"],
      }),
    });

    if (!response.ok) {
      return {
        content: `Jira API error: ${response.status} ${response.statusText}`,
        isError: true,
      };
    }

    // 3. 格式化结果
    const data = await response.json();
    const issues = data.issues.map((issue: any) => ({
      key: issue.key,
      summary: issue.fields.summary,
      status: issue.fields.status?.name,
      priority: issue.fields.priority?.name,
      assignee: issue.fields.assignee?.displayName || "Unassigned",
    }));

    return {
      content: JSON.stringify(issues, null, 2),
    };
  },
};

注册工具

// ~/.openclaw/tools/index.ts
import { jiraSearchTool } from "./jira";
import { jiraCreateTool } from "./jira-create";
import { jiraCommentTool } from "./jira-comment";

export const tools = [
  jiraSearchTool,
  jiraCreateTool,
  jiraCommentTool,
];

// 在 openclaw.yaml 中引用
// tools:
//   import: ~/.openclaw/tools/index.js

工具权限控制

# ~/.openclaw/openclaw.yaml
tools:
  permissions:
    # 默认策略
    default: allow

    # 禁止的工具
    deny:
      - fs.write        # 禁止写文件
      - code.execute    # 禁止执行代码

    # 需要确认的工具
    confirm:
      - sessions.delete # 删除会话需要确认
      - config.patch    # 修改配置需要确认

    # 沙箱模式下的特殊规则
    sandbox:
      default: deny
      allow:
        - fs.read
        - web.fetch

Hooks:生命周期拦截器

问题:如何在关键时刻介入系统行为?

Hook 允许你在特定事件发生时执行自定义逻辑。

可用的 Hook 点

flowchart TB
    subgraph Gateway["Gateway 生命周期"]
        H1["gateway:startup<br/>Gateway 启动"]
        H2["gateway:shutdown<br/>Gateway 关闭"]
    end

    subgraph Message["消息生命周期"]
        H3["message:received<br/>收到消息"]
        H4["message:before_send<br/>发送前"]
        H5["message:after_send<br/>发送后"]
    end

    subgraph Agent["Agent 生命周期"]
        H6["agent:before_turn<br/>Turn 开始前"]
        H7["agent:tool_call<br/>工具调用时"]
        H8["agent:after_turn<br/>Turn 结束后"]
    end

    subgraph Session["会话生命周期"]
        H9["session:created<br/>会话创建"]
        H10["session:deleted<br/>会话删除"]
    end

实现 Hook

// ~/.openclaw/hooks/content-filter.ts
import { Hook, HookContext, HookResult } from "@openclaw/hook-types";

export const contentFilterHook: Hook = {
  name: "content-filter",

  // 监听的事件
  events: ["message:received", "message:before_send"],

  // 执行优先级
  priority: 100,

  handler: async (ctx: HookContext): Promise<HookResult> => {
    const { event, data } = ctx;

    if (event === "message:received") {
      // 过滤入站消息
      const message = data.message as NormalizedMessage;

      // 检查是否包含敏感词
      const sensitiveWords = ["badword1", "badword2"];
      const hasSensitive = sensitiveWords.some(word =>
        message.text?.toLowerCase().includes(word)
      );

      if (hasSensitive) {
        // 阻止消息继续处理
        return {
          action: "reject",
          reason: "Message contains sensitive content",
        };
      }

      // 允许继续处理
      return { action: "continue" };
    }

    if (event === "message:before_send") {
      // 过滤出站消息
      const response = data.response as string;

      // 脱敏处理
      const sanitized = response.replace(/\b\d{11}\b/g, "[PHONE]");

      // 修改消息内容
      return {
        action: "modify",
        data: { ...data, response: sanitized },
      };
    }

    return { action: "continue" };
  },
};

配置 Hook

# ~/.openclaw/openclaw.yaml
hooks:
  internal:
    enabled: true
    path: ~/.openclaw/hooks

  # 内置 Hook 配置
  builtins:
    - name: rate-limiter
      enabled: true
      config:
        maxRequests: 100
        windowMs: 60000

    - name: logger
      enabled: true
      config:
        level: info
        format: json

Hook 执行顺序

sequenceDiagram
    autonumber
    participant Message as 原始消息
    participant Hook1 as Hook: content-filter
    participant Hook2 as Hook: rate-limiter
    participant Core as 核心处理

    Message->>Hook1: message:received
    Hook1->>Hook1: 检查敏感词

    alt 包含敏感词
        Hook1-->>Message: reject
    else 通过
        Hook1->>Hook2: continue
        Hook2->>Hook2: 检查频率限制

        alt 超出限制
            Hook2-->>Message: reject
        else 通过
            Hook2->>Core: continue
            Core-->>Message: 正常处理
        end
    end

Skills:封装复杂能力

问题:如何复用一套工具和提示词?

Skill 是工具、提示词、配置的组合,封装特定的能力。

Skill 目录结构

~/.openclaw/skills/translator/
├── skill.yaml              # Skill 清单
├── prompts/
│   ├── system.md           # 系统提示词
│   └── user-template.md    # 用户消息模板
├── tools/
│   ├── translate.ts        # 翻译工具
│   └── detect-language.ts  # 语言检测
└── config/
    └── defaults.yaml       # 默认配置

Skill 清单

# ~/.openclaw/skills/translator/skill.yaml
id: translator
name: Multi-language Translator
version: 1.0.0
description: Translate text between multiple languages with context awareness

# 入口提示词
prompt: ./prompts/system.md

# 提供的工具
tools:
  - ./tools/translate.ts
  - ./tools/detect-language.ts

# 配置 Schema
config:
  type: object
  properties:
    defaultSourceLang:
      type: string
      default: auto
    defaultTargetLang:
      type: string
      default: en
    preserveFormatting:
      type: boolean
      default: true

# 使用示例
examples:
  - input: "Translate this to Spanish: Hello, world!"
    output: "¡Hola, mundo!"

  - input: "What language is 'Bonjour'?"
    output: "The text 'Bonjour' is in French."

系统提示词

<!-- ~/.openclaw/skills/translator/prompts/system.md -->
# Translator Skill

You are a professional translator with expertise in multiple languages. Your capabilities include:

1. **Language Detection**: Automatically identify the source language
2. **Context-Aware Translation**: Consider context and cultural nuances
3. **Formatting Preservation**: Maintain original formatting (markdown, HTML, etc.)
4. **Glossary Support**: Use domain-specific terminology

## Workflow

1. When asked to translate:
   - Use `detect_language` to identify the source language if not specified
   - Use `translate` with appropriate context
   - Explain any cultural notes if relevant

2. When asked about a language:
   - Provide accurate linguistic information
   - Suggest related languages or dialects

## Available Tools

- `translate`: Translate text between languages
- `detect_language`: Identify the language of text

## Configuration

Default source language: {{config.defaultSourceLang}}
Default target language: {{config.defaultTargetLang}}
Preserve formatting: {{config.preserveFormatting}}

使用 Skill

# ~/.openclaw/openclaw.yaml
skills:
  enabled:
    - translator

  # Skill 配置
  translator:
    defaultTargetLang: zh  # 默认翻译成中文
    preserveFormatting: true
# 通过 CLI 激活 Skill
openclaw agent chat --skill translator

# 或在对话中引用
# "Use the translator skill to translate this..."

实战演练:开发一个完整的扩展

目标:创建一个 GitHub Issue 查询工具

1. 创建工具

// ~/.openclaw/tools/github-issue.ts
import { AgentTool, ToolContext, ToolResult } from "@openclaw/tool-types";

export const githubGetIssueTool: AgentTool = {
  name: "github.get_issue",
  description: "Get details of a GitHub issue by number",
  inputSchema: {
    type: "object",
    properties: {
      owner: {
        type: "string",
        description: "Repository owner (e.g., 'openclaw')",
      },
      repo: {
        type: "string",
        description: "Repository name (e.g., 'openclaw')",
      },
      issueNumber: {
        type: "number",
        description: "Issue number",
      },
    },
    required: ["owner", "repo", "issueNumber"],
  },

  handler: async (params, ctx: ToolContext): Promise<ToolResult> => {
    const { owner, repo, issueNumber } = params;

    // 获取 GitHub Token
    const token = process.env.GITHUB_TOKEN;
    if (!token) {
      return {
        content: "GITHUB_TOKEN not configured. Set it in your environment or config.",
        isError: true,
      };
    }

    try {
      const response = await fetch(
        `https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`,
        {
          headers: {
            "Authorization": `Bearer ${token}`,
            "Accept": "application/vnd.github+json",
          },
        }
      );

      if (!response.ok) {
        if (response.status === 404) {
          return { content: `Issue #${issueNumber} not found in ${owner}/${repo}`, isError: true };
        }
        return { content: `GitHub API error: ${response.status}`, isError: true };
      }

      const issue = await response.json();

      // 格式化输出
      const result = {
        number: issue.number,
        title: issue.title,
        state: issue.state,
        author: issue.user.login,
        created: issue.created_at,
        labels: issue.labels.map((l: any) => l.name),
        body: issue.body?.substring(0, 500) + (issue.body?.length > 500 ? "..." : ""),
        url: issue.html_url,
      };

      return { content: JSON.stringify(result, null, 2) };

    } catch (err) {
      return {
        content: `Failed to fetch issue: ${err instanceof Error ? err.message : String(err)}`,
        isError: true,
      };
    }
  },
};

2. 注册工具

# ~/.openclaw/openclaw.yaml
tools:
  import: ~/.openclaw/tools/github-issue.ts

env:
  GITHUB_TOKEN: ${GITHUB_TOKEN}

3. 测试工具

# 启动 Gateway
openclaw gateway start

# 测试工具调用
wscat -c "ws://127.0.0.1:18789" -H "Authorization: Bearer $TOKEN"
> {"kind":"request","id":"test","method":"message.send","params":{"sessionKey":"main","text":"What's the latest issue in openclaw/openclaw repo?"}}

< {"kind":"event","event":"agent","payload":{"type":"tool_call","name":"github.get_issue","args":{"owner":"openclaw","repo":"openclaw","issueNumber":1}}}
< {"kind":"event","event":"agent","payload":{"type":"tool_result","result":"{...}"}}
< {"kind":"event","event":"agent","payload":{"type":"text_delta","text":"The issue #1 in openclaw/openclaw is titled..."}}

常见陷阱

陷阱 1:插件加载失败

症状:

[ERROR] Failed to load plugin: signal
[ERROR] Error: Cannot find module '@openclaw/channel-types'

原因: 插件依赖未安装或模块路径不正确。

错误代码:

# 插件配置正确但依赖未安装
# ~/.openclaw/plugins/signal/openclaw.plugin.yaml
dependencies:
  - signal-cli  # 声明了但未安装

正确代码:

# 安装插件依赖
cd ~/.openclaw/plugins/signal
npm install @openclaw/channel-types

# 或使用 openclaw 命令自动安装
openclaw plugin install signal

# 验证插件加载
openclaw plugin list
# 输出
# signal (v1.0.0) - Signal messaging channel support [ENABLED]

调试方法:

# 查看插件加载日志
LOG_LEVEL=debug LOG_INCLUDE="plugins,loader" openclaw gateway start

# 输出示例
[DEBUG] plugins: Loading plugin from ~/.openclaw/plugins/signal
[DEBUG] plugins: Resolving dependencies: @openclaw/channel-types, signal-cli
[ERROR] plugins: Failed to resolve dependency: @openclaw/channel-types
[DEBUG] plugins: Plugin load failed: signal

源码位置: src/plugins/loader.ts


陷阱 2:Hook 执行顺序错误

症状:

[WARN] Hook 'rate-limiter' executed after 'content-filter'
[WARN] Content filter bypassed due to execution order

原因: Hook 的优先级设置不当,导致执行顺序与预期不符。

错误代码:

// rate-limiter 优先级低于 content-filter
// 但实际上应该在 content-filter 之前执行
export const rateLimiterHook: Hook = {
  name: "rate-limiter",
  events: ["message:received"],
  priority: 50,  // 较低优先级,后执行
  handler: async (ctx) => { ... },
};

export const contentFilterHook: Hook = {
  name: "content-filter",
  events: ["message:received"],
  priority: 100,  // 较高优先级,先执行
  handler: async (ctx) => { ... },
};

正确代码:

// 优先级越高,越先执行
// rate-limiter 应该最先执行(阻止恶意请求)
export const rateLimiterHook: Hook = {
  name: "rate-limiter",
  events: ["message:received"],
  priority: 1000,  // 最高优先级
  handler: async (ctx) => {
    // 先检查频率限制
    const allowed = await checkRateLimit(ctx.sessionKey);
    if (!allowed) {
      return { action: "reject", reason: "Rate limit exceeded" };
    }
    return { action: "continue" };
  },
};

// content-filter 其次执行
export const contentFilterHook: Hook = {
  name: "content-filter",
  events: ["message:received"],
  priority: 500,  // 中等优先级
  handler: async (ctx) => { ... },
};

// logger 最后执行(记录已过滤的消息)
export const loggerHook: Hook = {
  name: "logger",
  events: ["message:received"],
  priority: 100,  // 较低优先级
  handler: async (ctx) => { ... },
};

调试方法:

# 查看 Hook 执行顺序
LOG_LEVEL=debug LOG_INCLUDE="hooks" openclaw gateway start

# 输出示例
[DEBUG] hooks: Registering hooks for event: message:received
[DEBUG] hooks:   - rate-limiter (priority: 1000)
[DEBUG] hooks:   - content-filter (priority: 500)
[DEBUG] hooks:   - logger (priority: 100)
[DEBUG] hooks: Executing hooks in order...
[DEBUG] hooks: Executing rate-limiter...
[DEBUG] hooks: rate-limiter returned: continue
[DEBUG] hooks: Executing content-filter...
[DEBUG] hooks: content-filter returned: continue
[DEBUG] hooks: Executing logger...

源码位置: src/hooks/manager.ts


陷阱 3:工具注册重复

症状:

[ERROR] Tool jira.search already registered
[ERROR] Failed to register tool from ~/.openclaw/tools/custom-jira.ts

原因: 同一个工具被多次注册,可能是配置文件和代码都定义了同名工具。

错误代码:

# ~/.openclaw/openclaw.yaml
tools:
  # 通过配置定义
  jira.search:
    description: Search Jira issues
    # ...

  # 又通过 import 导入了同名工具
  import: ~/.openclaw/tools/jira.ts  # 里面也定义了 jira.search

正确代码:

# ~/.openclaw/openclaw.yaml
tools:
  # 方式一:只用配置定义(简单场景)
  jira.search:
    description: Search Jira issues
    # ...

  # 方式二:只用 import(复杂场景)
  # import: ~/.openclaw/tools/jira.ts

  # 如果两者都需要,使用不同的名称
  jira.search.v2:
    description: Search Jira issues (v2)
    # ...

调试方法:

# 查看已注册的工具
openclaw tools list

# 输出示例
# Tool Name              | Description
# -----------------------|----------------------------------
# sessions.list          | List all available sessions
# sessions.send          | Send a message to a session
# jira.search            | Search Jira issues
# fs.read                | Read file contents
# ...

# 查看工具来源
openclaw tools list --verbose

# 输出示例
# jira.search (from config: ~/.openclaw/openclaw.yaml)
# sessions.list (from builtin: src/agents/tools/builtin.ts)

源码位置: src/agents/tools/registry.ts


陷阱 4:Skill 配置不生效

症状:

[WARN] Skill 'translator' loaded but defaultTargetLang is ignored
[WARN] Agent still uses 'en' as target language

原因: Skill 配置未正确传递,或配置 Schema 与实际配置不匹配。

错误代码:

# 配置路径错误
skills:
  translator:
    targetLang: zh  # 应该是 defaultTargetLang

正确代码:

# ~/.openclaw/openclaw.yaml
skills:
  enabled:
    - translator

  # 配置必须与 skill.yaml 中的 config Schema 匹配
  translator:
    defaultSourceLang: auto
    defaultTargetLang: zh  # 正确的字段名
    preserveFormatting: true

调试方法:

# 验证 Skill 配置
openclaw skill validate translator

# 输出示例
# Validating skill: translator
# ✓ skill.yaml is valid
# ✓ prompts/system.md exists
# ✓ tools/translate.ts exists
# ✗ Config validation failed:
#   - Unknown property 'targetLang' at skills.translator
#   - Did you mean 'defaultTargetLang'?

# 查看有效配置
openclaw skill config translator

# 输出示例
# translator config:
#   defaultSourceLang: auto
#   defaultTargetLang: zh
#   preserveFormatting: true

源码位置: src/skills/loader.ts


设计启示:扩展系统的权衡

优势

设计 好处
插件隔离 核心稳定,扩展独立演进
工具 Schema LLM 能理解工具能力
Hook 模式 无侵入式扩展
Skill 封装 能力复用,降低使用门槛

劣势

问题 原因
性能开销 插件加载和通信开销
调试复杂 跨模块问题难追踪
版本兼容 插件 API 变更需要协调

最佳实践

  1. 工具设计

    • 单一职责,每个工具只做一件事
    • 输入验证,防止无效参数
    • 错误友好,返回可理解的错误信息
  2. Hook 设计

    • 快速执行,避免阻塞主流程
    • 幂等操作,支持重试
    • 优雅降级,Hook 失败不应导致系统崩溃
  3. 插件开发

    • 声明依赖,方便安装检查
    • 提供配置 Schema,支持校验
    • 编写测试,确保质量

小结

OpenClaw 的扩展机制让你可以:

扩展点 做什么 怎么做
Channel Plugin 接入新平台 实现 ChannelPlugin 接口
Custom Tool 扩展 Agent 能力 定义工具 Schema 和 Handler
Hook 介入生命周期 监听事件并返回结果
Skill 封装复杂能力 组合提示词、工具、配置

理解扩展点的关键:每个扩展点解决一个特定问题,通过标准接口与核心解耦

关键源码文件

文件 职责
src/plugins/loader.ts 插件加载器
src/plugins/registry.ts 插件注册表
src/agents/tools/registry.ts 工具注册表
src/hooks/manager.ts Hook 管理器
extensions/ 内置插件目录
skills/ 内置技能目录

在下一篇文章中,我们将进入 部署与安全——如何在生产环境中安全地运行 OpenClaw。


系列索引: OpenClaw 源码解析:目录索引

上一篇: OpenClaw 源码解析(三):Agent——AI 的躯壳与灵魂 下一篇: OpenClaw 源码解析(五):部署与安全