---
read_when:
    - Реалізація або оновлення клієнтів WS для Gateway
    - Налагодження невідповідностей протоколу або збоїв підключення
    - Повторне генерування схем/моделей протоколу
summary: 'Протокол WebSocket Gateway: рукостискання, кадри, керування версіями'
title: Протокол Gateway
x-i18n:
    generated_at: "2026-07-04T18:18:36Z"
    model: gpt-5.5
    postprocess_version: locale-links-v1
    provider: openai
    source_hash: 763dd5cba2f1aa0de95243a4996b4da1b4aa32c5c1a4b5b6c112d605e677bd70
    source_path: gateway/protocol.md
    workflow: 16
---

Gateway WS-протокол є **єдиною площиною керування + транспортом вузлів** для
OpenClaw. Усі клієнти (CLI, веб-інтерфейс, застосунок macOS, вузли iOS/Android, headless
вузли) підключаються через WebSocket і оголошують свою **роль** + **область дії** під час
рукостискання.

## Транспорт

- WebSocket, текстові фрейми з JSON-навантаженням.
- Перший фрейм **має** бути запитом `connect`.
- Фрейми до підключення обмежені 64 KiB. Після успішного рукостискання клієнти
  мають дотримуватися лімітів `hello-ok.policy.maxPayload` і
  `hello-ok.policy.maxBufferedBytes`. Коли діагностику ввімкнено,
  завеликі вхідні фрейми й повільні вихідні буфери генерують події `payload.large`
  до того, як gateway закриє або відкине відповідний фрейм. Ці події зберігають
  розміри, ліміти, поверхні та безпечні коди причин. Вони не зберігають тіло повідомлення,
  вміст вкладень, сире тіло фрейму, токени, cookies або секретні значення.

## Рукостискання (connect)

Gateway → Клієнт (виклик до підключення):

```json
{
  "type": "event",
  "event": "connect.challenge",
  "payload": { "nonce": "…", "ts": 1737264000000 }
}
```

Клієнт → Gateway:

```json
{
  "type": "req",
  "id": "…",
  "method": "connect",
  "params": {
    "minProtocol": 3,
    "maxProtocol": 4,
    "client": {
      "id": "cli",
      "version": "1.2.3",
      "platform": "macos",
      "mode": "operator"
    },
    "role": "operator",
    "scopes": ["operator.read", "operator.write"],
    "caps": [],
    "commands": [],
    "permissions": {},
    "auth": { "token": "…" },
    "locale": "en-US",
    "userAgent": "openclaw-cli/1.2.3",
    "device": {
      "id": "device_fingerprint",
      "publicKey": "…",
      "signature": "…",
      "signedAt": 1737264000000,
      "nonce": "…"
    }
  }
}
```

Gateway → Клієнт:

```json
{
  "type": "res",
  "id": "…",
  "ok": true,
  "payload": {
    "type": "hello-ok",
    "protocol": 4,
    "server": { "version": "…", "connId": "…" },
    "features": { "methods": ["…"], "events": ["…"] },
    "snapshot": { "…": "…" },
    "auth": {
      "role": "operator",
      "scopes": ["operator.read", "operator.write"]
    },
    "policy": {
      "maxPayload": 26214400,
      "maxBufferedBytes": 52428800,
      "tickIntervalMs": 15000
    }
  }
}
```

Поки Gateway ще завершує запуск sidecar-компонентів, запит `connect` може
повернути повторювану помилку `UNAVAILABLE` з `details.reason`, встановленим у
`"startup-sidecars"`, і `retryAfterMs`. Клієнти мають повторити цю відповідь
у межах свого загального бюджету підключення, а не показувати її як остаточний
збій рукостискання.

`server`, `features`, `snapshot` і `policy` усі є обов’язковими за схемою
(`packages/gateway-protocol/src/schema/frames.ts`). `auth` також є обов’язковим і повідомляє
узгоджені роль/області дії. `pluginSurfaceUrls` є необов’язковим і зіставляє назви поверхонь plugin,
наприклад `canvas`, зі scoped розміщеними URL.

Scoped URL поверхонь plugin можуть завершуватися. Вузли можуть викликати
`node.pluginSurface.refresh` з `{ "surface": "canvas" }`, щоб отримати свіжий
запис у `pluginSurfaceUrls`. Експериментальний рефакторинг Canvas plugin не
підтримує застарілий шлях сумісності `canvasHostUrl`, `canvasCapability` або
`node.canvas.capability.refresh`; поточні нативні клієнти й gateway мають використовувати поверхні plugin.

Коли токен пристрою не видано, `hello-ok.auth` повідомляє узгоджені
дозволи без полів токенів:

```json
{
  "auth": {
    "role": "operator",
    "scopes": ["operator.read", "operator.write"]
  }
}
```

Довірені бекенд-клієнти в тому самому процесі (`client.id: "gateway-client"`,
`client.mode: "backend"`) можуть опускати `device` на прямих loopback-з’єднаннях, коли
вони автентифікуються спільним gateway-токеном/паролем. Цей шлях зарезервований
для внутрішніх RPC площини керування й не дає застарілим базовим станам поєднання CLI/пристрою
блокувати локальну бекенд-роботу, як-от оновлення сесій subagent. Віддалені клієнти,
клієнти з браузерним origin, клієнти-вузли й явні клієнти з токеном пристрою/ідентичністю пристрою
далі використовують звичайні перевірки поєднання й підвищення областей дії.

Коли токен пристрою видано, `hello-ok` також містить:

```json
{
  "auth": {
    "deviceToken": "…",
    "role": "operator",
    "scopes": ["operator.read", "operator.write"]
  }
}
```

Вбудований bootstrap через QR/код налаштування є свіжим шляхом передачі на мобільний пристрій. Успішне
підключення базового коду налаштування повертає основний токен вузла плюс один обмежений
токен оператора:

```json
{
  "auth": {
    "deviceToken": "…",
    "role": "node",
    "scopes": [],
    "deviceTokens": [
      {
        "deviceToken": "…",
        "role": "operator",
        "scopes": ["operator.approvals", "operator.read", "operator.talk.secrets", "operator.write"]
      }
    ]
  }
}
```

Передача оператору навмисно обмежена, щоб QR-онбординг міг запустити
мобільний цикл оператора й завершити нативне налаштування без надання областей дії
мутації поєднання або `operator.admin`. Вона включає `operator.talk.secrets`, щоб
нативний клієнт міг прочитати конфігурацію Talk, потрібну після bootstrap. Ширший
доступ до поєднання й адміністрування потребує окремого затвердженого поєднання оператора або потоку
токена. Клієнти мають зберігати
`hello-ok.auth.deviceTokens` лише
коли connect використовував bootstrap-автентифікацію на довіреному транспорті, такому як `wss://` або
loopback/local pairing.

### Приклад вузла

```json
{
  "type": "req",
  "id": "…",
  "method": "connect",
  "params": {
    "minProtocol": 3,
    "maxProtocol": 4,
    "client": {
      "id": "ios-node",
      "version": "1.2.3",
      "platform": "ios",
      "mode": "node"
    },
    "role": "node",
    "scopes": [],
    "caps": ["camera", "canvas", "screen", "location", "voice"],
    "commands": ["camera.snap", "canvas.navigate", "screen.record", "location.get"],
    "permissions": { "camera.capture": true, "screen.record": false },
    "auth": { "token": "…" },
    "locale": "en-US",
    "userAgent": "openclaw-ios/1.2.3",
    "device": {
      "id": "device_fingerprint",
      "publicKey": "…",
      "signature": "…",
      "signedAt": 1737264000000,
      "nonce": "…"
    }
  }
}
```

## Фреймування

- **Запит**: `{type:"req", id, method, params}`
- **Відповідь**: `{type:"res", id, ok, payload|error}`
- **Подія**: `{type:"event", event, payload, seq?, stateVersion?}`

Методи з побічними ефектами потребують **ключів ідемпотентності** (див. схему).

## Ролі + області дії

Повну модель областей дії оператора, перевірки під час затвердження й семантику спільних секретів
див. у [Області дії оператора](/uk/gateway/operator-scopes).

### Ролі

- `operator` = клієнт площини керування (CLI/UI/автоматизація).
- `node` = хост можливостей (camera/screen/canvas/system.run).

### Області дії (оператор)

Поширені області дії:

- `operator.read`
- `operator.write`
- `operator.admin`
- `operator.approvals`
- `operator.pairing`
- `operator.talk.secrets`

`talk.config` з `includeSecrets: true` потребує `operator.talk.secrets`
(або `operator.admin`).
Коли секрети включено, клієнти мають читати активні облікові дані провайдера Talk
з `talk.resolved.config.apiKey`; `talk.providers.<id>.apiKey`
залишається у формі джерела й може бути об’єктом SecretRef або заредагованим рядком.

Зареєстровані plugin методи gateway RPC можуть запитувати власну область дії оператора, але
зарезервовані основні префікси адміністрування (`config.*`, `exec.approvals.*`, `wizard.*`,
`update.*`) завжди резолвляться в `operator.admin`.

Область дії методу є лише першим шлюзом. Деякі slash-команди, доступні через
`chat.send`, застосовують суворіші перевірки на рівні команди додатково. Наприклад, сталі
записи `/config set` і `/config unset` потребують `operator.admin`.

`node.pair.approve` також має додаткову перевірку області дії під час затвердження поверх
базової області дії методу:

- запити без команд: `operator.pairing`
- запити з non-exec командами вузла: `operator.pairing` + `operator.write`
- запити, що містять `system.run`, `system.run.prepare` або `system.which`:
  `operator.pairing` + `operator.admin`

### Можливості/команди/дозволи (вузол)

Вузли оголошують claims можливостей під час підключення:

- `caps`: високорівневі категорії можливостей, як-от `camera`, `canvas`, `screen`,
  `location`, `voice` і `talk`.
- `commands`: allowlist команд для invoke.
- `permissions`: деталізовані перемикачі (наприклад, `screen.record`, `camera.capture`).

Gateway розглядає їх як **claims** і застосовує серверні allowlist.

## Присутність

- `system-presence` повертає записи з ключами за ідентичністю пристрою.
- Записи присутності містять `deviceId`, `roles` і `scopes`, щоб UI могли показувати один рядок на пристрій
  навіть коли він підключається і як **operator**, і як **node**.
- `node.list` містить необов’язкові поля `lastSeenAtMs` і `lastSeenReason`. Підключені вузли повідомляють
  час свого поточного з’єднання як `lastSeenAtMs` з причиною `connect`; поєднані вузли також можуть повідомляти
  сталу фонову присутність, коли довірена подія вузла оновлює їхні метадані поєднання.

### Фонова подія активності вузла

Вузли можуть викликати `node.event` з `event: "node.presence.alive"`, щоб записати, що поєднаний вузол був
активним під час фонового пробудження, не позначаючи його підключеним.

```json
{
  "event": "node.presence.alive",
  "payloadJSON": "{\"trigger\":\"silent_push\",\"sentAtMs\":1737264000000,\"displayName\":\"Peter's iPhone\",\"version\":\"2026.4.28\",\"platform\":\"iOS 18.4.0\",\"deviceFamily\":\"iPhone\",\"modelIdentifier\":\"iPhone17,1\",\"pushTransport\":\"relay\"}"
}
```

`trigger` є закритим enum: `background`, `silent_push`, `bg_app_refresh`,
`significant_location`, `manual` або `connect`. Невідомі рядки trigger нормалізуються до
`background` gateway перед збереженням. Подія є сталою лише для автентифікованих сесій пристроїв
вузла; сесії без пристрою або без поєднання повертають `handled: false`.

Успішні gateway повертають структурований результат:

```json
{
  "ok": true,
  "event": "node.presence.alive",
  "handled": true,
  "reason": "persisted"
}
```

Старіші gateway можуть усе ще повертати `{ "ok": true }` для `node.event`; клієнти мають трактувати це як
підтверджений RPC, а не як стале збереження присутності.

## Обмеження областями дії для broadcast-подій

Надіслані сервером broadcast-події WebSocket обмежуються областями дії, щоб сесії з областю поєднання або лише вузлові сесії не отримували пасивно вміст сесії.

- **Фрейми чату, агента й результатів інструментів** (зокрема потокові події `agent` і результати викликів інструментів) потребують щонайменше `operator.read`. Сесії без `operator.read` повністю пропускають ці фрейми.
- **Визначені plugin broadcast-події `plugin.*`** обмежуються `operator.write` або `operator.admin`, залежно від того, як plugin їх зареєстрував.
- **Події статусу й транспорту** (`heartbeat`, `presence`, `tick`, життєвий цикл connect/disconnect тощо) залишаються необмеженими, щоб стан транспорту був видимий кожній автентифікованій сесії.
- **Невідомі сімейства broadcast-подій** за замовчуванням обмежуються областями дії (fail-closed), якщо зареєстрований обробник явно їх не послаблює.

Кожне клієнтське з’єднання зберігає власний per-client порядковий номер, тому broadcast-події зберігають монотонне впорядкування на цьому сокеті, навіть коли різні клієнти бачать різні відфільтровані за областю дії підмножини потоку подій.

## Поширені сімейства методів RPC

Публічна поверхня WS ширша за наведені вище приклади рукостискання/автентифікації. Це
не згенерований dump — `hello-ok.features.methods` є консервативним
списком discovery, побудованим із `src/gateway/server-methods-list.ts` плюс завантажених
експортів методів plugin/channel. Розглядайте його як discovery функцій, а не повний
перелік `src/gateway/server-methods/*.ts`.

  <AccordionGroup>
  <Accordion title="Система та ідентичність">
    - `health` повертає кешований або щойно перевірений знімок стану Gateway.
    - `diagnostics.stability` повертає нещодавній обмежений реєстратор діагностичної стабільності. Він зберігає операційні метадані, як-от назви подій, лічильники, розміри в байтах, показники пам’яті, стан черги/сеансу, назви каналів/Plugin і ідентифікатори сеансів. Він не зберігає текст чату, тіла webhook, результати інструментів, необроблені тіла запитів або відповідей, токени, cookies чи секретні значення. Потрібна область читання оператора.
    - `status` повертає зведення Gateway у стилі `/status`; чутливі поля включаються лише для клієнтів оператора з областю адміністратора.
    - `gateway.identity.get` повертає ідентичність пристрою Gateway, яку використовують потоки ретрансляції та сполучення.
    - `system-presence` повертає поточний знімок присутності для підключених пристроїв оператора/Node.
    - `system-event` додає системну подію та може оновлювати/транслювати контекст присутності.
    - `last-heartbeat` повертає останню збережену подію Heartbeat.
    - `set-heartbeats` вмикає або вимикає обробку Heartbeat на Gateway.

  </Accordion>

  <Accordion title="Моделі та використання">
    - `models.list` повертає каталог моделей, дозволених під час виконання. Передайте `{ "view": "configured" }` для налаштованих моделей розміру пікера (спершу `agents.defaults.models`, потім `models.providers.*.models`) або `{ "view": "all" }` для повного каталогу.
    - `usage.status` повертає вікна використання провайдера / зведення залишкових квот.
    - `usage.cost` повертає агреговані зведення витрат за діапазон дат.
      Передайте `agentId` для одного агента або `agentScope: "all"`, щоб агрегувати налаштованих агентів.
    - `doctor.memory.status` повертає готовність векторної пам’яті / кешованих embedding для активного робочого простору агента за замовчуванням. Передавайте `{ "probe": true }` або `{ "deep": true }` лише коли викликач явно хоче виконати live ping провайдера embedding. Клієнти, обізнані з Dreaming, також можуть передати `{ "agentId": "agent-id" }`, щоб обмежити статистику сховища Dreaming вибраним робочим простором агента; якщо `agentId` пропущено, зберігається fallback агента за замовчуванням і агрегуються налаштовані робочі простори Dreaming.
    - `doctor.memory.dreamDiary`, `doctor.memory.backfillDreamDiary`, `doctor.memory.resetDreamDiary`, `doctor.memory.resetGroundedShortTerm`, `doctor.memory.repairDreamingArtifacts` і `doctor.memory.dedupeDreamDiary` приймають необов’язкові параметри `{ "agentId": "agent-id" }` для переглядів/дій Dreaming вибраного агента. Коли `agentId` пропущено, вони працюють із налаштованим робочим простором агента за замовчуванням.
    - `doctor.memory.remHarness` повертає обмежений попередній перегляд harness REM лише для читання для віддалених клієнтів control plane. Він може містити шляхи робочого простору, фрагменти пам’яті, відрендерений grounded Markdown і кандидатів для deep promotion, тому викликачам потрібен `operator.read`.
    - `sessions.usage` повертає зведення використання за сеансами. Передайте `agentId` для одного
      агента або `agentScope: "all"`, щоб перелічити налаштованих агентів разом.
    - `sessions.usage.timeseries` повертає використання часових рядів для одного сеансу.
    - `sessions.usage.logs` повертає записи журналу використання для одного сеансу.

  </Accordion>

  <Accordion title="Канали та помічники входу">
    - `channels.status` повертає вбудовані + bundled зведення стану каналів/Plugin.
    - `channels.logout` виконує вихід із певного каналу/облікового запису, якщо канал підтримує вихід.
    - `web.login.start` запускає потік входу через QR/веб для поточного провайдера вебканалу з підтримкою QR.
    - `web.login.wait` очікує завершення цього потоку входу через QR/веб і в разі успіху запускає канал.
    - `push.test` надсилає тестовий push APNs на зареєстрований iOS Node.
    - `voicewake.get` повертає збережені тригери wake-word.
    - `voicewake.set` оновлює тригери wake-word і транслює зміну.

  </Accordion>

  <Accordion title="Повідомлення та журнали">
    - `send` — це прямий RPC вихідної доставки для надсилань, націлених на канал/обліковий запис/thread, поза chat runner.
    - `logs.tail` повертає налаштований хвіст файлового журналу Gateway з керуванням cursor/limit і max-byte.

  </Accordion>

  <Accordion title="Talk і TTS">
    - `talk.catalog` повертає каталог провайдерів Talk лише для читання для мовлення, потокової транскрипції та голосу в реальному часі. Він містить канонічні ідентифікатори провайдерів, псевдоніми реєстру, мітки, налаштований стан, необов’язковий результат `ready` на рівні групи, відкриті ідентифікатори моделей/голосів, канонічні режими, транспорти, стратегії brain і прапорці аудіо/можливостей реального часу, не повертаючи секрети провайдера й не змінюючи глобальну конфігурацію. Поточні Gateway встановлюють `ready` після застосування вибору провайдера під час виконання; клієнти мають трактувати його відсутність як неперевірений стан для сумісності зі старішими Gateway.
    - `talk.config` повертає ефективне корисне навантаження конфігурації Talk; `includeSecrets` потребує `operator.talk.secrets` (або `operator.admin`).
    - `talk.session.create` створює сеанс Talk, яким володіє Gateway, для `realtime/gateway-relay`, `transcription/gateway-relay` або `stt-tts/managed-room`. Для `stt-tts/managed-room` викликачі `operator.write`, які передають `sessionKey`, також мають передати `spawnedBy` для обмеженої видимості ключа сеансу; створення `sessionKey` без області та `brain: "direct-tools"` потребують `operator.admin`.
    - `talk.session.join` перевіряє токен сеансу managed-room, за потреби надсилає події `session.ready` або `session.replaced` і повертає метадані кімнати/сеансу плюс нещодавні події Talk без plaintext токена або збереженого hash токена.
    - `talk.session.appendAudio` додає вхідний аудіо PCM у base64 до сеансів ретрансляції в реальному часі та транскрипції, якими володіє Gateway.
    - `talk.session.startTurn`, `talk.session.endTurn` і `talk.session.cancelTurn` керують життєвим циклом turn managed-room із відхиленням застарілого turn до очищення стану.
    - `talk.session.cancelOutput` зупиняє аудіовихід асистента, насамперед для barge-in, обмеженого VAD, у сеансах ретрансляції Gateway.
    - `talk.session.submitToolResult` завершує виклик інструмента провайдера, створений сеансом ретрансляції в реальному часі, яким володіє Gateway. Передайте `options: { willContinue: true }` для проміжного результату інструмента, коли фінальний результат буде надано пізніше, або `options: { suppressResponse: true }`, коли результат інструмента має задовольнити виклик провайдера без запуску ще однієї відповіді асистента в реальному часі.
    - `talk.session.steer` надсилає голосове керування активним запуском у сеанс Talk із агентною підтримкою, яким володіє Gateway. Він приймає `{ sessionId, text, mode? }`, де `mode` — це `status`, `steer`, `cancel` або `followup`; пропущений режим класифікується за промовленим текстом.
    - `talk.session.close` закриває сеанс ретрансляції, транскрипції або managed-room, яким володіє Gateway, і надсилає термінальні події Talk.
    - `talk.mode` встановлює/транслює поточний стан режиму Talk для клієнтів WebChat/Control UI.
    - `talk.client.create` створює сеанс провайдера в реальному часі, яким володіє клієнт, використовуючи `webrtc` або `provider-websocket`, тоді як Gateway володіє конфігурацією, обліковими даними, інструкціями та політикою інструментів.
    - `talk.client.toolCall` дає змогу транспортам реального часу, якими володіє клієнт, пересилати виклики інструментів провайдера до політики Gateway. Перший підтримуваний інструмент — `openclaw_agent_consult`; клієнти отримують ідентифікатор запуску та очікують звичайних подій життєвого циклу чату перед надсиланням результату інструмента, специфічного для провайдера.
    - `talk.client.steer` надсилає голосове керування активним запуском для транспортів реального часу, якими володіє клієнт. Gateway визначає активний вбудований запуск із `sessionKey` і повертає структурований результат прийняття/відхилення замість мовчазного відкидання steering.
    - `talk.event` — це єдиний канал подій Talk для адаптерів реального часу, транскрипції, STT/TTS, managed-room, телефонії та зустрічей.
    - `talk.speak` синтезує мовлення через активного провайдера мовлення Talk.
    - `tts.status` повертає стан увімкнення TTS, активного провайдера, fallback-провайдерів і стан конфігурації провайдера.
    - `tts.providers` повертає видимий інвентар провайдерів TTS.
    - `tts.enable` і `tts.disable` перемикають стан налаштувань TTS.
    - `tts.setProvider` оновлює бажаного провайдера TTS.
    - `tts.convert` виконує одноразове перетворення тексту на мовлення.

  </Accordion>

  <Accordion title="Секрети, конфігурація, оновлення та майстер">
    - `secrets.reload` повторно розв’язує активні SecretRefs і замінює стан секретів під час виконання лише за повного успіху.
    - `secrets.resolve` розв’язує призначення секретів, націлених на команду, для певного набору команд/цілей.
    - `config.get` повертає поточний знімок конфігурації та hash.
    - `config.set` записує перевірене корисне навантаження конфігурації.
    - `config.patch` об’єднує часткове оновлення конфігурації. Деструктивна заміна масиву
      потребує відповідного шляху в `replacePaths`; вкладені масиви
      під елементами масиву використовують шляхи `[]`, як-от `agents.list[].skills`.
    - `config.apply` перевіряє + замінює повне корисне навантаження конфігурації.
    - `config.schema` повертає live корисне навантаження схеми конфігурації, яке використовують інструменти Control UI і CLI: схема, `uiHints`, версія та метадані генерації, включно з метаданими схеми Plugin + каналу, коли runtime може їх завантажити. Схема містить метадані полів `title` / `description`, отримані з тих самих міток і довідкового тексту, які використовує UI, включно з гілками вкладених об’єктів, wildcard, елементів масиву та композиції `anyOf` / `oneOf` / `allOf`, коли існує відповідна документація полів.
    - `config.schema.lookup` повертає корисне навантаження пошуку з областю шляху для одного шляху конфігурації: нормалізований шлях, поверхневий вузол схеми, відповідна підказка + `hintPath`, необов’язковий `reloadKind` і безпосередні зведення дочірніх елементів для деталізації в UI/CLI. `reloadKind` є одним із `restart`, `hot` або `none` і віддзеркалює планувальник перезавантаження конфігурації Gateway для запитаного шляху. Вузли схеми пошуку зберігають користувацьку документацію та поширені поля перевірки (`title`, `description`, `type`, `enum`, `const`, `format`, `pattern`, числові/рядкові/масивні/об’єктні межі та прапорці на кшталт `additionalProperties`, `deprecated`, `readOnly`, `writeOnly`). Зведення дочірніх елементів відкривають `key`, нормалізований `path`, `type`, `required`, `hasChildren`, необов’язковий `reloadKind`, а також відповідні `hint` / `hintPath`.
    - `update.run` запускає потік оновлення Gateway і планує перезапуск лише коли саме оновлення завершилося успішно; викликачі із сеансом можуть включити `continuationMessage`, щоб запуск продовжив один наступний turn агента через чергу продовження після перезапуску. Оновлення через менеджер пакетів і керовані supervised оновлення git-checkout із control plane використовують відокремлену передачу керованій службі замість заміни дерева пакетів або зміни checkout/build output усередині live Gateway. Запущена передача повертає `ok: true` з `result.reason: "managed-service-handoff-started"` і `handoff.status: "started"`; недоступні або невдалі передачі повертають `ok: false` з `managed-service-handoff-unavailable` або `managed-service-handoff-failed`, а також `handoff.command`, коли потрібне ручне оновлення в shell. Недоступна передача означає, що OpenClaw не має безпечної межі supervisor або тривкої ідентичності служби, як-от `OPENCLAW_SYSTEMD_UNIT` для systemd. Під час запущеної передачі sentinel перезапуску може короткочасно повідомляти `stats.reason: "restart-health-pending"`; продовження затримується, доки CLI не перевірить перезапущений Gateway і не запише фінальний sentinel `ok`.
    - `update.status` оновлює та повертає останній sentinel перезапуску оновлення, включно з версією, що працює після перезапуску, якщо вона доступна.
    - `wizard.start`, `wizard.next`, `wizard.status` і `wizard.cancel` відкривають onboarding-майстер через WS RPC.

  </Accordion>

  <Accordion title="Помічники агентів і робочих просторів">
    - `agents.list` повертає налаштовані записи агентів, включно з чинною моделлю та метаданими runtime.
    - `agents.create`, `agents.update` і `agents.delete` керують записами агентів і прив’язкою робочого простору.
    - `agents.files.list`, `agents.files.get` і `agents.files.set` керують початковими файлами робочого простору, відкритими для агента.
    - `tasks.list`, `tasks.get` і `tasks.cancel` відкривають реєстр завдань Gateway для SDK та операторських клієнтів.
    - `artifacts.list`, `artifacts.get` і `artifacts.download` відкривають зведення артефактів, отриманих із транскриптів, і завантаження для явної області `sessionKey`, `runId` або `taskId`. Запити запусків і завдань визначають власницьку сесію на сервері та повертають лише медіа транскриптів із відповідним походженням; небезпечні або локальні джерела URL повертають непідтримувані завантаження замість отримання на сервері.
    - `environments.list` і `environments.status` відкривають для клієнтів SDK виявлення локального для Gateway і вузлового середовища лише для читання.
    - `agent.identity.get` повертає чинну ідентичність помічника для агента або сесії.
    - `agent.wait` очікує завершення запуску та повертає термінальний знімок, коли він доступний.

  </Accordion>

  <Accordion title="Керування сесіями">
    - `sessions.list` повертає поточний індекс сесій, включно з метаданими `agentRuntime` для кожного рядка, коли налаштовано backend runtime агента.
    - `sessions.subscribe` і `sessions.unsubscribe` перемикають підписки на події зміни сесій для поточного клієнта WS.
    - `sessions.messages.subscribe` і `sessions.messages.unsubscribe` перемикають підписки на події транскриптів/повідомлень для однієї сесії.
    - `sessions.preview` повертає обмежені попередні перегляди транскриптів для конкретних ключів сесій.
    - `sessions.describe` повертає один рядок сесії Gateway для точного ключа сесії.
    - `sessions.resolve` визначає або канонізує ціль сесії.
    - `sessions.create` створює новий запис сесії.
    - `sessions.send` надсилає повідомлення в наявну сесію.
    - `sessions.steer` є варіантом переривання й скеровування для активної сесії.
    - `sessions.abort` перериває активну роботу для сесії. Викликач може передати `key` плюс необов’язковий `runId` або передати лише `runId` для активних запусків, які Gateway може зіставити із сесією.
    - `sessions.patch` оновлює метадані/перевизначення сесії та повідомляє визначену канонічну модель плюс чинний `agentRuntime`.
    - `sessions.reset`, `sessions.delete` і `sessions.compact` виконують обслуговування сесій.
    - `sessions.get` повертає повний збережений рядок сесії.
    - Виконання чату й надалі використовує `chat.history`, `chat.send`, `chat.abort` і `chat.inject`. `chat.history` нормалізовано для відображення в клієнтах UI: вбудовані теги директив вилучаються з видимого тексту, текстові XML-навантаження викликів інструментів (зокрема `<tool_call>...</tool_call>`, `<function_call>...</function_call>`, `<tool_calls>...</tool_calls>`, `<function_calls>...</function_calls>` і обрізані блоки викликів інструментів) та витеклі ASCII/повноширинні токени керування моделлю вилучаються, суто беззвучні рядки помічника з токенами, як-от точні `NO_REPLY` / `no_reply`, пропускаються, а надмірно великі рядки можуть замінюватися заповнювачами.
    - `chat.message.get` є додатковим обмеженим читачем повного повідомлення для одного видимого запису транскрипту. Клієнти передають `sessionKey`, необов’язковий `agentId`, коли вибір сесії має область агента, плюс `messageId` транскрипту, раніше показаний через `chat.history`, а Gateway повертає ту саму нормалізовану для відображення проєкцію без полегшеного ліміту обрізання історії, якщо збережений запис досі доступний і не є надмірно великим.
    - `chat.send` приймає одноходовий `fastMode: "auto"`, щоб використовувати швидкий режим для викликів моделі, запущених до автоматичного порогу, а потім запускати пізніші повторні спроби, fallback, результати інструментів або виклики продовження без швидкого режиму. Поріг за замовчуванням становить 60 секунд і може налаштовуватися для кожної моделі через `agents.defaults.models["<provider>/<model>"].params.fastAutoOnSeconds`. Викликач `chat.send` може передати одноходовий `fastAutoOnSeconds`, щоб перевизначити поріг для цього запиту.

  </Accordion>

  <Accordion title="Сполучення пристроїв і токени пристроїв">
    - `device.pair.list` повертає очікувані та схвалені спряжені пристрої.
    - `device.pair.setupCode` створює мобільний код налаштування та, за замовчуванням, URL даних PNG QR. Він вимагає `operator.admin` і навмисно пропущений у рекламованому виявленні. Результат містить `setupCode`, необов’язковий `qrDataUrl`, `gatewayUrl`, несекретну мітку `auth` і `urlSource`.
    - `device.pair.approve`, `device.pair.reject` і `device.pair.remove` керують записами сполучення пристроїв.
    - `device.token.rotate` ротує токен спряженого пристрою в межах його схваленої ролі та області викликача.
    - `device.token.revoke` відкликає токен спряженого пристрою в межах його схваленої ролі та області викликача.

    Код налаштування вбудовує короткочасний початковий обліковий засіб. Клієнти не повинні
    записувати його в журнал або зберігати поза потоком сполучення.

  </Accordion>

  <Accordion title="Сполучення Node, виклики й очікувана робота">
    - `node.pair.request`, `node.pair.list`, `node.pair.approve`, `node.pair.reject`, `node.pair.remove` і `node.pair.verify` охоплюють сполучення вузлів і перевірку bootstrap.
    - `node.list` і `node.describe` повертають відомий/підключений стан вузла.
    - `node.rename` оновлює мітку спряженого вузла.
    - `node.invoke` пересилає команду до підключеного вузла.
    - `node.invoke.result` повертає результат для запиту виклику.
    - `node.event` передає події, що походять від вузла, назад у gateway.
    - `node.pending.pull` і `node.pending.ack` є API черги підключеного вузла.
    - `node.pending.enqueue` і `node.pending.drain` керують надійною очікуваною роботою для offline/відключених вузлів.

  </Accordion>

  <Accordion title="Сімейства схвалень">
    - `exec.approval.request`, `exec.approval.get`, `exec.approval.list` і `exec.approval.resolve` охоплюють одноразові запити схвалення exec плюс пошук/відтворення очікуваних схвалень.
    - `exec.approval.waitDecision` очікує одне очікуване схвалення exec і повертає остаточне рішення (або `null` у разі timeout).
    - `exec.approvals.get` і `exec.approvals.set` керують знімками політики схвалення exec у gateway.
    - `exec.approvals.node.get` і `exec.approvals.node.set` керують локальною для вузла політикою схвалення exec через команди ретрансляції вузла.
    - `plugin.approval.request`, `plugin.approval.list`, `plugin.approval.waitDecision` і `plugin.approval.resolve` охоплюють потоки схвалення, визначені plugin.

  </Accordion>

  <Accordion title="Автоматизація, Skills та інструменти">
    - Автоматизація: `wake` планує негайне або під час наступного heartbeat впровадження тексту пробудження; `cron.get`, `cron.list`, `cron.status`, `cron.add`, `cron.update`, `cron.remove`, `cron.run`, `cron.runs` керують запланованою роботою.
    - `cron.run` залишається RPC у стилі додавання до черги для ручних запусків. Клієнти, яким потрібна семантика завершення, мають прочитати повернений `runId` і опитувати `cron.runs`.
    - `cron.runs` приймає необов’язковий непорожній фільтр `runId`, щоб клієнти могли відстежувати один поставлений у чергу ручний запуск без перегонів з іншими записами історії для того самого завдання.
    - Skills та інструменти: `commands.list`, `skills.*`, `tools.catalog`, `tools.effective`, `tools.invoke`.

  </Accordion>
</AccordionGroup>

### Поширені сімейства подій

- `chat`: оновлення чату UI, як-от `chat.inject`, та інші події чату
  лише з транскриптами. У протоколі v4 delta-навантаження несуть `deltaText`; `message` залишається
  накопиченим знімком помічника. Заміни не за префіксом установлюють `replace=true`
  і використовують `deltaText` як текст заміни.
- `session.message`, `session.operation` і `session.tool`: транскрипт,
  операція сесії в процесі виконання та оновлення потоку подій для підписаної
  сесії.
- `sessions.changed`: індекс сесій або метадані змінено.
- `presence`: оновлення знімка присутності системи.
- `tick`: періодична подія keepalive / liveness.
- `health`: оновлення знімка стану gateway.
- `heartbeat`: оновлення потоку подій heartbeat.
- `cron`: подія зміни запуску/завдання cron.
- `shutdown`: сповіщення про вимкнення gateway.
- `node.pair.requested` / `node.pair.resolved`: життєвий цикл сполучення вузла.
- `node.invoke.request`: широкомовний запит виклику вузла.
- `device.pair.requested` / `device.pair.resolved`: життєвий цикл спряженого пристрою.
- `voicewake.changed`: конфігурацію тригера wake-word змінено.
- `exec.approval.requested` / `exec.approval.resolved`: життєвий цикл схвалення exec.
- `plugin.approval.requested` / `plugin.approval.resolved`: життєвий цикл схвалення plugin.

### Допоміжні методи вузлів

- Вузли можуть викликати `skills.bins`, щоб отримати поточний список виконуваних файлів навичок
  для перевірок auto-allow.

### RPC реєстру завдань

Операторські клієнти можуть переглядати й скасовувати записи фонових завдань Gateway через
RPC реєстру завдань. Ці методи повертають очищені зведення завдань, а не необроблений
стан runtime.

- `tasks.list` вимагає `operator.read`.
  - Параметри: необов’язковий `status` (`"queued"`, `"running"`, `"completed"`,
    `"failed"`, `"cancelled"` або `"timed_out"`) або масив цих статусів,
    необов’язковий `agentId`, необов’язковий `sessionKey`, необов’язковий `limit` від `1` до
    `500` і необов’язковий рядок `cursor`.
  - Результат: `{ "tasks": TaskSummary[], "nextCursor"?: string }`.
- `tasks.get` вимагає `operator.read`.
  - Параметри: `{ "taskId": string }`.
  - Результат: `{ "task": TaskSummary }`.
  - Відсутні ідентифікатори завдань повертають форму помилки not-found Gateway.
- `tasks.cancel` вимагає `operator.write`.
  - Параметри: `{ "taskId": string, "reason"?: string }`.
  - Результат:
    `{ "found": boolean, "cancelled": boolean, "reason"?: string, "task"?: TaskSummary }`.
  - `found` повідомляє, чи мав реєстр відповідне завдання. `cancelled`
    повідомляє, чи runtime прийняв або записав скасування.

`TaskSummary` містить `id`, `status` і необов’язкові метадані, як-от `kind`,
`runtime`, `title`, `agentId`, `sessionKey`, `childSessionKey`, `ownerKey`,
`runId`, `taskId`, `flowId`, `parentTaskId`, `sourceId`, часові позначки, прогрес,
термінальне зведення й очищений текст помилки. `agentId` ідентифікує агента,
який виконує завдання; `sessionKey` і `ownerKey` зберігають контекст запитувача та керування.

### Допоміжні методи оператора

- Оператори можуть викликати `commands.list` (`operator.read`), щоб отримати інвентар команд середовища виконання для агента.
  - `agentId` необов’язковий; не вказуйте його, щоб читати робочий простір агента за замовчуванням.
  - `scope` керує тим, на яку поверхню націлюється основне `name`:
    - `text` повертає основний текстовий токен команди без початкового `/`
    - `native` і стандартний шлях `both` повертають нативні імена з урахуванням провайдера, коли вони доступні
  - `textAliases` містить точні slash-аліаси, як-от `/model` і `/m`.
  - `nativeName` містить нативну назву команди з урахуванням провайдера, коли вона існує.
  - `provider` необов’язковий і впливає лише на нативне іменування та доступність нативних команд Plugin.
  - `includeArgs=false` вилучає серіалізовані метадані аргументів із відповіді.
- Оператори можуть викликати `tools.catalog` (`operator.read`), щоб отримати каталог інструментів середовища виконання для агента. Відповідь містить згруповані інструменти та метадані походження:
  - `source`: `core` або `plugin`
  - `pluginId`: власник Plugin, коли `source="plugin"`
  - `optional`: чи є інструмент Plugin необов’язковим
- Оператори можуть викликати `tools.effective` (`operator.read`), щоб отримати фактичний інвентар інструментів середовища виконання для сеансу.
  - `sessionKey` обов’язковий.
  - Gateway виводить довірений контекст середовища виконання з сеансу на боці сервера замість приймати auth або контекст доставки, наданий викликачем.
  - Відповідь є серверною проєкцією активного інвентарю в межах сеансу, зокрема core, Plugin, channel і вже виявлені інструменти MCP-сервера.
  - `tools.effective` доступний лише для читання для MCP: він може пропустити прогрітий каталог MCP сеансу через фінальну політику інструментів, але не створює середовища виконання MCP, не підключає транспорти й не викликає `tools/list`. Якщо відповідного прогрітого каталогу немає, відповідь може містити повідомлення на кшталт `mcp-not-yet-connected`, `mcp-not-yet-listed` або `mcp-stale-catalog`.
  - Елементи фактичних інструментів використовують `source="core"`, `source="plugin"`, `source="channel"` або `source="mcp"`.
- Оператори можуть викликати `tools.invoke` (`operator.write`), щоб викликати один доступний інструмент через той самий шлях політики Gateway, що й `/tools/invoke`.
  - `name` обов’язковий. `args`, `sessionKey`, `agentId`, `confirm` і `idempotencyKey` необов’язкові.
  - Якщо наявні і `sessionKey`, і `agentId`, розв’язаний агент сеансу має збігатися з `agentId`.
  - Core-обгортки лише для власників, як-от `cron`, `gateway` і `nodes`, потребують ідентичності власника/адміністратора (`operator.admin`), хоча сам метод `tools.invoke` є `operator.write`.
  - Відповідь є конвертом для SDK із полями `ok`, `toolName`, необов’язковим `output` і типізованими полями `error`. Відмови через схвалення або політику повертають `ok:false` у payload замість обходу конвеєра політики інструментів Gateway.
- Оператори можуть викликати `skills.status` (`operator.read`), щоб отримати видимий інвентар навичок для агента.
  - `agentId` необов’язковий; не вказуйте його, щоб читати робочий простір агента за замовчуванням.
  - Відповідь містить придатність, відсутні вимоги, перевірки конфігурації та санітизовані параметри встановлення без розкриття необроблених секретних значень.
- Оператори можуть викликати `skills.search` і `skills.detail` (`operator.read`) для метаданих пошуку ClawHub.
- Оператори можуть викликати `skills.upload.begin`, `skills.upload.chunk` і `skills.upload.commit` (`operator.admin`), щоб підготувати приватний архів навички перед установленням. Це окремий шлях адміністративного завантаження для довірених клієнтів, а не звичайний потік установлення навичок ClawHub, і він вимкнений за замовчуванням, якщо не ввімкнено `skills.install.allowUploadedArchives`.
  - `skills.upload.begin({ kind: "skill-archive", slug, sizeBytes, sha256?, force?, idempotencyKey? })`
    створює завантаження, прив’язане до цього slug і значення force.
  - `skills.upload.chunk({ uploadId, offset, dataBase64 })` додає байти за точним декодованим offset.
  - `skills.upload.commit({ uploadId, sha256? })` перевіряє фінальний розмір і SHA-256. Commit лише завершує завантаження; він не встановлює навичку.
  - Завантажені архіви навичок є zip-архівами, що містять кореневий `SKILL.md`. Внутрішня назва каталогу архіву ніколи не вибирає ціль установлення.
- Оператори можуть викликати `skills.install` (`operator.admin`) у трьох режимах:
  - Режим ClawHub: `{ source: "clawhub", slug, version?, force? }` встановлює папку навички в каталог `skills/` робочого простору агента за замовчуванням.
  - Режим завантаження: `{ source: "upload", uploadId, slug, force?, sha256?, timeoutMs? }`
    встановлює підтверджене завантаження в каталог `skills/<slug>` робочого простору агента за замовчуванням. Значення slug і force мають збігатися з початковим запитом `skills.upload.begin`. Цей режим відхиляється, якщо не ввімкнено `skills.install.allowUploadedArchives`. Налаштування не впливає на встановлення ClawHub.
  - Режим інсталятора Gateway: `{ name, installId, timeoutMs? }`
    запускає оголошену дію `metadata.openclaw.install` на хості Gateway. Старіші клієнти все ще можуть надсилати `dangerouslyForceUnsafeInstall`; це поле застаріле, приймається лише для сумісності протоколу й ігнорується. Використовуйте `security.installPolicy` для рішень щодо встановлення, якими володіє оператор.
- Оператори можуть викликати `skills.update` (`operator.admin`) у двох режимах:
  - Режим ClawHub оновлює один відстежуваний slug або всі відстежувані встановлення ClawHub у робочому просторі агента за замовчуванням.
  - Режим конфігурації патчить значення `skills.entries.<skillKey>`, як-от `enabled`, `apiKey` і `env`.

### Подання `models.list`

`models.list` приймає необов’язковий параметр `view`:

- Не вказано або `"default"`: поточна поведінка середовища виконання. Якщо налаштовано `agents.defaults.models`, відповіддю є дозволений каталог, зокрема динамічно виявлені моделі для записів `provider/*`. Інакше відповіддю є повний каталог Gateway.
- `"configured"`: поведінка розміру picker. Якщо налаштовано `agents.defaults.models`, він усе одно має пріоритет, зокрема виявлення в межах провайдера для записів `provider/*`. Без allowlist відповідь використовує явні записи `models.providers.*.models`, повертаючись до повного каталогу лише тоді, коли немає налаштованих рядків моделей.
- `"all"`: повний каталог Gateway з обходом `agents.defaults.models`. Використовуйте це для діагностики та UI виявлення, а не для звичайних picker моделей.

## Схвалення exec

- Коли запит exec потребує схвалення, Gateway транслює `exec.approval.requested`.
- Клієнти операторів розв’язують це викликом `exec.approval.resolve` (потребує scope `operator.approvals`).
- Для `host=node` `exec.approval.request` має містити `systemRunPlan` (канонічні `argv`/`cwd`/`rawCommand`/метадані сеансу). Запити без `systemRunPlan` відхиляються.
- Після схвалення переспрямовані виклики `node.invoke system.run` повторно використовують цей канонічний `systemRunPlan` як авторитетний контекст command/cwd/session.
- Якщо викликач змінює `command`, `rawCommand`, `cwd`, `agentId` або `sessionKey` між підготовкою та фінальним схваленим переспрямуванням `system.run`, Gateway відхиляє запуск замість довіряти зміненому payload.

## Резервний варіант доставки агентом

- Запити `agent` можуть містити `deliver=true`, щоб запитати вихідну доставку.
- `bestEffortDeliver=false` зберігає сувору поведінку: нерозв’язані або лише внутрішні цілі доставки повертають `INVALID_REQUEST`.
- `bestEffortDeliver=true` дозволяє резервний перехід до виконання лише в межах сеансу, коли неможливо розв’язати зовнішній маршрут доставки (наприклад, внутрішні/webchat-сеанси або неоднозначні багатоканальні конфігурації).
- Фінальні результати `agent` можуть містити `result.deliveryStatus`, коли було запитано доставку, використовуючи ті самі статуси `sent`, `suppressed`, `partial_failed` і `failed`, які задокументовані для [`openclaw agent --json --deliver`](/uk/cli/agent#json-delivery-status).

## Версіонування

- `PROTOCOL_VERSION` міститься в `packages/gateway-protocol/src/version.ts`.
- Клієнти надсилають `minProtocol` + `maxProtocol`; сервер відхиляє діапазони, які не містять його поточний протокол. Поточні клієнти й сервери потребують протокол v4.
- Схеми + моделі генеруються з визначень TypeBox:
  - `pnpm protocol:gen`
  - `pnpm protocol:gen:swift`
  - `pnpm protocol:check`

### Константи клієнта

Еталонний клієнт у `src/gateway/client.ts` використовує ці значення за замовчуванням. Значення стабільні в межах protocol v4 і є очікуваною базою для сторонніх клієнтів.

| Константа                                 | Значення за замовчуванням                            | Джерело                                                                                   |
| ----------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------------------------------ |
| `PROTOCOL_VERSION`                        | `4`                                                   | `packages/gateway-protocol/src/version.ts`                                                 |
| `MIN_CLIENT_PROTOCOL_VERSION`             | `4`                                                   | `packages/gateway-protocol/src/version.ts`                                                 |
| Таймаут запиту (на RPC)                   | `30_000` ms                                           | `src/gateway/client.ts` (`requestTimeoutMs`)                                               |
| Таймаут preauth / connect-challenge       | `15_000` ms                                           | `src/gateway/handshake-timeouts.ts` (config/env може збільшити спарений бюджет server/client) |
| Початковий backoff перепідключення        | `1_000` ms                                            | `src/gateway/client.ts` (`backoffMs`)                                                      |
| Максимальний backoff перепідключення      | `30_000` ms                                           | `src/gateway/client.ts` (`scheduleReconnect`)                                              |
| Fast-retry clamp після закриття device-token | `250` ms                                           | `src/gateway/client.ts`                                                                    |
| Пільговий час force-stop перед `terminate()` | `250` ms                                           | `FORCE_STOP_TERMINATE_GRACE_MS`                                                            |
| Таймаут за замовчуванням `stopAndWait()`  | `1_000` ms                                            | `STOP_AND_WAIT_TIMEOUT_MS`                                                                 |
| Інтервал tick за замовчуванням (до `hello-ok`) | `30_000` ms                                      | `src/gateway/client.ts`                                                                    |
| Закриття через таймаут tick               | code `4000`, коли тиша перевищує `tickIntervalMs * 2` | `src/gateway/client.ts`                                                                    |
| `MAX_PAYLOAD_BYTES`                       | `25 * 1024 * 1024` (25 MB)                            | `src/gateway/server-constants.ts`                                                          |

Сервер оголошує фактичні `policy.tickIntervalMs`, `policy.maxPayload` і `policy.maxBufferedBytes` у `hello-ok`; клієнти мають дотримуватися цих значень, а не стандартних значень до handshake.

## Auth

- Автентифікація Gateway за спільним секретом використовує `connect.params.auth.token` або
  `connect.params.auth.password`, залежно від налаштованого режиму автентифікації.
- Режими з ідентичністю, як-от Tailscale Serve
  (`gateway.auth.allowTailscale: true`) або не-loopback
  `gateway.auth.mode: "trusted-proxy"`, задовольняють перевірку автентифікації підключення через
  заголовки запиту замість `connect.params.auth.*`.
- Private-ingress `gateway.auth.mode: "none"` повністю пропускає автентифікацію підключення
  за спільним секретом; не відкривайте цей режим на публічному/ненадійному ingress.
- Після спарювання Gateway видає **токен пристрою**, обмежений роллю підключення
  + scopes. Він повертається в `hello-ok.auth.deviceToken`, і клієнт має
  зберігати його для майбутніх підключень.
- Клієнти мають зберігати основний `hello-ok.auth.deviceToken` після будь-якого
  успішного підключення.
- Повторне підключення з цим **збереженим** токеном пристрою також має повторно використовувати збережений
  затверджений набір scopes для цього токена. Це зберігає доступ для читання/проби/статусу,
  який уже було надано, і не дає повторним підключенням непомітно звузитися до
  неявного scope лише для адміністратора.
- Складання автентифікації підключення на боці клієнта (`selectConnectAuth` у
  `src/gateway/client.ts`):
  - `auth.password` є ортогональним і завжди передається, коли заданий.
  - `auth.token` заповнюється в порядку пріоритету: спочатку явний спільний токен,
    потім явний `deviceToken`, потім збережений токен для пристрою (ключований за
    `deviceId` + `role`).
  - `auth.bootstrapToken` надсилається лише тоді, коли жоден із наведених вище варіантів не визначив
    `auth.token`. Спільний токен або будь-який визначений токен пристрою пригнічує його.
  - Автоматичне підвищення збереженого токена пристрою під час одноразової
    повторної спроби `AUTH_TOKEN_MISMATCH` дозволене **лише для довірених endpoints** —
    loopback або `wss://` із закріпленим `tlsFingerprint`. Публічний `wss://`
    без pinning не підходить.
- Вбудований bootstrap через код налаштування повертає основний токен вузла
  `hello-ok.auth.deviceToken` плюс обмежений токен оператора в
  `hello-ok.auth.deviceTokens` для довіреної передачі на мобільний пристрій. Токен оператора
  містить `operator.talk.secrets` для читання нативної конфігурації Talk, але
  не містить scopes мутації спарювання та `operator.admin`.
- Поки non-baseline bootstrap через код налаштування очікує схвалення, деталі `PAIRING_REQUIRED`
  містять `recommendedNextStep: "wait_then_retry"`, `retryable: true`
  і `pauseReconnect: false`. Клієнти мають продовжувати повторне підключення з тим самим
  bootstrap-токеном, доки запит не буде схвалено або токен не стане недійсним.
- Зберігайте `hello-ok.auth.deviceTokens` лише тоді, коли підключення використовувало bootstrap-автентифікацію
  на довіреному транспорті, як-от `wss://` або loopback/local pairing.
- Якщо клієнт надає **явний** `deviceToken` або явні `scopes`, цей
  запитаний викликачем набір scopes залишається авторитетним; кешовані scopes
  повторно використовуються лише тоді, коли клієнт повторно використовує збережений токен для пристрою.
- Токени пристроїв можна ротувати/відкликати через `device.token.rotate` і
  `device.token.revoke` (потрібен scope `operator.pairing`). Ротація або
  відкликання вузла чи іншої неоператорської ролі також потребує `operator.admin`.
- `device.token.rotate` повертає метадані ротації. Він повторює замінний
  bearer-токен лише для викликів із того самого пристрою, які вже автентифіковані цим
  токеном пристрою, щоб token-only клієнти могли зберегти заміну перед
  повторним підключенням. Ротації зі спільним/адміністративним доступом не повторюють bearer-токен.
- Видача, ротація та відкликання токенів залишаються обмеженими затвердженим набором ролей,
  записаним у записі спарювання цього пристрою; мутація токена не може розширити або
  націлитися на роль пристрою, яку схвалення спарювання ніколи не надавало.
- Для token-сесій спарених пристроїв керування пристроями є self-scoped, якщо
  викликач також не має `operator.admin`: викликачі без прав адміністратора можуть керувати лише
  токеном оператора для запису **власного** пристрою. Керування токенами вузла
  та інших неоператорських ролей доступне лише адміністратору, навіть для власного пристрою викликача.
- `device.token.rotate` і `device.token.revoke` також перевіряють цільовий набір scopes
  токена оператора щодо поточних scopes сесії викликача. Викликачі без прав адміністратора
  не можуть ротувати або відкликати ширший токен оператора, ніж уже мають.
- Помилки автентифікації містять `error.details.code` плюс підказки для відновлення:
  - `error.details.canRetryWithDeviceToken` (boolean)
  - `error.details.recommendedNextStep` (`retry_with_device_token`, `update_auth_configuration`, `update_auth_credentials`, `wait_then_retry`, `review_auth_configuration`)
- Поведінка клієнта для `AUTH_TOKEN_MISMATCH`:
  - Довірені клієнти можуть виконати одну обмежену повторну спробу з кешованим токеном для пристрою.
  - Якщо ця повторна спроба не вдається, клієнти мають зупинити автоматичні цикли повторного підключення й показати оператору вказівки щодо дій.
- `AUTH_SCOPE_MISMATCH` означає, що токен пристрою розпізнано, але він не покриває
  запитану роль/scopes. Клієнти не мають показувати це як недійсний токен;
  запропонуйте оператору повторно спарити або схвалити вужчий/ширший контракт scopes.

## Ідентичність пристрою + спарювання

- Вузли мають містити стабільну ідентичність пристрою (`device.id`), отриману з
  відбитка keypair.
- Gateways видають токени для кожного пристрою + ролі.
- Схвалення спарювання потрібні для нових ID пристроїв, якщо не ввімкнене локальне автоматичне схвалення.
- Автоматичне схвалення спарювання зосереджене на прямих підключеннях local loopback.
- OpenClaw також має вузький backend/container-local шлях самопідключення для
  довірених helper-потоків зі спільним секретом.
- Підключення same-host tailnet або LAN усе одно вважаються віддаленими для спарювання та
  потребують схвалення.
- WS-клієнти зазвичай містять ідентичність `device` під час `connect` (оператор +
  вузол). Єдині винятки для оператора без пристрою — явні довірені шляхи:
  - `gateway.controlUi.allowInsecureAuth=true` для сумісності з небезпечним HTTP лише на localhost.
  - успішна автентифікація оператора Control UI через `gateway.auth.mode: "trusted-proxy"`.
  - `gateway.controlUi.dangerouslyDisableDeviceAuth=true` (break-glass, серйозне зниження безпеки).
  - direct-loopback backend RPCs `gateway-client` на зарезервованому внутрішньому
    helper-шляху.
- Пропуск ідентичності пристрою має наслідки для scopes. Коли підключення оператора
  без пристрою дозволене через явний довірений шлях, OpenClaw усе одно очищає
  самостійно оголошені scopes до порожнього набору, якщо цей шлях не має названого
  винятку збереження scopes. Методи, обмежені scopes, тоді завершуються помилкою
  `missing scope`.
- `gateway.controlUi.dangerouslyDisableDeviceAuth=true` є шляхом збереження scopes break-glass
  для Control UI. Він не надає scopes довільним
  користувацьким backend або CLI-подібним WebSocket-клієнтам.
- Зарезервований direct-loopback backend helper-шлях `gateway-client` зберігає
  scopes лише для внутрішніх локальних control-plane RPCs; користувацькі backend IDs не
  отримують цього винятку.
- Усі підключення мають підписувати наданий сервером nonce `connect.challenge`.

### Діагностика міграції автентифікації пристрою

Для legacy клієнтів, які досі використовують поведінку підпису до challenge, `connect` тепер повертає
коди деталей `DEVICE_AUTH_*` у `error.details.code` зі стабільним `error.details.reason`.

Поширені помилки міграції:

| Повідомлення                | details.code                     | details.reason           | Значення                                           |
| --------------------------- | -------------------------------- | ------------------------ | -------------------------------------------------- |
| `device nonce required`     | `DEVICE_AUTH_NONCE_REQUIRED`     | `device-nonce-missing`   | Клієнт пропустив `device.nonce` (або надіслав порожнє значення). |
| `device nonce mismatch`     | `DEVICE_AUTH_NONCE_MISMATCH`     | `device-nonce-mismatch`  | Клієнт підписав зі застарілим/неправильним nonce.  |
| `device signature invalid`  | `DEVICE_AUTH_SIGNATURE_INVALID`  | `device-signature`       | Payload підпису не відповідає v2 payload.          |
| `device signature expired`  | `DEVICE_AUTH_SIGNATURE_EXPIRED`  | `device-signature-stale` | Підписана мітка часу виходить за межі дозволеного skew. |
| `device identity mismatch`  | `DEVICE_AUTH_DEVICE_ID_MISMATCH` | `device-id-mismatch`     | `device.id` не відповідає відбитку публічного ключа. |
| `device public key invalid` | `DEVICE_AUTH_PUBLIC_KEY_INVALID` | `device-public-key`      | Помилка формату/канонікалізації публічного ключа.  |

Ціль міграції:

- Завжди чекайте на `connect.challenge`.
- Підписуйте v2 payload, який містить серверний nonce.
- Надсилайте той самий nonce у `connect.params.device.nonce`.
- Рекомендований payload підпису — `v3`, який прив’язує `platform` і `deviceFamily`
  на додаток до полів device/client/role/scopes/token/nonce.
- Legacy підписи `v2` залишаються прийнятими для сумісності, але pinning метаданих
  спареного пристрою все одно керує політикою команд під час повторного підключення.

## TLS + pinning

- TLS підтримується для WS-підключень.
- Клієнти можуть необов’язково закріпити відбиток сертифіката Gateway (див. конфігурацію `gateway.tls`
  плюс `gateway.remote.tlsFingerprint` або CLI `--tls-fingerprint`).

## Scope

Цей протокол відкриває **повний API gateway** (статус, канали, моделі, чат,
агент, сесії, вузли, схвалення тощо). Точну поверхню визначають
схеми TypeBox у `packages/gateway-protocol/src/schema.ts`.

## Пов’язане

- [Протокол bridge](/uk/gateway/bridge-protocol)
- [Runbook Gateway](/uk/gateway)
