# Блоки (/ru/docs/flows/blocks)

Каждый блок делает одно дело. Его настройки — в инспекторе, когда вы выбираете блок; его порты — на холсте, входы слева
и выходы справа. На этой странице перечислены все виды блоков в тех группах, которые использует список блоков, а затем
объясняются значения, которые передаются между блоками, шаблоны вроде `{{body.id}}` и условия.

## Как идёт запуск [#how-a-run-moves]

- Блок выполняется, когда у каждого входа, в который ведёт связь, есть значение. Затем он отправляет значения из
  некоторых своих выходов, и дальше выполняются только блоки, связанные с этими выходами.
- Первым выполняется «Старт». Блоки «Значение» и «Получить переменную», в которые ничто не ведёт, тоже выполняются в
  самом начале.
- Блоки выполняются параллельно, когда это возможно. Блок «Показать и ждать» может ждать вас, пока блок «Опрос»
  продолжает работу.
- Несколько блоков могут завершиться ошибкой: у «Запрос», «Опрос», «Инструмент», «Сценарий», «Вычислить» и «Записать
  файл» есть выход `fail`. Если он связан, ошибка выходит через него, и запуск продолжается. Если нет, весь запуск
  завершается ошибкой на этом блоке.
- Запуск заканчивается, когда ничего не остаётся в работе.

В инспекторе каждого блока есть также **Название** (показывается на блоке; если пусто, это название вида) и
**Приостановлен (значения проходят насквозь)**. Приостановленный блок затемнён и пропускается: то, что приходит в него,
сразу выходит из его первого выхода.

## Триггеры [#triggers]

### Старт [#start]

Здесь начинается запуск: кнопка **Запустить**, агент (когда сценарий — инструмент), расписание, вебхук или
`echolot flow run`. В его инспекторе находятся **Триггеры** сценария; см. [Запуск и
триггеры](/docs/flows/running-and-triggers).

| Порт | Сторона | Что передаёт |
| --- | --- | --- |
| `run` | выход | Все входы сценария одним объектом. |
| по одному на каждый вход | выход | Значение этого входа. |

В сценарии ровно один блок «Старт». В списке блоков его нет.

### Результат [#output]

То, что возвращает запуск: агенту, вызвавшему сценарий, командной строке, вызывающему вебхук или родительскому
сценарию. В сценарии не больше одного такого блока.

| Порт | Сторона | Что передаёт |
| --- | --- | --- |
| `value` | вход | Результат. |

## Действия [#actions]

### Запрос [#request]

Отправляет сохранённый запрос рабочего пространства (HTTP, GraphQL или запрос MCP) с его авторизацией, скриптами,
проверками и окружением запуска.

| Настройка | Что делает |
| --- | --- |
| **Запрос** | Сохранённый запрос, выбирается из списка (обязательно). |
| **Переменные** | Имена через запятую. Каждое становится входом, который заполняет эту `{{variable}}` запроса. |

| Порт | Сторона | Что передаёт |
| --- | --- | --- |
| `send` | вход | Отправляет запрос. Если приходит объект, каждый его ключ становится переменной этой отправки. |
| по одному на каждое имя в **Переменные** | вход | Значение этой переменной. |
| `success` | выход | [Ответ](#the-response-object), если запрос выполнен успешно. |
| `fail` | выход | Ответ, если он не 2xx или не прошла проверка, либо ошибка, если ответа нет. |

Запрос успешен, если ответ 2xx, а его проверки, тесты скриптов и скрипты пройдены, как в [Прогоне](/docs/requests/runner).
Переменные, которые передаёт сценарий, побеждают переменные окружения только для этой отправки.

В сценарии **Place a test order** выход `success` блока **Sign in** связан с `send` блока **Create order**: заказ
создаётся только после того, как вход удался, а токен, который захватывает Sign in, уже лежит в `{{token}}`.

### Инструмент [#tool]

Выполняет один из собственных инструментов Echolot, например запускает мок, прогоняет папку, проверяет спецификацию
линтером или вызывает инструмент MCP-сервера, по тем же правилам, что и для агентов.

| Настройка | Что делает |
| --- | --- |
| **Инструмент** | Инструмент, выбирается из списка (обязательно). |
| **Аргументы** | Аргументы инструмента в JSON, например `{ "name": "{{value}}" }`. В текстовых значениях можно использовать шаблоны. |

| Порт | Сторона | Что передаёт |
| --- | --- | --- |
| `run` | вход | Запускает инструмент. Если приходит объект, его ключи добавляются к аргументам. |
| `result` | выход | То, что вернул инструмент. |
| `fail` | выход | Почему он не удался. |

Блок «Инструмент» не может запустить другой сценарий; для этого есть блок «Сценарий».

<Edition only="direct">
  Блок «Инструмент», вызывающий MCP-сервер, который работает как локальная программа (stdio), работает в загрузке с
  сайта. В редакции для App Store Echolot не может запустить эту программу: блок показывает предупреждение и
  завершается ошибкой при выполнении.
</Edition>

### Сценарий [#flow]

Запускает другой сценарий рабочего пространства и отправляет то, что получил его блок «Результат».

| Настройка | Что делает |
| --- | --- |
| **Сценарий** | Другой сценарий (обязательно). Сценарий не может запустить сам себя, и сценарии не могут запускать друг друга по кругу. |

| Порт | Сторона | Что передаёт |
| --- | --- | --- |
| `input` | вход | Объект со входами другого сценария. |
| `output` | выход | То, что вернул другой сценарий. |
| `fail` | выход | Почему другой сценарий не удался. |

Сценарии запускают друг друга на глубину не более 8. Секреты не передаются между сценариями как значения: сохраняйте их
как переменные.

## Люди [#people]

Блоки группы «Люди» приостанавливают запуск и о чём-то спрашивают вас. В приложении открывается лист; если запуск начал
агент, вопрос уходит клиенту агента; в командной строке вопрос задаётся в терминале. У запусков, которые начинают
расписания и вебхуки, спросить некого, поэтому блок группы «Люди» там завершается ошибкой, как и при
`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`. Используйте для входа по коду устройства: показывайте код, пока блок «Опрос» ждёт одобрения.

| Настройка | Что делает |
| --- | --- |
| **Название** | Заголовок листа. |
| **Сообщение** | Что показать (обязательно). |
| **Код** | Показывается крупно, с кнопкой **Скопировать код** (код устройства). |
| **Ссылка** | Показывается с кнопкой **Открыть в браузере**. |
| **QR** | **Показывать ссылку и как QR-код.** По умолчанию выключено. |

Во всех пяти можно использовать шаблоны.

| Порт | Сторона | Что передаёт |
| --- | --- | --- |
| `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]

Отправляет то, что пришло, в `true` или `false` по [условию](#conditions).

| Настройка | Что делает |
| --- | --- |
| **Условие** | Выражение 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
// Inputs: pages. Joins the lists that Collect gathered.
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}}`. |
| **Пока не** | [Условие](#conditions) для `response`, последнего ответа. По умолчанию: `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` и `condition` для `value`. |

| Порт | Сторона | Что передаёт |
| --- | --- | --- |
| `value` | вход | Проверяемое значение. |
| `passed` | выход | Значение, если выполняются все проверки. |
| `failed` | выход | Значение, если какая-то проверка не выполняется. |

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

### Уведомить [#notify]

Показывает уведомление macOS от Echolot. При первом разе 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"
}
```

- `body` — разобранный JSON, если ответ в формате JSON, иначе текст, а если ответ пуст, то `null`.
- Имена заголовков в нижнем регистре. Заголовок, отправленный несколько раз, объединяется через `, `.
- `time` — весь обмен в миллисекундах.
- Тело больше 8 МБ приходит как `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 выполнений блоков или через час.
Запуски, которые начинают вебхуки, останавливаются через две минуты.
