- 人工智能
- 大模型
- LLM 网关
- AI Agent
- 即时通讯
【免费下载链接】openclaw-zero-token
OpenClaw: Use All Major AI Models NO API Token! Claude/ChatGPT/Gemini/DeepSeek/Doubao/Grok/Qwen/Manus/Kimi
在 OpenClaw 中,src/plugin-sdk/是插件与核心(core)之间的公共契约(public contract):bundled 插件和第三方插件都通过它的 subpath 导入类型与辅助函数,而不是直接触碰核心内部实现。本文围绕仓库中plugin-sdk/CLAUDE.md这份边界规约展开,结合 入口清单脚本、entrypoints.ts、API 基线 与若干边界测试用例,说明这套“窄 subpath + 单一清单 + 可验证边界”的设计是如何落地、如何被自动化检查守护的,读完你可以掌握:如何正确引用 plugin-sdk 子路径、新增公开 subpath 时必须同步哪些位置、以及仓库用哪些测试与脚本把“不越界”变成硬约束。
一、定位:plugin-sdk 目录是“公开契约”而非普通目录
plugin-sdk/CLAUDE.md开篇即给出定位声明:
This directory is the public contract between plugins and core. Changes here can affect bundled plugins and third-party plugins.
这意味着对该目录的任何改动(新增导出、改名、删文件)都会波及仓库内所有 bundled 插件和外部第三方插件,因此它有一套独立于普通业务代码的变更纪律。从源码结构看,这个契约的物理形态是src/plugin-sdk/下的约 300 个文件:
- 根入口 index.ts:刻意保持极小的共享类型面,文件头注释明确写着 “Keep this entry intentionally tiny”;
- 每个功能域一个同名 subpath 文件,如 core.ts、provider-entry.ts、telegram.ts;
- 契约守护文件:entrypoints.ts、api-baseline.ts,以及一组
*-guardrails.test.ts、subpaths.test.ts等边界测试。
二、Single Source of Truth:文档与定义文件清单
规约文档把“事实来源”分为两类。写作时需特别注意:文档中列出的 5 个 SDK 专题文档(docs/plugins/sdk-overview.md、docs/plugins/sdk-entrypoints.md、docs/plugins/sdk-runtime.md、docs/plugins/sdk-migration.md、docs/plugins/architecture.md)在当前仓库快照的 docs/plugins 目录下并不存在(该目录实际包含 manifest.md、community.md 等);与主题最接近、且确实存在于仓库中的规划文档是 docs/refactor/plugin-sdk.md,它完整描述了“SDK + Runtime 两层架构”的目标设计。以下引用以该文档和规约原文为准,并按谨慎口径说明。
文档列出的 7 个定义文件全部真实存在,各自职责如下:
| 定义文件 | 职责 |
|---|---|
| package.json | 声明./plugin-sdk/*的 npmexports映射,每个 subpath 都指向./dist/plugin-sdk/<entry>.d.ts(types)与./dist/plugin-sdk/<entry>.js(default) |
| scripts/lib/plugin-sdk-entrypoints.json | subpath 入口的单一清单(manifest),当前含 238 个条目,首项为index,其后是core、provider-setup、telegram、discord等 |
| src/plugin-sdk/entrypoints.ts | 读取上述 JSON 清单,派生入口源文件映射、包导入规格、exports 表与 dist 产物列表 |
| src/plugin-sdk/api-baseline.ts | 基于 TypeScript Compiler API 生成 API 导出基线,用于检测公共 API 漂移 |
| src/plugin-sdk/plugin-entry.ts | 提供definePluginEntry规范入口辅助函数 |
| src/plugin-sdk/core.ts | 通道(channel)类插件的类型与辅助函数面 |
| src/plugin-sdk/provider-entry.ts | 提供defineSingleProviderPluginEntry,用于单一 provider 插件的快速装配 |
2.1 入口清单如何“驱动一切”
entrypoints.ts 全文只有 38 行,却是整个 subpath 体系的枢纽:
import pluginSdkEntryList from "../../scripts/lib/plugin-sdk-entrypoints.json" with { type: "json" }; export const pluginSdkEntrypoints = [...pluginSdkEntryList]; export const pluginSdkSubpaths = pluginSdkEntrypoints.filter((entry) => entry !== "index"); /** Map every SDK entrypoint name to its source file path inside the repo. */ export function buildPluginSdkEntrySources(entries: readonly string[] = pluginSdkEntrypoints) { return Object.fromEntries(entries.map((entry) => [entry, `src/plugin-sdk/${entry}.ts`])); } /** List the public package specifiers that should resolve to plugin SDK entrypoints. */ export function buildPluginSdkSpecifiers() { return pluginSdkEntrypoints.map((entry) => entry === "index" ? "openclaw/plugin-sdk" : `openclaw/plugin-sdk/${entry}`, ); } /** Build the package.json exports map for all plugin SDK subpaths. */ export function buildPluginSdkPackageExports() { return Object.fromEntries( pluginSdkEntrypoints.map((entry) => [ entry === "index" ? "./plugin-sdk" : `./plugin-sdk/${entry}`, { types: `./dist/plugin-sdk/${entry}.d.ts`, default: `./dist/plugin-sdk/${entry}.js`, }, ]), ); }从这段实现可以读出设计要点:
- JSON manifest 是唯一事实来源。入口名到源文件路径的映射是纯约定的
src/plugin-sdk/<entry>.ts,不扫描目录、不做推断,避免“目录里有文件但没暴露”或“暴露了但没有源文件”的漂移。 - 四种派生视图:
buildPluginSdkEntrySources(源文件路径)、buildPluginSdkSpecifiers(公开导入规格,如openclaw/plugin-sdk/telegram)、buildPluginSdkPackageExports(package.json exports 表)、listPluginSdkDistArtifacts(构建产物dist/plugin-sdk/<entry>.js/.d.ts清单)。任何一处分叉都会被后文的同步测试抓到。
2.2 package.json exports 与清单的强同步
index.test.ts 中有一条测试直接断言package.json的./plugin-sdk/*exports 必须等于buildPluginSdkPackageExports()的计算结果:
it("keeps package.json plugin-sdk exports synced with the manifest", async () => { // ... const currentPluginSdkExports = Object.fromEntries( Object.entries(packageJson.exports ?? {}).filter(([key]) => key.startsWith("./plugin-sdk")), ); expect(currentPluginSdkExports).toEqual(buildPluginSdkPackageExports()); });这与规约文档“Expanding The Boundary”一节的要求一一对应:新增或变更公开 subpath 时,package.jsonexports 必须与 scripts/lib/plugin-sdk-entrypoints.json、entrypoints.ts 保持对齐。当前 package.json 中已能看到./plugin-sdk、./plugin-sdk/core、./plugin-sdk/telegram、./plugin-sdk/provider-entry等条目,每条都是types+default双映射。
三、四条 Boundary Rules 逐条拆解
规约文档的“Boundary Rules”一节给出四条纪律,每一条在仓库里都有可验证的守护机制。
3.1 优先窄的、目的明确的 subpath,而不是宽泛的便捷 re-export
规则原文:“Prefer narrow, purpose-built subpaths over broad convenience re-exports.”
从源码结构看,这条规则落实在 index.ts 的自我约束上:根入口只导出类型(ChannelPlugin、OpenClawPluginApi、PluginRuntime等)和少量必要符号,且带一条弃用标记/** @deprecated Use OpenClawConfig instead */的ClawdbotConfig别名,说明连根入口的“存量宽导出”都在被逐步收窄。更硬的证据在 index.test.ts:
it("keeps the root runtime surface intentionally small", async () => { const runtimeExports = await readIndexRuntimeExports(); expect([...runtimeExports].toSorted()).toEqual([ "delegateCompactionToRuntime", "emptyPluginConfigSchema", "onDiagnosticEvent", "registerContextEngine", ]); });测试用正则遍历index.ts的export { ... } from与export * from链,断言根入口的运行时导出白名单恰好只有 4 个符号。另有does not expose runtime modules用例(index.test.ts)维护一份禁止出现在根入口的符号黑名单,包括chunkMarkdownText、buildMentionRegexes、monitorTelegramProvider、sendMessageSlack、probeSignal等——这些能力都被要求走专用 subpath(如telegram、slack或各*-runtime),而不是从根入口“顺路”拿到。
3.2 不暴露src/channels/**、src/agents/**、src/plugins/**等内部实现
规则原文:“Do not expose implementation convenience fromsrc/channels/**,src/agents/**,src/plugins/**, or other internals unless you are intentionally promoting a supported public contract.”
这条由仓库根目录的 extension-plugin-sdk-boundary.test.ts 与scripts/check-extension-plugin-sdk-boundary.mjs联合守护。该测试对三种越界模式分别断言清单(inventory)为空:
src-outside-plugin-sdk:extensions/**不允许 import 落在 plugin-sdk 之外的src/**路径;plugin-sdk-internal:禁止通过相对路径绕过(如从某 subpath 文件再../摸到 SDK 内部);relative-outside-package:禁止用相对导入跨出包边界。
const srcOutsideInventoryPromise = collectExtensionPluginSdkBoundaryInventory("src-outside-plugin-sdk"); // ... it("stays empty and sorted", async () => { const inventory = await srcOutsideInventoryPromise; expect(inventory).toEqual([]); // ... });“stays empty”的写法值得注意:它不是“检查当前没有越界”,而是把清单本身锁死为空集,任何一次新增越界导入都会让清单变非空、测试直接失败——这正是规约所要求的“intentionally promoting a supported public contract”才允许破例的落地方式。
3.3 优先api.runtime或聚焦的 SDK facade,而非让扩展直接摸宿主内部
规则原文:“Preferapi.runtimeor a focused SDK facade over telling extensions to reach into host internals directly.”
这条规则背后的架构在 docs/refactor/plugin-sdk.md 中被完整描述为两层结构:
- Plugin SDK(编译期、稳定、可发布):类型、辅助函数、配置工具;无运行时状态、无副作用;
- Plugin Runtime(执行面、注入式):所有触碰核心运行时行为的能力,统一经
OpenClawPluginApi.runtime注入,插件永远不 importsrc/**。
运行时注入点在 src/plugins/types.ts 中可以确认:
export type OpenClawPluginApi = { // ... /** * In-process runtime helpers for trusted native plugins. * * This surface is broader than hooks. Prefer hooks for third-party * automation/integration unless you need native registry integration. */ runtime: PluginRuntime; // ... };即:插件注册函数拿到的是api对象,需要文本分片、回复派发、路由解析、配对、媒体处理等宿主能力时,走api.runtime.channel.*等命名空间,而不是import { ... } from "openclaw/..."之外的深层路径。docs/refactor/plugin-sdk.md 的 Phase 5 还规划了 lint 规则 / CI 检查(“noextensions/**imports fromsrc/**”),与 3.2 节的 inventory 测试互为表里。
3.4 bundled 插件助手必须经extensions/<id>/api.ts+src/plugin-sdk/<id>.tsfacade 暴露
规则原文:当 core 或测试需要 bundled 插件的助手函数时,应通过extensions/<id>/api.ts与配套的src/plugin-sdk/<id>.tsfacade 暴露,而不是直接 importextensions/<id>/src/**或extensions/<id>/onboard.js。
配套的自动化护栏在 src/plugin-sdk/plugin-entry-guardrails.test.ts:
const CORE_PLUGIN_ENTRY_IMPORT_RE = /import\s*\{[^}]*\bdefinePluginEntry\b[^}]*\}\s*from\s*"openclaw\/plugin-sdk\/core"/; it("keeps bundled extension entry modules off direct definePluginEntry imports from core", async () => { // 遍历 extensions/ 下所有插件目录的 index.ts, // 命中上述正则即记为失败 expect(failures).toEqual([]); });它要求 bundled 插件入口不得直接从openclaw/plugin-sdk/core导入definePluginEntry,而应使用 plugin-entry.ts 暴露的规范入口辅助函数:
/** * Canonical entry helper for non-channel plugins. * * Use this for provider, tool, command, service, memory, and context-engine * plugins. Channel plugins should use `defineChannelPluginEntry(...)` from * `openclaw/plugin-sdk/core` so they inherit the channel capability wiring. */ export function definePluginEntry({ id, name, description, kind, configSchema, register }): DefinedPluginEntryprovider 类插件则有对应的defineSingleProviderPluginEntry(provider-entry.ts),它把 API Key 认证方法(createProviderApiKeyAuthMethod)、环境变量归并(resolveEnvVars)、wizard 装配(resolveWizardSetup)等样板封装掉,插件作者只需声明provider配置块。
四、API 基线:让“公共 API 漂移”可审计
规约文档把 “API baseline and export checks” 列为新增 subpath 时必须对齐的一项。其实现是 api-baseline.ts:
export type PluginSdkApiBaseline = { generatedBy: "scripts/generate-plugin-sdk-api-baseline.ts"; modules: PluginSdkApiModule[]; }; const GENERATED_BY = "scripts/generate-plugin-sdk-api-baseline.ts" as const; const DEFAULT_JSON_OUTPUT = "docs/.generated/plugin-sdk-api-baseline.json"; const DEFAULT_STATEFILE_OUTPUT = "docs/.generated/plugin-sdk-api-baseline.jsonl";其工作方式(从源码结构看):
- 用
ts.findConfigFile定位tsconfig.json,通过ts.createProgram建立全仓库类型程序(api-baseline.ts); - 对每个入口模块的每个导出符号,记录
exportName、kind(class/function/interface/type/enum/namespace/const/variable/unknown)、declaration文本与精确到行的源码位置(PluginSdkApiExport 类型定义); - 将绝对路径的声明导入规格归一化为仓库相对路径(
normalizeDeclarationImportSpecifier),保证基线文件跨机器可 diff; - 输出 JSON / JSONL 两份基线产物到
docs/.generated/,由scripts/generate-plugin-sdk-api-baseline.ts生成。
这意味着任何一次对 subpath 导出集合的增删改,都会体现在基线 diff 中——“删除或重命名公开导出”不再是一次无感知的顺手清理,而是留下可审查记录的显式动作,恰好呼应规约最后一条:Breaking removals or renames are major-version work, not drive-by cleanup.
五、Expanding The Boundary:扩张纪律的完整清单
规约文档“Expanding The Boundary”一节给出三条扩张纪律,可以整理成一份可操作的清单:
- 默认只做向后兼容的增量变更(Additive, backwards-compatible changes are the default)。
- 新增或变更公开 subpath 时,五处必须同步:
docs/plugins/*下的文档;- scripts/lib/plugin-sdk-entrypoints.json(清单);
- src/plugin-sdk/entrypoints.ts(派生逻辑,通常是零改动);
package.json的exports(由buildPluginSdkPackageExports()的同步测试守护);- API 基线与导出检查(
docs/.generated/plugin-sdk-api-baseline.json+ 各*.test.ts)。
- bundled provider 的 onboarding / config 助手:更新“生成的插件 facade”(即
src/plugin-sdk/<id>.ts系列),而不是让 core 测试或命令去触碰私有 extension 文件。 - 破坏性删除或改名 = major-version 级工作,禁止搭车式清理。
此外还有两条由package.jsonscripts 固化下来的 CI 级检查,与上述清单配套(见 package.json 的check与 lint 定义):
lint:plugins:no-monolithic-plugin-sdk-entry-imports:运行 scripts/check-no-monolithic-plugin-sdk-entry-imports.ts,防止代码退化为“一个大入口全量导入”的旧模式,强制走窄 subpath;pnpm build/build:plugin-sdk:dts:构建时先跑copy-plugin-sdk-root-alias.mjs与write-plugin-sdk-entry-dts.ts,为每个入口写出dist/plugin-sdk/<entry>.d.ts,与listPluginSdkDistArtifacts()声明的产物一一对应。
六、小结:一套可验证的契约工程
把plugin-sdk/CLAUDE.md的文本与仓库实现对照后,可以看到它并不是一份停留在纸面的规范:
| 规约条款 | 仓库中的验证机制 |
|---|---|
| 根入口保持极小 | index.test.ts 白名单断言(仅 4 个运行时符号) |
| subpath 清单单一来源 | entrypoints.ts 派生 exports/specifiers/dist 产物,index.test.ts断言 package.json 同步 |
| 禁止插件越界 import | extension-plugin-sdk-boundary.test.ts 三种模式 inventory 恒为空 |
| 插件入口走规范 helper | plugin-entry-guardrails.test.ts 正则扫描extensions/*/index.ts |
| 公共 API 漂移可审计 | api-baseline.ts 生成带行号的导出基线 |
对插件开发者而言,日常结论很直接:只从openclaw/plugin-sdk/<subpath>的窄入口导入(如openclaw/plugin-sdk/provider-entry、openclaw/plugin-sdk/telegram),运行时能力经注入的api.runtime获取;对核心维护者而言,任何一次触碰该目录的改动,都应以“清单、exports、文档、基线四处一致”作为合并前提。
附:关键文件索引
- 边界规约:plugin-sdk/CLAUDE.md(与 src/plugin-sdk/CLAUDE.md、src/plugin-sdk/AGENTS.md 内容一致)
- 架构规划:docs/refactor/plugin-sdk.md
- 入口清单:scripts/lib/plugin-sdk-entrypoints.json、src/plugin-sdk/entrypoints.ts
- 根入口与测试:src/plugin-sdk/index.ts、src/plugin-sdk/index.test.ts、src/plugin-sdk/subpaths.test.ts
- 入口 helper:src/plugin-sdk/plugin-entry.ts、src/plugin-sdk/provider-entry.ts、src/plugin-sdk/core.ts
- API 基线:src/plugin-sdk/api-baseline.ts
- 边界守护:extension-plugin-sdk-boundary.test.ts、src/plugin-sdk/plugin-entry-guardrails.test.ts、scripts/check-no-monolithic-plugin-sdk-entry-imports.ts
- 人工智能
- 大模型
- LLM 网关
- AI Agent
- 即时通讯
【免费下载链接】openclaw-zero-token
OpenClaw: Use All Major AI Models NO API Token! Claude/ChatGPT/Gemini/DeepSeek/Doubao/Grok/Qwen/Manus/Kimi
相关推荐
OpenClaw 插件边界(Extensions Boundary)权威指南:Plugin SDK 契约、边界规则与扩展机制
OpenClaw 插件边界(Extensions Boundary)权威指南:Plugin SDK 契约、边界规则与扩展机制 本文围绕仓库根目录 AGENTS.
人工智能大模型LLM 网关AI Agent即时通讯OpenClaw 扩展边界规范:内置插件如何通过 Plugin SDK 与清单契约与核心解耦
OpenClaw 扩展边界规范:内置插件如何通过 Plugin SDK 与清单契约与核心解耦 本文基于 OpenClaw 仓库中的扩展边界规范文档 extens
AI 应用AI Agent交互助手后端即时通讯网关OpenClaw Plugin SDK 边界契约:从 plugin-sdk/AGENTS.md 到公共入口点的完整治理指南
OpenClaw Plugin SDK 边界契约:从 plugin sdk/AGENTS.md 到公共入口点的完整治理指南 本文以 OpenClaw 仓库中的
人工智能大模型LLM 网关AI Agent即时通讯
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考