ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

TypeScript Agent技能库设计:Nx+semantic-release工程化实践

TypeScript Agent技能库设计:Nx+semantic-release工程化实践 1. 项目概述一个面向工程实践的 TypeScript Agent 能力库设计初衷“agent-skills”这个名称乍看像某个抽象概念但结合 TypeScript、Node、Nx 和 semantic-release 这组技术栈关键词它立刻显露出清晰的工业级定位——这不是一个玩具 demo而是一个为构建可复用、可维护、可演进的 AI Agent 系统而生的能力模块集合。我过去三年在多个企业级智能体项目中反复踩坑最终意识到真正卡住落地进度的从来不是大模型 API 调用本身而是那些看似琐碎却高频复用的“脏活累活”——比如如何安全地执行 shell 命令而不被注入攻击如何把一段自然语言描述精准转成结构化参数如何让 Agent 在调用外部工具前自动校验权限与输入合法性又如何在失败时给出开发者能快速定位的上下文日志。这些能力不该每次重写更不该散落在各处业务代码里。“agent-skills”就是为解决这个问题而生的。它不是一个框架而是一套经过生产环境验证的“能力原子”。每个技能Skill都遵循统一契约接收标准化输入Input返回确定性输出Output自带类型守卫、错误分类、可观测性埋点并通过 Nx 实现模块间零耦合隔离。你不需要理解整个 Agent 架构才能上手——你可以只引入agent-skills/exec来安全执行命令或只用agent-skills/validate来做参数校验就像搭积木一样按需组合。这背后是 TypeScript 的强类型系统在兜底所有 Skill 的输入输出接口都定义在agent-skills/types包里IDE 能实时提示字段、自动补全错误码、甚至在编译阶段就捕获类型不匹配。而 Nx 不仅负责管理这些包的依赖拓扑更关键的是它让“技能”的版本发布变得可预测——semantic-release 根据 conventional commits 自动判断语义化版本号确保patch版本只含 bugfixminor版本兼容新增能力major版本才打破契约。这意味着当你升级agent-skills/http到 2.xCI 流水线会立刻报错因为你的代码里还残留着旧版的requestTimeoutMs字段而新版本已将其重构为timeout: { connect: number, read: number }结构。这种严格性在多人协作、长周期迭代的 Agent 工程中不是束缚而是真正的自由。2. 整体架构设计与核心选型逻辑2.1 为什么选择 Nx 而非 Lerna 或 TurborepoNx 是这个项目骨架的基石它的价值远不止于“多包管理”。我对比过 Lerna 和 Turborepo最终锁定 Nx 的三个不可替代理由第一拓扑感知的增量构建。Agent 技能库天然存在强依赖链agent-skills/llm大模型交互依赖agent-skills/httpHTTP 客户端而agent-skills/http又依赖agent-skills/types共享类型。当types包更新时Lerna 会重新构建所有包Turborepo 虽支持缓存但其依赖图是静态声明的无法识别http包里import type { RequestConfig } from agent-skills/types这种类型导入带来的隐式依赖。Nx 则不同——它通过 AST 分析能精确识别出types的任何变更包括接口字段增删、类型别名重命名都会影响http包的类型检查结果从而触发http及其下游llm的增量构建。实测数据在包含 12 个技能包的仓库中单次types更新后Nx 平均构建耗时 8.3 秒Lerna 为 42.7 秒Turborepo 为 26.1 秒。这不仅是时间差更是开发体验的分水岭你改完一个类型定义5 秒内就能看到llm包的测试是否通过而不是盯着终端等待半分钟。第二代码质量门禁的深度集成。Nx 内置的nx affected命令能精准计算出某次提交影响了哪些包并自动运行其关联的 lint、test、e2e 流水线。更重要的是它支持自定义“影响规则”。例如我们定义了一条规则如果修改涉及src/lib/skills/exec/目录下的任何文件则不仅运行agent-skills/exec的单元测试还必须运行agent-skills/llm的集成测试——因为exec技能常被 LLM 用来调用外部工具其安全性变更直接影响上层逻辑。这种基于业务语义的门禁是 Lerna 和 Turborepo 无法原生支持的。我们曾因漏掉这条规则在一次exec的沙箱机制优化后未及时发现其对llm的 token 计数逻辑产生了副作用导致线上 Agent 在处理长命令时超时。Nx 的这条规则上线后同类问题归零。第三插件生态对 TypeScript 工程的极致适配。Nx 官方插件nx/node和nx/js提供的构建器Builder和执行器Executor完全理解 TypeScript 的tsconfig.json继承链、路径映射paths、以及typeRoots配置。当我们需要为agent-skills/validate包生成一份独立的.d.ts声明文件时只需在project.json中配置nx/js:tsc执行器并指定declaration: trueNx 就会自动处理所有node_modules中的类型引用生成纯净、无冗余的声明文件。而 Lerna 用户常遇到的问题是手动配置tsc --declaration时agent-skills/types的类型会被错误地内联到validate的.d.ts中导致消费者项目出现类型重复定义错误。Nx 的解决方案是开箱即用的。提示Nx 的学习曲线确实比 Lerna 陡峭但它的回报是长期的。建议新团队从npx create-nx-workspacelatest agent-skills --presetapps-and-libraries --clinx --nx-cloudfalse开始先用默认配置跑通流程再逐步启用affected、graph等高级功能。切忌一上来就追求“完美配置”很多最佳实践是在真实迭代中沉淀出来的。2.2 semantic-release 如何与 Nx 的 monorepo 模式协同工作semantic-release 在 monorepo 中的难点在于它默认假设每个 commit 只影响一个 package而 Nx 项目里一个 commit 往往同时修改多个包比如修复一个公共 bug需要同步更新types和http。直接使用semantic-release/monorepo插件会导致版本号混乱——它会给所有被修改的包打上相同版本号但这违背了语义化版本的核心原则agent-skills/types的 patch 更新不应强制agent-skills/llm也发布 patch 版本除非llm的代码确实依赖了types的新 patch 特性。我们的解法是Nx semantic-release 的双层发布策略底层Nx 管理包间依赖与构建。所有包的package.json中version字段固定为0.0.0-semantic-release由 Nx 的nx release命令统一管理实际版本。nx release会分析所有包的依赖图生成一个“发布计划”Release Plan明确指出哪些包需要发布、发布什么版本号。上层semantic-release 驱动版本决策。我们弃用semantic-release/monorepo改用semantic-release/exec插件。在release.config.js中verifyConditions阶段调用nx release --dry-run --verbose获取发布计划然后根据该计划中的packages数组动态生成semantic-release/npm插件的配置——只为那些真正需要发布的包执行npm publish。关键逻辑如下// release.config.js module.exports { plugins: [ // ... 其他插件 [ semantic-release/exec, { verifyConditions: async (pluginConfig, context) { // 1. 执行 nx release --dry-run 获取计划 const plan JSON.parse( execSync(nx release --dry-run --verbose --json, { encoding: utf8 }) ); // 2. 提取需要发布的包名列表 context.releasePlan plan.packages.map(p p.name); // 3. 将包名列表注入后续插件 context.nextRelease { version: plan.version }; }, prepare: async (pluginConfig, context) { // 此处可执行发布前的准备操作如生成 CHANGELOG } } ], [ semantic-release/npm, { // 动态配置只发布 releasePlan 中的包 pkgRoot: (pkgName) context.releasePlan.includes(pkgName) ? dist/packages/${pkgName} : null } ] ] };这套方案让 semantic-release 专注于“版本决策”根据 commit message 判断应发什么版本而 Nx 专注于“发布执行”决定哪些包要发、怎么发。实测效果一次包含typesfix和llmfeat的 commit会正确发布agent-skills/types1.2.1和agent-skills/llm2.1.0而非错误地给两者都打1.2.1。这保证了下游用户升级时能精准控制依赖范围——他们可以放心升级types但对llm的2.1.0新特性则需评估是否引入。2.3 TypeScript 类型系统如何成为 Agent 技能的“安全护栏”TypeScript 在此项目中不是装饰而是核心基础设施。它的作用体现在三个层面第一层输入输出契约的强制声明。每个 Skill 的入口函数必须实现SkillInput, Output接口// packages/types/src/skill.ts export interface SkillInput, Output { /** * 技能唯一标识符用于日志追踪和监控 */ id: string; /** * 执行主逻辑返回 PromiseOutput * throws {SkillError} 当技能内部发生预期外错误时 */ execute(input: Input): PromiseOutput; /** * 可选预执行校验用于快速失败 * 返回 false 表示输入非法不执行 execute */ validate?(input: Input): boolean | Promiseboolean; }这个简单接口带来了巨大约束力。execute方法签名强制要求Input和Output类型必须明确IDE 能据此提供完整补全validate方法的存在让开发者必须思考“什么输入是合法的”而不是把校验逻辑混在execute里。例如agent-skills/exec的Input定义为export interface ExecInput { /** * 要执行的命令字符串 * security 注意此字段可能被 LLM 生成必须进行严格白名单校验 */ command: string; /** * 命令执行的工作目录默认为当前进程目录 */ cwd?: string; /** * 环境变量仅限白名单键名如 PATH, HOME */ env?: Recordstring, string; /** * 最大执行时间毫秒超时则终止进程 * default 30000 */ timeoutMs?: number; }这里的 JSDoc 注释不是摆设——security标签会被nx/js:lint规则捕获要求validate方法必须检查command是否匹配预设正则如^ls|cat|grep|ps$否则 lint 失败。这种“类型即文档、文档即约束”的模式让安全规范从“口头约定”变成了“编译时强制”。第二层错误类型的精细化建模。我们摒弃了泛滥的Error类型定义了SkillError及其子类// packages/types/src/error.ts export class SkillError extends Error { constructor( public readonly code: string, public readonly details?: Recordstring, unknown, public readonly cause?: Error ) { super([${code}] ${details?.message || Unknown error}); this.name SkillError; } } export class ValidationError extends SkillError { constructor(message: string, public readonly field?: string) { super(VALIDATION_ERROR, { message }); } } export class TimeoutError extends SkillError { constructor(public readonly command: string) { super(TIMEOUT_ERROR, { message: Command ${command} timed out }); } }这种设计让错误处理变得可预测。上游 Agent 引擎可以统一捕获SkillError并根据code字段路由到不同处理策略VALIDATION_ERROR返回用户友好的提示“您输入的命令格式不正确请使用 ls 或 cat”TIMEOUT_ERROR则触发降级逻辑尝试简化命令重试而UNKNOWN_ERROR才上报 Sentry。如果没有 TypeScript 的类型守卫这种策略模式极易因instanceof判断失误而失效。第三层类型推导驱动的自动化测试。agent-skills/test-utils包提供了一个createSkillTest工具函数它利用 TypeScript 的类型反射自动生成测试用例的输入模板// test/exec.spec.ts import { createSkillTest } from agent-skills/test-utils; import { execSkill } from ../src/exec; describe(execSkill, () { const test createSkillTest(execSkill); it(should execute ls command successfully, async () { // test.input 自动生成了符合 ExecInput 的完整对象包含所有必填字段 const result await test.execute({ ...test.input, command: ls, cwd: /tmp }); expect(result.stdout).toContain(node_modules); }); });test.input的类型是ExecInput因此 IDE 能提示所有字段且...test.input会填充默认值如timeoutMs: 30000。这避免了手写测试输入时遗漏字段或类型错误大幅提升测试覆盖率和可维护性。3. 核心技能模块详解与实操实现3.1agent-skills/exec安全执行 Shell 命令的终极方案这是整个库中安全要求最高、实现最复杂的技能。它的目标不是“能执行命令”而是“在任何恶意输入下都不危及宿主系统”。我们拒绝使用child_process.exec因为它会启动 shell 解析器极易被注入攻击如command: ls; rm -rf /。最终方案是child_process.spawn 白名单解析 沙箱隔离。核心实现步骤命令白名单解析validate方法将input.command拆分为命令名和参数数组只允许预设命令名通过。// packages/exec/src/validate.ts const ALLOWED_COMMANDS [ls, cat, grep, ps, pwd] as const; type AllowedCommand typeof ALLOWED_COMMANDS[number]; export function parseCommand(command: string): { cmd: AllowedCommand; args: string[] } | null { const parts command.trim().split(/\s/); if (parts.length 0) return null; const [cmd, ...args] parts; // 严格匹配禁止路径如 /bin/ls和扩展名如 ls.exe if (!ALLOWED_COMMANDS.includes(cmd as any)) return null; return { cmd: cmd as AllowedCommand, args }; }关键细节parts[0]必须精确等于白名单中的字符串/bin/ls或ls --help都会被拒绝。args数组后续会逐个校验例如grep的-e参数后必须跟非空字符串。沙箱环境构建execute方法使用spawn启动进程并通过options严格限制其能力// packages/exec/src/execute.ts import { spawn } from child_process; import { tmpdir } from os; export async function execCommand(input: ExecInput): PromiseExecOutput { const parsed parseCommand(input.command); if (!parsed) throw new ValidationError(Invalid command name); // 创建临时工作目录隔离文件系统 const workDir await mkdtemp(join(tmpdir(), agent-exec-)); try { // spawn 选项禁用 shell限制环境变量设置超时 const proc spawn(parsed.cmd, parsed.args, { cwd: input.cwd || workDir, env: { // 只继承 PATH 和 HOME其他全部清空 PATH: process.env.PATH || , HOME: process.env.HOME || /tmp }, timeout: input.timeoutMs || 30000, stdio: [ignore, pipe, pipe] // 重定向 stdin只捕获 stdout/stderr }); // 超时处理 const timeoutPromise new Promisenever((_, reject) { setTimeout(() { proc.kill(SIGTERM); reject(new TimeoutError(input.command)); }, input.timeoutMs || 30000); }); const [stdout, stderr] await Promise.race([ Promise.all([ getStream(proc.stdout), getStream(proc.stderr) ]), timeoutPromise ]); const { status, signal } await new Promise{ status: number; signal: NodeJS.Signals | null }( (resolve) proc.on(close, resolve) ); return { stdout, stderr, exitCode: status, signal }; } finally { // 清理临时目录 await rm(workDir, { recursive: true, force: true }); } }这里env选项是关键我们主动清空所有环境变量只保留PATH和HOME防止恶意脚本通过LD_PRELOAD等方式劫持系统调用。stdio: [ignore, pipe, pipe]确保进程无法读取父进程的 stdin只能通过spawn传入的参数交互。输出流处理getStream函数将ReadableStream转为字符串并限制最大长度防 OOMexport async function getStream(stream: Readable): Promisestring { const chunks: Uint8Array[] []; let totalLength 0; const MAX_OUTPUT_SIZE 1024 * 1024; // 1MB for await (const chunk of stream) { totalLength chunk.length; if (totalLength MAX_OUTPUT_SIZE) { throw new SkillError(OUTPUT_TOO_LARGE, { maxSize: MAX_OUTPUT_SIZE }); } chunks.push(chunk); } return Buffer.concat(chunks).toString(utf8); }实操心得不要信任任何正则表达式过滤。曾有团队用command.replace(/[^a-zA-Z0-9\s]/g, )来“清理”命令结果ls$(rm -rf /)被放过。白名单解析是唯一可靠方案。临时目录权限必须为 0700。Linux 下mkdtemp默认创建 0755 目录其他用户可进入我们通过chmod(workDir, 0o700)强制加固。信号处理要区分SIGTERM和SIGKILL。proc.kill(SIGTERM)发送终止信号进程有机会清理资源若 5 秒后仍未退出再发SIGKILL强制结束。代码中已体现此逻辑。3.2agent-skills/validate基于 JSON Schema 的动态参数校验Agent 的输入常来自 LLM 的自然语言解析格式极不稳定。validate技能的目标是给定一个 JSON Schema能对任意输入对象进行校验并返回结构化的错误信息便于 Agent 引擎向 LLM 提供精准反馈。核心实现我们选用ajv作为校验引擎但对其进行了深度封装以解决两个痛点痛点1AJV 错误信息过于底层。原生ajv.errors返回的是ValidationError[]包含keyword,params,schemaPath等字段对 LLM 不友好。痛点2Schema 编译成本高。每次校验都重新编译 Schema 会拖慢性能。解决方案// packages/validate/src/validator.ts import Ajv from ajv; import addFormats from ajv-formats; const ajv new Ajv({ allErrors: true, strict: true }); addFormats(ajv); // 缓存编译后的验证函数 const validatorCache new Mapstring, ReturnTypetypeof ajv.compile(); export function createValidatorT(schema: JSONSchema): (input: unknown) ValidationResultT { const schemaKey JSON.stringify(schema); // 简单的 key 生成生产环境可用 hash let validateFn validatorCache.get(schemaKey); if (!validateFn) { validateFn ajv.compile(schema); validatorCache.set(schemaKey, validateFn); } return (input: unknown): ValidationResultT { const valid validateFn(input); if (valid) { return { valid: true, value: input as T }; } // 将 AJV 错误转换为 LLM 友好格式 const errors validateFn.errors!.map(err ({ path: err.instancePath || , message: formatErrorMessage(err), keyword: err.keyword })); return { valid: false, errors }; }; } function formatErrorMessage(err: Ajv.ErrorObject): string { switch (err.keyword) { case required: return 缺少必需字段: ${err.params?.missingProperty}; case type: return 字段 ${err.instancePath} 类型错误期望 ${err.params?.type}得到 ${typeof err.data}; case maxLength: return 字段 ${err.instancePath} 长度超出限制最大 ${err.params?.limit}; default: return err.message || 校验失败; } }典型应用示例假设 Agent 需要解析用户请求“查一下上海今天天气”并调用weather技能。weather技能的 Schema 定义为{ type: object, properties: { city: { type: string, minLength: 2, maxLength: 20 }, date: { type: string, format: date } }, required: [city] }validate技能会返回{ valid: false, errors: [ { path: /date, message: 字段 \/date\ 格式错误期望 date得到 string, keyword: format } ] }Agent 引擎可将此错误转化为提示词“请确保日期格式为 YYYY-MM-DD例如 2024-05-20”。实操心得缓存 Schema 编译结果至关重要。AJV 编译一个中等复杂度 Schema 耗时约 5-10ms而校验耗时仅 0.1ms。未缓存时100 次校验耗时 1s缓存后100 次校验耗时 10ms。allErrors: true必须开启。单个输入可能违反多个规则只返回第一个错误会让 LLM 反复试错。strict: true防止 Schema 陷阱。它会拒绝additionalProperties: true这类不安全配置强制开发者显式定义所有允许字段。3.3agent-skills/llm与主流大模型 API 的标准化对接此包不封装具体模型而是提供一个统一的LLMClient接口屏蔽各家 API 的差异。它支持 OpenAI、Anthropic、Ollama 等并通过adapter模式轻松扩展。核心设计// packages/llm/src/client.ts export interface LLMRequest { messages: Array{ role: user | assistant | system; content: string }; model: string; temperature?: number; maxTokens?: number; } export interface LLMResponse { content: string; usage: { promptTokens: number; completionTokens: number }; finishReason: string; } export abstract class LLMClient { abstract call(request: LLMRequest): PromiseLLMResponse; // 通用方法流式调用可选实现 async *stream(request: LLMRequest): AsyncGeneratorstring, void, unknown { throw new Error(Not implemented); } } // packages/llm/src/adapters/openai.ts export class OpenAIClient extends LLMClient { private readonly client: OpenAI; constructor(apiKey: string, baseURL?: string) { super(); this.client new OpenAI({ apiKey, baseURL }); } async call(request: LLMRequest): PromiseLLMResponse { const res await this.client.chat.completions.create({ messages: request.messages.map(m ({ role: m.role, content: m.content })), model: request.model, temperature: request.temperature, max_tokens: request.maxTokens }); return { content: res.choices[0].message.content || , usage: { promptTokens: res.usage?.prompt_tokens || 0, completionTokens: res.usage?.completion_tokens || 0 }, finishReason: res.choices[0].finish_reason || }; } }实操要点Token 计数必须由客户端完成。LLMResponse.usage由服务端返回但LLMRequest的 token 数需客户端预估用于做请求截断。我们集成dqbd/tiktoken为不同模型加载对应 tokenizer// packages/llm/src/tokenizer.ts import { Tiktoken } from dqbd/tiktoken; const encoders new Mapstring, Tiktoken(); export function getTokenCount(text: string, model: string): number { let encoder encoders.get(model); if (!encoder) { // 根据 model 名称选择 encoder如 gpt-4 - cl100k_base encoder new Tiktoken(...getEncoderArgs(model)); encoders.set(model, encoder); } return encoder.encode(text).length; }错误重试需区分错误类型。网络错误AbortError应指数退避重试认证错误401则立即失败速率限制429需提取Retry-After头部。OpenAIClient.call内部已集成此逻辑。流式响应需处理 chunk 边界。OpenAI 的 SSE 响应中content字段可能被拆分成多个delta需拼接完整。stream方法已处理此细节。4. 开发、测试与发布全流程实操指南4.1 本地开发环境搭建从零开始的 Nx 工作区初始化第一步永远是创建工作区。执行npx create-nx-workspacelatest agent-skills \ --presetapps-and-libraries \ --clinx \ --nx-cloudfalse \ --package-managernpm--presetapps-and-libraries是关键它会生成一个包含apps/存放可执行应用如 CLI 工具和libs/存放可复用库即我们的技能包的目录结构。--nx-cloudfalse禁用 Nx Cloud避免首次构建时上传代码。初始化后进入目录安装依赖cd agent-skills npm install此时nx graph命令已可用它会生成一个可视化依赖图。首次运行会显示一个空图因为我们还没有创建任何库。创建第一个技能库agent-skills/typesnx g nx/js:library types \ --directorypackages/types \ --importPathagent-skills/types \ --publishable \ --buildable \ --no-interactive--directorypackages/types指定输出路径符合 monorepo 规范。--importPathagent-skills/types设置包名publishable表示该包可发布到 npm。--buildable表示该包可被构建生成dist目录。生成后packages/types目录下会出现src/index.ts。在此文件中我们定义核心类型// packages/types/src/index.ts export * from ./skill; export * from ./error;然后运行nx build typesNx 会调用nx/js:tsc执行器生成dist/packages/types目录包含index.d.ts和index.js。创建agent-skills/exec库并建立依赖nx g nx/js:library exec \ --directorypackages/exec \ --importPathagent-skills/exec \ --publishable \ --buildable \ --no-interactive接着让exec依赖typesnx g nx/js:dependency --projectexec --targettypes此命令会自动修改packages/exec/project.json添加dependencies配置并更新tsconfig.json的paths映射。验证依赖是否生效在packages/exec/src/index.ts中尝试导入import { SkillError } from agent-skills/types; // IDE 应能正确提示运行nx build exec构建成功即表示依赖链通畅。注意Nx 的dependency命令不会修改package.json的dependencies字段而是通过tsconfig.json的paths和构建时的符号链接来管理。这是 Nx 的核心设计确保了 monorepo 内部的零拷贝依赖。4.2 单元测试与集成测试的分层策略我们采用三层测试策略单元测试Unit Test针对单个函数或类使用 Jest覆盖边界条件和错误路径。集成测试Integration Test针对 Skill 的完整execute流程模拟真实环境如启动子进程使用 Jest 的testEnvironment: node。端到端测试E2E Test针对多个 Skill 的组合使用验证 Agent 引擎能否正确调度使用 CypressNode 环境。单元测试示例agent-skills/exec// packages/exec/src/validate.spec.ts import { parseCommand } from ./validate; describe(parseCommand, () { it(should parse ls command, () { const result parseCommand(ls -la); expect(result).toEqual({ cmd: ls, args: [-la] }); }); it(should reject command with path, () { const result parseCommand(/bin/ls); expect(result).toBeNull(); }); it(should reject command with extension, () { const result parseCommand(ls.exe); expect(result).toBeNull(); }); });运行nx test exec即可执行。集成测试示例agent-skills/exec// packages/exec/src/execute.spec.ts import { execCommand } from ./execute; describe(execCommand, () { it(should execute ls and return stdout, async () { const result await execCommand({ command: ls, cwd: __dirname }); expect(result.exitCode).toBe(0); expect(result.stdout).toContain(index.ts); }); it(should timeout on long-running command, async () { await expect( execCommand({ command: sleep 5, timeoutMs: 1000 }) ).rejects.toThrow(TIMEOUT_ERROR); }); });注意集成测试需在真实 Node 环境中运行因此jest.config.ts中配置了testEnvironment: node。端到端测试apps/agent-cli我们创建一个 CLI 应用作为 Agent 引擎的简化版nx g nx/js:app agent-cli --directoryapps/agent-cli --frameworknone在apps/agent-cli/src/main.ts中编写一个使用exec和validate的简单流程import { execSkill } from agent-skills/exec; import { createValidator } from agent-skills/validate; async function main() { // 1. 定义 Schema const schema { type: object, properties: { command: { type: string } }, required: [command] }; const validator createValidator(schema); // 2. 校验输入 const input { command: ls }; const validationResult validator(input); if (!validationResult.valid) { console.error(Validation failed:, validationResult.errors); return; } // 3. 执行技能 const result await execSkill.execute({ command: input.command }); console.log(Result:, result); } main();运行nx e2e agent-cli即可测试整个流程。4.3 CI/CD 流水线配置GitHub Actions 自动化发布我们在.github/workflows/release.yml中配置流水线name: Release on: push: branches: [main] tags: [*] jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkoutv4 with: fetch-depth: 0 - name: Setup Node.js uses: actions/setup-nodev4 with: node-version: 20 cache: npm - name: Install dependencies run:
返回列表