插件(扩展)
快速上手(没用过插件?)
插件就是一个小代码模块,用来给 OpenClaw 扩展额外功能(命令、工具和 Gateway RPC)。
大多数时候,你会在想要一个 OpenClaw 核心还没内置的功能时用到插件(或者你想把可选功能从主安装中分离出来)。
快速路径:
- 看看已经加载了什么:
openclaw plugins list
- 安装一个官方插件(例如:Voice Call):
openclaw plugins install @openclaw/voice-call
npm 规格仅限注册中心(包名 + 可选的精确版本或 dist-tag)。Git/URL/文件规格和 semver 范围会被拒绝。
裸规格和 @latest 停留在稳定轨道。如果 npm 将其解析为预发布版本,OpenClaw 会停下来要求你用预发布标签(如 @beta/@rc)或精确的预发布版本号明确 opt-in。
- 重启 Gateway,然后在
plugins.entries.<id>.config下配置。
参见 Voice Call 了解完整的插件示例。 找第三方插件?参见 社区插件。
架构
OpenClaw 的插件系统有四层:
- 清单 + 发现
OpenClaw 从配置路径、工作区根目录、全局扩展根目录和内置扩展中找到候选插件。发现过程先读取
openclaw.plugin.json和包元数据。 - 启用 + 校验 核心决定发现的插件是启用、禁用、阻止,还是被选入独占槽位(如 memory)。
- 运行时加载 启用的插件通过 jiti 在进程内加载,并将能力注册到中央注册表。
- 表面消费 OpenClaw 其余部分读取注册表来暴露工具、渠道、提供商配置、钩子、HTTP 路由、CLI 命令和服务。
重要的设计边界:
- 发现 + 配置校验应该从清单/schema 元数据工作,不需要执行插件代码
- 运行时行为来自插件模块的
register(api)路径
这个分离让 OpenClaw 可以在完整运行时激活之前就校验配置、解释缺失/禁用的插件,并构建 UI/schema 提示。
执行模型
插件和 Gateway 在同一进程内运行。它们不是沙箱化的。加载的插件与核心代码拥有相同的进程级信任边界。
这意味着:
- 插件可以注册工具、网络处理器、钩子和服务
- 有 bug 的插件可能导致 Gateway 崩溃或不稳定
- 恶意插件等同于在 OpenClaw 进程中执行任意代码
对非内置插件使用白名单和显式的安装/加载路径。将工作区插件视为开发时代码,而非生产默认值。
重要的信任说明:
plugins.allow信任的是插件 id,不是来源。- 与内置插件同 id 的工作区插件,在启用/白名单时会有意遮蔽内置副本。
- 这是正常且有用的,适用于本地开发、补丁测试和热修复。
可用插件(官方)
- Microsoft Teams 从 2026.1.15 起为纯插件;使用 Teams 需安装
@openclaw/msteams。 - Memory (Core) — 内置记忆搜索插件(默认通过
plugins.slots.memory启用) - Memory (LanceDB) — 内置长期记忆插件(自动召回/捕获;设置
plugins.slots.memory = "memory-lancedb") - Voice Call —
@openclaw/voice-call - Zalo Personal —
@openclaw/zalouser - Matrix —
@openclaw/matrix - Nostr —
@openclaw/nostr - Zalo —
@openclaw/zalo - Microsoft Teams —
@openclaw/msteams - Google Antigravity OAuth(提供商认证)— 内置为
google-antigravity-auth(默认禁用) - Gemini CLI OAuth(提供商认证)— 内置为
google-gemini-cli-auth(默认禁用) - Qwen OAuth(提供商认证)— 内置为
qwen-portal-auth(默认禁用) - Copilot Proxy(提供商认证)— 本地 VS Code Copilot Proxy 桥接;区别于内置的
github-copilot设备登录(内置,默认禁用)
OpenClaw 插件是通过 jiti 在运行时加载的 TypeScript 模块。配置校验不执行插件代码;它使用插件清单和 JSON Schema。参见 插件清单。
插件可以注册:
- Gateway RPC 方法
- Gateway HTTP 路由
- Agent 工具
- CLI 命令
- 后台服务
- 上下文引擎
- 可选的配置校验
- Skills(在插件清单中列出
skills目录) - 自动回复命令(无需调用 AI Agent 即可执行)
插件与 Gateway 在同一进程内运行,所以请视为可信代码。 工具开发指南:插件 Agent 工具。
加载管线
启动时,OpenClaw 大致做这些事:
- 发现候选插件根目录
- 读取
openclaw.plugin.json和包元数据 - 拒绝不安全的候选
- 标准化插件配置(
plugins.enabled、allow、deny、entries、slots、load.paths) - 决定每个候选的启用状态
- 通过 jiti 加载启用的模块
- 调用
register(api)并将注册收集到插件注册表 - 将注册表暴露给命令/运行时表面
安全门控在运行时执行之前进行。当入口逃逸出插件根目录、路径为全局可写或路径所有权对非内置插件看起来可疑时,候选会被阻止。
清单优先行为
清单是控制平面的事实来源。OpenClaw 用它来:
- 识别插件
- 发现声明的渠道/skills/config schema
- 校验
plugins.entries.<id>.config - 为 Control UI 增补标签/占位符
- 显示安装/目录元数据
运行时模块是数据平面部分。它注册实际行为,如钩子、工具、命令或提供商流程。
加载器缓存的内容
OpenClaw 为以下内容保持短时进程内缓存:
- 发现结果
- 清单注册表数据
- 已加载的插件注册表
这些缓存减少突发启动和重复命令开销。可以理解为短暂的性能缓存,不是持久化。
运行时辅助工具
插件可以通过 api.runtime 访问选定的核心辅助工具。对于电话 TTS:
const result = await api.runtime.tts.textToSpeechTelephony({
text: "Hello from OpenClaw",
cfg: api.config,
});
说明:
- 使用核心
messages.tts配置(OpenAI 或 ElevenLabs)。 - 返回 PCM 音频缓冲区 + 采样率。插件需要自行重采样/编码以适配提供商。
- Edge TTS 不支持电话场景。
对于 STT/转录,插件可以调用:
const { text } = await api.runtime.stt.transcribeAudioFile({
filePath: "/tmp/inbound-audio.ogg",
cfg: api.config,
// MIME 无法可靠推断时可选:
mime: "audio/ogg",
});
说明:
- 使用核心媒体理解音频配置(
tools.media.audio)和提供商回退顺序。 - 没有产生转录输出时返回
{ text: undefined }(例如跳过/不支持的输入)。
Gateway HTTP 路由
插件可以用 api.registerHttpRoute(...) 暴露 HTTP 端点。
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"要求正常的 Gateway 认证,或"plugin"让插件自行管理认证/webhook 验证。match:可选。"exact"(默认)或"prefix"。replaceExisting:可选。允许同一插件替换自己已有的路由注册。handler:路由处理请求时返回true。
说明:
api.registerHttpHandler(...)已废弃。使用api.registerHttpRoute(...)。- 插件路由必须显式声明
auth。 - 相同
path + match冲突会被拒绝,除非replaceExisting: true,且一个插件不能替换另一个插件的路由。 - 不同
auth级别的重叠路由会被拒绝。exact/prefix回退链应保持在同一认证级别。
插件 SDK 导入路径
编写插件时使用 SDK 子路径,而非单体 openclaw/plugin-sdk 导入:
openclaw/plugin-sdk/core用于通用插件 API、提供商认证类型和共享辅助工具。openclaw/plugin-sdk/compat用于需要比core更广泛共享运行时辅助工具的内置/内部插件代码。openclaw/plugin-sdk/telegram用于 Telegram 渠道插件。openclaw/plugin-sdk/discord用于 Discord 渠道插件。openclaw/plugin-sdk/slack用于 Slack 渠道插件。openclaw/plugin-sdk/signal用于 Signal 渠道插件。openclaw/plugin-sdk/imessage用于 iMessage 渠道插件。openclaw/plugin-sdk/whatsapp用于 WhatsApp 渠道插件。openclaw/plugin-sdk/line用于 LINE 渠道插件。openclaw/plugin-sdk/msteams用于内置 Microsoft Teams 插件表面。- 内置扩展特定子路径也可用:
openclaw/plugin-sdk/acpx、openclaw/plugin-sdk/bluebubbles、openclaw/plugin-sdk/copilot-proxy、openclaw/plugin-sdk/device-pair、openclaw/plugin-sdk/diagnostics-otel、openclaw/plugin-sdk/diffs、openclaw/plugin-sdk/feishu、openclaw/plugin-sdk/google-gemini-cli-auth、openclaw/plugin-sdk/googlechat、openclaw/plugin-sdk/irc、openclaw/plugin-sdk/llm-task、openclaw/plugin-sdk/lobster、openclaw/plugin-sdk/matrix、openclaw/plugin-sdk/mattermost、openclaw/plugin-sdk/memory-core、openclaw/plugin-sdk/memory-lancedb、openclaw/plugin-sdk/minimax-portal-auth、openclaw/plugin-sdk/nextcloud-talk、openclaw/plugin-sdk/nostr、openclaw/plugin-sdk/open-prose、openclaw/plugin-sdk/phone-control、openclaw/plugin-sdk/qwen-portal-auth、openclaw/plugin-sdk/synology-chat、openclaw/plugin-sdk/talk-voice、openclaw/plugin-sdk/test-utils、openclaw/plugin-sdk/thread-ownership、openclaw/plugin-sdk/tlon、openclaw/plugin-sdk/twitch、openclaw/plugin-sdk/voice-call、openclaw/plugin-sdk/zalo和openclaw/plugin-sdk/zalouser。
兼容性说明:
openclaw/plugin-sdk仍然支持现有外部插件。- 新的和迁移的内置插件应使用渠道或扩展特定子路径;通用表面用
core,仅在需要更广泛共享辅助工具时用compat。
只读渠道检查
如果你的插件注册了渠道,建议在 resolveAccount(...) 之外实现 plugin.config.inspectAccount(cfg, accountId)。
原因:
resolveAccount(...)是运行时路径。它可以假设凭证已完全物化,在缺少必要密钥时快速失败。- 只读命令路径如
openclaw status、openclaw status --all、openclaw channels status、openclaw channels resolve和 doctor/配置修复流程不应该仅仅为了描述配置就物化运行时凭证。
推荐的 inspectAccount(...) 行为:
- 仅返回描述性的账户状态。
- 保留
enabled和configured。 - 在相关时包含凭证来源/状态字段,如:
tokenSource、tokenStatusbotTokenSource、botTokenStatusappTokenSource、appTokenStatussigningSecretSource、signingSecretStatus
- 报告只读可用性时不需要返回原始 token 值。返回
tokenStatus: "available"加上匹配的来源字段就够了。 - 凭证通过 SecretRef 配置但在当前命令路径不可用时使用
configured_unavailable。
这让只读命令可以报告”已配置但在此命令路径不可用”,而不是崩溃或误报账户未配置。
性能说明:
- 插件发现和清单元数据使用短时进程内缓存以减少突发启动/重载开销。
- 设置
OPENCLAW_DISABLE_PLUGIN_DISCOVERY_CACHE=1或OPENCLAW_DISABLE_PLUGIN_MANIFEST_CACHE=1可禁用这些缓存。 - 通过
OPENCLAW_PLUGIN_DISCOVERY_CACHE_MS和OPENCLAW_PLUGIN_MANIFEST_CACHE_MS调整缓存窗口。
发现和优先级
OpenClaw 按顺序扫描:
- 配置路径
plugins.load.paths(文件或目录)
- 工作区扩展
<workspace>/.openclaw/extensions/*.ts<workspace>/.openclaw/extensions/*/index.ts
- 全局扩展
~/.openclaw/extensions/*.ts~/.openclaw/extensions/*/index.ts
- 内置扩展(随 OpenClaw 发布,大多数默认禁用)
<openclaw>/extensions/*
大多数内置插件需要通过 plugins.entries.<id>.enabled 或 openclaw plugins enable <id> 显式启用。
默认开启的内置插件例外:
device-pairphone-controltalk-voice- 活跃 memory 槽位插件(默认槽位:
memory-core)
已安装的插件默认启用,但可以同样方式禁用。
工作区插件默认禁用,除非你显式启用或白名单它们。这是有意为之:一个检出的仓库不应悄悄变成生产 Gateway 代码。
加固说明:
- 如果
plugins.allow为空且非内置插件可被发现,OpenClaw 启动时会记录警告,列出插件 id 和来源。 - 候选路径在发现准入前做安全检查。OpenClaw 在以下情况阻止候选:
- 扩展入口解析到插件根目录外(包括符号链接/路径遍历逃逸),
- 插件根目录/源路径为全局可写,
- 对非内置插件路径所有权可疑(POSIX 所有者既非当前 uid 也非 root)。
- 没有安装/加载路径来源的已加载非内置插件会发出警告,以便你可以固定信任(
plugins.allow)或安装跟踪(plugins.installs)。
每个插件必须在其根目录包含 openclaw.plugin.json 文件。如果路径指向文件,插件根目录是文件所在目录且必须包含清单。
如果多个插件解析到同一 id,按上述顺序第一个匹配的生效,低优先级副本被忽略。
也就是说:
- 工作区插件有意遮蔽同 id 的内置插件
plugins.allow: ["foo"]按 id 授权活跃的foo插件,即使活跃副本来自工作区而非内置扩展根目录- 如果你需要更严格的来源控制,使用显式的安装/加载路径并在启用前检查解析后的插件来源
启用规则
启用状态在发现后解析:
plugins.enabled: false禁用所有插件plugins.deny始终优先plugins.entries.<id>.enabled: false禁用该插件- 工作区来源的插件默认禁用
- 白名单在
plugins.allow非空时限制活跃集 - 白名单是基于 id 的,不是基于来源的
- 内置插件默认禁用,除非:
- 内置 id 在默认开启集中,或
- 你显式启用它,或
- 渠道配置隐式启用了该内置渠道插件
- 独占槽位可以强制启用被选中的插件
当前核心中,内置默认开启的 id 包括本地/提供商辅助工具如 ollama、sglang、vllm,加上 device-pair、phone-control 和 talk-voice。
包组
插件目录可以包含带 openclaw.extensions 的 package.json:
{
"name": "my-pack",
"openclaw": {
"extensions": ["./src/safety.ts", "./src/tools.ts"]
}
}
每个条目成为一个插件。如果包列出了多个扩展,插件 id 变为 name/<fileBase>。
如果你的插件导入了 npm 依赖,在该目录安装使 node_modules 可用(npm install / pnpm install)。
安全防护:每个 openclaw.extensions 条目在符号链接解析后必须保持在插件目录内。逃逸出包目录的条目会被拒绝。
安全说明:openclaw plugins install 使用 npm install --ignore-scripts 安装插件依赖(不执行生命周期脚本)。保持插件依赖树为”纯 JS/TS”,避免需要 postinstall 构建的包。
渠道目录元数据
渠道插件可以通过 openclaw.channel 和 openclaw.install 宣传引导元数据和安装提示。这让核心目录保持无数据。
示例:
{
"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": "extensions/nextcloud-talk",
"defaultChoice": "npm"
}
}
}
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": {...} } } ] }。
插件 ID
默认插件 id:
- 包组:
package.jsonname - 独立文件:文件基名(
~/.../voice-call.ts→voice-call)
如果插件导出了 id,OpenClaw 会使用它,但当它与配置的 id 不匹配时会发出警告。
注册表模型
加载的插件不直接修改随意的核心全局变量。它们注册到中央插件注册表。
注册表跟踪:
- 插件记录(标识、来源、源头、状态、诊断)
- 工具
- 旧版钩子和类型化钩子
- 渠道
- 提供商
- Gateway RPC 处理器
- HTTP 路由
- CLI 注册器
- 后台服务
- 插件拥有的命令
核心功能从注册表读取,而非直接与插件模块交互。这让加载是单向的:
- 插件模块 → 注册表注册
- 核心运行时 → 注册表消费
这种分离对可维护性很重要。它意味着大多数核心表面只需要一个集成点:“读取注册表”,而不是”为每个插件模块做特殊处理”。
配置
{
plugins: {
enabled: true,
allow: ["voice-call"],
deny: ["untrusted-plugin"],
load: { paths: ["~/Projects/oss/voice-call-extension"] },
entries: {
"voice-call": { enabled: true, config: { provider: "twilio" } },
},
},
}
字段:
enabled:总开关(默认 true)allow:白名单(可选)deny:黑名单(可选;deny 优先)load.paths:额外的插件文件/目录slots:独占槽位选择器,如memory和contextEngineentries.<id>:按插件的开关 + 配置
配置变更需要重启 Gateway。
校验规则(严格):
entries、allow、deny或slots中的未知插件 id 是错误。- 未知的
channels.<id>key 是错误,除非插件清单声明了该渠道 id。 - 插件配置使用
openclaw.plugin.json中嵌入的 JSON Schema(configSchema)校验。 - 如果插件被禁用,其配置会保留并发出警告。
禁用 vs 缺失 vs 无效
这些状态是有意区分的:
- 禁用:插件存在,但启用规则关闭了它
- 缺失:配置引用了一个发现中未找到的插件 id
- 无效:插件存在,但其配置不匹配声明的 schema
OpenClaw 保留禁用插件的配置,这样重新开启时不会破坏已有设置。
插件槽位(独占类别)
某些插件类别是独占的(同时只能有一个活跃)。使用 plugins.slots 选择哪个插件拥有该槽位:
{
plugins: {
slots: {
memory: "memory-core", // 或 "none" 禁用 memory 插件
contextEngine: "legacy", // 或插件 id 如 "lossless-claw"
},
},
}
支持的独占槽位:
memory:活跃 memory 插件("none"禁用 memory 插件)contextEngine:活跃上下文引擎插件("legacy"是内置默认值)
如果多个插件声明 kind: "memory" 或 kind: "context-engine",只有被选中的插件为该槽位加载。其他的会被禁用并附带诊断信息。
上下文引擎插件
上下文引擎插件负责会话上下文编排——摄入、组装和压缩。在插件中通过 api.registerContextEngine(id, factory) 注册,然后用 plugins.slots.contextEngine 选择活跃引擎。
当你的插件需要替换或扩展默认上下文管线而不仅仅是添加记忆搜索或钩子时使用。
Control UI(schema + 标签)
Control UI 使用 config.schema(JSON Schema + uiHints)渲染更好的表单。
OpenClaw 根据发现的插件在运行时增补 uiHints:
- 为
plugins.entries.<id>/.enabled/.config添加每个插件的标签 - 合并插件提供的可选配置字段提示到:
plugins.entries.<id>.config.<field>
如果你想让插件配置字段显示好的标签/占位符(并标记密钥为敏感),在插件清单中的 JSON Schema 旁提供 uiHints。
示例:
{
"id": "my-plugin",
"configSchema": {
"type": "object",
"additionalProperties": false,
"properties": {
"apiKey": { "type": "string" },
"region": { "type": "string" }
}
},
"uiHints": {
"apiKey": { "label": "API Key", "sensitive": true },
"region": { "label": "Region", "placeholder": "us-east-1" }
}
}
CLI
openclaw plugins list
openclaw plugins info <id>
openclaw plugins install <path> # copy a local file/dir into ~/.openclaw/extensions/<id>
openclaw plugins install ./extensions/voice-call # relative path ok
openclaw plugins install ./plugin.tgz # install from a local tarball
openclaw plugins install ./plugin.zip # install from a local zip
openclaw plugins install -l ./extensions/voice-call # link (no copy) for dev
openclaw plugins install @openclaw/voice-call # install from npm
openclaw plugins install @openclaw/voice-call --pin # store exact resolved name@version
openclaw plugins update <id>
openclaw plugins update --all
openclaw plugins enable <id>
openclaw plugins disable <id>
openclaw plugins doctor
plugins update 仅对 plugins.installs 中跟踪的 npm 安装有效。
如果存储的完整性元数据在更新之间发生变化,OpenClaw 会警告并要求确认(用全局 --yes 跳过提示)。
插件也可以注册自己的顶级命令(例如:openclaw voicecall)。
插件 API(概述)
插件可以导出:
- 一个函数:
(api) => { ... } - 一个对象:
{ id, name, configSchema, register(api) { ... } }
register(api) 是插件附加行为的地方。常见注册包括:
registerToolregisterHookon(...)用于类型化生命周期钩子registerChannelregisterProviderregisterHttpRouteregisterCommandregisterCliregisterContextEngineregisterService
上下文引擎插件还可以注册运行时拥有的上下文管理器:
export default function (api) {
api.registerContextEngine("lossless-claw", () => ({
info: { id: "lossless-claw", name: "Lossless Claw", ownsCompaction: true },
async ingest() {
return { ingested: true };
},
async assemble({ messages }) {
return { messages, estimatedTokens: 0 };
},
async compact() {
return { ok: true, compacted: false };
},
}));
}
然后在配置中启用:
{
plugins: {
slots: {
contextEngine: "lossless-claw",
},
},
}
插件钩子
插件可以在运行时注册钩子。这让插件可以打包事件驱动的自动化,无需单独安装钩子包。
示例
export default function register(api) {
api.registerHook(
"command:new",
async () => {
// Hook logic here.
},
{
name: "my-plugin.command-new",
description: "Runs when /new is invoked",
},
);
}
说明:
- 通过
api.registerHook(...)显式注册钩子。 - 钩子资格规则仍然适用(OS/bins/env/config 要求)。
- 插件管理的钩子在
openclaw hooks list中显示为plugin:<id>。 - 你不能通过
openclaw hooks启用/禁用插件管理的钩子;改为启用/禁用插件。
Agent 生命周期钩子(api.on)
对于类型化运行时生命周期钩子,使用 api.on(...):
export default function register(api) {
api.on(
"before_prompt_build",
(event, ctx) => {
return {
prependSystemContext: "Follow company style guide.",
};
},
{ priority: 10 },
);
}
用于提示词构建的重要钩子:
before_model_resolve:在会话加载前运行(messages不可用)。用于确定性地覆盖modelOverride或providerOverride。before_prompt_build:在会话加载后运行(messages可用)。用于塑造提示词输入。before_agent_start:旧版兼容钩子。优先使用上面两个显式钩子。
核心强制的钩子策略:
- 运维人员可以通过
plugins.entries.<id>.hooks.allowPromptInjection: false按插件禁用提示词变更钩子。 - 禁用时,OpenClaw 阻止
before_prompt_build并忽略旧版before_agent_start返回的提示词变更字段,同时保留旧版modelOverride和providerOverride。
before_prompt_build 结果字段:
prependContext:为本次运行前置文本到用户提示词。适合轮次特定或动态内容。systemPrompt:完整系统提示词覆盖。prependSystemContext:前置文本到当前系统提示词。appendSystemContext:附加文本到当前系统提示词。
内嵌运行时的提示词构建顺序:
- 将
prependContext应用到用户提示词。 - 提供时应用
systemPrompt覆盖。 - 应用
prependSystemContext + 当前系统提示词 + appendSystemContext。
合并和优先级说明:
- 钩子处理器按优先级运行(越高越先)。
- 对合并的上下文字段,值按执行顺序拼接。
before_prompt_build的值在旧版before_agent_start备选值之前应用。
迁移建议:
- 将静态指引从
prependContext移到prependSystemContext(或appendSystemContext),这样提供商可以缓存稳定的系统前缀内容。 prependContext保留给每轮动态上下文,即应该与用户消息绑定的内容。
提供商插件(模型认证)
插件可以注册模型提供商,让用户可以在 OpenClaw 内运行 OAuth 或 API key 设置,在引导/模型选择器中显示提供商配置,并贡献隐式提供商发现。
提供商插件是模型提供商设置的模块化扩展接口。它们不仅仅是”OAuth 辅助工具”了。
提供商插件生命周期
提供商插件可以参与五个不同的阶段:
- 认证
auth[].run(ctx)执行 OAuth、API key 捕获、设备码或自定义设置,返回认证配置文件和可选的配置补丁。 - 非交互式设置
auth[].runNonInteractive(ctx)处理openclaw onboard --non-interactive,不需要提示。当提供商需要超出内置简单 API key 路径的自定义无头设置时使用。 - 向导集成
wizard.onboarding在openclaw onboard中添加条目。wizard.modelPicker在模型选择器中添加设置条目。 - 隐式发现
discovery.run(ctx)可以在模型解析/列出期间自动贡献提供商配置。 - 选择后跟进
onModelSelected(ctx)在模型被选择后运行。用于提供商特定的工作,如下载本地模型。
推荐这样拆分是因为这些阶段有不同的生命周期要求:
- 认证是交互式的,写凭证/配置
- 非交互式设置由标志/环境驱动,不能提示
- 向导元数据是静态的、面向 UI 的
- 发现应该安全、快速、容错
- 选择后钩子是绑定到所选模型的副作用
提供商认证契约
auth[].run(ctx) 返回:
profiles:要写入的认证配置文件configPatch:可选的openclaw.json变更defaultModel:可选的provider/model引用notes:可选的面向用户的说明
然后核心:
- 写入返回的认证配置文件
- 应用认证配置文件的配置关联
- 合并配置补丁
- 可选地应用默认模型
- 适当时运行提供商的
onModelSelected钩子
也就是说提供商插件拥有特定于提供商的设置逻辑,而核心拥有通用的持久化和配置合并路径。
提供商非交互式契约
auth[].runNonInteractive(ctx) 是可选的。当提供商需要无法通过内置通用 API key 流程表达的无头设置时实现它。
非交互式上下文包括:
- 当前和基础配置
- 解析后的引导 CLI 选项
- 运行时日志/错误辅助工具
- Agent/工作区目录
resolveApiKey(...)从标志、环境或现有认证配置文件读取提供商 key,同时遵循--secret-input-modetoApiKeyCredential(...)将解析后的 key 转换为带正确明文 vs secret-ref 存储的认证配置文件凭证
对以下提供商使用此接口:
- 需要
--custom-base-url+--custom-model-id的自托管 OpenAI 兼容运行时 - 提供商特定的非交互式验证或配置合成
不要在 runNonInteractive 中提示。用可操作的错误拒绝缺失的输入。
提供商向导元数据
wizard.onboarding 控制提供商在分组引导中的显示方式:
choiceId:认证选择值choiceLabel:选项标签choiceHint:简短提示groupId:组桶 idgroupLabel:组标签groupHint:组提示methodId:要运行的认证方法
wizard.modelPicker 控制提供商在模型选择中作为”现在设置”条目的显示方式:
labelhintmethodId
当提供商有多个认证方法时,向导可以指向一个显式方法或让 OpenClaw 合成按方法的选择。
OpenClaw 在插件注册时校验提供商向导元数据:
- 重复或空白的认证方法 id 会被拒绝
- 提供商没有认证方法时忽略向导元数据
- 无效的
methodId绑定降级为警告并回退到提供商剩余的认证方法
提供商发现契约
discovery.run(ctx) 返回以下之一:
{ provider }{ providers }null
插件拥有一个提供商 id 的常见情况使用 { provider }。
插件发现多个提供商条目时使用 { providers }。
发现上下文包括:
- 当前配置
- Agent/工作区目录
- 进程环境
- 解析提供商 API key 和发现安全 API key 值的辅助工具
发现应该是:
- 快速的
- 尽力而为的
- 失败时安全跳过的
- 对副作用谨慎的
它不应依赖提示或长时间运行的设置。
发现排序
提供商发现按有序阶段运行:
simpleprofilepairedlate
使用:
simple用于仅依赖环境的廉价发现profile当发现依赖认证配置文件时paired当提供商需要与另一个发现步骤协调时late用于昂贵的或本地网络探测
大多数自托管提供商应使用 late。
好的提供商插件边界
适合做成提供商插件的:
- 有自定义设置流程的本地/自托管提供商
- 提供商特定的 OAuth/设备码登录
- 本地模型服务器的隐式发现
- 选择后的副作用如模型拉取
不太适合的:
- 仅靠环境变量、base URL 和一个默认模型就能区分的简单 API key 提供商
这些仍然可以做成插件,但主要的模块化收益来自先提取行为丰富的提供商。
通过 api.registerProvider(...) 注册提供商。每个提供商暴露一个或多个认证方法(OAuth、API key、设备码等)。这些方法可以驱动:
openclaw models auth login --provider <id> [--method <id>]openclaw onboard- 模型选择器的”自定义提供商”设置条目
- 模型解析/列出期间的隐式提供商发现
示例:
api.registerProvider({
id: "acme",
label: "AcmeAI",
auth: [
{
id: "oauth",
label: "OAuth",
kind: "oauth",
run: async (ctx) => {
// Run OAuth flow and return auth profiles.
return {
profiles: [
{
profileId: "acme:default",
credential: {
type: "oauth",
provider: "acme",
access: "...",
refresh: "...",
expires: Date.now() + 3600 * 1000,
},
},
],
defaultModel: "acme/opus-1",
};
},
},
],
wizard: {
onboarding: {
choiceId: "acme",
choiceLabel: "AcmeAI",
groupId: "acme",
groupLabel: "AcmeAI",
methodId: "oauth",
},
modelPicker: {
label: "AcmeAI (custom)",
hint: "Connect a self-hosted AcmeAI endpoint",
methodId: "oauth",
},
},
discovery: {
order: "late",
run: async () => ({
provider: {
baseUrl: "https://acme.example/v1",
api: "openai-completions",
apiKey: "${ACME_API_KEY}",
models: [],
},
}),
},
});
说明:
run接收ProviderAuthContext,包含prompter、runtime、openUrl和oauth.createVpsAwareHandlers辅助工具。runNonInteractive接收ProviderAuthMethodNonInteractiveContext,包含opts、resolveApiKey和toApiKeyCredential辅助工具用于无头引导。- 需要添加默认模型或提供商配置时返回
configPatch。 - 返回
defaultModel以便--set-default可以更新 Agent 默认值。 wizard.onboarding在openclaw onboard中添加提供商选择。wizard.modelPicker在模型选择器中添加”设置此提供商”条目。discovery.run为插件自己的提供商 id 返回{ provider },或为多提供商发现返回{ providers }。discovery.order控制提供商相对于内置发现阶段的运行时机:simple、profile、paired或late。onModelSelected是选择后钩子,用于提供商特定的跟进工作如拉取本地模型。
注册消息渠道
插件可以注册行为类似内置渠道(WhatsApp、Telegram 等)的渠道插件。渠道配置在 channels.<id> 下,由你的渠道插件代码校验。
const myChannel = {
id: "acmechat",
meta: {
id: "acmechat",
label: "AcmeChat",
selectionLabel: "AcmeChat (API)",
docsPath: "/channels/acmechat",
blurb: "demo channel plugin.",
aliases: ["acme"],
},
capabilities: { chatTypes: ["direct"] },
config: {
listAccountIds: (cfg) => Object.keys(cfg.channels?.acmechat?.accounts ?? {}),
resolveAccount: (cfg, accountId) =>
cfg.channels?.acmechat?.accounts?.[accountId ?? "default"] ?? {
accountId,
},
},
outbound: {
deliveryMode: "direct",
sendText: async () => ({ ok: true }),
},
};
export default function (api) {
api.registerChannel({ plugin: myChannel });
}
说明:
- 配置放在
channels.<id>下(不是plugins.entries)。 meta.label用于 CLI/UI 列表中的标签。meta.aliases为标准化和 CLI 输入添加备选 id。meta.preferOver列出当两者都配置时跳过自动启用的渠道 id。meta.detailLabel和meta.systemImage让 UI 显示更丰富的渠道标签/图标。
渠道引导钩子
渠道插件可以在 plugin.onboarding 上定义可选的引导钩子:
configure(ctx)是基线设置流程。configureInteractive(ctx)可以完全接管已配置和未配置状态的交互式设置。configureWhenConfigured(ctx)只在已配置渠道时覆盖行为。
向导中的钩子优先级:
configureInteractive(如果存在)configureWhenConfigured(仅在渠道已配置时)- 回退到
configure
上下文详情:
configureInteractive和configureWhenConfigured接收:configured(true或false)label(提示使用的用户面渠道名称)- 加上共享的配置/运行时/提示器/选项字段
- 返回
"skip"保持选择和账户跟踪不变。 - 返回
{ cfg, accountId? }应用配置更新并记录账户选择。
编写新消息渠道(分步指南)
当你想要一个新聊天表面(“消息渠道”)而非模型提供商时使用。
模型提供商文档在 /providers/*。
- 选择 id + 配置形状
- 所有渠道配置在
channels.<id>下。 - 多账户设置优先使用
channels.<id>.accounts.<accountId>。
- 定义渠道元数据
meta.label、meta.selectionLabel、meta.docsPath、meta.blurb控制 CLI/UI 列表。meta.docsPath应指向文档页面如/channels/<id>。meta.preferOver让插件替换另一个渠道(自动启用优先它)。meta.detailLabel和meta.systemImage被 UI 用于详细文本/图标。
- 实现必需的适配器
config.listAccountIds+config.resolveAccountcapabilities(聊天类型、媒体、线程等)outbound.deliveryMode+outbound.sendText(基本发送)
- 按需添加可选适配器
setup(向导)、security(DM 策略)、status(健康/诊断)gateway(启动/停止/登录)、mentions、threading、streamingactions(消息操作)、commands(原生命令行为)
- 在插件中注册渠道
api.registerChannel({ plugin })
最小配置示例:
{
channels: {
acmechat: {
accounts: {
default: { token: "ACME_TOKEN", enabled: true },
},
},
},
}
最小渠道插件(仅出站):
const plugin = {
id: "acmechat",
meta: {
id: "acmechat",
label: "AcmeChat",
selectionLabel: "AcmeChat (API)",
docsPath: "/channels/acmechat",
blurb: "AcmeChat messaging channel.",
aliases: ["acme"],
},
capabilities: { chatTypes: ["direct"] },
config: {
listAccountIds: (cfg) => Object.keys(cfg.channels?.acmechat?.accounts ?? {}),
resolveAccount: (cfg, accountId) =>
cfg.channels?.acmechat?.accounts?.[accountId ?? "default"] ?? {
accountId,
},
},
outbound: {
deliveryMode: "direct",
sendText: async ({ text }) => {
// deliver `text` to your channel here
return { ok: true };
},
},
};
export default function (api) {
api.registerChannel({ plugin });
}
加载插件(extensions 目录或 plugins.load.paths),重启 Gateway,然后在配置中配置 channels.<id>。
Agent 工具
参见专门指南:插件 Agent 工具。
注册 Gateway RPC 方法
export default function (api) {
api.registerGatewayMethod("myplugin.status", ({ respond }) => {
respond(true, { ok: true });
});
}
注册 CLI 命令
export default function (api) {
api.registerCli(
({ program }) => {
program.command("mycmd").action(() => {
console.log("Hello");
});
},
{ commands: ["mycmd"] },
);
}
注册自动回复命令
插件可以注册无需调用 AI Agent 即可执行的自定义斜杠命令。适合开关命令、状态检查或不需要 LLM 处理的快速操作。
export default function (api) {
api.registerCommand({
name: "mystatus",
description: "Show plugin status",
handler: (ctx) => ({
text: `Plugin is running! Channel: ${ctx.channel}`,
}),
});
}
命令处理器上下文:
senderId:发送者 ID(如果有)channel:命令发送的渠道isAuthorizedSender:发送者是否为授权用户args:命令后的参数(如果acceptsArgs: true)commandBody:完整命令文本config:当前 OpenClaw 配置
命令选项:
name:命令名(不带前导/)nativeNames:斜杠/菜单表面的可选原生命令别名。使用default对所有原生提供商,或提供商特定 key 如discorddescription:命令列表中显示的帮助文本acceptsArgs:命令是否接受参数(默认 false)。如果为 false 且提供了参数,命令不匹配,消息继续传递到其他处理器requireAuth:是否需要授权发送者(默认 true)handler:返回{ text: string }的函数(可以是 async)
带授权和参数的示例:
api.registerCommand({
name: "setmode",
description: "Set plugin mode",
acceptsArgs: true,
requireAuth: true,
handler: async (ctx) => {
const mode = ctx.args?.trim() || "default";
await saveMode(mode);
return { text: `Mode set to: ${mode}` };
},
});
说明:
- 插件命令在内置命令和 AI Agent 之前处理
- 命令全局注册,跨所有渠道工作
- 命令名不区分大小写(
/MyStatus匹配/mystatus) - 命令名必须以字母开头,只能包含字母、数字、连字符和下划线
- 保留命令名(如
help、status、reset等)不能被插件覆盖 - 跨插件的重复命令注册会产生诊断错误
注册后台服务
export default function (api) {
api.registerService({
id: "my-service",
start: () => api.logger.info("ready"),
stop: () => api.logger.info("bye"),
});
}
命名约定
- Gateway 方法:
pluginId.action(例如voicecall.status) - 工具:
snake_case(例如voice_call) - CLI 命令:kebab 或 camel,但避免与核心命令冲突
Skills
插件可以在仓库中附带 Skill(skills/<name>/SKILL.md)。
通过 plugins.entries.<id>.enabled(或其他配置门控)启用,并确保它存在于你的工作区/托管 Skills 位置。
分发(npm)
推荐的打包方式:
- 主包:
openclaw(本仓库) - 插件:
@openclaw/*下的独立 npm 包(例如@openclaw/voice-call)
发布契约:
- 插件
package.json必须包含带一个或多个入口文件的openclaw.extensions。 - 入口文件可以是
.js或.ts(jiti 在运行时加载 TS)。 openclaw plugins install <npm-spec>使用npm pack,解压到~/.openclaw/extensions/<id>/,并在配置中启用。- 配置 key 稳定性:有作用域的包标准化为无作用域 id 用于
plugins.entries.*。
示例插件:Voice Call
本仓库包含一个语音通话插件(Twilio 或 log 备选):
- 源代码:
extensions/voice-call - Skill:
skills/voice-call - CLI:
openclaw voicecall start|status - 工具:
voice_call - RPC:
voicecall.start、voicecall.status - 配置(twilio):
provider: "twilio"+twilio.accountSid/authToken/from(可选statusCallbackUrl、twimlUrl) - 配置(dev):
provider: "log"(无网络)
参见 Voice Call 和 extensions/voice-call/README.md 了解设置和用法。
安全须知
插件与 Gateway 在同一进程内运行。请视为可信代码:
- 只安装你信任的插件。
- 优先使用
plugins.allow白名单。 - 记住
plugins.allow是基于 id 的,所以启用的工作区插件可以有意遮蔽同 id 的内置插件。 - 变更后重启 Gateway。
测试插件
插件可以(也应该)附带测试:
- 仓库内插件可以在
src/**下保留 Vitest 测试(例如src/plugins/voice-call.plugin.test.ts)。 - 独立发布的插件应运行自己的 CI(lint/build/test),并验证
openclaw.extensions指向构建后的入口(dist/index.js)。