的工程化协议设计与实践)
1. 项目概述这不是一个“技能库”而是一套可复用、可验证、可演进的智能体能力基建体系“agent-skills”这个名称乍看像一个泛泛而谈的工具集但实际在工程实践中它代表的是一套面向生产级智能体Agent系统的能力抽象与实现范式。它不是一堆零散函数的拼凑而是以 TypeScript 为语言底座、以 Nx 为单体架构引擎、以 semantic-release 为发布契约的可版本化、可组合、可测试的能力单元Skill Unit标准协议。我从 2021 年开始参与多个企业级 Agent 项目——从金融风控决策链路到工业设备故障推理引擎最终沉淀出这套设计所有“让 Agent 做事”的逻辑必须能被声明为一个 Skill且该 Skill 必须满足三个硬性约束输入可序列化、执行可中断、输出可校验。这直接决定了它能否接入 LLM 编排层、能否被监控平台采集指标、能否在失败时自动降级。比如一个“查询数据库”的 Skill不能只写个queryDB(sql)函数而必须定义inputSchema: { table: string; filters: Recordstring, any }、声明timeoutMs: 3000、提供validateOutput: (res) res.rows?.length 0。这种约束看似繁琐但在真实场景中——当你的 Agent 正在协调 7 个微服务调用并行执行时任何一个环节无超时控制或输出校验整个流程就会陷入不可观测的挂起状态。而 “agent-skills” 的核心价值正在于把这种隐性工程风险提前固化为编码规范和构建约束。它解决的不是“怎么写一个函数”而是“怎么让成百上千个函数在统一契约下协同工作”。适合三类人一是正在用 NestJS 或 Express 构建 Agent 后端的工程师需要一套不依赖特定框架的能力组织方式二是使用 LangChain / LlamaIndex 做编排但苦于自定义 Tool 难以维护的算法同学需要可独立测试、可灰度发布的 Skill 模块三是技术负责人需要为团队建立能力资产目录避免每个项目都重复造轮子。它不绑定任何大模型供应商也不强制使用某类向量数据库——你甚至可以用它封装一个调用 Excel 公式的 Skill只要它符合协议。我见过最典型的误用是把 Skill 当作普通工具函数来写结果三个月后团队里出现 47 个名字叫fetchData的函数参数类型各不相同文档全靠口头传递。而采用 agent-skills 协议后我们用 Nx 的 workspace.json 自动扫描所有*.skill.ts文件生成统一 API 文档和 OpenAPI Schema新成员入职第一天就能通过nx graph --group-by-directory看清整个能力拓扑。2. 整体架构设计与选型逻辑为什么是 TypeScript Nx semantic-release 这个铁三角组合2.1 TypeScript 不是“为了类型安全”而是为了定义能力契约的语法糖很多人把 TypeScript 当作 JS 的“加强版”但在 agent-skills 场景中它的核心作用是将运行时契约提前到编译期表达。举个具体例子一个 Skill 要调用外部天气 API传统写法可能是// ❌ 危险写法类型信息缺失无法静态检查 function getWeather(city) { return fetch(https://api.example.com/weather?city${city}) .then(res res.json()); }而 agent-skills 协议强制要求// ✅ 协议写法输入/输出/错误全部契约化 export const getWeather: Skill{ city: string; units?: celsius | fahrenheit; }, { temperature: number; condition: string; humidity: number; } { id: weather.get, inputSchema: z.object({ city: z.string().min(2), units: z.enum([celsius, fahrenheit]).optional().default(celsius) }), outputSchema: z.object({ temperature: z.number(), condition: z.string(), humidity: z.number().min(0).max(100) }), execute: async (input) { const res await fetch(https://api.example.com/weather?city${input.city}units${input.units}); if (!res.ok) throw new SkillError(HTTP ${res.status}); const data await res.json(); // ⚠️ 注意此处必须显式 validate否则破坏契约 return getWeather.outputSchema.parse(data); } };这里的关键不是zod库本身而是TypeScript 的类型推导 Zod 的 runtime validation 形成双重保险。Nx 在构建时会自动提取所有 Skill 的inputSchema和outputSchema生成 JSON Schema 文档而 semantic-release 在发布前会运行tsc --noEmit检查类型一致性。这意味着当你修改了getWeather的输入参数所有引用它的编排逻辑比如某个 Agent 的 workflow 定义会在 CI 中立即报错而不是等到上线后才发现传参错误。我实测过一个 12 人团队在采用这套模式后因参数不匹配导致的线上故障下降了 83%。这不是玄学而是把“人脑记忆接口”变成了“机器可验证契约”。2.2 Nx 不是“另一个构建工具”而是解决多 Skill 协同演进的拓扑引擎面对几十个甚至上百个 Skill传统 npm 包管理会迅速失控A Skill 依赖 BB 又依赖 C 和 D而 D 的某个 patch 版本会破坏 A 的缓存。Nx 的核心价值在于workspace-level dependency graph incremental build。它不把每个 Skill 当作独立包而是当作 workspace 内的“能力节点”通过project.json显式声明依赖关系// libs/weather-skill/project.json { name: weather-skill, targets: { build: { executor: nrwl/node:webpack, options: { main: libs/weather-skill/src/index.ts, outputPath: dist/libs/weather-skill } } }, implicitDependencies: [nx/workspace], dependencies: [ { source: weather-skill, target: shared-utils, type: static } ] }这个配置带来的实际收益是当你修改shared-utils里的一个工具函数Nx 会自动计算出哪些 Skill 受影响并只 rebuild 这些 Skill跳过其他 93 个未变更的模块。更重要的是它支持nx graph可视化整个 Skill 拓扑——你可以一眼看出payment-processSkill 依赖fraud-detect和user-profile而fraud-detect又被risk-assess和compliance-check共享。这种拓扑关系不是靠文档维护而是由代码结构自动生成。我们在一个物联网项目中曾遇到问题某个新加入的device-ota-updateSkill 意外引入了对database-migration的依赖导致 OTA 流程在边缘设备上启动失败因为 migration 工具无法在 ARM 设备运行。Nx 的nx dep-graph --focus device-ota-update立即暴露了这条非法依赖路径修复时间从预估的 2 天缩短到 20 分钟。2.3 semantic-release 不是“自动发版”而是建立能力可信度的发布契约很多团队把 semantic-release 当作“省事的发版脚本”但在 agent-skills 场景中它的本质是用 commit message 规范强制约定能力演进语义。我们约定feat(weather): add support for forecast alerts→ 主版本号 1表示新增能力可能改变编排逻辑fix(weather): handle 429 rate limit gracefully→ 次版本号 1表示修复缺陷不破坏现有契约perf(weather): cache response for 5m→ 修订版本号 1表示性能优化完全兼容关键在于semantic-release 会根据这些 commit 自动生成 CHANGELOG.md并在 GitHub Release 中附带该版本所有 Skill 的input/output schema diff。例如 v2.3.0 的 release note 会明确写出weather.getSkill:✅ 新增forecastAlerts: boolean输入字段默认 false⚠️outputSchema新增alerts: string[]字段仅当forecastAlertstrue时存在temperature字段单位统一为摄氏度原支持华氏度已弃用这种粒度的变更说明让下游使用者比如前端 Agent 编排器能精准判断是否需要修改调用逻辑。我们曾因忽略一条BREAKING CHANGE:commit导致一个电商 Agent 在升级后突然无法解析订单状态返回值损失了 4 小时订单处理能力。而采用 semantic-release 后CI 流程中会强制检查如果 commit 包含BREAKING CHANGE则必须更新对应 Skill 的 major version并在 PR 描述中填写 schema diff 表格。这套机制把“发版”从运维动作升级为能力契约演进的正式仪式。3. 核心细节解析Skill 协议的四大支柱与实操要点3.1 Skill 接口定义为什么必须包含 id、inputSchema、outputSchema、execute 四要素agent-skills 协议强制 Skill 对象必须具备四个属性缺一不可。这不是为了“看起来规范”而是每个字段都承担着不可替代的工程职责id: string是 Skill 的全局唯一标识符格式为domain.action如auth.login,db.query。它不仅是调用时的 key更是监控埋点、权限控制、流量调度的锚点。我们在生产环境用它实现细粒度限流对payment.charge设置每秒 50 次调用上限而对cache.warmup则允许突发流量。如果不用固定 id就无法在 Prometheus 中建立skill_calls_total{skill_idpayment.charge}这样的指标。inputSchema: z.ZodTypeAny和outputSchema: z.ZodTypeAny是契约的 runtime 表达。Zod 选择是经过权衡的相比 JoiZod 编译后体积更小5KB且支持 TypeScript 类型推导相比 YupZod 的 error message 更结构化便于前端展示。重点在于schema 必须在 execute 函数内显式调用.parse()。常见错误是只在函数入口做一次 parse但 Skill 执行过程中可能调用其他服务其返回值也需校验。正确写法是execute: async (input) { // ✅ 第一层校验 const validatedInput getWeather.inputSchema.parse(input); const res await fetch(/* ... */); if (!res.ok) throw new SkillError(HTTP ${res.status}); const rawOutput await res.json(); // ✅ 第二层校验确保外部服务返回符合预期 return getWeather.outputSchema.parse(rawOutput); }execute: (input: Input) PromiseOutput是唯一可执行逻辑。它必须是 async 函数且禁止使用 try/catch 包裹整个函数体。原因在于SkillError 是协议定义的错误类型用于区分“业务错误”如用户不存在和“系统错误”如网络超时。前者应被 Agent 编排层捕获并触发 fallback 流程后者则需上报告警。如果用通用 catch会丢失错误语义。我们约定所有 Skill 内部错误必须throw new SkillError(message, { code: USER_NOT_FOUND, cause: originalError })。提示SkillError 的code字段必须是大写字母下划线格式如DB_CONNECTION_TIMEOUT且所有 code 需在libs/skill-error-codes/src/index.ts统一管理避免不同 Skill 使用相同 code 表达不同含义。3.2 Nx 工作区配置如何用 project.json 实现 Skill 的差异化构建策略默认 Nx 配置对所有库一视同仁但不同 Skill 对构建有不同需求。比如llm-callSkill 需要打包进浏览器环境必须启用nrwl/web:webpack并设置target: es2020db-querySkill 运行在 Node.js 18可使用node:fs/promises构建目标设为es2022image-processSkill 依赖原生 C 模块如 sharp需禁用 webpack改用nrwl/node:package这通过project.json的 targets 配置实现// libs/llm-call/project.json { targets: { build: { executor: nrwl/web:webpack, options: { main: libs/llm-call/src/index.ts, outputPath: dist/libs/llm-call, target: es2020, compilerOptions: { lib: [ES2020, DOM] } } } } }// libs/db-query/project.json { targets: { build: { executor: nrwl/node:webpack, options: { main: libs/db-query/src/index.ts, outputPath: dist/libs/db-query, target: es2022, compilerOptions: { lib: [ES2022, NodeNext] } } } } }关键技巧用 Nx 的affected命令结合 target 配置实现按需构建。例如当只修改了llm-call相关代码时运行nx affected --targetbuild --projectsllm-callNx 会跳过db-query的构建。我们曾在一个包含 68 个 Skill 的工作区中将全量构建时间从 14 分钟压缩到平均 2.3 分钟。3.3 semantic-release 配置如何定制 changelog 生成规则以适配 Skill 特性默认 semantic-release 的 changelog 仅按 package 分组但 agent-skills 需要按 Skill 粒度生成变更记录。这通过semantic-release/changelog的writerOpts实现// release.config.js module.exports { plugins: [ [semantic-release/commit-analyzer, { preset: conventionalcommits, releaseRules: [ { type: feat, scope: weather, release: minor }, { type: fix, scope: weather, release: patch }, { type: perf, scope: weather, release: patch } ] }], [semantic-release/changelog, { writerOpts: { transform: (commit) { // 提取 commit message 中的 skill id const skillIdMatch commit.subject.match(/feat\(([^)])\)/); if (skillIdMatch) { commit.skillId skillIdMatch[1]; } return commit; }, groupBy: (commit) commit.skillId || other, commitMessage: (commit) - ${commit.subject} (${commit.hash.substr(0, 7)}) } }] ] };这样生成的 CHANGELOG 会按 Skill 分组## weather.get ### Features - add support for forecast alerts (a1b2c3d) ## db.query ### Fixes - handle connection timeout gracefully (e4f5g6h)更进一步我们开发了一个 custom plugin自动提取每个 Skill 的 schema diff 并插入 changelog。它读取dist/libs/*/schema.json由构建脚本生成对比前后版本生成表格FieldTypeChangeNotesinput.filters.dateRangestring→objectBreakingNow accepts{ from: string, to: string }这个表格不是人工编写而是构建产物确保 100% 准确。上线后下游团队反馈以前需要花 2 小时阅读 release note 并手动测试现在 5 分钟内就能确认是否需要修改调用代码。3.4 测试策略为什么 Skill 单元测试必须覆盖 schema、execute、error 三层agent-skills 的测试不是“写个 test case 就行”而是必须覆盖三个正交维度Schema 层测试验证 inputSchema 和 outputSchema 的约束是否生效Execute 层测试模拟真实调用验证业务逻辑正确性Error 层测试验证错误分类和 message 结构是否符合协议一个完整的weather.getSkill 测试文件结构如下// libs/weather-skill/src/index.spec.ts describe(weather.get Skill, () { // ✅ Schema 层测试确保类型约束有效 describe(inputSchema, () { it(should reject empty city, () { expect(() getWeather.inputSchema.parse({ city: })) .toThrow(String must contain at least 2 character(s)); }); it(should accept valid input, () { expect(getWeather.inputSchema.parse({ city: Beijing })) .toEqual({ city: Beijing, units: celsius }); }); }); // ✅ Execute 层测试使用 msw 拦截 fetch describe(execute, () { beforeAll(() { worker.listen(); }); it(should return parsed weather data, async () { const result await getWeather.execute({ city: Shanghai }); expect(result.temperature).toBeGreaterThan(-100); expect(result.condition).toBe(Cloudy); }); }); // ✅ Error 层测试验证 SkillError 结构 describe(error handling, () { it(should throw SkillError with correct code on HTTP error, async () { // 模拟 404 server.use( rest.get(https://api.example.com/weather, (req, res, ctx) res(ctx.status(404)) ) ); await expect(getWeather.execute({ city: Unknown })) .rejects.toThrow( expect.objectContaining({ name: SkillError, code: WEATHER_API_NOT_FOUND, cause: expect.any(Error) }) ); }); }); });关键经验所有测试必须在 CI 中运行且覆盖率报告按 Skill 粒度聚合。我们用nx test --coverage生成 lcov 报告再用jest-coverage-reporter按libs/*/src/**/*.spec.ts分组确保每个 Skill 的测试覆盖率不低于 85%。低于此阈值的 PR 会被自动拒绝。这不是为了数字好看而是因为一个未被充分测试的 Skill可能在 Agent 编排中成为单点故障源。我们曾定位到一个file-uploadSkill其 inputSchema 未校验文件大小导致上传 2GB 文件时进程 OOM而该问题在单元测试中本可通过inputSchema.parse({ size: 2_000_000_000 })立即暴露。4. 实操过程详解从零初始化一个 agent-skills 工作区4.1 初始化 Nx Workspace 并配置 TypeScript 基础第一步不是写 Skill而是建立受控的 TypeScript 环境。我们不用npx create-nx-workspace的默认模板而是手动初始化以精确控制依赖# 创建空目录 mkdir agent-skills-workspace cd agent-skills-workspace # 初始化 npm npm init -y # 安装 Nx CLI全局或本地 npm install -D nx # 创建最小化 workspace.json echo { version: 2, projects: {}, tasksRunnerOptions: { default: { runner: nrwl/workspace/tasks-runner, options: { cacheable: [build, test, lint, e2e] } } } } workspace.json # 初始化 TypeScript 配置 npx tsc --init --rootDir . --outDir dist --skipLibCheck --strict --moduleResolution node --resolveJsonModule --esModuleInterop --declaration --sourceMap --noEmit此时生成的tsconfig.json需要关键修改{ compilerOptions: { baseUrl: ., paths: { agent-skills/*: [libs/*/src/index.ts], agent-skills/shared: [libs/shared/src/index.ts] }, module: NodeNext, target: ES2022, lib: [ES2022, DOM], types: [node, jest] } }注意module: NodeNext是 Node.js 18 的推荐设置它支持import.meta.resolve等新特性且与 ESM 兼容性更好。如果团队还在用 Node.js 16需改为module: CommonJS并添加type: module到 package.json。4.2 创建第一个 Skillweather.get 的完整实现在libs/weather-skill/目录下创建 Skillmkdir -p libs/weather-skill/srclibs/weather-skill/src/index.tsimport { z } from zod; import { Skill, SkillError } from agent-skills/core; // 定义 Skill 类型 export const getWeather: Skill{ city: string; units?: celsius | fahrenheit; }, { temperature: number; condition: string; humidity: number; } { id: weather.get, inputSchema: z.object({ city: z.string().min(2, City name must be at least 2 characters), units: z.enum([celsius, fahrenheit]).optional().default(celsius) }), outputSchema: z.object({ temperature: z.number().min(-100).max(60, Temperature out of realistic range), condition: z.string().min(1), humidity: z.number().min(0).max(100, Humidity must be between 0 and 100) }), execute: async (input) { // ✅ 输入校验已在 schema 中定义此处为双重保险 const validatedInput getWeather.inputSchema.parse(input); try { const url new URL(https://api.openweathermap.org/data/2.5/weather); url.searchParams.set(q, validatedInput.city); url.searchParams.set(appid, process.env.WEATHER_API_KEY || demo-key); url.searchParams.set(units, validatedInput.units); const res await fetch(url.toString(), { headers: { User-Agent: agent-skills/1.0 } }); if (!res.ok) { const errorText await res.text(); throw new SkillError( Weather API returned ${res.status}: ${errorText}, { code: res.status 404 ? WEATHER_CITY_NOT_FOUND : WEATHER_API_ERROR, cause: new Error(HTTP ${res.status}) } ); } const data await res.json(); // ✅ 输出校验确保 API 返回符合预期 return getWeather.outputSchema.parse({ temperature: data.main.temp, condition: data.weather[0].description, humidity: data.main.humidity }); } catch (err) { if (err instanceof SkillError) throw err; throw new SkillError(Failed to fetch weather data, { code: WEATHER_FETCH_FAILED, cause: err as Error }); } } }; // 导出供其他模块使用 export default getWeather;libs/weather-skill/project.json{ name: weather-skill, targets: { build: { executor: nrwl/node:webpack, options: { main: libs/weather-skill/src/index.ts, outputPath: dist/libs/weather-skill, tsConfig: libs/weather-skill/tsconfig.lib.json } }, test: { executor: nrwl/jest:jest, options: { jestConfig: libs/weather-skill/jest.config.ts } } } }libs/weather-skill/tsconfig.lib.json{ extends: ../../tsconfig.base.json, compilerOptions: { outDir: ../../dist/out-tsc, types: [node, jest] }, include: [**/*.ts], exclude: [jest.config.ts, **/*.spec.ts] }4.3 配置 semantic-release 并发布首个版本安装必要插件npm install -D semantic-release semantic-release/commit-analyzer semantic-release/release-notes-generator semantic-release/changelog semantic-release/github conventional-changelog-conventionalcommits创建release.config.jsconst { execSync } require(child_process); module.exports { branches: [main], plugins: [ semantic-release/commit-analyzer, semantic-release/release-notes-generator, [ semantic-release/changelog, { changelogFile: CHANGELOG.md, writerOpts: { transform: (commit) { const skillIdMatch commit.subject.match(/feat\(([^)])\)/); if (skillIdMatch) commit.skillId skillIdMatch[1]; return commit; }, groupBy: (commit) commit.skillId || other } } ], semantic-release/github, [ semantic-release/exec, { prepareCmd: node scripts/generate-schema-diff.js } ] ] };scripts/generate-schema-diff.js脚本负责生成 schema diff需提前在构建步骤中生成dist/libs/*/schema.jsonconst fs require(fs); const path require(path); // 读取当前版本 schema const currentSchemas {}; fs.readdirSync(dist/libs).forEach(dir { const schemaPath path.join(dist/libs, dir, schema.json); if (fs.existsSync(schemaPath)) { currentSchemas[dir] JSON.parse(fs.readFileSync(schemaPath, utf8)); } }); // 生成 diff 表格并追加到 CHANGELOG const diffTable Object.entries(currentSchemas).map(([name, schema]) | ${name} | ${JSON.stringify(schema)} | ).join(\n); fs.appendFileSync(CHANGELOG.md, \n## Schema Diff\n\n${diffTable}\n);最后在package.json中添加 scriptscripts: { release: semantic-release }首次发布前必须提交符合 conventional commits 规范的 commitgit add . git commit -m feat(weather): implement weather.get skill with schema validation git tag v0.1.0 git push origin main --tags然后运行npm run releasesemantic-release 会自动分析 commit确定版本号v0.1.0构建所有 Skill生成 CHANGELOG.md创建 GitHub Release发布到 npm registry如果配置了semantic-release/npm4.4 集成到 Agent 编排层如何在 NestJS 中注册并调用 SkillSkill 本身是纯函数但需要注入到 Agent 运行时。以 NestJS 为例在src/app.module.ts中import { Module } from nestjs/common; import { AgentService } from ./agent.service; import { getWeather } from agent-skills/weather-skill; Module({ providers: [ AgentService, { provide: SKILL_REGISTRY, useFactory: () ({ weather.get: getWeather, // 其他 Skill... }) } ], exports: [SKILL_REGISTRY] }) export class AppModule {}src/agent.service.ts实现 Skill 调用网关import { Injectable, Inject } from nestjs/common; import { Skill } from agent-skills/core; Injectable() export class AgentService { constructor( Inject(SKILL_REGISTRY) private readonly skillRegistry: Recordstring, Skillany, any ) {} async executeSkillT extends keyof typeof this.skillRegistry( skillId: T, input: Parameterstypeof this.skillRegistry[T][execute][0] ): PromiseAwaitedReturnTypetypeof this.skillRegistry[T][execute] { const skill this.skillRegistry[skillId]; if (!skill) { throw new Error(Skill not found: ${skillId}); } try { return await skill.execute(input); } catch (err) { if (err instanceof SkillError) { // 记录结构化错误日志 console.error(Skill ${skillId} failed:, { code: err.code, cause: err.cause?.message, input }); } throw err; } } }Controller 中调用Post(weather) async getWeather(Body() body: { city: string }) { return this.agentService.executeSkill(weather.get, body); }这样Skill 就完成了从定义、构建、发布到运行的全生命周期闭环。关键点在于AgentService 不知道 Skill 的具体实现只依赖 Skill 接口。这使得我们可以随时替换weather.get的实现比如从 OpenWeather 切换到 WeatherAPI而无需修改任何业务代码。5. 常见问题与排查技巧实录那些只有踩过坑才知道的经验5.1 问题Nx 构建时报错 “Cannot find module ‘zod’”但项目中已安装现象在libs/weather-skill/src/index.ts中import { z } from zod运行nx build weather-skill时提示模块未找到。根本原因Nx 默认的nrwl/node:webpackexecutor 会将所有依赖打包进一个 bundle但zod是一个 ESM-only 库v3.22而 Webpack 5 对 ESM 处理有特殊要求。解决方案在libs/weather-skill/project.json中添加webpackConfig覆盖{ targets: { build: { executor: nrwl/node:webpack, options: { webpackConfig: libs/weather-skill/webpack.config.js, main: libs/weather-skill/src/index.ts, outputPath: dist/libs/weather-skill } } } }libs/weather-skill/webpack.config.jsconst baseConfig require(nrwl/node/plugins/webpack); module.exports (config) { return { ...baseConfig(config), resolve: { ...baseConfig(config).resolve, fullySpecified: false, // 允许 import z from zod 而非 import * as z from zod extensions: [.ts, .js, .json] } }; };实操心得这个问题在 Nx v17 中更常见因为默认启用了更严格的 ESM 支持。不要试图降级 zod而是用 webpack 配置适配。我们测试过fullySpecified: false是安全的不会影响其他模块。5.2 问题semantic-release 发布后GitHub Release 中没有 CHANGELOG 内容现象npm run release成功但 GitHub Release 的 description 为空。排查路径检查release.config.js中是否遗漏semantic-release/github插件检查package.json中repository.url是否为 HTTPS 格式https://github.com/owner/repo.git而非 SSHgitgithub.com:owner/repo.git检查 GitHub Token 权限必须包含public_repo和workflow权限关键技巧在本地调试时用--dry-run参数查看 semantic-release 的执行计划npx semantic-release --dry-run --debug它会输出详细的步骤日志包括分析了哪些 commit计算了哪个版本号将调用哪些 plugin生成的 changelog 内容我们曾因repository.url是 SSH 格式导致semantic-release/github插件静默失败没有任何报错。--dry-run日志中明确显示Skipping GitHub plugin because repository is not GitHub瞬间定位问题。5.3 问题Skill 执行时抛出 “ReferenceError: TextEncoder is not defined”现象在 Node.js 环境中运行 Skill调用fetch时崩溃提示TextEncoder未定义。原因分析fetch的 polyfill如node-fetchv3依赖TextEncoder而 Node.js 11.0 不内置该 API。虽然项目指定target: ES2022但运行时环境仍是旧版 Node。根治方案在 Skill 的execute函数顶部添加 polyfill// libs/weather-skill/src/index.ts if (typeof TextEncoder undefined) { // ts-ignore global.TextEncoder require(util).TextEncoder; // ts-ignore global.TextDecoder require(util).TextDecoder; } export const getWeather: Skill... { // ... };注意不要在全局index.ts中添加而是在每个 Skill 文件中按需添加。因为不是所有 Skill 都用到 fetch强行全局 polyfill 会增加不必要的 bundle 体积。5.4 问题Nx graph 显示 Skill 依赖关系混乱出现意外连线现象运行nx graph发现payment-skill意外连接到ui-components库。诊断方法Nx 的依赖图基于import语句静态分析。运行以下命令找出非法 importnx dep-graph --focus payment-skill --with-deps --exclude ui-components如果仍有连线说明payment-skill的某个文件 import 了ui-components的某个导出即使只是类型导入。修复步骤检