
1. OpenClaw.NET 兼容性目录到底解决什么问题OpenClaw.NET 的 Compatibility Catalog兼容性目录是一份集中管理插件与技能预期行为的清单文件路径固定在compat/public-smoke.json。它要回答的核心问题是某个 NPM 插件或 ClawHub 技能在当前运行时到底能不能正常加载、能不能正确暴露工具与技能、失败时会不会给出明确诊断码。对于做 NativeAOT 发布的团队来说这个问题尤其尖锐因为 AOT 编译会砍掉大量反射路径任何依赖动态加载的插件都可能在发布后才暴露问题。我见过太多项目把兼容性验证散落在各个测试文件里新增一个插件就补一段测试代码时间一长没人说得清哪些插件被验证过、验证到什么程度。Compatibility Catalog 把这个过程收敛成一份 JSON 清单构建期作为嵌入资源编译进OpenClaw.Core.dll运行时不需要访问文件系统对 NativeAOT 完全友好。清单里的每一条 entry 就是一份契约声明这个插件应该以什么状态加载、应该暴露哪些工具名、如果预期不兼容应该报出哪些诊断码。适合谁用三类人最直接受益。第一类是负责发布流程的工程师需要在 CI 里跑回归验证确认新版本没有破坏已有插件。第二类是外部集成方想自查自己的插件是否在兼容清单里、预期行为是什么。第三类是社区贡献者新增插件或技能时需要往清单里追加条目并本地验证。这份指南会从清单结构讲到 NativeAOT 下的加载机制再给出 CLI 与 REST API 两条调用路径的完整验证步骤最后把 TaoToken 的统一 Key 与 API 通道接进来让整个验证链路可以程序化消费。需要先明确一点Compatibility Catalog 不是插件市场也不是运行时注册表。它是一份静态清单描述的是预期实际加载结果由烟雾测试去断言。清单和测试代码分离好处是新增条目不需要改测试逻辑坏处是清单字段写错时编译期或运行期才会报出来。下面从清单结构开始拆。2. TaoToken 前置准备与 NativeAOT 加载机制在把 CLI 和 REST API 跑通之前需要先理解清单在 NativeAOT 下是怎么被加载的以及 TaoToken 的 Key 和 API 通道怎么接进来。这两件事看似无关实际上都影响验证链路能否在 CI 里稳定运行。先说 NativeAOT 的加载机制。compat/public-smoke.json在.csproj里以EmbeddedResource方式编译进OpenClaw.Core.dll运行时通过程序集资源流读取没有任何文件 I/O。反序列化走的是CoreJsonContext这是一个基于JsonSerializerContext的源生成上下文编译期就生成了序列化代码完全规避反射。这意味着如果你往清单里加了新字段但忘了在CoreJsonContext里声明对应类型AOT 模式下启动就会报缺少元数据。这是 NativeAOT 项目最常见的坑之一JIT 模式下可能正常AOT 发布后才炸。插件与主进程之间的通信走plugin-bridge.mjs协议是 JSON-RPC over stdio。这样做的好处是避免在主进程里动态加载托管程序集AOT 对动态加载的支持本来就有限。插件本身是 Node.js 侧的产物通过npx和clawhub命令链路安装所以 CI 环境里 Node.js 20 是硬依赖。再说 TaoToken 的接入。TaoToken 提供统一的 Key 和 API 通道Base URL 是https://taotoken.net/api官网在https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content。在 OpenClaw.NET 的兼容性验证场景里TaoToken 主要承担两个角色一是作为插件配置里的模型通道configJson字段里的apiKey可以指向 TaoToken 的 Key二是作为 REST API 验证时的外部调用目标用来确认 Gateway 的通道就绪状态。你需要先拿到一个 TaoToken 的 API Key。打开 API Keys 管理页https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite创建一个新 Key复制保存。这个 Key 后面会用在两处插件条目的configJson里以及 REST API 验证时的请求头。如果你还没决定用哪个模型可以先到模型对话页https://taotoken.net/chat?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite试一下通道是否正常。对于长期做编码和 Agent 场景的团队Coding Plan 页面https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite里有套餐说明这里不展开价格只提一点兼容性烟雾测试在 CI 里是定时跑的调用量取决于清单条目数量选套餐时把这个因素算进去。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有完整的参数说明。控制台在https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite可以查看调用记录和配额。如果你用的是 Claude Code 类的工具链Anthropic 兼容入口在https://taotoken.net/claude-code-anthropic?utm_sourcetaotoken_aicg_blog_endutm_contentclaude_code_anthropicutm_campaignrewrite不过 OpenClaw.NET 的验证链路主要走 REST这个入口作为备选了解即可。前置准备清单Node.js 20、.NET SDK版本跟项目global.json对齐、一个 TaoToken API Key、OPENCLAW_PUBLIC_SMOKE1环境变量。这四样齐了后面的步骤才能跑通。3. 可复制的目录配置片段与接入参数这一节给出可以直接复制进项目的配置片段。路径和字段名都跟 OpenClaw.NET 的实际约定一致不要随意改名否则编译期校验或运行期反序列化会失败。先看清单顶层结构。compat/public-smoke.json是一个带版本号的 JSON 对象entries字段是条目数组{ version: 2, entries: [ { id: agentseo-plugin, category: ts-jiti-plugin, kind: npm-plugin, spec: agentseo/openclaw-plugin0.1.4, packageName: agentseo/openclaw-plugin, pluginId: agentseo, expectedStatus: compatible, configJson: {\apiKey\:\test_key\}, expectedToolNames: [agentseo_audit, agentseo_keywords], expectedSkillNames: [agentseo] } ] }字段分三组。通用字段所有条目必填id是场景唯一标识在entries里不能重复category是场景分类取值有pure-skill、js-tool-plugin、ts-jiti-plugin、config-schema-plugin、unsupported-surface-pluginkind是资源类型取值clawhub-skill或npm-plugin。技能专用字段在kind clawhub-skill时必填slug是 ClawHub 里的技能标识符version是 SemVer 版本expectedRelativePath是安装后的预期相对路径比如skills/my-skill/SKILL.md。插件专用字段在kind npm-plugin时必填spec是 NPM 包规范packageName是包名pluginId是插件唯一标识expectedStatus是预期兼容性状态取值compatible或incompatible。注意expectedStatus是必填的编译期校验会拒绝缺失该字段的条目这是负面场景和正面场景的分界线。可选字段里configJson是 JSON 字符串形式的示例配置注意它是字符串不是对象里面的引号要转义。installExtraPackages是需要额外安装的依赖包列表。expectedToolNames和expectedSkillNames只在compatible场景下用用来断言工具和技能是否完整暴露。expectedDiagnosticCodes只在incompatible场景下用断言错误码集合。把 TaoToken 的 Key 接进configJson时写法是这样{ id: taotoken-channel-plugin, category: js-tool-plugin, kind: npm-plugin, spec: your-org/openclaw-plugin1.0.0, packageName: your-org/openclaw-plugin, pluginId: taotoken-channel, expectedStatus: compatible, configJson: {\apiKey\:\你的TaoTokenKey\,\baseUrl\:\https://taotoken.net/api\}, expectedToolNames: [channel_probe], expectedSkillNames: [channel-check] }这里baseUrl用https://taotoken.net/api不要加 UTM 参数API 端点保持干净。apiKey在 CI 里不要硬编码用环境变量注入清单里可以写占位符测试代码在加载前做替换。再看.csproj里的嵌入资源配置确保清单被编译进程序集ItemGroup EmbeddedResource Includecompat/public-smoke.json LogicalNameOpenClaw.Core.compat.public-smoke.json/LogicalName /EmbeddedResource /ItemGroupLogicalName决定了运行时读取资源用的名字测试代码里通过Assembly.GetManifestResourceStream拿到的就是这个逻辑名。如果你改了LogicalName记得同步改读取代码。CoreJsonContext的声明也要跟上新增字段类型必须在这里注册[JsonSourceGenerationOptions(PropertyNamingPolicy JsonKnownNamingPolicy.CamelCase)] [JsonSerializable(typeof(PublicSmokeManifest))] [JsonSerializable(typeof(CompatibilityEntry))] [JsonSerializable(typeof(ListCompatibilityEntry))] internal partial class CoreJsonContext : JsonSerializerContext { }PublicSmokeManifest和CompatibilityEntry是你的模型类字段名跟 JSON 里的 camelCase 对应。如果清单里加了新字段但模型类没加反序列化会静默忽略如果模型类加了字段但CoreJsonContext没注册AOT 模式下会报缺少元数据。这两个方向都要检查。4. CLI 与 REST API 验证步骤及成功结果配置就位后开始验证。先跑 CLI 路径再跑 REST API 路径最后跑烟雾测试。每一步都给出预期输出方便你对照。CLI 路径。OpenClaw CLI 提供compatibility catalog子命令简写是compat catalog。先看全量清单openclaw compatibility catalog预期输出是表格形式每行一个条目列出id、category、kind、expectedStatus。如果清单为空或加载失败会提示资源未找到这时候回去检查.csproj的EmbeddedResource配置。按状态过滤openclaw compatibility catalog --status compatible openclaw compatibility catalog --status incompatible按类型和分类组合过滤openclaw compatibility catalog --kind npm-plugin --category ts-jiti-pluginJSON 格式输出适合程序化消费openclaw compatibility catalog --jsonJSON 输出的结构跟清单本身一致但会经过PublicCompatibilityCatalog.CreateCatalog()转换成富目录多出subject、installCommand、summary、scenarioType、guidance这些派生字段。比如installCommand对技能是openclaw clawhub install {slug}对插件是openclaw plugins install {spec} --dry-run。scenarioType把expectedStatus映射成positive或negative。REST API 路径。Gateway 通过/api/integration/compatibility路由族暴露清单curl -s http://localhost:5000/api/integration/compatibility/catalog带过滤参数curl -s http://localhost:5000/api/integration/compatibility/catalog?compatibilityStatuscompatible curl -s http://localhost:5000/api/integration/compatibility/catalog?kindnpm-plugincategoryts-jiti-plugin/catalog端点支持compatibilityStatus、kind、category三个查询参数。/export端点返回完整兼容性报告包含运行时模式AOT / JIT、安全态势、通道就绪状态curl -s http://localhost:5000/api/integration/compatibility/export如果 Gateway 前面有鉴权请求头里带上 TaoToken 的 Keycurl -s -H Authorization: Bearer 你的TaoTokenKey \ http://localhost:5000/api/integration/compatibility/export成功结果的特征/catalog返回的 JSON 里entries数组长度跟清单一致每条 entry 的expectedStatus字段存在且取值合法。/export返回的报告里runtimeMode字段显示AOT或JITchannelReadiness显示各通道的就绪状态。如果runtimeMode是AOT但清单加载失败多半是CoreJsonContext缺类型声明。烟雾测试路径。设置环境变量后跑测试export OPENCLAW_PUBLIC_SMOKE1 dotnet test OpenClaw.Net.slnx --filter CategoryPublicSmoke测试类PublicCompatibilitySmokeTests会读取清单并迭代执行。对 ClawHub 技能通过npx clawhub安装并校验expectedRelativePath文件存在。对compatible插件执行安装、加载然后断言expectedToolNames和expectedSkillNames完整暴露。对incompatible插件执行安装、加载断言加载失败且诊断码集合至少包含expectedDiagnosticCodes里的全部条目。成功输出是测试全部通过TRX 报告里Outcome为Passed。如果某个条目断言失败报告里会指出是哪个id、哪个断言维度失败。CI 里这个作业失败即视为整个流水线失败需要在合并前修复。5. 本篇常见错误排查这一节对照真实报错给出原因和解决方案。报错信息按出现频率排序。报错一plugin failed to load测试报告里出现这个通常是configJson格式错误或字段类型不匹配。configJson是字符串里面的 JSON 要正确转义。比如{apiKey:test_key}写成字符串是{\apiKey\:\test_key\}少一个反斜杠就解析失败。解决方案是先用--dry-run验证openclaw plugins install your-org/openclaw-plugin1.0.0 --dry-run--dry-run会走配置校验但不实际加载能提前暴露 schema 问题。报错二expected tool not found插件加载成功但工具没暴露。原因可能是插件未声明该工具或者expectedToolNames里工具名拼写错误。校对时注意大小写和下划线工具名是精确匹配。解决方案是把插件实际暴露的工具名打印出来对照openclaw plugins inspect your-org/openclaw-plugin1.0.0 --tools报错三编译期npm-plugin must declare expectedStatus新条目缺少expectedStatus字段。NPM 插件条目必须显式指定compatible或incompatible编译期校验会拒绝缺失该字段的条目。解决方案是补上字段不要留空。报错四烟雾测试整体未运行环境变量没设置。OPENCLAW_PUBLIC_SMOKE1必须设置否则测试整体跳过报告里显示Skipped而不是Passed。CI 里检查这个变量是否在作业级别注入。报错五clawhub安装失败Node.js 未安装或版本过低。npx clawhub需要 Node.js 20。解决方案是安装 Node.js 20 并确保npx在 PATH 里。CI 里用actions/setup-node指定版本。报错六expectedDiagnosticCodes不匹配错误码命名变更或新增。诊断码有config_one_of_mismatch、unsupported_cli_registration、unsupported_surface_call、schema_required_missing等。如果插件升级后错误码变了清单里的expectedDiagnosticCodes要同步更新。查阅最新诊断码列表必要时同步更新清单。报错七AOT 模式启动报缺少元数据新增字段未在CoreJsonContext中声明。这是 NativeAOT 特有的坑JIT 模式下可能正常AOT 发布后才报。解决方案是在源生成上下文里添加对应类型[JsonSerializable(typeof(你的新类型))] internal partial class CoreJsonContext : JsonSerializerContext { }报错八local proxy failed或401REST API 验证时出现401检查请求头里的Authorization是否正确带上 TaoToken Key。出现local proxy failed检查 Gateway 是否正常启动、端口是否被占用。如果 Gateway 配置了上游通道确认baseUrl指向https://taotoken.net/api且没有多余路径。报错九reading choices相关错误这个报错通常出现在调用模型通道时响应体里没有choices字段。检查请求体格式是否符合 OpenAI 兼容规范model字段是否填了 TaoToken 支持的模型 ID。如果用的是 Claude Code 类入口确认走的是 Anthropic 兼容路径而不是 OpenAI 路径。排查顺序建议先确认环境变量和 Node.js 版本再确认清单 JSON 语法然后确认CoreJsonContext类型注册最后确认 TaoToken Key 和 Base URL。大部分问题在前两步就能定位。6. 把验证链路接进 CI 与后续接入清单和验证步骤跑通后下一步是接进 CI。GitHub Actions 里public-compatibility-smoke作业承担回归验证触发条件是定时执行或手动派发依赖 Node.js 20执行流程是dotnet test加--filter CategoryPublicSmoke报告产物是 TRX 格式并作为 artifact 上传。失败语义是任意条目断言失败即整个作业失败。贡献新条目的流程在compat/public-smoke.json的entries数组末尾追加条目确保必填字段完整。NPM 插件必须包含expectedStatus、spec、packageName、pluginId技能必须包含slug、version、expectedRelativePath。本地设置OPENCLAW_PUBLIC_SMOKE1后执行dotnet test OpenClaw.Net.slnx --filter CategoryPublicSmoke。如果引入了新的category或kind需要同步升级清单顶层version字段、更新PublicCompatibilityCatalog中的枚举与转换逻辑、更新文档里的场景分类表格。数据转换逻辑值得单独说一下。清单在运行时通过PublicCompatibilityCatalog.CreateCatalog()转换成富目录核心映射规则是subject按slug、packageName、pluginId、id的优先级取第一个非空值installCommand对技能是openclaw clawhub install {slug}对插件是openclaw plugins install {spec} --dry-runsummary根据category和expectedStatus生成人类可读描述scenarioType把compatible映射成positive、incompatible映射成negativeguidance是上下文相关的操作建议比如配置 schema 错误时提示参考插件文档。TaoToken 的接入点在这里也清晰了插件条目的configJson里带 TaoToken Key 和 Base URLREST API 验证时请求头带 KeyCI 里 Key 通过环境变量注入。如果你需要程序化消费兼容性报告/export端点的输出可以直接对接外部门户或归档系统。接入文档在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteAPI Keys 管理在https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite控制台在https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite。最后提一个实操细节清单里的version字段是顶层版本号不是插件版本。新增category或kind时才需要升级它普通条目追加不用动。这个字段的作用是让消费方知道清单结构有没有破坏性变更。如果你在 CI 里缓存了清单版本号变了要重新拉取。