agent-skills:面向生产环境的智能体能力契约体系
2026/9/16 7:57:27 网站建设 项目流程

1. 项目概述:这不是一个“技能库”,而是一套可演进的智能体行为协议

你看到“agent-skills”这个标题时,第一反应可能是——又一个 TypeScript 工具函数合集?或者某个 AI Agent 的插件市场?但实际接触过 Nx 生态、参与过企业级 monorepo 构建、亲手调试过 semantic-release 发布流水线的人会立刻意识到:这四个字母背后,是一套面向生产环境的智能体能力契约体系。它不提供大模型调用封装,不内置 LLM 推理逻辑,也不做任何 prompt 工程抽象;它解决的是更底层、更顽固的问题:当多个 Agent 在同一系统中协同工作时,如何让它们“说同一种语言”、遵守同一套行为边界、被统一方式验证与升级。

核心关键词agent-skills不是名词堆砌,而是动宾结构——“为 agent 定义 skills”。这里的skills指的不是人类意义上的“技能”,而是可注册、可发现、可校验、可版本化、可跨 runtime 执行的原子能力单元。它天然绑定 TypeScript 类型系统(保障契约一致性)、Node 运行时(提供执行上下文)、Nx 工作区架构(支撑多能力模块隔离与复用)、semantic-release(实现能力变更的语义化发布)。你不需要懂 LLM,但必须理解:一个 skill 就是一个带明确输入/输出 Schema、有副作用约束、能被 runtime 动态加载的函数式接口。

适合谁参考?三类人最需要:

  • 正在用 Nx 管理大型 AI 应用 monorepo 的前端/全栈工程师,你每天都在处理libs/agent-coreapps/agent-gatewaypackages/skill-web-search这类包,却苦于能力模块之间类型不互通、版本不兼容、发布后无法追溯变更影响;
  • 设计 Agent 编排引擎的技术负责人,你已实现 workflow 调度,但每次新增一个“发送邮件”skill,都要手动改 schema、补文档、写测试、发 patch,而团队成员提交的sendEmail.ts里悄悄用了fs.writeFileSync,导致线上 sandbox 环境崩溃;
  • TypeScript 深度使用者,你厌倦了any类型在 Agent 能力链路中泛滥成灾,想用const skill = defineSkill({ ... })这样的 API,让 IDE 在写agent.use(sendEmail)时就报错:“参数 email 字段缺失”,而不是等到 runtime 报Cannot read property 'email' of undefined

它不承诺“一键接入大模型”,但能让你在三天内,把团队分散在 7 个 Git 分支里的 23 个独立能力模块,收敛到一套可类型检查、可自动化测试、可按语义版本号管理的@org/agent-skills包体系中。这才是标题真正的分量。

2. 整体设计思路:为什么必须用 Nx + TypeScript + semantic-release 组合?

2.1 放弃单 repo,选择 Nx monorepo:解决能力模块的“散装困境”

我见过太多团队用单个skills/目录存放所有能力文件:web-search.tsdb-query.tssend-sms.ts……初看清爽,半年后变成灾难现场。问题不在代码本身,而在协作熵增

  • A 同学改了web-search.ts的返回结构,没通知 B 同学,B 的report-generator.ts调用时直接 crash;
  • C 同学为修复紧急 bug,在send-sms.ts里硬编码了阿里云短信 SDK 版本,导致 D 同学的notification-center模块因依赖冲突无法构建;
  • E 同学想给db-query.ts加个超时参数,但不敢动,因为不知道哪些地方在用它,文档早已失效。

Nx 的核心价值,不是“更快的构建”,而是强制模块契约。当你执行nx g @nx/node:library --name=web-search --directory=skills,Nx 自动生成:

  • libs/skills/web-search/src/index.ts(导出 skill 定义)
  • libs/skills/web-search/src/lib/web-search.skill.ts(核心逻辑)
  • libs/skills/web-search/project.json(构建、测试、发布配置)
  • libs/skills/web-search/tsconfig.json(严格隔离的 tsconfig,禁止跨模块随意 import)

更重要的是,Nx 的project graph能实时可视化依赖关系。运行nx graph,你会看到apps/agent-routerlibs/skills/web-searchlibs/core/http-client的清晰链条。当你要升级http-client,Nx 自动告诉你哪些 skills 会受影响,甚至能生成影响范围报告。这不是工具炫技,而是把“模块间契约”从口头约定,变成可执行、可审计的工程事实。

提示:不要用nx g @nx/js:library。Agent skills 必须强类型,JS 库无法提供编译期类型保护。TypeScript 是底线,不是选项。

2.2 TypeScript 不是语法糖,而是能力契约的“法律文本”

agent-skills的核心类型定义长这样:

// libs/core/src/lib/skill-definition.ts export interface SkillInput { readonly [key: string]: unknown; } export interface SkillOutput { readonly [key: string]: unknown; } export interface SkillDefinition<TInput extends SkillInput, TOutput extends SkillOutput> { readonly id: string; readonly version: string; readonly inputSchema: JSONSchema7; // 使用 json-schema 验证运行时输入 readonly outputSchema: JSONSchema7; // 验证运行时输出 readonly handler: (input: TInput, context: SkillContext) => Promise<TOutput>; readonly metadata: { readonly description: string; readonly tags: readonly string[]; readonly sideEffects: readonly ('network' | 'storage' | 'external-api')[]; // 声明副作用,供 sandbox 策略使用 }; } export function defineSkill<TInput extends SkillInput, TOutput extends SkillOutput>( definition: SkillDefinition<TInput, TOutput> ): SkillDefinition<TInput, TOutput> { return definition; }

注意三个关键设计点:

  1. readonly修饰符:强制输入/输出不可变。避免 skill 内部意外修改传入对象,导致上游数据污染。这是很多 JS skill 实现崩溃的根源——A skill 修改了 shared context 对象,B skill 读取时得到脏数据。
  2. JSONSchema7显式声明:TypeScript 类型只在编译期存在,runtime 需要真实 schema 验证。inputSchema不是装饰器或注释,而是必须提供的、可序列化的 JSON Schema 对象。defineSkill函数内部会校验该 schema 是否与泛型TInput一致,不一致则编译失败。
  3. sideEffects显式枚举:不是字符串随意填,而是限定枚举值。这直接对接 runtime 的 sandbox 策略——若 skill 声明了'storage',sandbox 可拒绝在无持久化权限的环境中加载它。

这套类型不是为了“看起来专业”,而是为了在nx build阶段就拦截住 80% 的契约违规。比如某同学写了:

// 错误示例:sideEffects 值非法 export const sendEmail = defineSkill({ id: 'send-email', version: '1.2.0', inputSchema: { type: 'object', properties: { to: { type: 'string' } } }, outputSchema: { type: 'object', properties: { success: { type: 'boolean' } } }, handler: async (input) => { /* ... */ }, metadata: { description: 'Send email via SMTP', tags: ['email'], sideEffects: ['smtp'] // ❌ 'smtp' 不在允许枚举中!编译报错 } });

TypeScript 编译器会立刻报错:Type '"smtp"' is not assignable to type '"network" | "storage" | "external-api"'.这比写完代码再跑测试发现 sandbox 拒绝加载,早了至少 5 分钟。

2.3 semantic-release:让 skill 版本号成为可信的“能力变更日志”

很多团队用npm version patch && npm publish手动管理 skill 版本,结果是:

  • v1.0.1修复了一个 typo,v1.0.2却悄悄修改了inputSchema的 required 字段,下游毫无感知;
  • v1.1.0标注为 “feat: add timeout option”,但实际移除了旧的retryCount参数,属于 breaking change,却没升v2.0.0
  • 团队成员对“什么算 breaking change”没有共识,有人认为改返回字段名不算 break,有人认为只要 schema 变就是 break。

semantic-release 的价值,在于用 commit message 规范,替代人工判断。在agent-skills项目中,我们约定:

  • feat:开头的 commit → minor version(如v1.2.0
  • fix:开头的 commit → patch version(如v1.2.1
  • BREAKING CHANGE:出现在 commit body → major version(如v2.0.0

更重要的是,semantic-release 会自动生成CHANGELOG.md,内容不是空洞的“更新了 X 功能”,而是精确到每个 skill 的变更:

## [2.1.0](https://github.com/org/agent-skills/compare/v2.0.1...v2.1.0) (2024-06-15) ### skills/web-search - **feat**: Add `region` parameter to support multi-region search ([#42](https://github.com/org/agent-skills/pull/42)) - **docs**: Update README with new region usage example ([#43](https://github.com/org/agent-skills/pull/43)) ### skills/db-query - **fix**: Correct SQL injection vulnerability in WHERE clause ([#45](https://github.com/org/agent-skills/pull/45))

这个 changelog 不是给人看的,是给机器读的。CI 流水线在发布前会解析它,自动检查:

  • web-searchinputSchema有新增 required 字段,则 commit 必须含BREAKING CHANGE:
  • db-queryoutputSchema移除了字段,则必须升 major;
  • 若仅文档更新,semantic-release 会跳过发布,避免污染 npm registry。

这就是为什么agent-skills必须绑定 semantic-release——它把“能力变更”从模糊的团队沟通,变成了可审计、可回溯、可自动校验的工程事实。

3. 核心细节解析:从定义一个 skill 到发布一个可信赖的能力单元

3.1 定义 skill:defineSkill的 5 个必填字段,缺一不可

web-searchskill 为例,完整定义如下(省略具体实现):

// libs/skills/web-search/src/lib/web-search.skill.ts import { defineSkill } from '@org/core'; import { JSONSchema7 } from 'json-schema'; export const webSearch = defineSkill({ id: 'web-search', version: '1.3.0', // ✅ 必须显式声明,不能从 package.json 读取 inputSchema: { type: 'object', required: ['query'], properties: { query: { type: 'string', minLength: 1 }, maxResults: { type: 'integer', minimum: 1, maximum: 10 }, region: { type: 'string', enum: ['us', 'cn', 'eu'] } } } as const satisfies JSONSchema7, outputSchema: { type: 'object', required: ['results'], properties: { results: { type: 'array', items: { type: 'object', required: ['title', 'url'], properties: { title: { type: 'string' }, url: { type: 'string', format: 'uri' } } } } } } as const satisfies JSONSchema7, handler: async (input, context) => { // 实际搜索逻辑,使用 context.http.fetch return { results: [] }; }, metadata: { description: 'Perform a web search using a third-party API', tags: ['search', 'web'], sideEffects: ['network', 'external-api'] } });

逐字段解析其设计意图:

  • id:全局唯一标识符,格式为kebab-case。它不是文件名,也不是包名,而是 skill 的“身份证号”。web-searchweb_search是两个不同 skill,即使代码完全一样。ID 用于 runtime 动态注册、依赖注入、权限控制。
  • version:语义化版本号,必须硬编码在定义中。为什么?因为 skill 可能被多个包引用,若从package.json读取,当@org/skills-web-searchv1.3.0 被@org/agent-corev2.0.0 引用时,agent-core里的webSearch.version会是v2.0.0,而非v1.3.0,造成版本混乱。硬编码确保每个 skill 的版本信息与其定义强绑定。
  • inputSchema/outputSchema:使用as const satisfies JSONSchema7强制类型推导。satisfies是 TypeScript 4.9+ 特性,它既保证类型安全,又保留字面量类型(如enum: ['us','cn']的精确值),避免as JSONSchema7导致类型擦除。schema 必须是const,确保 runtime 可以JSON.stringify序列化,用于网络传输或持久化存储。
  • handler:签名固定为(input: TInput, context: SkillContext) => Promise<TOutput>context是关键——它不暴露原始 Node.jsglobalprocess,而是提供受控的、可 mock 的服务:context.http.fetchcontext.storage.getcontext.logger.info。这保证 skill 在不同 runtime(Node、Deno、Web Worker)中行为一致,且便于单元测试。
  • metadatasideEffects是 sandbox 策略的输入。若 skill 声明['network'],sandbox 可限制其只能访问白名单域名;若声明['storage'],则需用户显式授权。tags用于 skill discovery,Agent 编排引擎可通过findSkills({ tags: ['search'] })动态加载。

注意:defineSkill函数内部会对inputSchemaTInput泛型进行双向校验。若 schema 中required: ['query'],但泛型TInput没有query: string,编译报错。这是 TypeScript 类型系统与 JSON Schema 的双重保险。

3.2 构建与测试:Nx 的 project.json 如何定制 skill 的 CI 流水线

每个 skill library 的project.json不是模板生成的摆设,而是定制化 CI 的蓝图。以web-search为例:

{ "root": "libs/skills/web-search", "sourceRoot": "libs/skills/web-search/src", "projectType": "library", "targets": { "build": { "executor": "@nx/node:package", "outputs": ["{workspaceRoot}/dist/libs/skills/web-search"], "options": { "outputPath": "dist/libs/skills/web-search", "tsConfig": "libs/skills/web-search/tsconfig.lib.json", "project": "libs/skills/web-search/package.json", "externalDependencies": ["all"] } }, "test": { "executor": "@nx/jest:jest", "options": { "jestConfig": "libs/skills/web-search/jest.config.ts", "passWithNoTests": true } }, "lint": { "executor": "@nx/eslint:eslint", "options": { "lintFilePatterns": ["libs/skills/web-search/**/*.ts"] } }, "release": { "executor": "@semantic-release/exec:exec", "options": { "cmd": "npx semantic-release --branches main --ci" } } } }

关键配置解读:

  • build.executor: @nx/node:package:使用 Nx 官方 Node 打包器,而非tsc。它会自动处理exports字段、生成.d.ts声明文件、并支持conditional exports(如import('node:fs')的正确解析)。externalDependencies: ["all"]表示所有 node 内置模块和第三方依赖都 external,最终 bundle 只包含 skill 业务代码,体积最小化。
  • test.options.jestConfig:指向jest.config.ts,而非jest.config.js。TS 配置文件可直接 import 其他 TS 文件,便于复用@org/core/test-utils中的 mock context 工具。
  • release.executor:自定义 release target,直接调用semantic-releaseCLI。--branches main强制只在 main 分支发布,避免 feature 分支误发。--ci启用 CI 模式,从环境变量读取 GitHub token。

实操心得:不要在project.json中写prepublishOnlyscript。Nx 的 target 依赖机制更可靠。例如,releasetarget 可设置dependsOn: ["build", "test"],确保每次发布前必经构建和测试。手动 npm script 容易被绕过,而 Nx target 是强制的。

3.3 发布与消费:如何让下游项目安全地使用一个 skill

发布后,web-search会以@org/skills-web-search@1.3.0形式出现在 npm registry。下游项目(如apps/agent-router)如何安全消费?

第一步:安装与类型导入

# 在 apps/agent-router 目录下 npm install @org/skills-web-search@1.3.0

然后在代码中:

// apps/agent-router/src/main.ts import { webSearch } from '@org/skills-web-search'; // ✅ 直接 import,类型自动推导 import { registerSkill } from '@org/core'; // 注册 skill,传入 runtime context registerSkill(webSearch, { http: { fetch: customFetch }, // 注入定制化 fetch storage: memoryStorage, // 注入内存存储 logger: consoleLogger // 注入日志 });

第二步:类型安全的调用

// 调用时,IDE 自动提示 input 结构 const result = await webSearch.handler( { query: 'TypeScript', maxResults: 5, region: 'us' }, // ✅ 输入符合 schema context ); // result 类型自动为 { results: Array<{ title: string; url: string }> } console.log(result.results[0].title); // ✅ 安全访问,无 any

第三步:版本锁定与升级策略

apps/agent-routerpackage.json中,@org/skills-web-search的版本应锁定为^1.3.0,而非*latest。Nx 提供nx migrate命令,当@org/skills-web-search发布v1.4.0(minor)时,运行:

nx migrate @org/skills-web-search@1.4.0 # 生成 migrations.json,描述变更:如 "Update web-search inputSchema to add 'language' field" nx migrate --run-migrations

migrate会:

  • 检查apps/agent-router中所有对webSearch.handler的调用,确认是否传入了新 required 字段;
  • 若缺失,生成 codemod 自动补全;
  • 若存在BREAKING CHANGE,则拒绝自动升级,要求人工介入。

这才是企业级 skill 消费的正确姿势——不是npm update一把梭,而是受控、可追溯、有自动化辅助的升级流程。

4. 实操过程:从零搭建一个可发布的 skill monorepo

4.1 初始化 Nx workspace:选择正确的 preset 和 flags

不要用npx create-nx-workspace@latest的交互式向导。它默认选appspreset,不适合agent-skills。直接命令行初始化:

npx create-nx-workspace@latest agent-skills \ --preset=ts \ --appName=none \ --linter=eslint \ --no-nxCloud \ --skipGit \ --packageManager=pnpm

关键参数说明:

  • --preset=ts:选择纯 TypeScript preset,而非reactnext等前端 preset。agent-skills是 Node.js 库集合,不需要 Web bundler。
  • --appName=none:不创建任何 app,只建 workspace。skills 是 library,不是可执行应用。
  • --no-nxCloud:禁用 Nx Cloud。agent-skills的构建缓存应由企业私有 cache server 托管,而非 SaaS。
  • --packageManager=pnpm:pnpm 的硬链接机制比 npm/yarn 更节省磁盘空间,尤其在 monorepo 中有数十个 skills 时,node_modules体积可减少 70%。

初始化后,删除默认生成的apps/目录,创建libs/corelibs/skills目录:

rm -rf apps/ mkdir -p libs/core libs/skills

4.2 创建 core 包:定义 skill 基础契约与工具

运行命令生成 core library:

nx g @nx/node:library --name=core --directory=core --no-publishable --no-standalone-config

--no-publishable表示此包不发布到 npm,仅内部使用;--no-standalone-config避免生成冗余的tsconfig.json。然后编辑libs/core/src/index.ts

export * from './lib/skill-definition'; export * from './lib/skill-registry'; export * from './lib/skill-context'; export * from './lib/skill-error';

重点实现SkillRegistry

// libs/core/src/lib/skill-registry.ts import { SkillDefinition } from './skill-definition'; type SkillRegistry = Map<string, SkillDefinition<any, any>>; const registry = new Map<string, SkillDefinition<any, any>>(); export function registerSkill<TInput, TOutput>( skill: SkillDefinition<TInput, TOutput>, context: SkillContext ): void { if (registry.has(skill.id)) { throw new Error(`Skill with id "${skill.id}" already registered`); } // 运行时校验:inputSchema 是否与 handler 参数类型一致(通过反射) registry.set(skill.id, skill); } export function getSkill<TInput, TOutput>(id: string): SkillDefinition<TInput, TOutput> | undefined { return registry.get(id) as SkillDefinition<TInput, TOutput> | undefined; }

registerSkill不仅存入 map,还应在 runtime 做 schema 与 handler 的一致性校验(通过Function.prototype.toString()解析参数名,或使用reflect-metadata)。这是编译期类型之外的 runtime 保险。

4.3 创建第一个 skill:web-search,并配置 semantic-release

生成 skill library:

nx g @nx/node:library --name=web-search --directory=skills --publishable --importPath=@org/skills-web-search

--publishable表示此包将发布到 npm;--importPath指定包名,符合 scoped package 规范。

然后配置 semantic-release。在 workspace 根目录创建.releaserc

{ "branches": ["main"], "plugins": [ "@semantic-release/commit-analyzer", "@semantic-release/release-notes-generator", "@semantic-release/npm", "@semantic-release/github" ], "npm": { "pkgRoot": "dist/libs/skills/web-search" } }

关键点:npm.pkgRoot指向 Nx 构建后的 dist 目录,而非源码目录。semantic-release 会从dist/中读取package.jsonindex.js发布。

最后,配置 CI(以 GitHub Actions 为例).github/workflows/release.yml

name: Release on: push: branches: [main] jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: '18' registry-url: 'https://registry.npmjs.org' - run: pnpm install - run: pnpm nx build web-search - name: Release env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} run: npx semantic-release

注意:fetch-depth: 0是必须的,semantic-release 需要完整 commit history 计算版本。pnpm nx build web-search确保发布前构建成功。

4.4 验证发布流程:本地模拟与真实发布

本地验证(不触发真实发布):

# 在 libs/skills/web-search 目录 pnpm nx build cd dist/libs/skills/web-search npm pack # 生成 .tgz 包,检查内容

检查package.json是否包含:

{ "name": "@org/skills-web-search", "version": "1.3.0", "main": "index.js", "types": "index.d.ts", "exports": { ".": { "require": "./index.js", "import": "./index.mjs" } } }

真实发布前,先发布到 npm 的@orgscope 下的nexttag:

# 在 dist/libs/skills/web-search 目录 npm publish --tag next

然后在下游项目测试:

npm install @org/skills-web-search@next

确认一切正常后,再让 semantic-release 发布到latesttag。这种两阶段发布,是避免latest污染的黄金实践。

5. 常见问题与排查技巧实录:那些只有踩过坑才懂的细节

5.1 问题:nx build报错 “Cannot find module 'node:util'”,但node -v显示 v18+

根因:Nx 默认使用@nrwl/node:packageexecutor,其底层esbuild打包器对 Node.js 内置模块的解析规则与tsc不同。node:util是 Node.js v14.18+ 的 ESM 内置模块别名,esbuild在某些版本中未正确识别。

解决方案

  1. 升级 Nx 到 v17.3.0+(已修复);
  2. 或在project.json中显式指定target
"options": { "target": "es2020", "outputPath": "dist/libs/skills/web-search", "tsConfig": "libs/skills/web-search/tsconfig.lib.json", "project": "libs/skills/web-search/package.json", "externalDependencies": ["all"] }

target: "es2020"告诉 esbuild 生成兼容 Node.js v14+ 的代码,正确处理node:前缀。

实测心得:不要在tsconfig.json中加"moduleResolution": "node"。Nx 的 executor 有自己的 module resolution 逻辑,手动覆盖反而引发冲突。

5.2 问题:semantic-release在 CI 中卡住,日志显示 “No commits found”

根因:GitHub Actions 默认actions/checkout只 fetch 最新 commit(fetch-depth: 1),而 semantic-release 需要从上次 tag 到当前 commit 的完整历史来分析变更。

解决方案

  • 在 workflow 中设置fetch-depth: 0(已强调);
  • 或更精准地,只 fetch 从上次 tag 开始的 commits:
- uses: actions/checkout@v4 with: fetch-depth: 0 # 或者更高效: # fetch-depth: 100 # ref: ${{ github.event.pull_request.head.sha }}

额外技巧:在本地调试时,用npx semantic-release --dry-run --debug查看详细日志,确认它识别到的 last release tag 是否正确。

5.3 问题:skill 的inputSchemaformat: 'uri'在 runtime 校验失败,但 TypeScript 编译通过

根因JSONSchema7类型定义中,format是可选字段,TypeScript 不校验其值是否合法。'uri'是 JSON Schema 规范中的标准 format,但某些 runtime validator(如ajv)默认不启用 format 检查,需显式配置。

解决方案
在 skill runtime 的 validator 初始化时:

import Ajv from 'ajv'; const ajv = new Ajv({ formats: { uri: true, // ✅ 启用 uri format 校验 email: true } });

经验总结:TypeScript 类型和 JSON Schema 是两套校验体系。前者保编译期,后者保 runtime。agent-skills的设计哲学是:TypeScript 做尽可能多的静态检查,JSON Schema 做 runtime 的最后一道防线,两者缺一不可。不要因为 TS 编译通过,就忽略 schema 的 runtime 配置。

5.4 问题:pnpm工作区中,@org/skills-web-searchpeerDependencies未被自动安装

场景web-search依赖axios作为peerDependencies,但下游项目apps/agent-router未安装axios,导致 runtimerequire('axios')失败。

根因peerDependencies是 npm 的概念,pnpm 的 hard link 机制下,peer dep 不会自动 hoist 到 rootnode_modules,除非显式声明。

解决方案

  1. libs/skills/web-search/package.json中,将axios放入dependencies(推荐),因为 skill 是独立发布的包,应自带运行时依赖;
  2. 或在apps/agent-router/package.json中,显式添加axiosdependencies
  3. 最佳实践:使用@nx/js:installexecutor 自动管理 peer deps。在project.json中添加:
"install-peer-deps": { "executor": "@nx/js:install", "options": { "peerDependencies": ["axios"] } }

然后在releasetarget 的dependsOn中加入"install-peer-deps"

5.5 问题:defineSkillsideEffects枚举值被误写为'networking',但 TypeScript 未报错

根因sideEffects的类型定义为readonly ('network' | 'storage' | 'external-api')[],但若开发者写:

sideEffects: ['networking'] as const

TypeScript 会推导为readonly ['networking'],与联合类型不匹配,应该报错。但有时因 tsconfig 配置或 IDE 缓存未触发。

排查技巧

  • 在 VS Code 中,将光标放在sideEffects上,按Ctrl+Click,查看 TypeScript 解析的类型是否为readonly ['networking']
  • 运行pnpm tsc --noEmit(不生成 js,只做类型检查),这是最权威的校验;
  • defineSkill函数内部,添加运行时断言:
function defineSkill(/* ... */) { // 运行时校验 sideEffects const validSideEffects = ['network', 'storage', 'external-api'] as const; for (const effect of definition.metadata.sideEffects) { if (!validSideEffects.includes(effect as any)) { throw new Error(`Invalid sideEffect: ${effect}. Must be one of ${validSideEffects.join(', ')}`); } } // ... }

这才是双保险——编译期 + 运行时。

6. 进阶扩展:如何让 agent-skills 支持浏览器环境与 Deno

6.1 浏览器适配:用exports字段实现环境感知打包

Node.js skill 不能直接在浏览器运行,但agent-skills的设计允许渐进式适配。关键在package.jsonexports字段:

{ "name": "@org/skills-web-search", "version": "1.3.0", "exports": { ".": { "import": "./index.mjs", "require": "./index.cjs", "browser": "./browser/index.mjs", "deno": "./deno/index.mjs" } } }
  • browser字段指向./browser/index.mjs,其中handler使用fetch替代context.http.fetch
  • deno字段指向./deno/index.mjs,其中handler使用Deno.fetch
  • index.mjs(ESM)和index.cjs(CommonJS)由 Nx 构建生成,browser/deno/目录需手动维护或通过 codemod 生成。

这样,下游项目在 Webpack/Vite 中 import 时,会自动选择browser字段;在 Deno 中 import,则选择deno字段。无需修改 skill 业务逻辑,只需替换底层 runtime 适配层。

6.2 Deno 支持:利用 Deno 的 import map 与权限模型

Deno 的核心优势是权限模型。agent-skillssideEffects可直接映射到 Deno 权限:

// deno/index.mjs export const webSearch = defineSkill({ // ... same id/version/schema handler: async (input, context) => {

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询