Clawdbot 源码解读 7: 扩展机制

前言

在上一篇中,我们梳理了 Agent 作用域、会话管理、Pi Agent 运行时与工具注册。要让 Clawdbot 支持新的消息渠道(如 LINE、Microsoft Teams)、新的模型提供商认证或新的 Agent 能力,需要依赖 插件(extensions) 与可选的 技能(skills) 。本文是《Clawdbot 源码解读》系列的第七篇,我们将深入 扩展系统与实战:插件系统架构(发现、清单、加载、配置、CLI 注册)、技能系统与「插件 vs 技能」的区别、扩展开发最佳实践,以及从需求到部署完整开发一个渠道插件的流程与常见陷阱。

学习目标

  • 理解插件发现顺序(config paths、workspace、global、bundled)与清单(clawdbot.plugin.json)的约束
  • 掌握插件加载流程:清单校验、配置校验、jiti 加载、register(api) 与各类 api.register*
  • 区分「插件」与「技能」:插件是运行时模块(渠道/工具/CLI/服务),技能是 AgentSkills 兼容的说明目录(SKILL.md
  • 能够从零实现一个渠道插件(清单、入口、ChannelPlugin 适配器、认证/收发/状态)并完成安装与配置

前置知识

  • 已阅读系列前六篇(架构、CLI、配置、Gateway、渠道与路由、Agent 与工具)
  • 对第五篇中的 ChannelPlugin、适配器(config、gateway、outbound、pairing、status)有印象

一、核心概念

1.1 插件系统架构概览

Clawdbot 的插件是 TypeScript 模块 ,在运行时由 jiti 加载(支持 .ts/.mts 等),与 Gateway 同进程运行,视为受信任代码。插件系统由以下几部分组成:

  • 发现(Discovery) :按固定顺序扫描「插件候选」路径,得到 PluginCandidate[](source、rootDir、origin、package 信息等)。不执行插件代码。
  • 清单(Manifest) :每个插件根目录必须提供 clawdbot.plugin.json ,包含 idconfigSchema (JSON Schema)、可选的 kindchannelsprovidersskillsnamedescriptionversionuiHints。配置校验 仅依赖清单与 schema,不加载插件实现。
  • 配置状态(Config state)plugins.enabledplugins.allow/plugins.denyplugins.load.pathsplugins.slots.memoryplugins.entries.<id>.enabled/config 决定哪些插件被启用以及传入的配置对象。
  • 加载器(Loader) :在发现与清单校验通过后,用 jiti 加载插件入口(如 index.ts),解析 defaultregister/activate,调用 register(api)api 提供 registerChannelregisterToolregisterGatewayMethodregisterCliregisterCommandregisterHookregisterServiceregisterProviderregisterHttpRoute 等。同一 id 多份实现时,按优先级只启用一份(后者标记为 overridden)。
  • CLI 注册 :插件可通过 api.registerCli(registrar) 向 Commander 注册子命令(如 clawdbot voice-call ...),或通过 api.registerCommand(def) 注册「自动回复命令」(不经过 LLM,直接执行 handler)。

发现顺序 (见 src/plugins/discovery.ts):

1)plugins.load.paths 中的路径(文件或目录);

2)工作区扩展 <workspace>/.clawdbot/extensions

3)全局扩展 ~/.clawdbot/extensions

4)内置目录(bundled,如仓库内 extensions/)。同一 id 先出现的优先,后出现的标记为 disabled(overridden)。

1.2 技能系统与「插件 vs 技能」的区别

  • 技能(Skill) :来自 AgentSkills 兼容的 技能目录 ,内含 SKILL.md(YAML frontmatter + 说明),用于教 Agent 如何使用工具、何时调用。技能 是运行时模块,只是被 Agent 系统读取并拼进 system prompt / 工具说明。

    • 来源:bundled (随安装包)、managed~/.clawdbot/skills)、workspace<workspace>/skills),以及配置中的 skills.load.extraDirs
    • 插件可通过清单中的 skills 数组声明「本插件自带的技能目录」(相对插件根目录)。这些目录在插件启用后由 resolvePluginSkillDirs 解析,并入技能加载路径,参与与 workspace/managed/bundled 的优先级规则。
  • 插件 vs 技能

    • 插件 :扩展运行时能力(新渠道、新工具、新 CLI、新 Gateway 方法、新服务、新提供商认证)。必须实现 register(api) 并满足清单 + configSchema。
    • 技能 :扩展 Agent 的「说明书」与工具使用策略,不提供新代码入口;插件可以 顺带 提供技能目录(如 extensions/open-prose/skills),由清单的 skills 字段声明。

1.3 扩展开发最佳实践与检查清单

  • 清单 :始终提供 clawdbot.plugin.json,且 configSchema 为合法 JSON Schema(可为空对象 { "type": "object", "additionalProperties": false })。
  • 依赖 :插件依赖放在插件自己的 package.jsondependencies;避免在根 package.json 增加仅插件需要的依赖。不要使用 workspace:*dependencies 里引用 monorepo 包(npm install 会出问题);可将 clawdbot 放在 devDependenciespeerDependencies,运行时通过 jiti alias 解析 clawdbot/plugin-sdk
  • 安装 :官方安装流程为 clawdbot plugins install <npm-spec>,会执行 npm install --omit=dev 在插件目录;因此 运行时依赖 必须在 dependencies
  • 渠道插件 :实现完整的 ChannelPlugin (meta、config/configSchema、pairing、outbound、gateway、status 等),并在 register(api)api.registerChannel({ plugin });清单中 channels 数组声明本插件注册的 channel id。
  • 配置 :用户配置写在 plugins.entries.<id>.config,与 configSchema 一致;启用/禁用由 plugins.entries.<id>.enabledplugins.allow/plugins.deny 控制。
  • 测试与发布 :为插件编写单元测试、在本地用 plugins.load.paths 指向插件目录验证,再发布 npm 或提供安装文档。

二、代码解析

2.1 发现与清单

发现src/plugins/discovery.ts):
discoverClawdbotPlugins({ workspaceDir, extraPaths }) 会:

  • extraPaths(即 plugins.load.paths)中的每一项调用 discoverFromPath(支持单文件或目录);
  • 若为目录,优先看 package.jsonclawdbot.extensions 数组,有则把这些入口当作候选;否则找目录下的 index.ts/index.js 等;若为多子目录则递归 discoverInDirectory
  • 再扫描 workspaceDir/.clawdbot/extensions~/.clawdbot/extensions、以及 resolveBundledPluginsDir() 指向的 bundled 目录。
  • 每个候选得到 PluginCandidateidHintsource(入口文件绝对路径)、rootDir(插件根目录,用于找清单)、originconfig|workspace|global|bundled)、packageName/packageVersion 等。

清单src/plugins/manifest.ts):

  • resolvePluginManifestPath(rootDir)path.join(rootDir, "clawdbot.plugin.json")
  • loadPluginManifest(rootDir) :读 JSON,校验必填字段 idconfigSchema(且为对象),解析 kindchannelsprovidersskillsnamedescriptionversionuiHints,返回 PluginManifestLoadResult

清单注册表src/plugins/manifest-registry.ts):

  • loadPluginManifestRegistry({ config, workspaceDir, cache, candidates, diagnostics }) :若未传 candidates 则先调用 discoverClawdbotPlugins;对每个 candidate 在其 rootDir 上调用 loadPluginManifest ;通过则得到 PluginManifestRecord(id、name、description、version、kind、channels、providers、skills、origin、rootDir、source、configSchema、schemaCacheKey、configUiHints)。
  • 用于「仅校验配置」或供加载器决定要加载哪些插件;重复 id 会记一条诊断,后加载的同 id 在 loader 里会被标记为 overridden。

2.2 加载与注册

加载器src/plugins/loader.ts):

  • loadClawdbotPlugins(options)
    1. normalizePluginsConfig(cfg.plugins) → 得到 enabledallow/denyloadPathsslots.memoryentries
    2. discoverClawdbotPlugins + loadPluginManifestRegistry,得到候选与清单记录。
    3. 解析 plugin-sdk 路径,createJiti 时设置 alias: { "clawdbot/plugin-sdk": pluginSdkAlias },便于插件里 import ... from "clawdbot/plugin-sdk"
    4. 按候选顺序遍历:若清单缺失或 id 重复则跳过或标记 disabled;resolveEnableState(id, origin, normalized) 决定是否启用;resolveMemorySlotDecision 处理 memory 类插件的唯一启用;validatePluginConfig 用清单的 configSchema 校验 entries[id].config
    5. 通过后 jiti(candidate.source) 加载模块,resolvePluginModuleExport(mod)defaultregister/activate;调用 register(api)apicreateApi(record, { config, pluginConfig }) 创建。
    6. 插件在 register(api) 里调用 api.registerChannel(...)api.registerTool(...) 等,这些会写入 PluginRegistry(channels、tools、gatewayHandlers、cliRegistrars、commands、hooks、services、providers、httpRoutes 等)。
    7. 结果缓存在 registryCache (可选),并 setActivePluginRegistryinitializeGlobalHookRunner(registry)

Registry 与 createApisrc/plugins/registry.ts):

  • createPluginRegistry({ logger, coreGatewayHandlers, runtime }) 返回 registrycreateApi(record, { config, pluginConfig })
  • createApi 返回的 ClawdbotPluginApi 包含:idnameversiondescriptionsourceconfigpluginConfigruntimelogger,以及:
    • registerChannel(registration) :把 ChannelPlugin 压入 registry.channels,并记录 record.channelIds
    • registerTool(tool, opts) :支持单工具或工厂函数;名字写入 record.toolNames,工厂/工具压入 registry.tools
    • registerGatewayMethod(method, handler) :若与 core 或已有方法冲突则报错,否则写入 registry.gatewayHandlers
    • registerCli(registrar) :压入 registry.cliRegistrars,后续由 CLI 程序在构建时调用。
    • registerCommand(def) :压入 registry.commands,用于自动回复层的「插件命令」(先于 Agent 执行)。
    • registerHookregisterHttpRouteregisterServiceregisterProviderresolvePathon(hookName, handler) 等同理。
  • 渠道注册时要求 plugin.id 非空;重复的 channel id 不会在这里去重,但同一插件 id 只能注册一次渠道(由前面「同 id 仅启用一份」保证)。

2.3 Plugin SDK 与渠道插件接口

Plugin SDKsrc/plugin-sdk/index.ts):

  • 对外 réexport 渠道开发所需类型与 helpers:ChannelPluginChannelMetaChannelConfigAdapterChannelOutboundAdapterChannelPairingAdapterChannelStatusAdapterbuildChannelConfigSchemaemptyPluginConfigSchema 、各渠道的 ConfigSchema (如 LineConfigSchema)、账号解析、normalize、onboarding、allowlist、ack reactions、typing、directory 等。
  • 插件只需 import { ... } from "clawdbot/plugin-sdk",无需直接依赖 clawdbot 的完整源码;运行时通过 loader 的 jiti alias 指向仓库内 src/plugin-sdk/index.ts(或 dist 产物)。

ChannelPlugin 结构src/channels/plugins/types.plugin.ts):

  • ChannelPlugin<ResolvedAccount> 包含:idmeta(ChannelMeta)、capabilitiesconfig(ChannelConfigAdapter)、configSchema(可选)、pairingsecuritygroupsoutboundstatusgatewayauthmessagingdirectoryresolveractions 等可选适配器。
  • 渠道插件至少实现:metaconfig (listAccountIds、resolveAccount、defaultAccountId、setAccountEnabled、deleteAccount、isConfigured、describeAccount 等)、outbound (发送)、gateway(startAccount/stopAccount,拉取或接收消息);若需配对/状态/目录则实现对应适配器。

三、实战:从零开发一个渠道插件(以 LINE 为例)

下面以仓库内 extensions/line 为例,走通「需求 → 清单 → 入口 → ChannelPlugin → 配置与部署」的完整流程。

3.1 需求与产物

  • 需求:支持 LINE Messaging API 作为消息渠道(收消息、发消息、多账号、配对、状态)。
  • 产物 :一个 npm 包或本地目录,包含 clawdbot.plugin.json、入口 index.ts、以及实现 ChannelPlugin 的模块(如 src/channel.ts)、运行时桥接(src/runtime.ts)、可选 CLI/卡片命令(src/card-command.ts)等。

3.2 清单

在插件根目录创建 clawdbot.plugin.json

json 复制代码
{
  "id": "line",
  "channels": ["line"],
  "configSchema": {
    "type": "object",
    "additionalProperties": false,
    "properties": {}
  }
}
  • id:与配置中 plugins.entries.line 及渠道 channels.line 对应。
  • channels:声明本插件会注册的 channel id,便于配置校验与文档。
  • configSchema:LINE 的完整配置由 SDK 的 LineConfigSchema 在运行时通过 buildChannelConfigSchema(LineConfigSchema) 提供;清单里可用空 schema 或与 channels.line 结构一致的 schema。
  • 若插件还提供技能目录,可加 "skills": ["skills"](相对插件根目录)。

3.3 入口与注册

index.ts (简化自 extensions/line/index.ts):

typescript 复制代码
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
import { emptyPluginConfigSchema } from "clawdbot/plugin-sdk";

import { linePlugin } from "./src/channel.js";
import { registerLineCardCommand } from "./src/card-command.js";
import { setLineRuntime } from "./src/runtime.js";

const plugin = {
  id: "line",
  name: "LINE",
  description: "LINE Messaging API channel plugin",
  configSchema: emptyPluginConfigSchema(),
  register(api: ClawdbotPluginApi) {
    setLineRuntime(api.runtime);
    api.registerChannel({ plugin: linePlugin });
    registerLineCardCommand(api);
  },
};

export default plugin;
  • register(api) 中:先保存 api.runtime 供 channel 实现里解析账号、发消息等使用;再 api.registerChannel({ plugin: linePlugin }) 注册渠道;若有自定义卡片命令则 registerLineCardCommand(api) (内部可能调用 api.registerCommand 或 CLI)。
  • 配置校验不执行插件代码,仅用清单的 configSchema;若清单里用了空 schema,实际渠道配置结构由 buildChannelConfigSchema(LineConfigSchema) 在类型侧约束。

3.4 ChannelPlugin 结构要点

linePluginextensions/line/src/channel.ts)需实现:

  • meta:id、label、selectionLabel、detailLabel、docsPath、blurb、systemImage、quickstartAllowFrom 等,供 Control UI / 向导展示。
  • pairing:idLabel、normalizeAllowEntry、notifyApproval(审核通过后通过 LINE API 推一条通知)。
  • capabilities:chatTypes(direct、group)、reactions、threads、media、nativeCommands、blockStreaming。
  • config :listAccountIds、resolveAccount、defaultAccountId、setAccountEnabled、deleteAccount、isConfigured、describeAccount、resolveAllowFrom、formatAllowFrom 等,与 channels.line 配置结构对应。
  • configSchemabuildChannelConfigSchema(LineConfigSchema),与核心配置 Zod 一致。
  • reload :configPrefixes 如 ["channels.line"],便于配置热重载。
  • outbound:发送文本、附件等,内部调用 LINE Messaging API(需 channelAccessToken)。
  • gateway:startAccount(启动 webhook 或轮询)、stopAccount;与 ChannelManager 生命周期一致。
  • status (可选):collectStatusIssues,用于 clawdbot channels status --probe

认证与收发:

  • 认证:LINE 使用 Channel Access Token (与 channel secret);可放在 channels.line.channelAccessToken 或通过 token 文件/环境变量读取。
  • 收发:入站由 webhook 或 gateway 的 HTTP 处理接收,normalize 成内部消息格式后进 auto-reply;出站通过 outbound 调用 LINE Push/Reply API。
  • 状态:status 适配器可检查 token 是否有效、webhook 是否可达等。

3.5 配置与部署

  • 启用插件 :在 clawdbot.jsonplugins.entries.line.enabled: true(或通过 plugins.allow 包含 line);若从 npm 安装则先 clawdbot plugins install @clawdbot/line(若包存在),再在 plugins.entries.line.configchannels.line 下配置账号与 token。
  • 渠道配置channels.line 下配置默认账号、多账号、allowFrom、webhook 等,与 LineConfigSchema 一致。
  • 本地开发 :在仓库内可直接把 extensions/line 作为 bundled 被发现;或在外层通过 plugins.load.paths: ["/path/to/line"] 指向插件根目录,重启 Gateway 后生效。

四、架构图解

graph TB subgraph 发现 A[plugins.load.paths] --> D[PluginCandidate] B[workspace/.clawdbot/extensions] --> D C[~/.clawdbot/extensions] --> D E[bundled extensions/] --> D end D --> F[loadPluginManifestRegistry] F --> G[PluginManifestRecord] G --> H[resolveEnableState / MemorySlot] H --> I[jiti 加载入口] I --> J[register api] J --> K[PluginRegistry] K --> L[channels / tools / gatewayHandlers / cliRegistrars / commands ...]
sequenceDiagram participant Boot as Gateway/CLI 启动 participant Loader as loadClawdbotPlugins participant Discovery as discoverClawdbotPlugins participant Manifest as loadPluginManifestRegistry participant Jiti as jiti(入口) participant Register as register(api) participant Registry as PluginRegistry Boot->>Loader: loadClawdbotPlugins Loader->>Discovery: discoverClawdbotPlugins Discovery-->>Loader: candidates Loader->>Manifest: loadPluginManifestRegistry(candidates) Manifest-->>Loader: manifest records loop 每个启用候选 Loader->>Loader: validatePluginConfig(schema, entries[id].config) Loader->>Jiti: jiti(candidate.source) Jiti-->>Loader: module Loader->>Register: register(createApi(record, config)) Register->>Registry: api.registerChannel / registerTool / ... end Loader-->>Boot: PluginRegistry

五、常见陷阱与解决方案

陷阱 现象 解决方案
清单缺少 configSchema 插件报错 "missing config schema"、Doctor 报错 clawdbot.plugin.json 中提供 configSchema,至少为 { "type": "object", "additionalProperties": false }
插件 id 与配置不一致 配置不生效或重复 id 被禁用 清单 idplugins.entries.<id>channels.<id> 保持一致;同 id 只保留一份来源
运行时依赖在 devDependencies clawdbot plugins install 后运行报缺模块 将运行时依赖写在 dependencies;install 使用 --omit=dev
workspace:* 在 dependencies npm install 失败或解析错误 插件不要用 workspace:* 依赖根包;用 devDependencies/peerDependencies + jiti alias
渠道未实现 gateway.startAccount 渠道显示但收不到消息 实现 gateway 适配器,在 startAccount 中启动 webhook 或轮询,并调 dock 注册
清单 channels 未声明 配置里写 channels.xxx 被校验报未知 key 在清单中 channels: ["xxx"] 声明本插件注册的 channel id
技能目录未在清单声明 插件自带的 skills 未被加载 clawdbot.plugin.json 中加 skills: ["相对路径"],且路径存在且含 SKILL.md

六、总结

本文介绍了 Clawdbot 的 插件与技能 扩展机制:

  • 插件 :发现(config / workspace / global / bundled)→ 清单校验(id、configSchema 必填)→ 配置启用与 memory slot → jiti 加载 → register(api) → 注册渠道/工具/Gateway/CLI/命令/钩子/服务/提供商。
  • 技能 :AgentSkills 兼容目录(SKILL.md),来源为 bundled、managed、workspace、extraDirs;插件可通过清单 skills 数组附带技能目录。
  • 渠道插件 :实现 ChannelPlugin (meta、config、pairing、outbound、gateway、status 等),在入口里 api.registerChannel({ plugin }) ;清单中声明 channelsconfigSchema
  • 实战:以 LINE 为例,从清单、入口、ChannelPlugin 结构到认证/收发/状态与配置部署,并给出常见陷阱与对策。

参考资源

  • 项目仓库:github.com/clawdbot/cl...
  • 官方文档:docs.clawd.bot
  • 插件总览:docs/plugin.md;清单规范:docs/plugins/manifest.md;技能:docs/tools/skills.md
  • 发现与清单:src/plugins/discovery.tssrc/plugins/manifest.tssrc/plugins/manifest-registry.ts
  • 加载与注册:src/plugins/loader.tssrc/plugins/registry.tssrc/plugins/config-state.ts
  • Plugin SDK:src/plugin-sdk/index.ts;渠道类型:src/channels/plugins/types.plugin.ts
  • 插件技能目录解析:src/agents/skills/plugin-skills.ts
  • LINE 扩展示例:extensions/line/clawdbot.plugin.jsonindex.tssrc/channel.ts
相关推荐
Rocktech_ruixun12 分钟前
机器人本地跑LLM大模型对主板硬件有什么要求?瑞迅科技RK3588/3568方案选型解析
人工智能·科技·嵌入式硬件·机器人·边缘计算
yxlalm16 分钟前
Spring AI+RAG 01-项目背景与技术选型
java·人工智能·spring
tedcloud12318 分钟前
Wand-Enhancer 怎么搭建?开源 Wand 客户端增强与远程控制工具介绍
大数据·服务器·人工智能·开源·音视频
科技苑31 分钟前
如何用Python编程实现一个简单的Web爬虫?
人工智能·python
迅利科技43 分钟前
新能源汽车零部件研发,SIMULIA一站式仿真解决方案如何缩短研发周期
人工智能·汽车
嘉琪coder1 小时前
我做了一个 Chrome 扩展,把 YouTube 播放列表批量变成 AI 可读的本地 Markdown
chrome·开源·浏览器
Databuff1 小时前
AI SSH工具,集齐SSH、SFTP、知识库RAG、AI助手
网络·人工智能·ssh
Blockchina2 小时前
从一篇文章到一条完整视频:用 Codex 搭建可复用的 AI 视频生产线
人工智能
Proaiapi2 小时前
一张图介绍gpt-image-2.5
人工智能·gpt
czxxxc2 小时前
当AI开始“动手”:一场从“会说”到“会做”的静默转折
人工智能