agent-skills:AI智能体能力模块的TypeScript工程化范式

📅 发布时间:2026/9/16 18:36:54
agent-skills:AI智能体能力模块的TypeScript工程化范式
1. “agent-skills”不是库名而是一套可复用AI智能体能力模块的设计范式你点开 GitHub 搜索agent-skills大概率会失望——它既不是 npm 上下载量破百万的明星包也不是官方文档里明确定义的标准术语。它没有 README.md没有版本号甚至没有独立仓库。但恰恰是这种“不存在感”让它在真实工程现场高频出现在 Nx 工作区的libs/agent-skills目录下在 TypeScript 类型定义文件agent-skills/src/types.ts里在语义化发布semantic-release生成的 changelog 中反复被提及。它不是一个产品而是一种被团队自发沉淀下来的、面向 AI 智能体Agent的能力抽象层实践模式。我第一次见到这个词是在一个为专利审查员开发的辅助系统中。当时团队要让 AI 模型不仅能回答“这个专利是否新颖”还要能“自动提取权利要求书中的技术特征”、“比对 IPC 分类号”、“定位相似专利的附图标记”。这些动作明显超出了 prompt engineering 的范畴它们需要调用外部工具、处理结构化数据、执行带状态的多步操作。我们没去造轮子而是把每个原子能力封装成独立函数extractClaimsFromPdf()、classifyIPCByTitle()、fetchSimilarPatents()……然后统一放进agent-skills这个逻辑命名空间里。后来发现隔壁做工业 PLC 代码生成的团队目录结构一模一样再后来Jetson Orin NX 边缘推理项目组也建了同名 lib——他们封装的是captureCameraStream()、quantizeModelOnDevice()、triggerHardwareReset()这类物理世界交互技能。这说明什么说明agent-skills已经脱离了字面意义成为一种隐性行业共识当 AI 系统从“问答机器”进化为“执行主体”就必须把“能做什么”这件事从模型权重里剥离出来变成可测试、可组合、可版本化的 TypeScript 模块。它解决的核心问题非常朴素不让 AI 的“手”和“脑”长在同一棵树上。模型负责决策调用哪个 skillskill 负责执行怎么调用 API / 怎么读写文件 / 怎么控制硬件。这种分离直接决定了整个系统的可维护性上限。为什么必须用 TypeScript因为 Skill 不是黑盒函数。它有明确的输入 Schema比如ExtractClaimsInput必须包含pdfBuffer: Uint8Array和pageRange?: [number, number]有严格的输出类型ExtractClaimsOutput包含claims: Array{number: string; text: string}还有可能抛出的特定错误PdfParseError、PageOutOfRangeError。JavaScript 无法在编译期捕获这些契约而 TypeScript 的 interface generic discriminated union 能把契约刻进代码基因里。我见过太多项目因为一个skill返回的result字段类型模糊导致下游 Agent 在 runtime 突然崩溃——而这类错误在agent-skills的类型定义里早就在tsc --noEmit阶段被拦住了。Nx 在这里扮演的是“能力工厂”的基建角色。它不是为了管理一堆微服务而是为了管理这一堆高度内聚、低耦合的技能模块。每个agent-skills下的子模块如agent-skills/pdf-extractor、agent-skills/ipc-classifier都是一个独立的 Nx library拥有自己的tsconfig.json、自己的jest.config.ts、自己的nx.json构建配置。更重要的是Nx 的 project graph 能清晰告诉你agent-skills/ipc-classifier依赖了agent-skills/shared-types但绝不允许反向依赖。这种强制的依赖拓扑让技能模块天然具备“可插拔”属性——今天用 GPT-4 做决策引擎明天换成本地部署的 Qwen2只要 Skill 接口不变Agent 逻辑完全不用动。提示不要把agent-skills当成一个要发布的开源库去 npm install。它的价值在于目录结构、类型契约和构建约束。真正要复用的是那个libs/agent-skills文件夹本身以及里面每一行经过严格类型校验的export function xxx(): PromiseYyy。2. 从零搭建 agent-skills 工作区Nx 初始化与核心约束设计搭建agent-skills并非简单运行npx create-nx-workspace就完事。关键在于初始化时就植入三个不可妥协的约束单根类型定义、技能隔离边界、语义化发布流水线。这三者共同构成了agent-skills可靠性的基石。我见过太多团队在初期放任自流结果半年后skills目录变成类型地狱——某个pdf-parser模块偷偷引入了axios而hardware-controller模块又依赖了pdf-parser最终导致边缘设备部署时打包体积爆炸。下面是我经过五次生产项目验证的初始化流程。2.1 创建工作区并锁定 TypeScript 版本首先放弃create-nx-workspace的交互式引导。它默认创建的 monorepo 结构过于宽泛容易让agent-skills混入其他无关领域。直接使用命令行精准控制npx create-nx-workspacelatest my-agent-project \ --presetapps \ --appNameagent-core \ --stylecss \ --lintereslint \ --packageManagerpnpm \ --nxCloudfalse \ --interactivefalse进入项目后立即执行关键一步将 TypeScript 降级到 5.3.3。别被热搜词里“typescript 7.0 弃用 baseurl”干扰——那是未来的事。当前2024年中最稳定的组合是 TypeScript 5.3.x Nx 18.x。原因很实际TypeScript 5.4 引入的moduleResolution: bundler默认行为会破坏 Nx 对paths别名的解析逻辑导致import { extractClaims } from my-org/agent-skills/pdf-extractor在 Jest 测试中报错Cannot find module。执行pnpm add -D typescript5.3.3 pnpm exec tsc --version # 确认输出为 Version 5.3.3接着修改根目录tsconfig.base.json强制启用strict模式并禁用危险选项{ compilerOptions: { strict: true, noImplicitAny: true, strictNullChecks: true, strictFunctionTypes: true, strictBindCallApply: true, strictPropertyInitialization: true, noImplicitThis: true, alwaysStrict: true, skipLibCheck: false, forceConsistentCasingInFileNames: true, moduleResolution: node, resolveJsonModule: true, esModuleInterop: true, allowSyntheticDefaultImports: true, isolatedModules: true, incremental: true, composite: true, declaration: true, declarationMap: true, sourceMap: true, outDir: ./dist } }注意skipLibCheck: false是硬性要求。很多团队为求快设为true结果在agent-skills中引用types/node时因类型冲突导致tsc编译失败却无提示直到 CI 流水线卡住才暴露问题。2.2 创建 agent-skills 核心库及其类型枢纽运行以下命令创建主技能库pnpm nx g nx/workspace:library agent-skills --directorylibs --publishable --importPathmy-org/agent-skills --unitTestRunnerjest --bundlerswc这会在libs/agent-skills下生成标准结构。但关键改造在libs/agent-skills/src/index.ts// libs/agent-skills/src/index.ts export * as pdf from ./lib/pdf; export * as ipc from ./lib/ipc; export * as hardware from ./lib/hardware; export * as patent from ./lib/patent; // 核心所有公共类型必须从此处导出形成单一入口 export type { PdfExtractResult, IpcClassificationResult, HardwareCommandResponse, PatentSearchResult, } from ./lib/types;同时在libs/agent-skills/src/lib/types.ts中定义全局类型枢纽// libs/agent-skills/src/lib/types.ts export interface SkillError extends Error { code: string; cause?: unknown; } export type SkillResultT { success: true; data: T; } | { success: false; error: SkillError; }; // 所有 Skill 函数必须返回 SkillResultT export type SkillFnInput, Output (input: Input) PromiseSkillResultOutput; // 全局错误码枚举避免字符串散落各处 export enum SkillErrorCode { PDF_PARSE_FAILED PDF_PARSE_FAILED, IPC_CLASSIFICATION_TIMEOUT IPC_CLASSIFICATION_TIMEOUT, HARDWARE_UNREACHABLE HARDWARE_UNREACHABLE, PATENT_API_RATE_LIMIT PATENT_API_RATE_LIMIT, }这个设计看似简单实则解决了三个致命问题第一SkillResultT统一了所有 Skill 的返回形态Agent 层无需为每个 Skill 写不同的错误处理逻辑第二SkillErrorCode枚举让错误分类可被 IDE 自动补全杜绝了pdf-parse-error和pdf_parse_error这类拼写不一致引发的监控告警失效第三SkillFnInput, Output泛型约束确保任何新添加的 Skill 都必须符合契约——如果你试图写一个不返回PromiseSkillResult...的函数TypeScript 会立刻报错。2.3 用 Nx Project Graph 强制技能隔离Nx 的真正威力在于用project.json的implicitDependencies和targets.dependencies实现“物理隔离”。编辑libs/agent-skills/project.json{ name: agent-skills, targets: { build: { executor: nx/js:tsc, outputs: [{options.outputPath}], options: { outputPath: dist/libs/agent-skills, main: libs/agent-skills/src/index.ts, tsConfig: libs/agent-skills/tsconfig.lib.json, assets: [libs/agent-skills/*.md] }, configurations: { production: { optimization: true, extractLicenses: true, inspect: false } } } }, implicitDependencies: [my-org/agent-skills-shared-types], // ⚠️ 关键只允许依赖共享类型 tags: [type:library, scope:agent] }然后创建一个专门的shared-types库注意不是agent-skills的子模块而是同级独立库pnpm nx g nx/workspace:library agent-skills-shared-types --directorylibs --publishable --importPathmy-org/agent-skills-shared-types将libs/agent-skills/src/lib/types.ts中的SkillError、SkillResult、SkillErrorCode等基础类型全部移入agent-skills-shared-types。这样agent-skills的任何子模块如pdf-extractor都只能依赖my-org/agent-skills-shared-types而绝不能直接 import 其他技能模块。Nx 的nx graph命令会清晰显示pdf-extractor → agent-skills-shared-typesipc-classifier → agent-skills-shared-types但pdf-extractor ↛ ipc-classifier。这种拓扑是agent-skills可组合性的物理保障。2.4 配置 semantic-release 实现技能版本自治agent-skills的每个子模块如pdf-extractor必须拥有独立的版本号和发布历史而非整个agent-skills库共用一个版本。这要求 semantic-release 的配置粒度下沉到 library 级别。在libs/agent-skills/pdf-extractor/project.json中添加{ targets: { release: { executor: nx-plugin-semantic-release:release, options: { branches: [main], plugins: [ semantic-release/commit-analyzer, semantic-release/release-notes-generator, [ semantic-release/exec, { prepareCmd: pnpm exec tsc --build libs/agent-skills/pdf-extractor/tsconfig.lib.json pnpm exec jest --config libs/agent-skills/pdf-extractor/jest.config.ts } ], [ semantic-release/npm, { npmPublish: true, pkgRoot: dist/libs/agent-skills/pdf-extractor } ], semantic-release/github ] } } } }关键点在于prepareCmd它强制在发布前执行tsc编译和jest单元测试。这意味着任何未通过类型检查或单元测试的 Skill 修改都无法进入发布流水线。我曾在一个项目中看到hardware-controller模块因忘记更新SkillResult类型导致其triggerReset()函数返回了原始Promisevoid而非PromiseSkillResultvoid。这个错误在pnpm nx build agent-skills时就被tsc拦截根本不会走到pnpm nx release这一步。3. 技能模块的原子化设计以 pdf-extractor 为例的完整实现链agent-skills的灵魂不在顶层设计而在每一个具体 Skill 的实现细节。一个合格的 Skill 模块必须同时满足可测试性、可观测性、可调试性、可降级性四大特性。下面以pdf-extractor为例展示从需求分析到上线的完整闭环。这不是一个简单的“读取 PDF 提取文本”的 demo而是面向专利审查场景的真实工程实现。3.1 需求拆解为什么不能用 pdfjs-dist表面看pdf-extractor的需求很简单“从 PDF 中提取权利要求书文本”。但深入业务场景你会发现一堆隐藏约束页码精度权利要求书总在 PDF 的最后几页但具体起始页码未知需动态识别格式鲁棒性用户上传的 PDF 可能是扫描件OCR 后文本、纯文字 PDF、混合 PDF部分页面扫描部分文字法律效力要求提取的文本必须与原始 PDF 的字符位置严格对应以便后续做“文本高亮定位”性能边界单个 PDF 最大 200MB需在 30 秒内完成否则触发 Agent 超时熔断。如果直接用pdfjs-dist会立刻撞墙它擅长渲染但不提供页面内容的结构化 DOM 树它无法区分扫描页和文字页它没有内置 OCR 能力。因此pdf-extractor的设计必须分层层级技术选型职责是否可替换解析层pdf-libpdfjs-dist解析 PDF 结构识别页面类型文字/扫描✅ 可替换为popplerCLIOCR 层tesseract.jsWebWorker对扫描页执行 OCR输出带坐标信息的文本✅ 可替换为云端 OCR API结构层自研ClaimLocator基于字体大小、缩进、编号模式“1.”、“2.”定位权利要求区块❌ 核心业务逻辑不可替换输出层PdfTextBlock类型封装文本、坐标、置信度供下游高亮渲染✅ 可扩展字段这个分层就是agent-skills的典型架构基础设施层infra可替换业务逻辑层domain不可替换契约层contract绝对稳定。3.2 类型定义用 TypeScript 刻画业务契约在libs/agent-skills/pdf-extractor/src/lib/types.ts中定义精确的输入输出export interface PdfExtractInput { /** * PDF 文件二进制数据 * example new Uint8Array([0x25, 0x50, 0x44, 0x46]) */ pdfBuffer: Uint8Array; /** * 可选指定处理页码范围提升大文件性能 * default [0, -1] 处理全部页面 */ pageRange?: [number, number]; /** * OCR 置信度阈值低于此值的文本块将被过滤 * default 0.7 */ ocrConfidenceThreshold?: number; } export interface PdfTextBlock { /** * 文本内容已去除首尾空白 */ text: string; /** * 在 PDF 页面中的绝对坐标单位PDF 点1/72 英寸 */ bbox: { x1: number; y1: number; x2: number; y2: number; }; /** * 所属页面索引从 0 开始 */ pageNumber: number; /** * OCR 置信度仅扫描页有效文字页为 1.0 */ confidence: number; /** * 文本块类型用于下游样式渲染 */ type: claim | title | description | other; } export interface PdfExtractResult { /** * 成功提取的文本块列表 */ blocks: PdfTextBlock[]; /** * 检测到的页面类型统计 */ pageStats: { total: number; textPages: number; scannedPages: number; }; /** * 处理耗时毫秒 */ durationMs: number; }注意bbox字段的注释“单位PDF 点1/72 英寸”。这是法律文书处理的关键——下游的高亮渲染组件必须能将bbox坐标精确映射到 Canvas 上。如果这里只写coordinates: [number, number, number, number]就会导致前端工程师反复追问“这个坐标系原点在哪是左上角还是左下角单位是什么”拖慢整个迭代节奏。TypeScript 的 JSDoc 注释就是最高效的跨职能沟通协议。3.3 实现用 async/await Promise.race 实现超时熔断pdf-extractor的核心函数extractClaims()必须内置超时保护。不能依赖 Agent 层的全局 timeout因为 Skill 本身可能因网络OCR API、CPUTesseract、I/O大文件读取等不同原因卡死。实现如下// libs/agent-skills/pdf-extractor/src/lib/extractor.ts import { SkillFn, SkillResult, SkillErrorCode } from my-org/agent-skills-shared-types; import { PdfExtractInput, PdfExtractResult, PdfTextBlock } from ./types; import { parsePdfStructure } from ./parsers/pdf-structure; import { performOcr } from ./ocr/tesseract-worker; import { locateClaims } from ./locators/claim-locator; export const extractClaims: SkillFnPdfExtractInput, PdfExtractResult async ( input: PdfExtractInput ): PromiseSkillResultPdfExtractResult { const startTime Date.now(); try { // Step 1: 解析 PDF 结构识别页面类型 const structure await parsePdfStructure(input.pdfBuffer, input.pageRange); // Step 2: 并行处理文字页和扫描页 const [textPagesResult, scannedPagesResult] await Promise.all([ processTextPages(structure.textPages), processScannedPages(structure.scannedPages, input.ocrConfidenceThreshold ?? 0.7) ]); // Step 3: 合并结果并定位权利要求 const allBlocks [...textPagesResult, ...scannedPagesResult]; const claimBlocks locateClaims(allBlocks); const durationMs Date.now() - startTime; return { success: true, data: { blocks: claimBlocks, pageStats: { total: structure.totalPages, textPages: structure.textPages.length, scannedPages: structure.scannedPages.length, }, durationMs, }, }; } catch (error) { const durationMs Date.now() - startTime; // 关键所有错误必须包装为 SkillError并携带 ErrorCode const skillError: SkillError { name: PdfExtractError, message: error instanceof Error ? error.message : Unknown extraction error, stack: error instanceof Error ? error.stack : undefined, code: getErrorCode(error), // 根据 error 类型映射 ErrorCode cause: error, }; return { success: false, error: skillError, }; } }; // 熔断实现任何子步骤超过 15 秒即中断 const withTimeout T(promise: PromiseT, ms: number): PromiseT { return Promise.race([ promise, new Promisenever((_, reject) setTimeout(() reject(new Error(Operation timed out after ${ms}ms)), ms) ), ]); }; const processTextPages async (pages: TextPage[]): PromisePdfTextBlock[] { return withTimeout( Promise.all(pages.map(page extractTextFromPage(page))), 15_000 // 15秒熔断 ); };这个实现体现了agent-skills的工程哲学错误不是异常而是可分类、可监控、可重试的状态。getErrorCode(error)函数会根据error.name或error.code映射到SkillErrorCode.PDF_PARSE_FAILED或SkillErrorCode.OCR_TIMEOUT这样 Prometheus 监控就能按error_code标签聚合快速定位是 PDF 解析层还是 OCR 层出了问题。3.4 测试用 Jest 模拟真实世界不确定性agent-skills的测试不是为了“覆盖所有分支”而是为了验证 Skill 在边界条件下的行为是否符合契约。pdf-extractor的测试用例必须覆盖空 PDF 缓冲区new Uint8Array(0)应返回success: false且code: PDF_PARSE_FAILED超大 PDF模拟内存不足用jest.mock(pdf-lib)模拟parseDocument抛出OutOfMemoryError验证是否正确映射为SkillErrorCode.PDF_PARSE_FAILEDOCR 低置信度文本手动构造一个confidence: 0.3的PdfTextBlock验证ocrConfidenceThreshold: 0.7是否将其过滤权利要求编号识别提供一段包含1. A device...和2. The device of claim 1...的文本验证locateClaims()是否返回两个type: claim的块关键技巧用jest.mock替换底层依赖但保留类型签名。例如// libs/agent-skills/pdf-extractor/src/lib/__mocks__/tesseract-worker.ts import type { OcrResult } from ../ocr/tesseract-worker; // 模拟 OCR 返回固定结果但保持类型一致 export const performOcr jest.fn().mockImplementation( async (): PromiseOcrResult ({ blocks: [ { text: 1. A method for..., bbox: { x1: 100, y1: 200, x2: 500, y2: 230 }, confidence: 0.95, }, ], }) );这样测试代码能享受 TypeScript 的类型安全而不会因 mock 失去类型检查。我见过太多项目mock 返回any类型导致测试通过但线上崩溃——因为blocks字段名写成了textBlocks而不是blocks。4. Agent 与 Skills 的协同机制基于 TypeScript 类型的运行时契约验证agent-skills的终极价值不在于 Skill 本身有多强大而在于它如何被 Agent 安全、高效地调用。一个典型的 Agent如专利审查 Agent会维护一个SkillRegistry它不是简单的 Mapstring, Function而是一个类型安全的技能注册中心。这个设计让 Agent 的决策逻辑从“字符串匹配”升级为“类型推导”。4.1 技能注册表用 TypeScript 的 keyof 和 typeof 构建类型反射在apps/agent-core/src/lib/skill-registry.ts中定义注册表import type { SkillFn, SkillResult } from my-org/agent-skills-shared-types; import type * as Skills from my-org/agent-skills; // 核心从导入的 Skills 模块中自动推导所有可用 Skill 名称和类型 type SkillName keyof typeof Skills; type SkillDefinitionT extends SkillName ReturnType(typeof Skills)[T]; // 注册表接口key 是 Skill 名称value 是 Skill 函数 interface SkillRegistry { [key in SkillName]: SkillDefinitionkey; } // 实际注册表运行时 const registry: SkillRegistry { pdf: Skills.pdf.extractClaims, ipc: Skills.ipc.classifyByTitle, patent: Skills.patent.searchSimilar, hardware: Skills.hardware.triggerReset, }; // 类型安全的调用函数 export const invokeSkill async T extends SkillName( skillName: T, input: ParametersSkillDefinitionT[0] ): PromiseSkillResultReturnTypeSkillDefinitionT[data] { const skill registry[skillName]; if (!skill) { throw new Error(Skill ${skillName} not found in registry); } return skill(input) as PromiseSkillResultReturnTypeSkillDefinitionT[data]; };这个设计的精妙之处在于SkillName keyof typeof Skills。它不是硬编码字符串pdf | ipc而是从my-org/agent-skills的实际导出中自动获取。如果某天你删除了Skills.pdfTypeScript 会立刻报错Type pdf is not assignable to type keyof typeof Skills。同样invokeSkill(pdf, {...})的第二个参数input其类型会自动推导为PdfExtractInput因为Skills.pdf.extractClaims的第一个参数类型就是PdfExtractInput。4.2 运行时类型验证防止“类型正确但值错误”的陷阱TypeScript 只能保证编译期类型但无法阻止运行时传入错误的值。例如PdfExtractInput要求pdfBuffer是Uint8Array但如果传入一个ArrayBuffertsc不会报错但pdf-lib会抛出TypeError: Expected Uint8Array。为此agent-skills在每个 Skill 入口添加轻量级运行时验证// libs/agent-skills/pdf-extractor/src/lib/extractor.ts import { validatePdfBuffer } from ./validators/pdf-buffer-validator; export const extractClaims: SkillFnPdfExtractInput, PdfExtractResult async ( input: PdfExtractInput ): PromiseSkillResultPdfExtractResult { // 运行时验证确保 pdfBuffer 是有效的 Uint8Array const validation validatePdfBuffer(input.pdfBuffer); if (!validation.valid) { return { success: false, error: { name: PdfBufferValidationError, message: validation.error, code: SkillErrorCode.PDF_BUFFER_INVALID, }, }; } // ... 后续逻辑 };validatePdfBuffer的实现极其简单但至关重要// libs/agent-skills/pdf-extractor/src/lib/validators/pdf-buffer-validator.ts export const validatePdfBuffer (buffer: unknown): { valid: boolean; error?: string } { if (!(buffer instanceof Uint8Array)) { return { valid: false, error: pdfBuffer must be an instance of Uint8Array }; } if (buffer.length 0) { return { valid: false, error: pdfBuffer cannot be empty }; } if (buffer[0] ! 0x25 || buffer[1] ! 0x50 || buffer[2] ! 0x44 || buffer[3] ! 0x46) { return { valid: false, error: pdfBuffer does not start with %PDF signature }; } return { valid: true }; };这个验证只做三件事检查类型、检查长度、检查 PDF 签名。它不解析整个 PDF所以开销可以忽略不计 0.1ms但能拦截 90% 的上游调用错误。我曾在一个项目中因前端传入base64字符串而非Uint8Array导致pdf-lib在解析时崩溃错误堆栈长达 200 行根本无法定位问题源头。加入这个验证后错误直接变成清晰的PDF_BUFFER_INVALID前端工程师 5 秒内就能修复。4.3 技能组合用 Promise.allSettled 实现容错编排真实的 Agent 决策很少只调用一个 Skill。例如专利审查 Agent 的一个典型工作流是pdf.extractClaims()提取权利要求文本ipc.classifyByTitle()获取 IPC 分类号建议patent.searchSimilar()查找相似专利hardware.triggerReset()如果检测到硬件异常这四个 Skill 的执行顺序并非严格串行而是部分并行。但Promise.all()一旦某个 Skill 失败整个 Promise 就 reject导致其他成功的结果丢失。agent-skills的标准做法是Promise.allSettled()// apps/agent-core/src/lib/workflows/patent-review.ts import { invokeSkill } from ../skill-registry; import type { PdfExtractResult, IpcClassificationResult, PatentSearchResult } from my-org/agent-skills; export const runPatentReview async ( pdfBuffer: Uint8Array ): Promise{ claims: PdfExtractResult | null; ipc: IpcClassificationResult | null; similarPatents: PatentSearchResult | null; hardwareReset: boolean; } { const results await Promise.allSettled([ invokeSkill(pdf, { pdfBuffer }), invokeSkill(ipc, { title: Method for wireless charging }), invokeSkill(patent, { keywords: [wireless, charging, coil] }), invokeSkill(hardware, { command: reset }), ]); // 关键即使某个 Skill 失败也不影响其他结果 return { claims: results[0].status fulfilled ? results[0].value.data : null, ipc: results[1].status fulfilled ? results[1].value.data : null, similarPatents: results[2].status fulfilled ? results[2].value.data : null, hardwareReset: results[3].status fulfilled results[3].value.success, }; };allSettled的返回值是一个数组每个元素都有status: fulfilled | rejected字段。Agent 层可以根据status做差异化处理对fulfilled的结果直接使用对rejected的结果记录error.code并决定是否重试如PATENT_API_RATE_LIMIT可重试PDF_PARSE_FAILED则直接失败。这种容错能力让agent-skills构建的系统在真实复杂环境中具备极强的韧性。5. 生产环境落地CI/CD 流水线、监控告警与降级策略agent-skills从开发完成到稳定运行中间隔着一条深沟生产环境的不确定性。一个在本地 Jest 测试中 100% 通过的 Skill可能在 CI 环境因 Node.js 版本差异失败在生产环境因内存限制崩溃。下面是我在线上系统中验证过的、针对agent-skills的完整运维方案。5.1 CI 流水线用 Nx Affected 检测最小变更集agent-skills的 monorepo 往往包含数十个 Skill 模块。每次 PR 都全量构建所有模块既浪费资源又拖慢反馈。Nx 的affected命令是解药。在.github/workflows/ci.yml中name: CI on: [pull_request] jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkoutv3 - uses: pnpm/action-setupv2 - name: Install dependencies run: pnpm install - name: Detect affected projects id: affected run: echo AFFECTED$(pnpm nx print-affected --selectprojects --baseorigin/main --headHEAD) $GITHUB_ENV - name: Build affected projects if: env.AFFECTED ! [] run: pnpm nx run-many --targetbuild --projects${{ env.AFFECTED }} --parallel3 - name: Test affected projects if: env.AFFECTED ! [] run: pnpm nx run-many --targettest --projects${{ env.AFFECTED }} --parallel3 - name: Release affected libraries if: github.event_name push github.ref refs/heads/main run: pnpm nx run-many --targetrelease --projects${{ env.AFFECTED }}pnpm nx print-affected会分析 Git diff精确找出哪些 Skill 模块的源码被修改。例如PR 只改了libs/agent-skills/pdf-extractor/src/lib/extractor.ts那么env.AFFECTED就是[agent-skills-pdf-extractor]流水线只会构建、测试、发布这一个模块。这将平均 CI 时间从 12 分钟缩短到 2.3 分钟且避免了“改了一个 PDF Skill却意外发布了硬件控制 Skill”这类事故。