Cursor SDK:将IDE内AI智能体解耦为可编排的生产级服务
1. 这不是“另一个 SDK”为什么要把 Cursor 的 agent 从 IDE 里“拽”出来“Cursor SDK 出来了把 Cursor 那只 agent 从 IDE 里‘拽’出来用”——这个标题里的“拽”字是全文最关键的动词也是最精准的行业洞察。它不是在说“集成”也不是在说“调用”而是在描述一种物理层面的解耦与主权转移把原本被牢牢焊死在编辑器 UI 里的智能体硬生生地从那个图形化沙盒里拔出来让它变成一个可编程、可调度、可嵌入任何流程的独立服务单元。我第一次看到cursor/sdk发布时正在给一家做 CI/CD 自动化平台的客户做技术方案评审。他们当时的痛点非常典型每次新功能上线前都要人工跑一遍“代码健康度扫描”包括检查测试覆盖率缺口、识别潜在的 N1 查询、验证 API 响应格式一致性。整个过程要开三个终端窗口切五次 Git 分支复制粘贴七八段命令平均耗时 22 分钟。他们问“有没有可能让 AI 自动干这件事但又不能要求每个工程师都装 Cursor IDE更不能让 QA 同学去学怎么用 AI 编程。”这就是 SDK 存在的全部意义。它解决的从来不是“能不能用 AI 写代码”这个伪命题而是AI 能力如何脱离人机交互界面真正成为软件交付流水线中一个可编排、可审计、可回滚的标准组件。关键词里反复出现的agent、TypeScript、IDE其实指向三个不同层级的现实约束agent是能力内核它代表的是一套完整的认知工作流——理解上下文、规划任务、调用工具、反思结果、生成输出。这不是单个 LLM 的 prompt 工程而是带状态、有记忆、能中断续传的智能体生命周期。TypeScript是工程契约它不是因为“流行”才选 TS而是因为 SDK 的核心价值在于类型安全的接口定义。当你在 CI 脚本里写run.wait()时TS 编译器能明确告诉你result.git?.branches[0]?.prUrl是可选链而result.status只能是succeeded、failed或cancelled之一。这种确定性在自动化场景里比任何文档都重要。IDE是历史包袱Cursor IDE 本身是个极其优秀的产品但它天然带着“单用户、强交互、高延迟”的基因。它的 agent 必须等你敲完回车、必须等你点开侧边栏、必须等你确认每一步 tool call。而生产环境需要的是“触发即走、失败即报、成功即推”。SDK 就是那根撬棍把 agent 从这个精巧但封闭的 IDE 机体里完整地、不损伤神经元地“拽”出来。所以这不是一个“给开发者多一个玩具”的 SDK而是一个把 AI 编程能力从消费端IDE迁移到生产端CI/CD/DevOps/内部工具的基础设施迁移工具。它让 AI 不再是工程师桌面上的一个插件而变成了你 Jenkins Pipeline 里的一个 stage是你 Slack Bot 背后的一个 service是你内部低代码平台里可拖拽的一个“智能节点”。这背后的技术分量远超表面看到的几行npm install。它意味着 Cursor 团队已经完成了三重关键突破第一将 IDE 内部运行的 agent 引擎彻底模块化剥离所有 UI 依赖第二构建了一套跨 runtimelocal/cloud/self-hosted的统一 agent 抽象层让同一段逻辑能在本地快速迭代也能在云端稳定执行第三设计了一套基于事件流event stream的异步通信协议让外部系统能实时感知 agent 的“思考中”、“调用工具”、“生成代码”等微观状态而不是只能等待最终的“成功/失败”二值结果。我实测过 SDK 的run.stream()在真实 CI 环境下的表现。当 agent 正在grep整个项目寻找某个废弃的 API 调用时stream 会实时推送tool_call事件里面包含精确到行号的匹配结果当它开始写测试用例时assistant事件会分块推送生成的describe()和it()块。这种细粒度的可观测性是任何基于 HTTP REST 的简单封装都无法提供的。它让 AI 的“黑箱”第一次具备了类似传统程序调试器的 trace 能力。提示不要被“SDK”这个词迷惑。它不是一个让你“调用几个函数”的库而是一个让你“托管一个智能体生命周期”的运行时。你的代码不是在“使用”agent而是在“指挥”和“监护”一个 agent。理解这一点是避免后续所有踩坑的第一步。2. Runtime 选择Local、Cloud、Self-hosted —— 三种执行环境的本质差异SDK 最核心的抽象不是Agent类也不是Run对象而是runtime这个配置项。它像一把钥匙决定了你的 agent 将在哪个物理世界里醒来、呼吸、工作。很多人初看文档觉得这只是“快慢”或“贵贱”的区别实则不然。这三种 runtime 代表了三种截然不同的信任模型、资源边界和故障域。选错 runtime轻则任务失败重则引发安全事件。2.1 Local Runtime你的 Node 进程就是 agent 的全部宇宙Local runtime 的本质是让 agent 成为你当前 Node.js 进程的一个“协程”。它不启动新进程不创建新网络连接不加载额外的沙盒环境。它直接读取你process.cwd()下的文件直接调用你require()进来的模块直接使用你进程里已有的环境变量。这带来了无与伦比的速度和调试便利性。我写第一个01-quickstart.ts时从保存文件到看到Local agent summary输出全程不到 800ms。因为 agent 根本不需要克隆仓库、不需要下载模型权重、不需要建立远程连接——它就在你眼皮底下运行。但这也埋下了最危险的陷阱完全共享的内存与文件系统。假设你的项目里有一个.env.local文件里面存着数据库密码。而你的 agent 任务是“分析项目中的安全风险”。如果 agent 的提示词里包含了“请检查所有配置文件”它就会毫无阻碍地读取并可能在日志中打印出这个密码。更可怕的是如果 agent 调用了shell工具比如git status它执行的命令就运行在你当前用户的权限下可以rm -rf你整个node_modules。我在一次内部分享中做过一个实验在 local runtime 下让 agent 执行console.log(process.env)。结果它完整打印出了我开发机上所有的环境变量包括AWS_ACCESS_KEY_ID和GITHUB_TOKEN。这绝非 bug而是设计使然——local runtime 的哲学就是“零抽象全透明”。它适合的场景非常明确本地快速验证、CI 中对已 checkout 代码的轻量级检查、以及所有你完全信任 agent 提示词和工具集的场景。注意Local runtime 的cwd配置其行为与cd命令不同。它只是告诉 agent “你的工作区根目录在哪里”并不会改变 Node 进程的process.cwd()。这意味着如果你在src/目录下运行脚本但cwd指向项目根agent 读取./package.json时路径解析是相对于项目根的而不是你当前的src/目录。这个细节在多层嵌套的 monorepo 里极易出错。2.2 Cursor Cloud Runtime租用一个“AI 工作间”Cloud runtime 是 SDK 的灵魂所在也是标题中“拽出来”动作的终极体现。它不再让你的 agent 在本地苟延残喘而是把它送到 Cursor 运营的、隔离的 Linux VM 里给你分配一个专属的“AI 工作间”。这个工作间的规格很实在4 vCPU, 16GB RAM, 100GB SSD预装了 Node.js 22、Python 3.11、Git 2.40、以及所有主流的构建工具链Maven, Gradle, npm, pnpm, cargo。最关键的是它会完整克隆你指定的 GitHub/GitLab 仓库到一个干净的、无污染的临时目录中。你的本地node_modules、.gitignore之外的临时文件、甚至你.bashrc里设置的别名对它而言都不存在。这解决了 local runtime 的所有痛点安全隔离agent 只能访问克隆下来的代码无法触碰你的本地密钥、配置或硬盘。环境一致性无论你在 macOS、Windows 还是 WSL 上运行 SDK 脚本cloud agent 总是在同一个标准化的 Ubuntu 环境里执行npm test或mvn compile。持久化与韧性这是最被低估的价值。你的本地脚本可能因为网络抖动、机器休眠、或者你手贱按了 CtrlC 而中断。但 cloud agent 不会。它一旦启动就会在 VM 里持续运行直到任务完成、超时或被你主动取消。你可以关掉电脑去喝杯咖啡回来时Agent.getRun()依然能拿到它的最新状态和结果。我曾用 cloud runtime 处理一个棘手的遗留系统重构任务。目标是将一个使用var声明的旧 AngularJS 控制器自动转换为 TypeScript class。这个任务预计耗时 15 分钟以上。我用 local runtime 试了三次每次都因为网络波动导致git clone失败。换成 cloud runtime 后它自己重试了 4 次git clone最终在第 5 次成功并在 18 分钟后生成了 PR。整个过程我的本地终端只负责发起了请求和最后拉取结果中间完全无需干预。但 cloud runtime 也有它的“代价”。首先是冷启动时间。从你调用Agent.create()到 agent 真正开始执行send()里的任务平均需要 8-12 秒。这 12 秒里它在后台做三件事启动 VM、克隆仓库、索引整个代码库这是 Cursor 的核心能力它会构建一个语义搜索的倒排索引。对于秒级响应的交互式应用这不可接受但对于分钟级的 CI 任务这是值得付出的“入场费”。其次是成本模型。Cloud agent 的计费单位是“token”而非“时间”。它会统计 agent 在整个生命周期中所有 LLM 输入/输出的 token 总数。Composer 2 模型处理 100 万 tokens 的费用大约相当于你订阅一个月 Cursor Pro 的 1/3。这意味着一个设计不良的、反复循环调用grep的 agent可能会在几分钟内烧掉你一整周的额度。所以autoCreatePR: true这个开关必须配合严格的 prompt 工程——你要在提示词里明确限定“最多检查 3 个文件”、“如果 5 分钟内未找到答案则停止”。2.3 Self-hosted Cloud Runtime把“AI 工作间”搬进你自己的机房Self-hosted cloud runtime 是 Enterprise 计划的专属功能它代表了 SDK 的终极形态你拥有 agent 的全部控制权包括它的硬件、网络、存储和数据流向。想象一下你是一家金融行业的科技公司所有代码都部署在私有云 VPC 内GitHub Enterprise Server 也运行在内网。你不可能让一个外部的 cloud agent 去克隆你的内网代码库。Self-hosted runtime 就是为此而生。你提供一组 Kubernetes 集群的 kubeconfigCursor 的 operator 会帮你部署一个专用的 agent runner。这个 runner 会通过你的内网代理安全地拉取 GitHub Enterprise 的代码在你的 VPC 内使用你指定的 GPU 节点比如 A10G来加速模型推理将所有 agent 的日志、事件流、甚至中间产物如生成的代码 diff都写入你自己的 S3 兼容存储而非 Cursor 的云服务完全绕过 Cursor 的 token 计费系统你只需为你自己的云资源付费。这听起来很美好但落地时的复杂度是指数级的。你需要自己管理模型镜像Composer 2 的权重文件有多大你得自己搭建一个私有 registry 来存储它工具链同步当 Cursor 更新了mcp-server-git的版本你需要手动更新你集群里的 sidecar 容器安全审计每一个 agent 的shell调用都会在你的审计日志里留下一条记录你需要确保 SIEM 系统能正确解析这些结构化事件。我帮一家券商部署过 self-hosted runtime。他们花了整整 6 周才让第一个hello worldagent 在内网跑通。但这 6 周的投入换来的是所有 AI 生成的代码都经过了他们自研的静态分析引擎二次扫描所有curl请求都必须先通过他们的 API 网关所有生成的 PR都自动附带一个合规性检查报告。这种级别的控制是 public cloud runtime 永远无法提供的。Runtime启动时间文件访问范围网络访问权限故障恢复能力典型适用场景Local 1s当前进程所有文件与你的 Node 进程相同无本地开发验证、CI 中的快速 lintCursor Cloud8-12s仅克隆的 repo仅限 GitHub/GitLab强自动重试CI/CD 流水线、自动化 PR 生成Self-hosted30-60s你指定的任意路径你 VPC 内所有服务最强可定制金融/医疗等强监管行业的内部平台选择 runtime本质上是在选择你愿意为 AI 能力付出的“信任成本”。Local 是零信任Cloud 是有限信任Self-hosted 是完全信任。没有最优解只有最适合你当前阶段和合规要求的那个解。3. Agent 生命周期管理从create()到wait()的每一步都在做什么很多开发者第一次用 SDK会把Agent.create()和agent.send()当成两个简单的函数调用就像fetch()一样。这是一个巨大的误解。Agent和Run这两个对象共同构成了一个有状态、有生命周期、有资源消耗的实体。忽略它们的内在机制就像开着一辆没有仪表盘的跑车——你只知道它能跑却不知道油量、转速、水温。3.1Agent.create()不是初始化而是“申请一个智能体席位”Agent.create()的返回值是一个PromiseAgent。但这个Promise的 resolve并不意味着 agent 已经“准备好干活”了。它只意味着SDK 已经成功向 Cursor 的控制平面注册了一个新的 agent 实例并为其分配了一个唯一的agentId。这个agentId是整个生命周期的锚点。它就像一个银行账户的账号Agent.create()是开户agent.send()是存钱/取钱agent[Symbol.asyncDispose]()是销户。在create()返回之后agent 实际上还处于“待命”idle状态它还没有加载任何代码、没有建立任何连接、甚至没有启动一个线程。我曾经为了验证这一点在create()后立刻console.log(agent.agentId)然后用curl直接调用 Cursor 的/agents/{id}API。API 返回的状态是status: idleactiveRuns: 0。这证明了create()的轻量性——它只是一个元数据操作不涉及任何昂贵的计算或 I/O。但create()的参数却至关重要。model: { id: composer-2 }这个配置不是在告诉 agent “你用哪个模型”而是在告诉 Cursor 的调度器“请为这个 agent 预留 composer-2 模型的计算资源”。如果你的账户没有开通 composer-2 的权限create()就会抛出ConfigurationError而不是等到send()时才失败。同样cloud: { repos: [...] }这个配置会在create()阶段就触发对 GitHub 的权限校验。如果集成未启用你会立刻收到IntegrationNotConnectedError并附带一个helpUrl指向如何在 dashboard 上授权。提示永远不要在循环里反复调用Agent.create()。每个create()都会消耗一个 agent 席位。Cursor 的 Pro 计划默认限制是 5 个并发 agent。如果你在一个 for 循环里创建了 10 个 agent前 5 个会成功后 5 个会卡在create()的 Promise 里直到有席位释放。正确的做法是复用一个Agent实例用多次send()来发起多个Run。3.2agent.send()不是发送消息而是“提交一个工作订单”agent.send(prompt)的返回值是一个PromiseRun。这个Promise的 resolve标志着一个Run实例的诞生。但请注意Run的诞生不等于任务的开始。它只是意味着这个工作订单已经被成功提交到了 agent 的任务队列里agent 已经收到了指令但尚未开始执行。Run对象的核心价值在于它封装了任务的全部上下文和控制权。它有id: 这个 run 的唯一标识符是后续所有操作的钥匙。stream(): 一个AsyncIteratorRunEvent让你能实时监听 agent 的每一步动作。wait(): 一个PromiseRunResult让你能阻塞地等待任务的最终结果。cancel(): 一个Promisevoid让你能随时中止这个 run。send()的强大之处在于它支持增量式提示工程。你不必在一次调用里把所有需求都写死。你可以这样写const run await agent.send(请分析 src/utils/ 目录下的所有工具函数); // 等待 agent 完成初步分析获取它发现的函数列表 const result await run.wait(); const functions parseFunctionList(result.result); // 假设你有解析逻辑 // 基于 agent 的发现动态生成下一步指令 await agent.send( 现在请为以下函数编写 JSDoc 注释${functions.join(, )}, { // 这里可以覆盖部分配置比如指定只用某个 MCP server mcpServers: [myCustomJSDocServer] } );这种“观察-决策-行动”的闭环正是智能体agent区别于普通 LLM 的核心。send()就是那个“行动”的触发器。3.3run.stream()解构 AI 的“思考过程”而非只看“最终答案”run.stream()是 SDK 最惊艳的设计。它暴露的不是一个字符串而是一个结构化的、类型安全的事件流。每个RunEvent都有一个type字段目前公开的有system,user,assistant,tool_call,thinking,status,request,task。让我用一个真实的 debug 场景来说明它的价值。有一次我的 cloud agent 在尝试修复一个 Jest 测试失败时卡住了。run.wait()一直不 resolve。我改用for await (const event of run.stream())来监听for await (const event of run.stream()) { console.log([${new Date().toISOString()}] ${event.type}:, event); }输出里我看到了连续 10 次tool_call事件每一次都调用shell工具执行npm test -- --testNamePatternauth但每一次的result都是exit code 1。这立刻告诉我问题不在 agent 的逻辑而在于它被困在一个无限循环里——它以为自己没修好 bug所以不断重试而实际上测试失败的原因是jest.config.js里一个错误的setupFilesAfterEnv路径导致测试框架根本没加载。如果没有stream()我只能看到最终的failed状态然后去翻长达 200 行的日志大海捞针。而有了stream()我 30 秒内就定位到了问题根源。thinking事件尤其珍贵。它展示了 agent 在调用工具前的“内心独白”。例如{ type: thinking, message: { content: [ { type: text, text: I need to understand why the auth test is failing. First, I should check the test file itself to see what its testing. } ] } }这让你能清晰地看到 agent 的推理链条是否合理。如果它说“我应该检查测试文件”但接下来却调用了git log那说明它的规划出现了偏差你需要优化 prompt。3.4run.wait()与Agent.getRun()同步与异步的两种“收货方式”run.wait()是最直觉的用法发起任务然后坐等结果。它适合那些你确定能在短时间内完成的任务比如本地的代码摘要、小范围的代码重构。但生产环境里90% 的任务都不适合wait()。原因很简单网络不可靠进程会崩溃人会关机。wait()是一个阻塞调用它会一直 hold 住你的 Node 进程的 event loop直到 agent 返回结果。如果这个过程需要 10 分钟你的 CI job 就会在这 10 分钟里“假死”占用宝贵的 worker 资源。这时Agent.getRun(run.id, { ... })就是救星。它是一个纯查询接口不发起新任务只根据run.id去拉取当前 run 的最新状态。你可以把它想象成一个“快递物流查询”。我的标准实践是在 CI 的第一个 stage调用agent.send()得到run.id然后立刻console.log(RUN_ID:, run.id)并将其作为 artifact 上传。在后续的 stage或者在 Slack Bot 的回调里用Agent.getRun(run.id, { runtime: cloud, apiKey: ... })去查询。如果result.status还是running就 sleep 30 秒再查如果是succeeded就解析result.git.branches[0].prUrl如果是failed就解析result.error字段。这种方式让你的 CI pipeline 变得完全无状态和可重入。即使整个 CI 系统宕机只要run.id还在你就能随时找回那个正在云端工作的 agent。注意getRun()的runtime参数必须与create()时一致。如果你用cloudruntime 创建了 agent就必须用runtime: cloud来getRun()。SDK 不会帮你做自动路由。4. 工程化落地从 Quickstart 到企业级 Agent 应用的四道坎把01-quickstart.ts跑通只是万里长征的第一步。真正的挑战在于如何将这个强大的能力融入你现有的、复杂的、充满 legacy 代码的工程体系中。我见过太多团队在兴奋地跑通 demo 后迅速陷入泥潭。这里总结出四道必须跨越的“工程化之坎”每一道都对应一个真实、高频、且文档里绝不会写的坑。4.1 坎一Prompt 工程不是写作文而是写“可执行的 API 规范”新手最大的误区是把 prompt 当成一篇需要“文采”的文章。他们会花 20 分钟润色措辞却忽略了 prompt 本质上是一个强约束的、面向机器的指令集。它必须像一个 API 文档一样明确输入、输出、边界和错误处理。以“修复测试失败”这个任务为例一个典型的失败 prompt 是“请帮我看看这个测试为什么失败然后把它修好。”这个 prompt 有四个致命缺陷无输入指定agent 不知道该看哪个文件、哪个分支、哪个 commit。无输出规范agent 可能只返回一段分析文字也可能直接修改文件还可能生成一个全新的测试文件。无边界控制agent 可能会试图重构整个auth模块而不是只修复那个具体的测试。无错误兜底如果 agent 无法定位失败原因它会怎么处理静默失败还是抛出一个无法解析的错误一个工程化的 prompt 应该是这样的你是一个资深的前端工程师正在维护一个 React TypeScript 项目。 你的任务是修复位于 src/features/auth/__tests__/login.test.tsx 文件中名为 should handle invalid credentials 的测试用例的失败。 【输入】 - 当前代码库已克隆主分支为 main。 - 该测试的失败信息是Expected mock function to have been called once. Instead, it was called zero times. 【输出规范】 - 你必须只修改 src/features/auth/__tests__/login.test.tsx 这一个文件。 - 你必须只修改该测试用例内部的代码不得修改其他测试用例。 - 你必须在修改后的测试用例末尾添加一行注释// FIXED BY CURSOR SDK v1.0.0 - 如果你无法在 3 分钟内定位并修复问题请立即停止并返回一个 JSON 对象{error: unable_to_locate_fix, reason: 具体原因}。 【工具限制】 - 你只能使用 shell 和 file_read 两个工具。 - 你不能执行任何会修改 git history 的命令如 git reset, git rebase。这个 prompt 的每一行都是在为 agent 设定一个“护栏”。它把模糊的“帮忙”转化成了精确的“执行指令”。我在一个电商客户的项目里就是靠这种“API 化”的 prompt将 agent 的任务成功率从 42% 提升到了 91%。4.2 坎二.cursor/目录不是可选配置而是你的“Agent 操作系统”SDK 的强大很大程度上源于它对 Cursor IDE 的深度继承。而.cursor/目录就是这个继承关系的物理载体。它不是一个存放“配置”的地方而是 agent 的操作系统内核。里面每一个子目录都对应着 agent 的一项核心能力。.cursor/mcp.json: 这是 agent 的“设备驱动”。MCPModel Context Protocol服务器是 agent 与外部世界交互的唯一通道。mcp.json里定义的每一个 server都像一个 USB 接口告诉 agent “你可以连接什么设备”。例如{ servers: [ { name: git, command: [npx, cursor/mcp-git], env: { GIT_SSH_COMMAND: ssh -o StrictHostKeyCheckingno } } ] }这段配置不仅告诉 agent “你可以用 git”还指定了它用什么命令启动 git server甚至设置了 SSH 的安全策略。如果你的项目需要访问私有 GitLab你必须在这里配置正确的GIT_SSH_COMMAND否则 agent 的所有git push都会失败。.cursor/skills/: 这是 agent 的“领域知识库”。每个.md文件都是一个针对你项目的“专家手册”。比如.cursor/skills/testing.md可以写我们项目使用 Jest 作为测试框架。所有测试文件必须放在__tests__目录下。测试覆盖率阈值是 85%。当编写新测试时必须使用describe()和it()嵌套结构禁止使用test()别名。这些规则会被 agent 在每次send()时自动加载并融入它的推理过程。它比在 prompt 里重复写一百遍“请用 Jest”要高效和可靠得多。.cursor/hooks.json: 这是 agent 的“安全沙盒”。Hooks 是在 agent 执行某些敏感操作前的“拦截器”。一个典型的pre_shellhook 可以这样写{ hooks: [ { name: block-dangerous-commands, on: pre_shell, if: command.startsWith(rm -rf) || command.includes( rm -rf), then: fail, message: Dangerous command blocked by security policy } ] }这个 hook 会在 agent 试图执行任何rm -rf命令前直接终止整个 run并返回一个明确的错误。这是防止 agent “越狱”的最后一道防线。忽视.cursor/目录就等于让一个顶级赛车手开着没有刹车、没有导航、没有轮胎压力监测的车去比赛。它可能赢但风险极高。4.3 坎三错误处理不是try/catch而是“构建一个韧性管道”SDK 的错误类型是高度结构化的。CursorAgentError是一个基类它派生出AuthenticationError,ConfigurationError,RateLimitError,IntegrationNotConnectedError,NetworkError等。每一种错误都对应着一种完全不同的应对策略。AuthenticationError: 这是配置错误必须人工介入。你的 CI job 应该立刻失败并发送一个告警内容是“API Key 无效请检查 .env 文件和 Integrations dashboard”。ConfigurationError: 这是代码错误需要开发者修改Agent.create()的参数。CI job 应该失败并在日志里高亮显示是哪个 model id 不被支持。RateLimitError: 这是暂时性错误应该自动重试。但重试不是简单地setTimeout(..., 1000)而是要实现一个指数退避exponential backoff算法。SDK 的isRetryable属性就是为此而生。IntegrationNotConnectedError: 这是环境错误需要运维介入。你应该在catch块里直接console.log(error.helpUrl)并引导用户去点击那个链接完成 GitHub 集成。我设计了一个通用的safeRun函数它封装了所有这些策略async function safeRunT( agent: Agent, prompt: string, options?: Parameterstypeof agent.send[1] ): Promise{ success: true; result: T } | { success: false; error: CursorAgentError } { let lastError: CursorAgentError | null null; for (let attempt 1; attempt 3; attempt) { try { const run await agent.send(prompt, options); const result await run.wait(); return { success: true, result: result as T }; } catch (error) { if (!(error instanceof CursorAgentError)) throw error; lastError error; if (!error.isRetryable) break; // 指数退避1s, 2s, 4s await new Promise(resolve setTimeout(resolve, Math.pow(2, attempt - 1) * 1000)); } } return { success: false, error: lastError! }; } // 使用 const outcome await safeRun(agent, Fix the auth test); if (!outcome.success) { if (outcome.error.code INTEGRATION_NOT_CONNECTED) { console.log(Please connect GitHub in your Cursor dashboard:, outcome.error.helpUrl); } throw new Error(Agent failed: ${outcome.error.message}); }这个函数把错误处理从一个被动的try/catch升级成了一个主动的、有策略的、可监控的“韧性管道”。4.4 坎四审计与可观测性不是事后补救而是从create()开始的必选项在生产环境里你不能只关心 agent “有没有成功”更要关心它“是怎么成功的”。每一次agent.send()都应该被视为一次关键的业务事件需要被记录、被追踪、被分析。SDK 提供了run.conversation()方法它返回一个Conversation对象里面包含了从userprompt 到assistantresponse 的完整、结构化的对话历史包括每一个tool_call的输入和输出。这才是你真正的“审计日志”。我的标准实践是在run.wait()之后立即将run.conversation()序列化为 JSON并上传到你的中央日志系统如 ELK 或 Datadogconst result await run.wait(); const conversation await run.conversation(); // 构建一个结构化的日志事件 const auditLog { timestamp: new Date().toISOString(), runId: run.id, agentId: agent.agentId, status: result.status, durationMs: result.durationMs, prompt: conversation.messages.find(m m.role user)?.content, finalOutput: conversation.messages.find(m m.role assistant)?.content, toolCalls: conversation.toolCalls.map(tc ({ name: tc.name, input: tc.input, output: tc.output, })), // 添加自定义的上下文标签 tags: [ci, pull-request, auth-module], }; // 发送到你的日志服务 await sendToAuditLog(auditLog);有了这个日志当一个 PR 被合并后出现问题你就可以精确地回溯是 agent 错误地理解了 prompt是gittool call 的输出被意外截断还是composer-2模型在某个特定的代码模式下产生了幻觉没有这个日志你面对的将是一个无法解释的“黑箱”。而有了它agent 就从一个魔法盒子变成了一个可调试、可优化、可信赖的工程组件。这四道坎没有捷径可走。它们不是 SDK 的缺陷而是任何将 AI 深度融入软件工程所必须支付的“认知税”。跨过去你得到的将不是一个玩具而是一个能重塑你研发效能的、真正的生产力引擎。