# 块 (/zh/docs/flows/blocks)

每个块只做一件事。选中块后，它的设置显示在检查器中；它的端口在画布上，输入在左侧，输出在右侧。
本页按块选取器使用的分组列出每一种块，然后说明在块之间传递的值、`{{body.id}}` 这样的模板，以及条件。

## 运行如何推进 [#how-a-run-moves]

- 一个块会在所有接有连线的输入都有值之后运行。随后它从部分输出发出值，只有连在这些输出上的块才会接着运行。
- “开始”块最先运行。没有任何连线接入的“值”块和“读取变量”块也会在开始时运行。
- 能并行的块会并行运行。“显示并等待”块在等你操作的同时，“轮询”块可以继续进行。
- 有几种块可能失败：“请求”“轮询”“工具”“流程”“求值”和“写入文件”都有 `fail` 输出。它接了线时，失败会从这里发出，运行继续；没接线时，整个运行会在该块失败。
- 没有任何块还在运行时，运行结束。

每个块的检查器中还有**标题**（显示在块上；为空时显示这种块的名称）和**已暂停（值直接通过）**。已暂停的块会变暗并被跳过：传入的内容直接从它的第一个输出传出。

## 触发器 [#triggers]

### 开始 [#start]

运行开始的地方：**运行**按钮、智能体（当流程作为工具时）、计划、Webhook 或 `echolot flow run`。它的检查器中是流程的**触发器**；见[运行和触发](/docs/flows/running-and-triggers)。

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `run` | 出 | 流程的所有输入，合成一个对象。 |
| 每个输入一个 | 出 | 该输入的值。 |

一个流程恰好有一个“开始”块。它不在块选取器中。

### 输出 [#output]

运行返回的内容：返回给调用流程的智能体、命令行、Webhook 的调用方或父流程。一个流程最多有一个。

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `value` | 入 | 结果。 |

## 动作 [#actions]

### 请求 [#request]

发送工作区中一个已存储的请求（HTTP、GraphQL 或 MCP 请求），连同它的认证、脚本、检查和运行所用的环境。

| 设置 | 作用 |
| --- | --- |
| **请求** | 从列表中选择的已存储请求（必填）。 |
| **变量** | 名称，以逗号分隔。每个名称成为一个输入，用来填充请求中对应的 `{{variable}}`。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `send` | 入 | 发送请求。传入对象时，它的每个键成为本次发送的一个变量。 |
| **变量**中的每个名称一个 | 入 | 该变量的值。 |
| `success` | 出 | 请求成功时的[响应](#the-response-object)。 |
| `fail` | 出 | 返回了 2xx 以外的状态码或检查未通过时的响应；什么也没有返回时则是错误。 |

请求返回 2xx，且它的检查、脚本测试和脚本全部通过，才算成功，这与[运行器](/docs/requests/runner)相同。流程传入的变量仅在这一次发送中优先于环境中的变量。

在流程 **Place a test order** 中，**Sign in** 的 `success` 输出连到 **Create order** 的 `send`：只有登录成功之后才会下单，而 Sign in 捕获的令牌已经在 `{{token}}` 中。

### 工具 [#tool]

运行 Echolot 自己的一个工具，例如启动模拟服务器、运行文件夹、检查规范，或调用 MCP 服务器的工具，规则与智能体相同。

| 设置 | 作用 |
| --- | --- |
| **工具** | 从列表中选择的工具（必填）。 |
| **参数** | 工具的参数，写成 JSON，例如 `{ "name": "{{value}}" }`。文本值可以使用模板。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `run` | 入 | 运行工具。传入对象时，它的键会加入参数。 |
| `result` | 出 | 工具返回的内容。 |
| `fail` | 出 | 失败的原因。 |

“工具”块不能运行另一个流程；请改用“流程”块。

<Edition only="direct">
  调用以本地程序（stdio）运行的 MCP 服务器的“工具”块在直接下载版中可用。在 App Store 版中，Echolot 无法启动该程序：块会显示警告，并在运行时失败。
</Edition>

### 流程 [#flow]

运行工作区中的另一个流程，并发送其“输出”块收到的内容。

| 设置 | 作用 |
| --- | --- |
| **流程** | 另一个流程（必填）。流程不能运行自己，流程之间也不能循环互相运行。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `input` | 入 | 包含另一个流程各输入的对象。 |
| `output` | 出 | 另一个流程返回的内容。 |
| `fail` | 出 | 另一个流程失败的原因。 |

流程之间互相运行，最多嵌套 8 层。机密不会以值的形式在流程之间传递：请改为将它们存储为变量。

## 人员 [#people]

“人员”块会暂停运行并向你询问。在 App 中会打开一个面板；由智能体启动时，问题会交给智能体的客户端；在命令行中，则在终端里询问。
由计划和 Webhook 启动的运行没有人可问，因此“人员”块在那里会失败，`echolot flow run --no-input` 也一样。

### 询问 [#ask]

用一个小表单询问若干值。

| 设置 | 作用 |
| --- | --- |
| **消息** | 要问什么。可使用模板。 |
| **字段** | 表单的字段（必填）。每个字段有 `name`（它的端口和键）、显示在旁边的 `label`、`type`（`text`、`number`、`bool`、`secret` 或 `choice`）、用于选择项的 `choices`（以逗号分隔）以及 `default`。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `run` | 入 | 发起询问。传入的对象用来填充消息中的模板。 |
| `answers` | 出 | 所有回答，合成一个对象。 |
| 每个字段一个 | 出 | 该字段的回答。 |
| `cancelled` | 出 | 此人点按了**取消**时为 `true`。 |

面板上有**取消**和**继续**。`secret` 字段的回答在运行日志中会被隐藏。

### 显示并等待 [#show-and-wait]

显示一条消息、一个可拷贝的代码和一个可打开的链接，然后等待**继续**，或等到有值到达 `until`。
适用于设备登录：在“轮询”块等待批准的同时显示代码。

| 设置 | 作用 |
| --- | --- |
| **标题** | 面板的标题。 |
| **消息** | 要显示的内容（必填）。 |
| **代码** | 以大号字显示，并带有**拷贝代码**（设备代码）。 |
| **链接** | 与**在浏览器中打开**一起显示。 |
| **二维码** | **同时将链接显示为二维码。** 默认关闭。 |

这五项都可以使用模板。

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `run` | 入 | 显示面板。传入的对象用来填充模板。 |
| `until` | 入 | 有值到达时自动继续，例如“轮询”块的 `success`。 |
| `done` | 出 | 传入 `run` 的内容。 |

接了 `until` 的线时，面板上会显示**完成后会自动继续。** 点按**停止等待**会关闭面板，并让该块失败。

### 批准 [#approve]

在流程继续之前请你批准，用在无法撤销的操作之前。

| 设置 | 作用 |
| --- | --- |
| **消息** | 问题（必填）。可使用模板。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `run` | 入 | 发起询问。传入的对象用来填充消息中的模板。 |
| `approved` | 出 | 点按**批准**之后，传入 `run` 的内容。 |
| `declined` | 出 | 点按**拒绝**之后，传入 `run` 的内容。 |

批准始终由人来做。智能体运行流程并回答“批准”块时，Echolot 会先把问题交给你。
在 **Place a test order** 中，“批准”块 **Keep the order?** 针对商店刚刚创建的订单询问 `Keep order {{body.id}} for CHF {{body.total.amount}}?`；点按**拒绝**会把它送往 **Cancel order**。

<Shot id="flows-approve-sheet" alt="Place a test order 的“批准”面板：Keep the order? 以及订单 id 和以瑞士法郎计的总额，下方是“拒绝”和“批准”按钮" />

## 逻辑 [#logic]

### 如果 [#if]

按[条件](#conditions)把传入的内容送到 `true` 或 `false`。

| 设置 | 作用 |
| --- | --- |
| **条件** | JavaScript 表达式（必填），例如 `value.status == 200`。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `value` | 入 | 要测试的值。 |
| `true`、`false` | 出 | 同一个值，从两者之一送出。 |

**Morning stock check** 用“如果”块 **Running low?**，对商店返回的商品检查 `value.body.items.some(p => p.stock.bern < 5)`。

### 分支 [#switch]

把传入的内容送到第一个条件成立的情况，否则送到 `default`。

| 设置 | 作用 |
| --- | --- |
| **情况** | 一个列表（必填）。每个情况有 `name`（它的输出端口）和 `condition`。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `value` | 入 | 要测试的值。 |
| 每个情况一个 | 出 | 该情况是第一个成立的情况时，送出这个值。 |
| `default` | 出 | 没有任何情况成立时，送出这个值。 |

### 求值 [#evaluate]

对传入的内容运行 JavaScript。最后一个表达式就是结果：`({ id: value.body.id })` 或 `a + b`。
带有自己的 `return` 的代码会作为函数体运行，`return` 给出的值就是结果。Promise 会被等待。
`pm.test` 和 `pm.expect` 的用法与[脚本](/docs/requests/scripts-and-tests)中相同，它们的结果计入运行的测试。`console.log` 的输出进入运行日志。

| 设置 | 作用 |
| --- | --- |
| **代码** | JavaScript（必填）。 |
| **输入** | 输入名称，以逗号分隔；每个名称是代码中的一个变量。为空时为 `value`。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| **输入**中的每个名称一个 | 入 | 该变量的值。 |
| `result` | 出 | 结果。 |
| `fail` | 出 | 代码抛出异常时的错误。 |

```js
// 输入：pages。把“收集”块汇集的列表连接起来。
return pages.flat();
```

### 延迟 [#delay]

等待一段时间，然后把值传下去。

| 设置 | 作用 |
| --- | --- |
| **毫秒** | 等待多久（必填）。可以是数字，或 `{{wait}}` 这样的模板。 |

端口：`value` 入，`value` 出。

## 循环 [#loops]

### 重复 [#repeat]

按**次数**发送 `index`，从 0 开始计数，然后发送 `done`。

| 设置 | 作用 |
| --- | --- |
| **次数** | 重复多少次（必填）：0 到 100,000。可以是数字，或 `{{pages}}` 这样的模板。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `run` | 入 | 启动循环。 |
| `index` | 出 | 0、1、2……每轮一次。 |
| `done` | 出 | 最后一轮之后，传入 `run` 的内容。 |

### 遍历 [#for-each]

依次发送列表中的每一项，然后发送 `done`。它没有设置。

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `list` | 入 | 列表。传入其他内容会让该块失败。 |
| `item` | 出 | 每轮一项。 |
| `index` | 出 | 该项的位置，从 0 开始。 |
| `done` | 出 | 最后一轮之后的整个列表。 |

### 收集 [#collect]

把循环期间到达的内容汇集成一个列表，并在循环结束时发送。它没有设置。

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `item` | 入 | 每轮一个值。 |
| `list` | 出 | 汇集的全部内容，在它所属的循环结束之后发出。 |

循环的每一轮都会先结束，下一轮才开始，它所到达的块在每一轮中都会再次运行。循环之外的值，例如来自“开始”块的输入，对每一轮都保持可用。

### 轮询 [#poll]

一遍又一遍地发送已存储的请求，直到条件成立（登录已批准、作业已完成）或截止时间到。

| 设置 | 作用 |
| --- | --- |
| **请求** | 已存储的请求（必填）。 |
| **变量** | 名称，以逗号分隔；每个名称成为一个输入，用来填充对应的 `{{variable}}`。 |
| **直到** | 针对 `response`（最近一次响应）的[条件](#conditions)。默认值：`response.status >= 200 && response.status < 300`。 |
| **间隔** | 两次发送之间的秒数。默认为 5。可以是数字或模板。 |
| **截止时间** | 放弃之前等待的秒数。默认为 300。可以是数字或模板。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `send` | 入 | 开始轮询。传入对象时，它的键成为变量。 |
| **变量**中的每个名称一个 | 入 | 该变量的值。 |
| `success` | 出 | 满足条件的响应。 |
| `timeout` | 出 | 截止时间到时的最后一个响应。`timeout` 没有接线时，该块改为失败。 |
| `fail` | 出 | 某次发送失败时的错误。 |

与“请求”块不同，2xx 以外的响应不会让“轮询”块失败：它会继续发送，直到**直到**条件成立。

## 数据 [#data]

### 值 [#value]

一个固定的值：文本、数字、`true` 或 `false`、列表或对象。

| 设置 | 作用 |
| --- | --- |
| **值** | 文本、数字或 JSON（必填）。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `run` | 入 | 每有内容到达，就再发送一次这个值。 |
| `value` | 出 | 这个值。没有任何连线接入 `run` 时，它在开始时发送一次。 |

### 模板 [#template]

文本中的 `{{name}}` 由传入的对象和运行的变量填充。

| 设置 | 作用 |
| --- | --- |
| **文本** | 带[模板](#templates)的文本（必填）。 |

端口：`values` 入（一个对象），`text` 出。

### 提取 [#select]

按路径从传入的内容中取出若干值，每个值一个输出。

| 设置 | 作用 |
| --- | --- |
| **字段** | 一个列表（必填）。每个字段有 `name`（它的输出端口）和 `path`，例如 `$.body.user_code`。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `value` | 入 | 要从中取值的值，通常是响应。 |
| 每个字段一个 | 出 | 该路径上的值；那里没有内容时为 `null`。 |
| `all` | 出 | 所有字段，合成一个对象。 |

路径采用 JSONPath 的通用部分：

| 路径 | 取出的内容 |
| --- | --- |
| `$.body.items[0].sku` | 先取一个键，再取列表的第一项。 |
| `$.body.items[-1]` | 最后一项。 |
| `$.body.items[*].sku` | 每一项的 `sku`，以列表形式返回。 |
| `$['content-type']` | 含有点号无法表示的字符的键。 |
| `body.id` | 没有 `$` 时，路径从该值开始。 |

### 设置变量 [#set-variable]

把值存储为变量，之后的块和请求就能用 `{{name}}` 读取。

| 设置 | 作用 |
| --- | --- |
| **变量** | 一个列表（必填）。每项有 `name`、指向传入内容的 `path`（为空时取全部内容）、`scope` 和 `secret`。 |

| 作用域 | 值存放的位置 |
| --- | --- |
| `run` | 仅限本次运行及其发送的请求。 |
| `environment` | 使用中的环境（默认）。未选择环境时，运行会在这里失败。 |
| `workspace` | 工作区自己的变量。 |
| `global` | 流程不能写入：运行会失败，并提示你改为存储到环境或工作区。 |

启用 `secret` 后，值会存入钥匙串，绝不写入文件，并且从离开上一个块的那一刻起，就会在运行日志和智能体能看到的内容中被隐藏。
`path` 什么也没找到时，变量保留原来的值，因此没有返回新刷新令牌的令牌响应不会抹掉你已有的那个。见[机密](/docs/workspaces/secrets)。

端口：`value` 入，`value` 出（传入的内容，原样传下去）。

### 读取变量 [#get-variable]

按请求看到的方式读取变量：先是运行自己的，然后依次是环境的、工作区的和全局的。

| 设置 | 作用 |
| --- | --- |
| **名称** | 变量（必填）。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `run` | 入 | 每有内容到达，就再读取一次。 |
| `value` | 出 | 变量的值；没有时为 `null`。没有任何连线接入 `run` 时，它在开始时读取一次。 |

## 输出 [#output]

### 显示 [#display]

把传入的内容原样传下去，方便你查看：运行之后选择该块，在检查器中查看**上次运行**。

| 设置 | 作用 |
| --- | --- |
| **格式** | `auto`、`text`、`json`、`table` 或 `image`。默认为 `auto`。 |

端口：`value` 入，`value` 出。

### 日志 [#log]

向运行日志写入一行。

| 设置 | 作用 |
| --- | --- |
| **消息** | 这一行，可使用模板。`{{value}}` 是传入的内容；消息为空时写入全部内容。 |

端口：`value` 入，`value` 出。在 **Place a test order** 中，无法下单时，**Order failed** 会记录 `Create order answered {{status}}`。

### 断言 [#assert]

检查传入的内容。每项检查都是运行的一个测试结果，有一项失败就会让 `echolot flow run` 失败。

| 设置 | 作用 |
| --- | --- |
| **检查** | 一个列表（必填）。每项检查有 `name` 和针对 `value` 的 `condition`。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `value` | 入 | 要检查的值。 |
| `passed` | 出 | 所有检查都成立时的这个值。 |
| `failed` | 出 | 任何一项检查失败时的这个值。 |

```js
value.status === 200   // Answers 200
value.time < 2000      // Within two seconds
```

### 通知 [#notify]

显示 Echolot 发出的 macOS 通知。macOS 会在第一次时请求权限。在命令行中，通知改为打印出来。

| 设置 | 作用 |
| --- | --- |
| **标题** | 通知的标题（必填）。可使用模板。 |
| **消息** | 通知的文本。可使用模板。 |

端口：`value` 入，`value` 出。**Morning stock check** 以“通知”块 **Restock** 结束，标题为 `Restock Bern`。

### 写入文件 [#write-file]

把传入的内容写入工作区文件夹内的文件。

| 设置 | 作用 |
| --- | --- |
| **路径** | 相对于工作区文件夹，例如 `exports/items.json`（必填）。可使用模板。 |
| **格式** | `json`（带格式）、`text` 或 `csv`。默认为 `json`。 |

| 端口 | 方向 | 传递的内容 |
| --- | --- | --- |
| `value` | 入 | 要写入的内容。 |
| `path` | 出 | 文件的路径，相对于工作区。 |
| `fail` | 出 | 无法写入的原因。 |

已存在的文件会被替换。工作区自己的文件不可写入：`requests`、`flows`、`environments`、`mocks`、`mcp-servers` 和 `specs` 文件夹、`workspace.toml` 以及隐藏文件。
对于 CSV，对象列表会变成每个对象一行、每个键一列；其他内容则每行写一个值。

## 块之间的值 [#values-between-blocks]

值是 JSON：文本、数字、`true` 和 `false`、列表、对象或 `null`。

### 响应对象 [#the-response-object]

“请求”块和“轮询”块把响应作为对象发送：

```json
{
  "status": 201,
  "reason": "Created",
  "headers": { "content-type": "application/json", "location": "/v1/orders/ord_9Px3Kd" },
  "body": { "id": "ord_9Px3Kd", "total": { "amount": 449, "currency": "CHF" } },
  "time": 182.4,
  "url": "https://demo.echolot.dev/v1/orders"
}
```

- 响应为 JSON 时，`body` 是解析后的 JSON，否则是文本，为空时是 `null`。
- 标头名称为小写。多次发送的标头用 `, ` 连接。
- `time` 是整个交换的耗时，单位为毫秒。
- 超过 8 MB 的正文会以 `null` 到达，并带有 `"truncated": true`。完整的响应仍保存在[历史记录](/docs/workspaces/history)中。

### 模板 [#templates]

标注为“可使用模板”的设置会用传入块的内容填充 `{{name}}`：

- `{{status}}`、`{{body.id}}` 或 `{{body.items[0].sku}}`：传入对象中的一个键，或指向其中的[路径](#select)。
- “日志”“通知”和“写入文件”中的 `{{value}}`：传入的整个值。
- “设置变量”块以作用域 `run` 存储的变量。

匹配不到任何内容的名称保持原样，因此“请求”块自己的 `{{variables}}` 在发送时仍会从环境中解析。对象和列表会写成 JSON 文本。

### 条件 [#conditions]

“如果”“分支”“轮询”和“断言”接受一个结果为真或假的 JavaScript 表达式。`value` 是传入的内容；“轮询”的**直到**把它读作 `response`，这个写法在其他地方同样可用。

```js
value.status == 200
response.status === 200 && !!(response.body && response.body.access_token)
value.body.items.length > 0
```

抛出异常的表达式会让该块失败（在“断言”中，则是让那一项检查失败）。

## 限制 [#limits]

为避免连错线后无限运行，一次运行在块运行 10,000 次或一小时后停止。由 Webhook 启动的运行在两分钟后停止。
