# Вопросы человеку

> POST …/dialogs и sparku ask — варианты, подтверждение, таймаут, вложение; как агент спрашивает разрешения.

## Как это работает

`POST /v1/integrations/{slug}/dialogs` показывает вопрос на островке и держит
соединение открытым, пока человек не ответит (или вопрос не снят). Ответ приходит
в теле того же HTTP-ответа — ждать нужно `curl` или HTTP-клиенту, который умеет
не закрывать соединение по таймауту раньше срока `timeout` из тела запроса.

У интеграции — не больше одного вопроса без ответа одновременно: второй `dialogs`
раньше ответа на первый получает `409 dialog_pending`. Диалоги должны быть отдельно
включены в настройках интеграции.

## Тело запроса

Вопрос с вариантами — есть `options` или `custom`:

```json
{
  "message": "Выкатить в прод?",
  "title": "Деплой",
  "options": ["yes", {"id": "no", "label": "Отмена", "detail": "ничего не делать"}],
  "multiple": false,
  "custom": false,
  "timeout": "10m"
}
```

Подтверждение — без `options` и `custom`:

```json
{"message": "Удалить ветку feature/x?", "accept": "Удалить", "decline": "Оставить", "comment": true}
```

| Поле | Тип | По умолчанию | Правило |
|---|---|---|---|
| `message` | строка | — | обязательное, до 600 символов |
| `title` | строка | адрес интеграции | до 60 символов |
| `options` | массив строк или `{id, label, detail?}` | — | до 9 вариантов; строка — сама себе и `id`, и `label` |
| `multiple` | bool | `false` | выбор нескольких — нужно ≥ 2 вариантов |
| `custom` | bool | `false` | добавляет поле свободного ответа |
| `accept` / `decline` | строка | «Да» / «Нет» | подписи кнопок подтверждения, до 30 символов |
| `comment` | bool | `false` | у отказа можно оставить комментарий |
| `timeout` | число секунд или `"90s"` / `"10m"` / `"2h"` | `600` (10 мин) | от 5 секунд до суток |
| `attachment` | абсолютный путь | — | файл, который человек может открыть из вопроса; должен существовать |

С `options` или `custom` — вопрос с вариантами; иначе — подтверждение двумя
кнопками. Указать одновременно `options`/`custom` и `accept`/`decline` нельзя —
`accept`/`decline` относятся только к подтверждению.

## Ответ

Всегда `200 OK`, форма тела зависит от исхода:

| Ситуация | Тело |
|---|---|
| выбрал вариант(ы) | `{"answer": "chose", "options": ["yes"]}` |
| свой ответ (`custom`) | `{"answer": "custom", "text": "…"}` |
| подтвердил | `{"answer": "accepted"}` |
| отказал | `{"answer": "declined"}` или `{"answer": "declined", "comment": "…"}` |
| истёк `timeout` | `{"answer": "expired"}` |
| вопрос снят | `{"answer": "withdrawn"}` |

`withdrawn` приходит, если: клиент отключился до ответа (тогда, впрочем, отвечать
уже некому — вопрос просто снимается с островка), SparkU закрывается, API выключили
или интеграцию выключили/удалили, пока вопрос ждал ответа.

Спрятать вопрос кнопкой «Скрыть» можно только у интеграции с карточкой — вернуть
его на экран можно кнопкой «Ответить» в карточке; до этого он остаётся без ответа
на сервере.

## Пример: curl

```bash
curl --unix-socket ~/.sparku/run/api.sock \
  -H "Authorization: Bearer $SPARKU_TOKEN_DEPLOY" \
  -d '{"message": "Выкатить в прод?", "accept": "Да", "decline": "Нет"}' \
  http://sparku/v1/integrations/deploy/dialogs
```

```json
{"answer": "accepted"}
```

## `sparku ask`

```
sparku ask <slug> "вопрос" [-o id:подпись …] [--multi] [--custom]
           [--accept Да --decline Нет --comment] [--title …] [--timeout 10m] [--attach файл]
```

```bash
sparku ask deploy "Деплоить в прод?" -o yes:Да -o no:Нет
sparku ask deploy "Куда катить?" -o prod:Прод -o stage:Стейджинг --custom
sparku ask deploy "Удалить ветку?" --accept Удалить --decline Оставить --comment
```

Выбранные варианты печатаются в stdout по одному на строку (id, не подпись),
свой ответ (`custom`) — текстом, комментарий отказа — тоже в stdout.

### Коды выхода `ask`

Отдельные от `<sysexits.h>`, которыми пользуются `push`/`end`/`doctor` — чтобы в
shell работало `if sparku ask deploy "Деплоить?"; then …`:

| Код | Значение |
|---|---|
| `0` | ответ получен (выбор или свой текст — в stdout) |
| `1` | отказ (`declined`) |
| `2` | время вышло, вопрос снят или «Скрыть», SparkU закрылся |

Ошибки транспорта (нет сокета, неверный токен, `409 dialog_pending`) возвращают
обычные коды `sparku` — `69`, `77`, `69` соответственно, см.
[CLI sparku](/docs/api/ru/cli.md#коды-выхода).

```bash
if sparku ask deploy "Выкатить в прод?"; then
  ./deploy.sh
else
  echo "деплой отменён"
fi
```

## Для ИИ-агентов

Агент вроде Claude Code или Codex, который выполняет команды в shell, может
спросить подтверждения у человека тем же путём, каким это делает CI-скрипт: через
`sparku ask` в интеграцию, которую человек завёл заранее (например, «Агент»
с адресом `agent`; если у неё есть карточка, вопрос можно спрятать и вернуться к нему). Это не отдельный протокол — агент такой же клиент API, как
любой другой процесс.

```bash
if sparku ask agent "Удалить ветку feature/old и её PR на GitHub?" \
     --accept Удалить --decline "Не трогать" --timeout 90s; then
  git push origin --delete feature/old
  gh pr close 42
else
  echo "отменено человеком" >&2
  exit 1
fi
```

Пока вопрос висит на островке, агент блокируется на вызове `sparku ask` — это
ожидаемо: решение принимает человек, и до его ответа выполнять опасный шаг нельзя.
Если человек не отвечает `--timeout`, по умолчанию 10 минут, `ask` вернёт `2`, и
агент должен трактовать это как отказ, а не как разрешение продолжать.

> [!TIP]
> У инструмента, которым агент запускает команды, обычно свой таймаут (у Claude Code —
> 2 минуты по умолчанию). Ставьте `--timeout` меньше него, иначе агент прервёт ожидание
> раньше, чем человек успеет ответить, а вопрос снимется с островка.

Строку-инструкцию для агента (в `CLAUDE.md`, `AGENTS.md` и т. п.) можно написать так:

```text
Перед необратимыми действиями (удаление, force-push, деплой, миграции) спроси
человека: sparku ask agent "<что и зачем>" --accept Да --decline Нет --timeout 90s
Код 0 — можно, 1 — отказ, 2 — ответа нет: считай отказом.
```
