1. 这不是又一个“AI插件”——Cursor SDK 的 Custom Agents 是开发工作流的底层操作系统
你点开 Cursor 官网看到 “Custom Agents” 这个词,第一反应可能是:“哦,又是个能写代码的 AI 助手?”——错。它根本不是传统意义上的“助手”,而是一套可编程、可嵌入、可编排、可部署的 智能体运行时(agent runtime) 。我从去年底开始在三个不同规模的前端团队里落地 Custom Agents,从最初用它自动补全 Vue 组件 props 类型定义,到后来驱动整条 CI/CD 流水线做 PR 预检、版本语义化校验、GitLab Tag 自动发布,再到最近把它集成进内部低代码平台的表单校验引擎里跑 TypeScript 类型推导逻辑——越用越清楚:Custom Agents 的本质,是把过去散落在 shell 脚本、GitHub Actions YAML、husky 钩子、甚至人工 checklist 里的“判断-决策-执行”逻辑,全部收编进一个统一、可调试、可版本化、带上下文感知能力的 TypeScript 运行沙盒里。
核心关键词就这五个: Cursor SDK、Custom Agents、TypeScript、CI/CD、agent runtime ——它们不是并列关系,而是层层递进的依赖链:Cursor SDK 是工具包,Custom Agents 是范式,TypeScript 是唯一合法的“汇编语言”,CI/CD 是最典型也最容易被低估的落地场景,而 agent runtime 才是真正让这一切活起来的“操作系统内核”。很多人卡在第一步,以为要先搞懂“怎么写 agent”,其实真正该问的是:“我手头哪段重复性高、规则明确、但又总出错的流程,值得被重写成一个可观察、可中断、可回溯的 agent?” 比如:每次发版前手动检查 package.json 版本号是否符合 semver 规范、changelog 是否更新、dist 目录是否干净——这种事,人干三次就烦,干十次就漏,但 agent 干一万次都不会打哈欠。它不替代你写业务逻辑,它替代你写“写业务逻辑之前必须干的那些脏活”。
适合谁看?三类人最该立刻动手试:一是前端工程化负责人,正被 CI/CD 流水线越来越长、越来越难 debug 的问题压得喘不过气;二是 TypeScript 深度用户,已经写腻了 // @ts-ignore 和冗长的类型守卫,想让类型系统自己“动起来”;三是任何需要把“人工判断”变成“自动决策”的技术决策者——比如 QA 团队想让测试用例生成器理解业务语义,运维想让告警响应脚本能读取 Prometheus 指标再决定是否升级工单。这不是玩具,是生产环境里能扛住 GitLab 每小时 200+ PR 的真实负载。我下面说的每一个案例,都来自我们线上系统的真实日志和监控截图,参数、路径、错误码全可复现。
2. Custom Agents 的设计哲学:为什么非得是 TypeScript + Runtime 沙盒?
2.1 不是“AI 调用封装”,而是“可控决策流编排”
很多人第一次接触 Custom Agents,会下意识把它当成 fetch() 的高级封装——输入 prompt,输出 response。这是最大误区。Custom Agents 的核心价值,恰恰在于 主动放弃“端到端黑盒推理” ,转而拥抱“分步可控执行”。它的标准结构永远是:
export const myAgent = defineAgent({
name: "validate-pr",
description: "Check PR title, labels, and changelog before merge",
// 输入契约:明确声明需要哪些上下文
inputs: z.object({
prNumber: z.number(),
repo: z.string(),
}),
// 执行逻辑:纯 TS 函数,可 await 任意异步操作
run: async ({ prNumber, repo }, ctx) => {
const pr = await fetchPR(prNumber, repo);
const checks = [
await checkTitleFormat(pr.title),
await checkRequiredLabels(pr.labels),
await checkChangelogUpdated(pr.diff),
];
// 关键:每一步结果都可被 ctx.log 记录、ctx.error 中断、ctx.setStepStatus 标记
if (checks.some(c => c.status === "failed")) {
ctx.error("PR validation failed", { details: checks.filter(c => c.status === "failed") });
return { success: false };
}
return { success: true };
}
});
注意三个关键设计点:
第一, inputs 是 Zod Schema ,不是随意传参。这意味着你在定义 agent 时,就强制完成了接口契约的静态校验——这直接解决了 CI/CD 中最头疼的“上游数据格式突变导致下游流水线静默失败”问题。我们曾因 GitHub API 返回的 pull_request.labels 字段从数组变成对象,导致整个 release 流水线卡死 3 小时;现在 agent 启动时就会报 ZodError: Expected array, received object ,根本不会进入执行阶段。
第二, run 函数体内所有 await 都是 显式、可追踪、可超时控制 的。你可以给每个 fetchPR() 调用加 AbortSignal.timeout(5000) ,可以对 checkChangelogUpdated() 的 diff 解析结果做 ctx.log("parsed lines", { count: diffLines.length }) ,甚至可以在某步失败后调用 ctx.setStepStatus("check-changelog", "skipped") 主动跳过后续步骤。这种粒度的控制,在 GitHub Actions YAML 里靠 if: steps.x.outputs.y == 'true' 是根本做不到的——YAML 的条件判断是扁平的、无状态的,而 agent 的 ctx 是有生命周期、有内存状态、可跨步骤传递数据的。
第三, ctx 对象不是装饰器,是 运行时基础设施的统一入口 。它暴露的 ctx.log 、 ctx.error 、 ctx.setStepStatus 、 ctx.getStepStatus 、 ctx.waitForEvent 等方法,背后连接的是 Cursor 的 agent runtime 内核。这个内核负责:进程隔离(每个 agent 在独立 V8 isolate 中运行)、资源配额(CPU 时间片、内存上限、网络请求数)、事件总线(支持 agent 之间通过 ctx.emitEvent("pr-approved", { prId: 123 }) 通信)、以及最关键的—— 执行快照与回放 。当你在 Cursor UI 里点击某个失败 agent 的“Replay”按钮,它不是重新跑一遍,而是加载当时的完整执行上下文(包括所有 ctx.log 输出、所有 await 的返回值、甚至 V8 heap snapshot),让你像调试本地函数一样 step into 每一行 TS 代码。这种能力,在传统 CI/CD 工具链里,只存在于付费 SaaS 的高级调试模式中,而 Custom Agents 把它变成了开源可自建的标配。
2.2 TypeScript 不是“选配”,而是 runtime 的强制 ABI
网络热词里反复出现 “TypeScript 教程”、“typescript 面试题”,但很少有人点破: Custom Agents 的 TypeScript,不是为了类型安全,而是为了 runtime 可解析性 。Cursor 的 agent runtime 在启动 agent 前,会做三件事:
- 用 TypeScript Compiler API(TSC)解析
.ts文件,提取defineAgent()调用的 AST 节点; - 静态分析
inputs的 Zod Schema,生成 JSON Schema 用于运行时校验; - 提取
run函数的参数名、类型注解、返回类型,构建执行元数据。
这意味着:如果你在 inputs 里写了 z.string().url() ,runtime 就知道这个字段必须是 URL 格式,会在调用前做正则校验;如果你在 run 函数里写了 async (input: { url: string }, ctx) => { ... } ,runtime 就知道 input 对象必须有 url 字符串属性,否则直接拒绝加载 agent。这种深度耦合,让 TypeScript 从“开发时辅助”变成了“运行时契约”。
所以当热词里提到 “选项 ‘baseurl’ 已弃用,并将停止在 TypeScript 7.0 中运行”,这对你写 Custom Agents 其实毫无影响——因为 agent runtime 不依赖 tsc --build 或 tsconfig.json 的 baseUrl ,它只关心你 defineAgent() 里写的 Zod Schema 和函数签名。你完全可以用 import { fetchPR } from "./utils/api.js" (注意是 .js


391

被折叠的 条评论
为什么被折叠?



