OpenClaw源码剖析 #03 · Plugin SDK:扩展机制与公开 API

一、Plugin SDK 定位

Plugin SDK 是 OpenClaw 的扩展接入层——定义扩展如何与核心交互、如何注册能力、如何参与生命周期。

┌──────────────────────────────────────────────────────────────┐
│                       OpenClaw Core                          │
│  ┌────────────┐  ┌────────────┐  ┌────────────┐             │
│  │  Gateway   │  │   Agent    │  │  Config    │             │
│  │            │  │  Runtime   │  │            │             │
│  └────────────┘  └────────────┘  └────────────┘             │
│                         ▲                                     │
│                         │ plugin-sdk/*                        │
│                         │ (公开 API)                          │
└─────────────────────────┼───────────────────────────────────┘
                          │
┌─────────────────────────┼───────────────────────────────────┐
│              Plugin SDK 公开接口 (src/plugin-sdk/)           │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              OpenClawPluginApi                        │   │
│  │  registerProvider() · registerChannel()              │   │
│  │  registerTool() · registerHook() · registerCommand() │   │
│  │  registerHttpRoute() · registerService()             │   │
│  └──────────────────────────────────────────────────────┘   │
│  ┌──────────────────────────────────────────────────────┐   │
│  │              Plugin Manifest (openclaw.plugin.json)   │   │
│  │  id · configSchema · kind · providers · channels    │   │
│  └──────────────────────────────────────────────────────┘   │
└─────────────────────────┼───────────────────────────────────┘
                          │
┌─────────────────────────┼───────────────────────────────────┐
│                    Extensions                                │
│  extensions/providers/   extensions/channels/   extensions/   │
│  openai/                 telegram/               skill-workshop/│
└─────────────────────────────────────────────────────────────┘

核心原则(来自 AGENTS.md):

Extensions cross into core only via openclaw/plugin-sdk/*,
manifest metadata, injected runtime helpers,
documented barrels (api.ts, runtime-api.ts).

扩展只能通过 SDK 公开接口进入核心,禁止直接引用 src/**


二、模块结构

2.1 核心文件

文件职责
src/plugin-sdk/index.tsSDK 公开导出(barrel file)
src/plugins/types.tsOpenClawPluginApi 接口定义
src/plugins/manifest.tsopenclaw.plugin.json 解析
src/plugins/manifest-types.tsManifest 类型定义
src/plugins/activation-planner.ts插件激活计划生成器
src/plugins/bundle-manifest.ts第三方 Bundle 格式支持

2.2 插件种类(PluginKind)

Kind说明注册方法
provider模型提供商(40+)registerProvider()
channel消息渠道(30+)registerChannel()
tool工具能力registerTool()
hook生命周期钩子registerHook() / on()

三、Manifest 系统

文件:src/plugins/manifest.ts

3.1 Plugin Manifest

每个插件根目录必须有 openclaw.plugin.json

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// src/plugins/manifest.ts
export const PLUGIN_MANIFEST_FILENAME = "openclaw.plugin.json";

export type PluginManifest = {
  id: string;
  configSchema: JsonSchemaObject;
  enabledByDefault?: boolean;
  kind?: PluginKind | PluginKind[];
  channels?: string[];        // 拥有的渠道 ID
  providers?: string[];        // 拥有的 Provider ID
  skills?: string[];
  name?: string;
  description?: string;
  version?: string;
  contracts?: PluginManifestContracts;  // 声明式能力契约
  activation?: PluginManifestActivation; // 激活触发条件
  setup?: PluginManifestSetup;           // Setup 元数据
  channelConfigs?: Record<string, PluginManifestChannelConfig>;
};

3.2 激活条件(Activation)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
export type PluginManifestActivationCapability = "provider" | "channel" | "tool" | "hook";

export type PluginManifestActivation = {
  onStartup?: boolean;           // Gateway 启动时激活
  onProviders?: string[];        // 指定 Provider 被使用时激活
  onAgentHarnesses?: string[];   // 指定 Agent Harness 被选中时激活
  onCommands?: string[];         // 指定命令被调用时激活
  onChannels?: string[];         // 指定 Channel 被使用时激活
  onRoutes?: string[];           // 指定路由被访问时激活
  onCapabilities?: PluginManifestActivationCapability[];  // 能力类型
};

3.3 能力契约(Contracts)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
export type PluginManifestContracts = {
  embeddedExtensionFactories?: string[];
  agentToolResultMiddleware?: string[];
  externalAuthProviders?: string[];
  memoryEmbeddingProviders?: string[];
  speechProviders?: string[];
  realtimeTranscriptionProviders?: string[];
  realtimeVoiceProviders?: string[];
  mediaUnderstandingProviders?: string[];
  imageGenerationProviders?: string[];
  videoGenerationProviders?: string[];
  musicGenerationProviders?: string[];
  webFetchProviders?: string[];
  webSearchProviders?: string[];
  tools?: string[];
};

3.4 第三方 Bundle 格式

文件:src/plugins/bundle-manifest.ts

OpenClaw 支持加载其他生态的插件格式:

格式Manifest 路径
Codex.codex-plugin/plugin.json
Claude.claude-plugin/plugin.json
Cursor.cursor-plugin/plugin.json
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
export function detectBundleManifestFormat(rootDir: string): PluginBundleFormat | null {
  if (fs.existsSync(path.join(rootDir, CODEX_BUNDLE_MANIFEST_RELATIVE_PATH))) {
    return "codex";
  }
  if (fs.existsSync(path.join(rootDir, CURSOR_BUNDLE_MANIFEST_RELATIVE_PATH))) {
    return "cursor";
  }
  if (fs.existsSync(path.join(rootDir, CLAUDE_BUNDLE_MANIFEST_RELATIVE_PATH))) {
    return "claude";
  }
  // ...
}

四、OpenClawPluginApi 接口

文件:src/plugins/types.ts

4.1 注册方法一览

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
export type OpenClawPluginApi = {
  id: string;
  name: string;
  version?: string;
  description?: string;
  source: string;
  rootDir?: string;
  registrationMode: PluginRegistrationMode;
  config: OpenClawConfig;
  pluginConfig?: Record<string, unknown>;
  runtime: PluginRuntime;

  // ===== Provider 注册 =====
  registerProvider(provider: ProviderPlugin): void;
  registerSpeechProvider(provider: SpeechProviderPlugin): void;
  registerRealtimeTranscriptionProvider(provider: RealtimeTranscriptionProviderPlugin): void;
  registerRealtimeVoiceProvider(provider: RealtimeVoiceProviderPlugin): void;
  registerMediaUnderstandingProvider(provider: MediaUnderstandingProviderPlugin): void;
  registerImageGenerationProvider(provider: ImageGenerationProviderPlugin): void;
  registerVideoGenerationProvider(provider: VideoGenerationProviderPlugin): void;
  registerMusicGenerationProvider(provider: MusicGenerationProviderPlugin): void;
  registerWebFetchProvider(provider: WebFetchProviderPlugin): void;
  registerWebSearchProvider(provider: WebSearchProviderPlugin): void;

  // ===== Channel 注册 =====
  registerChannel(registration: OpenClawPluginChannelRegistration | ChannelPlugin): void;

  // ===== Tool 注册 =====
  registerTool(tool: AnyAgentTool | OpenClawPluginToolFactory, opts?: OpenClawPluginToolOptions): void;
  registerTrustedToolPolicy(policy: PluginTrustedToolPolicyRegistration): void;
  registerToolMetadata(metadata: PluginToolMetadataRegistration): void;

  // ===== Hook 注册 =====
  registerHook(events: string | string[], handler: InternalHookHandler, opts?: OpenClawPluginHookOptions): void;
  on<K extends PluginHookName>(hookName: K, handler: PluginHookHandlerMap[K], opts?: { priority?: number; timeoutMs?: number }): void;

  // ===== Command 注册 =====
  registerCommand(command: OpenClawPluginCommandDefinition): void;

  // ===== HTTP Route 注册 =====
  registerHttpRoute(params: OpenClawPluginHttpRouteParams): void;

  // ===== Gateway Method 注册 =====
  registerGatewayMethod(method: string, handler: GatewayRequestHandler, opts?: { scope?: OperatorScope }): void;

  // ===== 生命周期 =====
  registerService(service: OpenClawPluginService): void;
  registerRuntimeLifecycle(lifecycle: PluginRuntimeLifecycleRegistration): void;
  onConversationBindingResolved(handler: (event: PluginConversationBindingResolvedEvent) => void): void;

  // ===== 其他 =====
  registerCli(registrar: OpenClawPluginCliRegistrar, opts?: { commands?: string[]; descriptors?: OpenClawPluginCliCommandDescriptor[] }): void;
  registerReload(registration: OpenClawPluginReloadRegistration): void;
  registerConfigMigration(migrate: PluginConfigMigration): void;
  registerMemoryCapability(capability: MemoryPluginCapability): void;
  registerAgentHarness(harness: AgentHarness): void;
  // ...
};

4.2 ProviderPlugin 结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
export type ProviderPlugin = {
  id: string;
  pluginId?: string;
  label: string;
  docsPath?: string;
  aliases?: string[];
  envVars?: string[];

  // 认证方法
  auth: ProviderAuthMethod[];

  // 目录发现
  catalog?: ProviderPluginCatalog;        // 在线目录(可能需要网络)
  staticCatalog?: ProviderPluginCatalog; // 离线静态目录

  // 模型解析钩子
  resolveDynamicModel?(ctx: ProviderResolveDynamicModelContext): ProviderRuntimeModel | null;
  normalizeModelId?(ctx: ProviderNormalizeModelIdContext): string | null;
  normalizeResolvedModel?(ctx: ProviderNormalizeResolvedModelContext): ProviderRuntimeModel | null;
  prepareRuntimeAuth?(ctx: ProviderPrepareRuntimeAuthContext): ProviderPreparedRuntimeAuth | null;

  // Replay/Compaction 策略
  buildReplayPolicy?(ctx: ProviderReplayPolicyContext): ProviderReplayPolicy | null;
  sanitizeReplayHistory?(ctx: ProviderSanitizeReplayHistoryContext): AgentMessage[] | null;

  // Wizard UI
  wizard?: ProviderPluginWizard;

  // ...
};

4.3 ChannelPlugin 结构

1
2
3
export type OpenClawPluginChannelRegistration = {
  plugin: ChannelPlugin;
};

五、激活计划(Activation Planner)

文件:src/plugins/activation-planner.ts

5.1 触发类型

1
2
3
4
5
6
7
export type PluginActivationPlannerTrigger =
  | { kind: "command"; command: string }
  | { kind: "provider"; provider: string }
  | { kind: "agentHarness"; runtime: string }
  | { kind: "channel"; channel: string }
  | { kind: "route"; route: string }
  | { kind: "capability"; capability: PluginManifestActivationCapability };

5.2 激活计划生成

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
export function resolveManifestActivationPlan(
  params: ResolveManifestActivationPlanParams,
): PluginActivationPlan {
  const registry = loadPluginManifestRegistryForPluginRegistry({
    config: params.config,
    workspaceDir: params.workspaceDir,
    env: params.env,
    includeDisabled: true,
  });

  const entries = registry.plugins
    .flatMap((plugin) => {
      // 按 origin 过滤
      if (params.origin && plugin.origin !== params.origin) return [];
      // 按 onlyPluginIds 过滤
      if (onlyPluginIdSet && !onlyPluginIdSet.has(plugin.id)) return [];

      const reasons = listManifestActivationTriggerReasons(plugin, params.trigger);
      if (reasons.length === 0) return [];

      return [{ pluginId: plugin.id, origin: plugin.origin, reasons }];
    })
    .toSorted((left, right) => left.pluginId.localeCompare(right.pluginId));

  return {
    trigger: params.trigger,
    pluginIds: [...new Set(entries.map((entry) => entry.pluginId))],
    entries,
    diagnostics: registry.diagnostics,
  };
}

5.3 原因列表

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
export type PluginActivationPlannerReason =
  | PluginActivationPlannerHintReason      // 运行时 Hint
  | PluginActivationPlannerManifestReason; // Manifest 声明

// Hint 原因
| "activation-agent-harness-hint"
| "activation-capability-hint"
| "activation-channel-hint"
| "activation-command-hint"
| "activation-provider-hint"
| "activation-route-hint"

// Manifest 原因
| "manifest-channel-owner"
| "manifest-command-alias"
| "manifest-hook-owner"
| "manifest-provider-owner"
| "manifest-setup-provider-owner"
| "manifest-tool-contract"

六、注册模式(Registration Mode)

文件:src/plugins/types.ts

1
2
3
4
5
6
export type PluginRegistrationMode =
  | "full"           // 完整运行时激活,可启动长生命周期副作用
  | "discovery"      // 只读能力发现,跳过 socket/worker/client 初始化
  | "setup-only"     // 仅渠道配置入口
  | "setup-runtime"  // 渠道配置 + 运行时通道入口
  | "cli-metadata";   // CLI 命令元数据收集

七、Provider 认证系统

7.1 认证方法

1
2
3
4
5
6
7
8
9
export type ProviderAuthMethod = {
  id: string;
  label: string;
  hint?: string;
  kind: ProviderAuthKind;  // "oauth" | "api_key" | "token" | "device_code" | "custom"
  wizard?: ProviderPluginWizardSetup;
  run: (ctx: ProviderAuthContext) => Promise<ProviderAuthResult>;
  runNonInteractive?: (ctx: ProviderAuthMethodNonInteractiveContext) => Promise<OpenClawConfig | null>;
};

7.2 认证上下文

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
export type ProviderAuthContext = {
  config: OpenClawConfig;
  env?: NodeJS.ProcessEnv;
  agentDir?: string;
  workspaceDir?: string;
  prompter: WizardPrompter;
  runtime: RuntimeEnv;
  opts?: ProviderAuthOptionBag;
  secretInputMode?: SecretInputMode;
  allowSecretRefPrompt?: boolean;
  isRemote: boolean;
  openUrl: (url: string) => Promise<void>;
  oauth: { createVpsAwareHandlers: typeof createVpsAwareOAuthHandlers };
};

7.3 Wizard Setup

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
export type ProviderPluginWizardSetup = {
  choiceId?: string;
  choiceLabel?: string;
  choiceHint?: string;
  assistantPriority?: number;
  assistantVisibility?: "visible" | "manual-only";
  groupId?: string;
  groupLabel?: string;
  onboardingScopes?: Array<"text-inference" | "image-generation">;
  modelAllowlist?: { allowedKeys?: string[]; initialSelections?: string[]; loadCatalog?: boolean };
  modelSelection?: { promptWhenAuthChoiceProvided?: boolean; allowKeepCurrent?: boolean };
};

八、插件命令系统

8.1 命令定义

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
export type OpenClawPluginCommandDefinition = {
  name: string;                    // 命令名(无前导斜杠)
  nativeNames?: Partial<Record<string, string>>;
  nativeProgressMessages?: Partial<Record<string, string>>;
  description: string;
  agentPromptGuidance?: readonly string[];
  acceptsArgs?: boolean;
  requireAuth?: boolean;
  requiredScopes?: OperatorScope[];
  ownership?: "plugin" | "reserved";
  handler: PluginCommandHandler;
};

8.2 命令上下文

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
export type PluginCommandContext = {
  senderId?: string;
  channel: string;
  channelId?: ChannelId;
  isAuthorizedSender: boolean;
  senderIsOwner?: boolean;
  gatewayClientScopes?: string[];
  sessionKey?: string;
  sessionId?: string;
  args?: string;
  commandBody: string;
  config: OpenClawConfig;
  from?: string;
  to?: string;
  accountId?: string;
  messageThreadId?: string | number;
  threadParentId?: string;
  requestConversationBinding: (params?: PluginConversationBindingRequestParams) => Promise<PluginConversationBindingRequestResult>;
  detachConversationBinding: () => Promise<{ removed: boolean }>;
  getCurrentConversationBinding: () => Promise<PluginConversationBinding | null>;
};

九、Plugin 生命周期

插件加载
    │
    ├─► Manifest 解析(loadPluginManifest)
    │      └─► 验证 configSchema、id、kind
    │
    ├─► 激活计划生成(resolveManifestActivationPlan)
    │      └─► 按 trigger 匹配激活条件
    │
    ├─► 插件运行时加载
    │      ├─► 模块加载(index.ts / index.js)
    │      ├─► register(api) 调用
    │      └─► activate(api) 调用
    │
    └─► 能力注册
          ├─► registerProvider() → ProviderRegistry
          ├─► registerChannel() → ChannelRegistry
          ├─► registerTool() → ToolRegistry
          └─► registerHook() → HookSystem

十、设计权衡

10.1 Manifest-First 原则

OpenClaw 采用 Manifest-First 设计——在加载插件运行时代码之前,先通过 Manifest 声明式地了解插件能力。

优势

  • Setup/配置 UI 可以在插件运行前显示
  • 激活计划可以在不导入插件的情况下生成
  • 诊断和错误可以在运行时前发现

代价

  • 双重定义(Manifest + 代码)可能存在不一致
  • 需要解析和验证逻辑

10.2 能力契约(Contracts)vs 运行时钩子

OpenClaw 使用 contracts 字段声明插件拥有的能力,替代运行时检查。

1
2
3
4
5
// 而非运行时检查
// if (plugin.registerProvider) { ... }

// 使用声明式契约
contracts?: { imageGenerationProviders?: string[] };

10.3 多格式兼容

通过 bundle-manifest.ts 支持 Codex、Claude、Cursor 三种第三方格式,降低用户迁移成本。


下一步

下一篇:04 - Agent Runtime:任务编排与执行,深入 Agent 运行时如何接收任务、调度工具、执行编排。


OpenClaw 源码剖析系列 · 2026 · skyseraph

SkySeraph
SkySeraph
AI for All & All for AI
留言 Comments