关于 workflows-create
workflows-create 是一项 Agent 技能,可将已确认的自然语言需求转为 Zapier 持久化工作流,并通过实验性 SDK CLI 进行测试和部署。它涵盖应用与字段发现、工作流构建及部署验证。
主要功能
- 01明确工作流意图与启动模式
- 02发现应用、操作、触发器和字段
- 03构建持久化 TypeScript 工作流
- 04部署前测试草稿
- 05发布并验证工作流
适用场景
- 01根据自然语言需求创建工作流
- 02测试并部署连接应用的工作流
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ zapier/ agent-skills --skill workflows-create安装后在 Claude Code 中输入/workflows-create确认已加载。
详细介绍
Zapier 工作流创建
根据自然语言创建完整、持久的工作流,在适当时对其进行测试,并通过 Zapier SDK 实验性 Code Workflows 命令部署。
请使用公开的 SDK CLI 路径。不要使用 zapier-sdk-code-substrate。
兼容性检查
使用此技能前,请运行 workflows-doctor 套件兼容性检查。如果未安装或无法加载 workflows-doctor,请运行 workflows-install,或从 zapier/agent-skills 安装 workflows-doctor,然后再继续。如果检查报告 SDK/技能存在偏差,请按照其刷新说明操作,停止本次技能调用;如有需要,重新加载 Agent 工作区,并请用户重新运行原始请求。
前提条件
开始时请验证:
zapier-sdk --version
zapier-sdk get-profile --json
zapier-sdk --stability experimental --help
为 @zapier/zapier-sdk 传入 "latest"。服务器会将其解析为沙盒可安装的最新版本,并在发布时存储确切版本;草稿则会原样存储传入的值。无需访问注册表,也无需计算发布日期。这不是 npm 的 latest 标签:沙盒使用 pnpm install --config.minimumReleaseAge=1440 安装,而 @zapier/zapier-sdk 每天会发布多次,因此该标签经常指向发布时间过近、无法安装的版本。
--zapier-durable-version 用于设置运行时引擎版本;切勿将 @zapier/zapier-durable 放入 --dependencies。默认情况下省略此标志:新草稿会采用服务器支持的版本,更新则会保留已存储的版本固定值。仅当重新发布时需要沿用从 get-workflow-version 获取的版本,或有意固定版本或升级时,才显式传入该标志。对于 durable 运行时版本,范围(^1.2.3、~1.2)会被拒绝并返回 400,因此显式值必须是确切版本或 latest。
只有 @zapier/zapier-sdk 会由服务器解析;其他依赖项会按原样存储,并在每次安装时重新解析,因此浮动版本声明可能在同一次运行的不同 tick 之间发生变化。更新草稿时,请声明源代码导入的每个依赖项,包括固定版本的 zod,这样未发布的测试才具备所需内容。沙盒会从 --dependencies 安装,而非本地的 package.json;缺少导入项会导致运行失败并报错 Cannot find package。
用户还必须为工作流将执行的任何应用操作配置应用连接:https://zapier.com/app/assets/connections
Surface 工具箱(SDK CLI)
- 发现应用/操作/连接:
zapier-sdk list-apps --search "<name>" --json、list-connections <appKey> --owner me --json、list-actions <appKey> --action-type <type> --json、list-action-input-fields、get-action-input-fields-schema、list-action-input-field-choices - 发现触发器:
zapier-sdk --stability experimental list-triggers <appKey> --json、list-trigger-input-fields、list-trigger-input-field-choices - 测试探测:
zapier-sdk run-action <appKey> <actionType> <actionKey> --connection <connectionId> --inputs '<json>' --json;Phase 2 触发器示例为run-action <appKey> read <triggerKey> --connection <connectionId> --inputs '<published params>' --max-items 1 --json - 暂存:
zapier-sdk --stability experimental create-workflow、create-workflow-draft、update-workflow-draft、get-workflow-draft - 测试运行:
zapier-sdk --stability experimental run-workflow-draft <workflow-id> <draft-id> <draft-revision> --input '<JSON>' --json、get-workflow-run <run-id> --json - 部署:
zapier-sdk --stability experimental publish-workflow-draft、discard-workflow-draft - 验证并运行:
zapier-sdk --stability experimental get-workflow、list-workflow-versions、get-workflow-version、trigger-workflow、get-trigger-run、list-workflow-runs、get-workflow-run
selected_api 是 list-apps/get-app 中的 implementation_id。
如需完整的操作输入契约,请在发现字段的同时调用 zapier-sdk get-action-input-fields-schema <appKey> <actionType> <actionKey> --json。其 data 是架构文档,而不是 list-action-input-fields 返回的可编辑字段数组。传入相同的最终输入和连接上下文;支持的标志请查看已安装命令的 --help。
对于 AI by Zapier 步骤,请加载已安装的 workflows-ai-by-zapier 配套工具,以获取针对 SDK 的发现交接信息。它是对操作所属架构的补充,不是另一套配置契约。如果未安装该配套工具,请使用此处的发现流程,不要因此阻止契约已明确的步骤。
将工作流生成为本地项目 — <kebab-case-workflow-name>/,其中包含 package.json、workflow.ts 和源文件导入的所有辅助模块(lib/format.ts)— 并在完成时报告项目所在位置;如果文件不止一个,还要列出文件名。package.json 仅用于本地类型检查,绝不会随 source_files 发布;后续阶段会遍历此目录来构建该映射,因此目录中只保留此工作流自身的文件。
如果在 package.json 中添加构建脚本,目前请使用 --skipLibCheck,以避免 SDK/durable 的传递类型声明导致类型检查失败:
{
"scripts": {
"build": "tsc --target es2022 --module nodenext --moduleResolution nodenext --skipLibCheck --outDir dist workflow.ts"
}
}
durable.run is not a function 是 defineDurable 调用的代码结构缺陷,而不是版本不匹配。不要为了修复它而更改 @zapier/zapier-durable 或 @zapier/zapier-sdk 的版本;应修正调用,使其传入名称和 run 函数。
入口文件也可以使用 .mts、.js、.mjs、.cjs 或 .cts 扩展名,而非 .ts,但必须且只能有一个。Publish 会将顶层的 index.*、package.json 或 .zapierrc,以及任何 _zapier_* 名称视为保留名称并拒绝发布;嵌套的 lib/index.ts 则可以使用。
根据自然语言创建完整的 durable 工作流,进行测试、部署,并验证其已上线。
工作流是一个使用 @zapier/zapier-durable 中 defineDurable 的 TypeScript 模块。每个 ctx.step() 都会记录到日志中:运行重试或重放时,已完成的步骤会返回记录的结果,而不会重复执行。已发布的工作流会独立于此对话运行在 Zapier 的基础设施上。
本技能中针对不同界面的章节,会将下文用到的各项能力 — 操作发现、触发器发现、连接列表、测试探测、发布和测试运行 — 映射到相应界面的具体工具或命令。本技能中提到“你的发现工具”或“你的发布机制”时,请使用该映射。
第 1 阶段:理解意图
从用户的请求中提取:
- 步骤及其顺序。
- 涉及的应用。
- 步骤之间传递的数据。
- 手动输入字段或触发器输入字段。
- 条件逻辑和无结果时的处理方式。
- 工作流由什么触发——应用触发器、定时计划、外部 webhook POST,还是仅手动运行。
- 等待、回调、重试或人工审批关卡。
- 启动模式(必填——见下文)。
在开始发现之前,先总结拟议的工作流。只有在确实需要且没有安全默认值时,才提出有针对性的澄清问题,例如目标位置含糊不清、缺少收件人,或搜索无结果时应如何处理。在用户同意工作流方案之前,不要生成源代码。
确定启动模式(必填)
每个工作流都必须且只能有一种启动模式,并且必须在此处确定:
trigger— 工作流按计划运行,或响应外部事件自行启动。其最终部署版本带有trigger配置,无需任何人调用即可运行。manual— 工作流没有触发器,只能通过你所在平台的工作流运行机制按需运行。这是有意选择的模式,并非缺少触发器。
首先检查构建指令中是否指定触发器。根据意图线索推断:"每天早上"、"每日"、"按计划"等表示定时 trigger;"当 <app> 中发生 X 时"或"每当创建新的 <record> 时"表示事件驱动的 trigger;明确说"手动"、"按需"或"我运行时"表示 manual。
如果启动模式不明确——既没有清晰的触发器线索,也没有明确的手动运行线索——请询问用户是要按需手动运行,还是添加触发器;如果要添加,则询问具体是哪种触发器。绝不要仅仅因为没有指定触发器,就默认采用 manual。未经确认便发布不带触发器的工作流,正是此分类步骤要防止的问题。
在后续阶段中沿用已选定的启动模式:在第 3 阶段的构建计划中确认该模式,并将其作为第 5 阶段部署验证的门槛。
哪些情况不属于意图缺失
- 已命名的资源——Slack
#channel、电子表格标题、文件夹或项目名称——属于用户提供的意图。在向用户询问任何信息之前,先在发现阶段(见第 2 阶段)解析其不透明的 API 标识符。 - 可选参数、产品默认值返回的字段值,或有安全默认值的实现偏好,都不属于意图缺失。采用默认值并继续。
- 对范围有限的 AI 搜索步骤所作的自然语言范围描述——例如"活跃的 Platform 史诗级任务"——属于用户提供的意图。将该范围写入 AI 步骤的指令中;不要要求用户将其转换为精确的查询语法。
语义事件子集
当用户描述的是应用事件中的语义子集——"供应商提出集成请求时"、"消息看起来很紧急时"、"反馈具有可操作性时"——不要强迫他们在构建前自行制定标签、发件人列表或关键字规则。除非用户提供了确定性规则,否则可用的发件人、主题、正文或标签筛选条件都不是可靠的语义筛选方式。将可选筛选条件留空,配置范围更广的源事件,然后在工作流中使用范围有限的 AI 步骤进行判断,并跳过不匹配的事件。只有当范围更广的事件不安全、会产生过多噪声,或用户明确要求更窄的触发条件时,才询问是否缩小触发范围。
第 2 阶段:发现应用、操作、连接、触发器和字段
使用你所在界面的发现工具查找真实标识符。切勿编造应用键、操作键、操作类型、字段名称、字段值、应用版本或连接 ID;训练数据从一开始就可能过时。
一次处理一个应用或操作。记录发现的键、类型、字段、选项、连接 ID 和输出结构。如果有多个可能的候选项,请列出并让用户选择。不要引入发现结果中未返回的应用。
遵循返回的可编辑字段、选项、架构和锁定项。使用相同的操作/版本、连接和已确定的父级输入来解析依赖项;这些内容发生变化时,重新获取信息。缺少嵌套契约时:仅使用限定版本的已安装配套工具作为后备,否则说明限制。元数据和后备方案均不授予权限。
在询问前解析具名资源
请求中提供的、人类可读的目标是用户指定的意图,而不是缺失的必填值。将其解析为操作所需的 API 标识符:
- 如果字段提供动态选项,优先使用操作字段中的动态选项。
- 否则,使用所选连接调用该应用有文档记录的读取/列表操作或 API 来查找资源。
- 找到唯一精确匹配项时,自动继续。
- 仅当查询未返回匹配项、返回多个可能的匹配项,或可用工具界面无法解析时,才询问用户。
在尝试可用的查询方式之前,不要要求用户粘贴不透明 ID。如果操作可直接接受用户提供的人类可读名称,则将其作为最终后备方案。未指定发布身份时,使用操作默认值;如果操作要求选择,则对于无人值守工作流,优先使用机器人式发布身份。
使用 AI by Zapier 执行 AI 步骤、Agent 工具循环和语义任务
如果你的界面提供 AI by Zapier 配套工具,请先按照该界面的说明加载它,再配置步骤。
当工作流需要推断含义,而不是从完整规则中复制或计算时,使用受限的 AI by Zapier 步骤。摘要、生成、从多样化文本中提取/分类、风险分析、模糊匹配和消歧都属于此类,即使用户从未提到“AI”。直接映射、精确格式化、算术、用户提供的正则表达式/关键词表以及完全明确的条件,都应保持确定性。除非用户明确要求使用其他提供商应用,或需要 AbZ 不具备的能力,否则优先使用 AI by Zapier;仅提及提供商/模型并不是弃用 AbZ 的理由。遵循明确的 Code by Zapier 请求。
当触发器和前序步骤已包含所有证据时,仅使用提示词式 AbZ。只有在模型必须选择、检查、调整或重复查询时,才嵌入读取/搜索工具;预先确定的搜索和最终副作用,仍应作为持久工作流步骤,在 AI 返回类型化输出后执行。
发现当前的 get_completion 操作、其可编辑字段、依赖选项,以及可用时的完整输入架构。契约以元数据为准;如果架构不可用,只能使用你所在界面有文档记录的 AbZ 后备方案,不能依赖标签或记忆中的示例。使用实时模型默认值,除非用户要求其他受支持的选项。inputs.authentication_id: "0" 是托管模型的凭据输入,不是外层工作流或嵌套工具的连接。不要调用 get_completion_agentic。
将稳定指令与触发器/前序步骤的上下文分开传递,根据发现的契约配置输出,并核实实际结果。默认文本输出位于 result.data[0].output;已配置的输出位于其命名条目字段中。
持久工作流仍负责触发器、分支、目标、审批、副作用和完成行为;受限的 LLM/工具循环不是持久化的 Agent 自动化。
映射连接
对于每个需要身份验证的应用,请使用连接发现或服务端绑定。选择别名时,使用简短的 snake_case 名称,例如 slack_work;应用键绑定服务会提供自己的约定。外层 SDK 别名由测试/发布流程解析;切勿嵌入令牌或原始凭据。
操作输入中的连接引用是独立的。只有在操作架构要求时才提供发现到的标识符,并使用其公布的格式;不要用外层别名替代、解码 ID,也不要假定外层绑定会重写嵌套输入。如果你的界面无法发现必需的引用,请说明这一限制。
某些内置应用按设计无需身份验证——Schedule、Webhooks、Code、Formatter、Filter、Delay by Zapier。它们不需要别名,也不会出现在连接绑定中。架构自带身份验证的应用也属于这种情况,例如 AI by Zapier 会将其发现到的 authentication_id 作为操作输入传递。
确认操作输出结构
如果某个操作的输出会传递给后续步骤,请使用你所用界面的读取探测机制进行测试,不要猜测其字段。当探测结果过大,无法完整读取时,应查看一个值已缩短的记录,而不是只看键名;键名无法显示值究竟是字符串还是对象。写入探测可能会创建或更改真实数据,因此请先说明副作用,并等待确认后再运行。
在工作流中,sdk.runAction 会返回 { data, nextCursor }。工作流源代码通过 result.data[0] 读取第一项,或通过 result.data 读取完整项目列表。不要为操作输出臆造诸如 value.results || value.items 的回退链。
发现触发器
对于由触发器启动的工作流,请使用你所用界面的触发器发现工具,发现触发器、其配置字段及字段选项。根据发现的值构建触发器配置:
{
"selected_api": "[email protected]",
"action": "new_row",
"authentication_id": "connection-id-or-null",
"params": {}
}
selected_api 是发现到的触发器结果中的实现标识符,绝不是显示名称。发送时可直接使用该标识符、附加 @latest 后缀,或固定版本(<app_key>@<app_version>);发布时会解析该标识符,发布调用返回的版本值以及之后读取到的版本值都会携带固定版本。重新发布时请复用该固定值,以免后续发布时不知不觉切换到更新的应用版本。
请与发现到的每个触发器参数类型保持一致:数组字段必须是 JSON 数组("dow": ["1"]),字符串字段必须是普通字符串("hod": "9:00 AM");需要数组时传入标量会在发布时被拒绝,并返回明确错误。对于无需身份验证的触发器,请使用 null 或省略 authentication_id;否则请使用所选触发器连接 ID。
Webhooks by Zapier 和其他捕获钩子应用都是真正的触发器。请像处理其他触发器一样发现并配置它们。请确认其操作键、身份验证和参数,不要假定每个捕获钩子都无需身份验证——向服务提供商注册订阅的应用通常需要连接。Webhooks by Zapier 本身无需身份验证(authentication_id: null),params 为空,并提供 hook_v2(解析后的载荷,常用默认值)和 hook_raw(原始正文和标头,最大 2 MB);请通过发现确认键值——这对键值仅适用于该应用,并非通用模式。捕获钩子没有可供采样的记录:请根据请求中说明发送方将发布的内容输入字段。
采样触发器
- 使用你所用界面的读取探测机制,运行键值与触发器自身键值完全相同的读取操作——绝不能使用名称相似的操作;参数必须与将要发布的
params完全一致。 - 读取一条完整记录。绝不要按行截断输出。
- 仅输入记录中显示的内容(第 4 阶段);未显示的字段不指定固定类型。
在适用时参考现有工作流
如果存在类似的可用工作流,它可以提供真实的源代码、连接绑定和触发器配置。这是可选步骤,并非必须先做。使用当前界面的工作流检查工具读取它,只借鉴与新工作流相关的模式。
第 3 阶段:确认构建计划
展示此计划:
Workflow: <kebab-case-name>
Input: { field1, field2 }
Connections:
alias = connectionId (connection title)
Start mode: trigger (<selected_api.action with typed params>)
Trigger sample: <trigger key> with <params> → <keys seen> — or — none, fields read untyped
— or —
Start mode: manual — on-demand only
Steps:
1. <step-name> - <AppName>.<actionType>.<actionKey>
2. <step-name> - <AppName>.<actionType>.<actionKey>
Return: <summary of output>
Start mode 行是必需的,且必须准确写明第 1 阶段中判定的两种模式之一。
如果缺少必需细节或存在歧义,请在此处停止并提出一个有针对性的问题。如果用户已提供所有信息,或通过发现过程无歧义地解决了所有问题,那么用户创建工作流的请求即表示已获批准:简要展示计划,并在同一轮中继续执行。
第 4 阶段:编写工作流源代码
源代码是一个 { path: contents } 映射。每次提交源代码都会整体替换该映射,服务端不会合并:未提交的文件会被删除,因此务必发送完整文件集。
入口点只能有一个,通常是 workflow.ts。小型工作流应放在其中;当工作流变得复杂时,将纯辅助函数、较长的架构或提示词文本移至相对路径模块,并附上扩展名(./lib/format.ts,在映射中以该键保存),同时将 defineDurable 和每个 ctx.step 留在入口点中,以便编辑器读取步骤图。
入口点必须:
- 从
@zapier/zapier-durable导入defineDurable,并从@zapier/zapier-sdk导入createZapierSdk,使用模块顶部的静态import语句——源代码中每个文件需要的所有程序包都必须以相同方式声明。不得在函数内部或通过动态import()导入。 - 将声明的每个依赖项固定到已验证真实存在且发布至少 24 小时的精确版本——绝不编造或猜测版本。注册表文档
https://registry.npmjs.org/<name>(作用域包:@scope%2Fname)会在time下列出每个版本的发布时间。@zapier/zapier-sdk是例外:精确版本或字面值"latest"均可,因为平台会将"latest"解析为实际安装的版本。@zapier/zapier-durable是运行时引擎本身:其版本由发布步骤设置,不得将其声明为依赖项。 - 在模块作用域创建一个 SDK 客户端:
const sdk = createZapierSdk()。 - 存在工作流输入时,使用 Zod 验证输入,将架构作为
inputSchema传入,并在验证前规范化字符串化的输入。只要求运行所必需的字段;将触发器提供的每个字段标记为.nullable().optional()——仅使用.optional()或.default()仍会在收到null时抛出错误,而触发器负载经常会发送null——并在使用位置通过??提供回退值。 - 将每个外部调用放在一个命名的
ctx.step中。每个应用操作步骤必须且只能包含一个sdk.runAction;经过身份验证的 HTTP 步骤必须包含一个sdk.fetch。 - 将确定性验证、分支、保护逻辑和数据整形放在步骤之外。
- 将
new Date()、Date.now()和Math.random()等重放时可能变化的值放在步骤内,以便只记录一次。 - 使用发现到的准确
read、search或write操作类型。 - 对外层 SDK 连接使用别名;对于归架构所有的操作输入引用,请遵循“映射连接”的说明。
- 使用唯一且描述清晰的 kebab-case 动词-名词步骤名称,例如
find-jira-issue、send-approval-email,修改已发布的工作流时应保留这些名称。重命名步骤会丢失其日志身份。避免使用step-1之类的通用名称。
采样记录中的字段会按键映射到其架构键:"user": { "id": "U0…", "name": "alice" } 会变为 user: z.object({ id: z.string(), name: z.string() }).loose().nullable().optional();若样本中从未出现某个键,则将其设为 z.unknown(),并在使用位置缩窄类型(typeof v === "string" ? v : v?.id)。
输入可能以 JSON 字符串形式传入:
function normalizeInput(rawInput: unknown): unknown {
if (typeof rawInput === "string") {
return JSON.parse(rawInput);
}
return rawInput;
}
使用有效且便于编辑器识别的模块结构
每个 defineDurable 调用都必须将 run 解析为函数。优先使用对象形式 defineDurable({ name, inputSchema, run });defineDurable("name", run) 也可以。不要传入没有工作流名称的裸函数,也不要传入没有 run 的对象;这些写法会导致 durable.run is not a function。
默认在模块级定义 SDK 和常量,使用具名 durable,并为每个应用步骤编写直接调用 runAction 的主体:
import { defineDurable } from "@zapier/zapier-durable";
import { createZapierSdk } from "@zapier/zapier-sdk";
import { z } from "zod";
const sdk = createZapierSdk();
const InputSchema = z.object({
ticketId: z.string(),
label: z.string().nullable().optional(),
});
const JIRA_APP_KEY = "JiraSoftwareCloudCLIAPI";
const JIRA_CONNECTION = "jira_work";
const SLACK_APP_KEY = "SlackCLIAPI";
const SLACK_CONNECTION = "slack_work";
const SLACK_CHANNEL = "C0123456";
const workflow = defineDurable({
name: "notify-about-jira-issue",
inputSchema: InputSchema,
run: async (ctx, rawInput) => {
const input = InputSchema.parse(normalizeInput(rawInput));
const issuePage = await ctx.step("find-jira-issue", async () =>
sdk.runAction({
appKey: JIRA_APP_KEY,
actionType: "search",
actionKey: "search_issues",
connection: JIRA_CONNECTION,
inputs: { query: input.ticketId },
}),
);
const issue = issuePage.data[0];
if (!issue) {
return { skipped: true, reason: "issue-not-found" };
}
const label = input.label ?? "Jira";
const message = `${label} ${issue.key}: ${issue.summary}`;
const notification = await ctx.step("notify-slack-channel", async () =>
sdk.runAction({
appKey: SLACK_APP_KEY,
actionType: "write",
actionKey: "send_channel_message",
connection: SLACK_CONNECTION,
inputs: { channel: SLACK_CHANNEL, text: message },
}),
);
return { notification };
},
});
可选触发器字段缺失或改名绝不是停止执行的理由:使用回退值解析该字段——如上面的 input.label ?? "Jira"——并运行操作;绝不要因为未收到可选值而 throw 或提前 return。可选字段应写作 .nullable().optional(),而不是 .nullish()——其他产品界面只读取这种写法。
要保留架构中未命名的键,请在对象架构中添加 .loose()。只有在载荷完全没有已知字段时才使用 z.record(z.string(), z.unknown()) 或 z.looseObject({}),绝不要使用单参数 z.record(z.unknown())——它会在平台的 zod 中解析时崩溃。
当字符串字面量步骤 ID 对应的内联回调主体只有一次直接的 sdk.runAction 调用,且包含 appKey、actionType 和 actionKey 时,Zapier 编辑器会将 ctx.step 显示为应用操作步骤(带应用图标)。没有操作、包含多个操作或使用动态循环 ID 的步骤会显示为代码步骤;对于这些模式,这是预期行为——循环和扇出确实需要动态 ID。
仅通过 SDK 访问应用
对于外部连接,使用 createZapierSdk() 和已发布的别名,或使用“映射连接”中所述的架构自有操作输入引用。不存在全局 Zapier 客户端、ctx 上的连接对象,也不存在 process.env 中的连接令牌。
await ctx.step("send-slack-message", async () =>
sdk.runAction({
appKey: "SlackCLIAPI",
actionType: "write",
actionKey: "direct_message",
connection: "slack_work",
inputs,
}),
);
const gitlabUser = await ctx.step("fetch-gitlab-user", async () => {
const response = await sdk.fetch("https://gitlab.com/api/v4/user", {
connection: "gitlab_work",
method: "GET",
});
if (!response.ok) {
throw new Error(`GitLab returned ${response.status}`);
}
return response.json();
});
// Wrong: ctx.zapier and ctx.connections are undefined.
await ctx.zapier.fetch(url, { connection: ctx.connections.slack });
如果存在 Zapier 操作,优先使用 sdk.runAction——它能处理动态字段和依赖字段、应用专属身份验证方案以及标准化响应。只有在没有操作能提供所需行为时,才使用经过身份验证的 sdk.fetch。在步骤内部处理 Response,并返回可序列化的 JSON 或文本;绝不要将原始响应或正文流作为步骤输出返回。
过滤模式
使用提前返回来筛除不符合条件的运行,并将无副作用的条件逻辑放在 ctx.step 外部——过滤器不会给步骤图带来价值。对于复杂的多条件过滤器,请使用具名 const 绑定:
const tags = input.tags ?? [];
const isActiveAndUrgent = input.status === "active" && tags.includes("urgent");
const isHighPriority = (input.priority ?? 0) > 8;
const shouldRun = isActiveAndUrgent || isHighPriority;
if (!shouldRun) {
return { skipped: true, reason: "filter-did-not-match" };
}
Durable 模式
在工作流作用域中等待,不要在步骤内等待:
await ctx.wait("wait-before-followup", 3600);
将可能因日志重放而变化的值记录一次:
const createdAt = await ctx.step("capture-created-at", async () =>
new Date().toISOString(),
);
使用回调请求人工审批:
const [approvalPromise, callbackUrl] = await ctx.createCallback({
name: "wait-for-approval",
payloadSchema: z.object({ approved: z.boolean() }),
timeoutSeconds: 86400,
});
await ctx.step("send-approval-request", async () =>
sdk.runAction({
appKey: "ExampleCLIAPI",
actionType: "write",
actionKey: "send_message",
connection: "example_connection",
inputs: { callbackUrl },
}),
);
const approval = await approvalPromise;
if (!approval.approved) {
return { skipped: true, reason: "denied" };
}
对于相互独立的工作,在步骤外使用 Promise.all,并为每个项目设置一个步骤:
const results = await Promise.all(
items.map((item, index) =>
ctx.step(`process-item-${index}`, async () =>
sdk.runAction({
appKey: "ExampleCLIAPI",
actionType: "write",
actionKey: "do_something",
connection: "example_connection",
inputs: { item },
}),
),
),
);
对不稳定的外部调用使用步骤重试:
const result = await ctx.step({
name: "call-flaky-service",
maxAttempts: 3,
retryDelaySeconds: 5,
run: async () =>
sdk.runAction({
appKey: "ExampleCLIAPI",
actionType: "write",
actionKey: "do_something",
connection: "example_connection",
inputs: {},
}),
});
阶段 5:测试、发布并验证
测试和发布的具体方式因平台而异——请参阅对应平台的章节。以下原则则始终适用:
- 默认采用私有工作流,除非用户明确要求账号范围内可见。
- 在启用触发器上线前,通过实际测试运行验证工作流。如果平台支持直接运行源代码而无需创建工作流,请先测试再创建。如果平台支持执行未发布的草稿(草稿测试运行),请先测试未发布的草稿,测试通过后再发布。只有在两种方式都不支持时,才先发布不带触发器或已禁用的工作流,进行测试,并在测试通过后添加触发器。
- 如果测试可能会更改已连接的应用,请先说明每项可能产生的副作用,并等待确认。优先使用符合抽样记录结构、且无害的代表性输入——如果工作流会在执行外部写入前筛选事件,那么使用明确不匹配的样本可测试安全跳过路径。如果用户已经要求创建工作流,这项警告不构成第二道审批关卡;只有在测试效果具有破坏性、成本高、涉及敏感信息或以其他方式出乎意料时,才应事先询问。
- 修正源代码并重新测试,直到测试结果符合已确认的计划。只能发布经过测试的源代码。绝不让未经测试的版本处于启用状态。
- 触发器的认领是异步的,也可能静默失败。发布带触发器的工作流后,应轮询平台提供的工作流回读结果,直到触发器处于活动状态,且工作流处于预期的启用状态。即使工作流仍处于启用状态,认领失败也应视为部署失败。只有工具结果确认两者均已验证后,才能报告成功——绝不能推断或声称已激活。过渡回读状态(
unclaimed/pending)可能只需继续轮询。 - 请根据已确认的启动模式验证部署,而不能只检查
enabled——不带触发器的工作流回读结果会显示enabled: true,因此单独检查enabled会错误地通过本应有触发器、实际却没有触发器的工作流。工作流回读结果中是否实际存在触发器,才是判断依据:trigger模式的工作流回读结果必须包含触发器;manual模式的工作流则不能包含触发器。如有不一致,应与用户核实,而不是报告成功。 - 如果触发器提供了
webhook_url,请将其展示给用户——这是供外部服务使用的接收 URL,与工作流级别的触发器/调用 URL 不同,适合分享。大多数触发器没有webhook_url,这属于正常情况。如果接收钩子触发器处于活动状态,但未提供webhook_url,请引导用户通过编辑器链接找到触发器步骤,而不要编造 URL。除非用户明确要求,否则不要输出以已认证账号身份调用工作流的工作流级别触发器 URL。
最后报告:
- 工作流名称和 ID。
- 测试是否通过。
- 工作流是否已启用。
- 工作流是私有还是账号可见。
- 已确认的启动模式:
trigger(应用触发器或接收钩子触发器)或manual(仅按需启动),以及启动模式检查已确认部署与之相符。 - 如果存在触发器的
webhook_url,请提供该 URL。 - 编辑器链接:
https://zapier.com/workflow/<workflow-id>。
故障特征
- 现有操作报告 action-not-found:工作流可能使用了错误的
actionType;请检查该操作,并使用发现的确切read、search或write类型。 - 读取
fetch或从undefined读取连接时调用失败:源代码使用了ctx.zapier、ctx.connection或ctx.connections;请改用模块作用域中的createZapierSdk()客户端和连接别名。 - 带触发器的工作流发布后变为禁用状态:异步触发器认领失败;请确认
selected_api、操作键、params和authentication_id均来自同一个已发现的触发器结果,然后重新发布。
约束
- 切勿在源代码中嵌入凭据、令牌或密钥。请使用外部连接别名,并且只在操作输入中使用架构所公布的、已发现的引用。
- 切勿编造应用键、操作键、操作类型、字段名称、触发器值、版本或连接 ID。
- 在执行任何可能产生副作用的运行、写入探测或发布之前,先征得确认。
- 读取并修复发布错误,不要猜测着绕过它们。
SDK CLI 机制:测试、部署、验证
阶段 5(CLI):创建工作流、暂存草稿并测试
先创建一个私有工作流容器,再创建其草稿。该容器初始处于禁用状态,且没有已发布版本。从响应中记录工作流 ID 和草稿 ID:
zapier-sdk --stability experimental create-workflow "<workflow-name>" --description "<brief>" --private --json
zapier-sdk --stability experimental create-workflow-draft <workflow-id> --json
通过遍历项目目录来构建 source_files,以确保包含其中的所有文件,不会静默遗漏:
WF_DIR="<working-directory>/<kebab-case-workflow-name>"
SOURCE_FILES="$(cd "$WF_DIR" && find . -type f ! -name package.json ! -path './node_modules/*' -print \
| sed 's|^\./||' \
| while IFS= read -r f; do jq -n --arg k "$f" --argjson v "$(jq -Rs . < "$f")" '{($k): $v}'; done \
| jq -s 'add')"
这会根据每个文件相对于项目的路径为其建立键(lib/format.ts 仍为 lib/format.ts),并精确保留其字节,包括末尾换行符。不要手动拼凑只包含一个键的 {"workflow.ts": …} 映射:丢失文件的映射看起来和正常映射完全一样,但接下来的发布会从工作流中删除该文件。
zapier-sdk --stability experimental update-workflow-draft <workflow-id> <draft-id> "$SOURCE_FILES" \
--dependencies '{"@zapier/zapier-sdk":"latest","zod":"4.3.6"}' \
--connections '{"slack_work":{"connectionId":"12345678"}}' \
--app-versions '{"SlackCLIAPI":{"implementationName":"SlackCLIAPI","version":"1.42.1"}}' \
--trigger '<trigger config JSON, for triggered workflows>' \
--json
对于已确认的手动工作流,将 --trigger 替换为 --manual。对于触发式工作流,将其配置作为一个 JSON --trigger 对象传入。省略未使用的 --app-versions 和 --connections 选项,并将示例值替换为已发现的值。每个连接别名都映射到一个包含 connectionId 的对象。
不要使用旧的 --trigger-app、--trigger-action、--trigger-auth 或 --trigger-params 标志。当前的触发器配置方式是使用单个 JSON --trigger 对象。
从 SDK 发现输出中获取应用实现和版本信息,例如 list-apps、get-app、list-actions 或触发器/操作结果元数据。如果没有公开实现/版本绑定信息,就省略 --app-versions,不要猜测。不要向 CLI 传入“if needed”之类的占位文本。
更新草稿前,请确认其标志与阶段 3 中确认的启动模式一致:Start mode: trigger 应传入 --trigger,而不是 --manual;Start mode: manual 应传入 --manual,而不是 --trigger。两者必须且只能传入一个。一个版本要么由触发器启动,要么设置为 manual: true,绝不能同时满足;平台会拒绝同时设置这两项。即使平台将其 manual 输入视为可选,仍必须确定启动模式:该标志仅可写入,并非必填字段,且强制执行按账户划分并由标志控制,因此它的可选性只是分阶段推出所致,并不意味着可以跳过这项决定。设置为手动是有意选择的分支,而不是触发器难以配置时的兜底方案:如果用户要求触发器,那么无法构建其配置就意味着存在必须解决的阻碍,绝不能因此改为手动。
读取已保存的草稿并记录其 draft_revision,然后运行该修订版本:
zapier-sdk --stability experimental get-workflow-draft <workflow-id> <draft-id> --json
zapier-sdk --stability experimental run-workflow-draft <workflow-id> <draft-id> <draft-revision> \
--input '<JSON matching input schema>' \
--json
如果工作流不接收任何输入,则省略 --input。该命令会启动一个与工作流关联、尚未发布的草稿运行,并在执行完成前返回。
记录返回的 id,并轮询 get-workflow-run <run-id> --json。满足以下条件即表示测试通过:
status为"finished",output与已确认的计划一致,error为null,且顶层errors为空。kind为"draft",并且workflow_id、workflow_draft_id和draft_revision与所测试的草稿一致。
如果测试失败,请修复源代码,重新构建完整映射,更新同一草稿,并测试其新修订版本。如果发生修订冲突,请重新读取草稿并协调变更,然后再测试。
第 6 阶段(CLI):发布已测试的草稿
仅在测试通过后发布,并使用已测试的修订版本,以免并发编辑导致未经测试的内容上线:
zapier-sdk --stability experimental publish-workflow-draft <workflow-id> <draft-id> \
--draft-revision <tested-draft-revision> \
--enabled \
--json
发布会消耗草稿并创建不可变的工作流版本。如果发生修订版本冲突,请重新读取草稿并测试其当前修订版本,然后再发布。如果用户想在正式上线前通过 Zapier 编辑器进行审核,请保留已测试但未发布的草稿,并将其编辑器链接发给用户;待用户同意后再发布。只有在获得明确确认后,才能丢弃未发布的内容。
第 7 阶段(CLI):验证
使用 get-workflow、list-workflow-versions、get-workflow-version 读取数据。对于已触发的工作流,请重新读取 get-workflow(轮询;声明是异步的)并确认 enabled: true——如果尽管指定了 --enabled,其值仍为 false,则表示触发器声明失败;请检查 selected_api、操作键、params 和 authentication_id 是否都来自同一个已发现的触发器结果(形状错误的 params 值本来就会导致发布失败)。
根据第 3 阶段确认的启动模式进行检查,不要只看 enabled:
Start mode: trigger要求get-workflow同时满足enabled: true和triggers[]非空。triggers[]为空意味着触发器已被丢弃或从未发送;不要报告工作流已完成。Start mode: manual要求triggers[]为空。triggers[]非空表示不匹配;请停止操作并与用户确认。
triggers[] 是此项检查的依据。只写入的 manual 标志不会出现在 get-workflow 或版本回读结果中,因此不要在这些位置查找它。
如果 catch-hook 触发器已启用,但其 triggers[] 条目没有 details.webhook_url,则已安装的 @zapier/zapier-sdk 可能早于该字段。运行 workflows-doctor 检查更新;同时,请引导用户通过编辑器链接查看触发器步骤。
手动运行(确认副作用后):trigger-workflow <workflow-id> --input '<JSON>' --json。如果返回触发器 ID 时尚未提供运行 ID,请通过 get-trigger-run <trigger-id> --json 获取,再检查 list-workflow-runs / get-workflow-run。