Paseo SDK 实战食谱:用 TypeScript 编排 Issue 流转、并行审查与常驻 Agent
【免费下载链接】paseoOrchestrate multiple coding agents from desktop and mobile项目地址: https://gitcode.com/gh_mirrors/pa/paseo
@getpaseo/client是 Paseo 的 TypeScript SDK,它通过 WebSocket 驱动本地 Paseo 守护进程(daemon)来创建、运行和回收编码 Agent。本篇文章以官方 SDK Recipes 文档为主线,完整呈现四个可直接落地的高频模式——把 Issue 变成可见工作、并行运行多个审查者、跨进程重启保持常驻角色、以及安全清理临时 Agent——并结合仓库源码与官方示例深入讲解其底层机制。读完你将掌握如何在自己的集成程序里创建、复用、追踪和归档 Agent,并把它们与 Paseo App 中的手动任务无缝并排。
前置准备:连接守护进程
SDK Recipes 中的所有示例都假设你已经有一个“已连接的client”,它从公共包根导入:
import { createPaseoClient } from "@getpaseo/client"; const client = createPaseoClient({ url: "ws://127.0.0.1:6767/ws" }); await client.connect();守护进程默认监听ws://127.0.0.1:6767/ws,可通过npx @getpaseo/cli启动(详见 public-docs/sdk/index.md)。若守护进程设置了密码,或需要连接远程 daemon,则在配置中传入password字段:
const client = createPaseoClient({ url: "wss://devbox.example.com/ws", password: "my-secret", });从源码看,createPaseoClient在 packages/client/src/index.ts#L506-L526 中构造DaemonClient,并返回带有connect()、close()、agents、workspaces、providers、config等命名空间的对象。connect()会一直等到守护进程完成自我标识才 resolve;而close()只关闭 SDK 连接,不会归档任何 Agent——Agent 会继续在 daemon 上运行并保持可见(详见 public-docs/sdk/quickstart.md)。
食谱一:把一个 Issue 变成可见的工作
这是最经典的集成场景:把外部系统(Issue 跟踪器、告警、Webhook)中的一条任务,转化为一个由编码 Agent 执行的可见工作项。
type Issue = { id: string; title: string; description: string; repositoryPath: string; }; async function startIssue(issue: Issue) { const workspace = await client.workspaces.open(issue.repositoryPath); const agent = await workspace.agents.create({ config: { provider: "codex/gpt-5.5", }, title: issue.title, labels: { "issue-provider": "my-tracker", "issue-id": issue.id, }, prompt: [ `Implement issue ${issue.id}: ${issue.title}`, "", issue.description, "", "Run focused tests and summarize the result.", ].join("\n"), }); return { workspaceId: workspace.id, agentId: agent.id }; }这个模式与仓库中的官方示例 packages/client/examples/issue-to-agent.ts 完全一致。其核心设计有四点:
workspaces.open(path)以目录为身份:重复打开同一目录会复用已有的活跃 workspace(源码位于 packages/client/src/index.ts#L209-L213),避免重复创建项目。- 通过 workspace handle 创建 Agent:
workspace.agents.create()由 handle 自动补全cwd与workspaceId的放置参数,避免调用方重复传目录(见 packages/client/src/index.ts#L818-L829)。config.provider始终是provider/model格式,比如codex/gpt-5.5、claude/claude-sonnet-5。 - Label 是集成方的元数据:
"issue-provider"、"issue-id"这类键是应用自有的,用于后续检索和去重。 create()在会话建立后即返回,此时 prompt 仍在运行中,Agent 会立即出现在 Paseo App 里,和手动启动的任务并排显示。
关键要点:持久化返回的workspaceId与agentId。在下一个 Webhook 或页面加载时,用workspaces.ref()和agents.ref()恢复句柄,而不是重复创建造成重复 Agent:
const agent = client.agents.ref(savedAgentId); await agent.refresh(); await agent.run("继续完成剩余部分。");ref()不会立即联系 daemon,它只是构建一个“零观察”的句柄;需要确认 Agent 是否仍存在时,先调用refresh()——若不存在会返回null(详见 public-docs/sdk/agents.md)。
食谱二:并行运行多个审查者
把一个 diff 同时交给不同角度的审查者,收集所有结论。官方食谱用Promise.all并发创建,再用waitForFinish()并发等待:
const prompts = [ "Review the diff for correctness and missed edge cases.", "Review the diff for security and unsafe input handling.", "Review the diff for unnecessary complexity.", ]; const reviewers = await Promise.all( prompts.map((prompt, index) => client.agents.create({ config: { provider: index === 1 ? "claude/claude-sonnet-5" : "codex/gpt-5.5", }, cwd: process.cwd(), title: `Review ${index + 1}`, prompt, }), ), ); const results = await Promise.all(reviewers.map((reviewer) => reviewer.waitForFinish())); for (const result of results) { console.log(result.status, result.lastMessage); }该模式与仓库示例 packages/client/examples/parallel-review.ts 高度吻合,示例中还演示了最佳实践:把创建出的 Agent 句柄收集进数组,在finally中用Promise.allSettled逐一归档,避免并发审查结束后留下游离 Agent。
关于等待与状态:waitForFinish()默认最多等待 10 分钟(源码中DEFAULT_WAIT_FOR_FINISH_MS = 10 * 60_000,见 packages/client/src/index.ts#L61-L65),可以传入毫秒数调整。它返回四种状态之一:
| 状态 | 含义 |
|---|---|
idle | 本轮结束,Agent 可以接收下一条 prompt。 |
permission | Agent 正在 Paseo 中等待某人响应权限请求。 |
error | Provider 以错误结束本轮。 |
timeout | 等待超时,Agent 可能仍在运行。超时不会取消 Agent。 |
由于waitForFinish在 packages/client/src/index.ts#L962-L971 的实现里会把最终快照写回句柄,所以等待完成后可以直接读result.lastMessage与result.status。
食谱三:跨进程重启保持常驻角色
有些场景需要一个“一直存在”的角色(比如规划者 Planner、长期审查员),程序重启后不重建,而是找到旧实例继续对话。官方食谱给出的getPlanner模式:
async function getPlanner() { const listed = await client.agents.list({ filter: { includeArchived: false }, page: { limit: 100 }, }); const existing = listed.entries.find(({ agent }) => agent.labels["my-app-role"] === "planner"); if (existing) return client.agents.ref(existing.agent); return client.agents.create({ config: { provider: "claude/claude-sonnet-5", }, cwd: process.cwd(), title: "Planner", labels: { "my-app-role": "planner" }, }); } const planner = await getPlanner(); const plan = await planner.run("Plan the next small, shippable improvement.");Labels 是应用拥有的元数据。当多个工具可能在同一台 daemon 上管理 Agent 时,务必对键做命名空间化(如my-app-role而非role),避免与其他工具冲突。创建时打上 label,列表时由 daemon 侧完成过滤匹配:
const page = await client.agents.list({ filter: { labels: { "issue-provider": "my-tracker" } }, });这与 public-docs/sdk/agents.md 中“Find agents by label”一节一致:agents.list()的filter支持includeArchived、labels等条件,page.limit控制每页条数。
常驻角色找到后的续话:ref()得到句柄后直接run()即可发送新一轮 prompt。run()与send()的区别在于:run()会等待本轮结束并返回结果(其底层实现为发送消息后调用waitForFinish,见 packages/client/src/index.ts#L950-L961),而send()是即发即忘;按需选择。
食谱四:安全清理临时 Agent
临时创建的“冒烟测试”Agent 用完必须归档,否则会永久留在 daemon 上占用会话。官方食谱用try/finally保证任何路径下都会清理,且只清理自己创建的 Agent:
const temporaryAgents = []; try { const agent = await client.agents.create({ config: { provider: "codex/gpt-5.5", }, cwd: process.cwd(), title: "Temporary smoke test", }); temporaryAgents.push(agent); const result = await agent.run("Reply with READY and nothing else.", { timeoutMs: 2 * 60_000, }); if (result.status !== "idle") { throw new Error(result.error ?? result.status); } } finally { await Promise.allSettled(temporaryAgents.map((agent) => agent.archive())); }几个值得强调的细节:
- 只归档自己创建的 Agent:食谱明确警告“Do not archive agents your integration did not create”。因为 Agent 归档是软删除并关闭其运行时(源码 packages/client/src/index.ts#L973-L979 将
archive()实现为 daemon 的archiveAgentRPC 并回写archivedAt),误删他人任务不可逆。 - 用
allSettled而非all:确保某个归档失败不会阻断其余清理。 - 超时参数:
run()的第二个参数可传timeoutMs(此处 2 分钟),结合waitForFinish的四状态结果判断成功与否。
关闭 SDK 连接不会归档 Agent。如果你希望临时 Agent 在程序结束后消失,必须显式归档,正如 public-docs/sdk/agents.md 结尾所强调的:"Archive temporary agents explicitly, preferably infinally"。
从源码看这些食谱背后的句柄机制
理解 Recipes 的关键在于 Paseo SDK 的“句柄(handle)”设计——一个句柄持有稳定的 Agent ID 或 Workspace ID,并暴露回合生命周期,而不直接暴露 daemon RPC。相关实现集中在 packages/client/src/index.ts:
createAgentHandleFactory(index.ts#L858-L997):为 Agent 构建句柄。workspaceId、cwd、status、pendingPermissions、lastUsage、runtimeInfo、archivedAt等属性全部读取“句柄最近观察到的那份快照”,永不主动拉取;来自ref()的句柄从未观察到任何快照,因此这些属性在refresh()、run()、waitForFinish()或subscribe()投递快照之前一律为null。createWorkspaceHandleFactory(index.ts#L774-L856):Workspace 句柄同样持有 ID 与快照,agents.create()会先用当前快照(必要时refresh())取得workspaceDirectory,再把它作为放置参数传给底层createAgent——这正是“handle 负责摆放,调用方无需重复目录”的实现依据。toDaemonAgentCreateOptions(index.ts#L528-L549):把provider/model字符串拆分为provider与model,并把prompt转为initialPrompt、parent转为callerAgentId,是 Recipes 中各参数落地为 RPC 的转换层。createPaseoClient(index.ts#L506-L526):将所有 action 命名空间(agents、workspaces、providers、config等)组装成客户端对象,close()依次释放订阅并关闭传输。
关于父子的补充:通过 workspace 创建子 Agent 时传入parent可建立父子关系,归档父 Agent 会级联归档其子;若子 Agent 需要独立存活,先调用detach()解除关系。这一机制与 public-docs/sdk/agents.md 中“Create a subagent”一节对应。
把这些食谱组合成完整集成
四个食谱可以自然串联成一个完整的 Webhook 集成生命周期:
- 收到 Issue→ 食谱一:
workspaces.open(repositoryPath)+workspace.agents.create(...),持久化workspaceId/agentId与 label(issue-provider、issue-id)。 - PR 就绪→ 食谱二:并发创建多个审查者,
waitForFinish()汇总结果。 - 长期角色→ 食谱三:用
agents.list()按 label 查找,命中则ref()复用,否则创建;重启后依然找得到。 - 临时任务→ 食谱四:
try/finally中显式归档自建 Agent;整个程序的退出只调用client.close()关闭连接,不隐式清理任何东西。
Workspace 归档与 Agent 归档相互独立:workspace.archive()只归档工作区,Agent 需按你的集成所拥有的生命周期分别归档(详见 public-docs/sdk/workspaces.md)。这套“创建 → 标记 → 复用 → 回收”的闭环,就是基于@getpaseo/client构建稳定自动化工作流的核心方法论,更多细节可继续阅读 public-docs/sdk/agents.md、public-docs/sdk/workspaces.md、public-docs/sdk/providers.md 与 public-docs/sdk/events.md。
【免费下载链接】paseoOrchestrate multiple coding agents from desktop and mobile项目地址: https://gitcode.com/gh_mirrors/pa/paseo
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考