> ## Documentation Index
> Fetch the complete documentation index at: https://docs2.openclaw.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Рефакторинг жизненного цикла сообщений

Эта страница описывает целевой дизайн для замены разрозненных помощников входящих сообщений каналов, отправки ответов, потоковой передачи предпросмотра и исходящей доставки одним устойчивым жизненным циклом сообщений.

Кратко:

* Базовыми примитивами ядра должны быть **receive** и **send**, а не **reply**.
* Ответ — это только связь исходящего сообщения.
* Ход — это удобство для обработки входящих сообщений, а не владелец доставки.
* Отправка должна быть основана на контексте: `begin`, рендеринг, предпросмотр или потоковая передача, финальная отправка, фиксация, сбой.
* Прием тоже должен быть основан на контексте: нормализация, дедупликация, маршрутизация, запись, диспетчеризация, подтверждение платформы, сбой.
* Публичный SDK Plugin должен свестись к одной небольшой поверхности исходящих сообщений канала.

## Проблемы

Текущий стек каналов вырос из нескольких обоснованных локальных потребностей:

* Простые входящие адаптеры используют `runtime.channel.inbound.run`.
* Богатые адаптеры используют `runtime.channel.inbound.runPreparedReply`.
* Устаревшие помощники используют `dispatchInboundReplyWithBase`,
  `recordInboundSessionAndDispatchReply`, помощники полезной нагрузки ответа, разбиение ответа на фрагменты,
  ссылки на ответы и помощники исходящего runtime.
* Потоковая передача предпросмотра живет в диспетчерах, специфичных для каналов.
* Устойчивость финальной доставки добавляется вокруг существующих путей полезной нагрузки ответа.

Такая форма исправляет локальные ошибки, но оставляет OpenClaw слишком много публичных
понятий и слишком много мест, где семантика доставки может расходиться.

Проблема надежности, которая это выявила:

```text theme={"theme":{"light":"min-light","dark":"min-dark"}}
Telegram polling update acked
  -> assistant final text exists
  -> process restarts before sendMessage succeeds
  -> final response is lost
```

Целевой инвариант шире, чем Telegram: как только ядро решает, что видимое
исходящее сообщение должно существовать, намерение должно быть сохранено до попытки
отправки на платформу, а квитанция платформы должна быть зафиксирована после успеха.
Это дает OpenClaw восстановление с семантикой at-least-once. Поведение exactly-once существует только
для адаптеров, которые могут доказать нативную идемпотентность или согласовать попытку
с неизвестным результатом после отправки с состоянием платформы перед повторным воспроизведением.

Это конечное состояние для этого рефакторинга, а не описание каждого текущего
пути. Во время миграции существующие исходящие помощники все еще могут откатываться к
прямой отправке, когда best-effort запись в очередь завершается сбоем. Рефакторинг завершен только
тогда, когда устойчивые финальные отправки fail closed или явно отказываются от этого с документированной
неустойчивой политикой.

## Цели

* Один жизненный цикл ядра для всех путей приема и отправки сообщений каналов.
* Устойчивые финальные отправки по умолчанию в новом жизненном цикле сообщений после того, как адаптер
  объявляет replay-safe поведение.
* Общая семантика предпросмотра, редактирования, потоковой передачи, финализации, повторных попыток, восстановления и квитанций.
* Небольшая поверхность SDK Plugin, которую сторонние plugins смогут изучать и поддерживать.
* Совместимость для существующих вызывающих сторон совместимости входящих ответов во время миграции.
* Четкие точки расширения для новых возможностей каналов.
* Без платформенно-специфичных ветвлений в ядре.
* Без сообщений канала с дельтами токенов. Потоковая передача каналов остается предпросмотром сообщения,
  редактированием, добавлением или доставкой завершенного блока.
* Структурированные метаданные происхождения OpenClaw для операционного/системного вывода, чтобы видимые
  сбои Gateway не возвращались в общие комнаты с включенными ботами как новые prompts.

## Не цели

* Не переводить каждый существующий канал на устойчивую доставку сообщений в первой фазе.
* Не принуждать каждый канал к одинаковому нативному транспортному поведению.
* Не обучать ядро темам Telegram, нативным потокам Slack, редактированиям Matrix,
  карточкам Feishu, голосу QQ или активностям Teams.
* Не публиковать все внутренние помощники миграции как стабильный API SDK.
* Не делать так, чтобы повторные попытки заново воспроизводили завершенные неидемпотентные операции платформы.

## Эталонная модель

У Vercel Chat есть хорошая публичная ментальная модель:

* `Chat`
* `Thread`
* `Channel`
* `Message`
* методы адаптера, такие как `postMessage`, `editMessage`, `deleteMessage`,
  `stream`, `startTyping`, и получение истории
* адаптер состояния для дедупликации, блокировок, очередей и постоянного хранения

OpenClaw должен заимствовать словарь, а не копировать поверхность.

Что нужно OpenClaw сверх этой модели:

* Устойчивые намерения исходящей отправки до прямых транспортных вызовов.
* Явные контексты отправки с началом, фиксацией и сбоем.
* Контексты приема, знающие политику подтверждения платформы.
* Квитанции, которые переживают перезапуск и могут управлять редактированием, удалением, восстановлением и
  подавлением дубликатов.
* Более маленький публичный SDK. Встроенные plugins могут использовать внутренние помощники runtime, но
  сторонние plugins должны видеть один согласованный API сообщений.
* Специфичное для агента поведение: сессии, расшифровки, потоковая передача блоков, прогресс инструментов,
  approvals, медиа-директивы, тихие ответы и история упоминаний в группах.

Промисов в стиле `thread.post()` недостаточно для OpenClaw. Они скрывают
границу транзакции, которая решает, можно ли восстановить отправку.

## Модель ядра

Новый домен должен жить во внутреннем пространстве имен ядра, например
`src/channels/message/*`.

У него четыре понятия:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
core.messages.receive(...)
core.messages.send(...)
core.messages.live(...)
core.messages.state(...)
```

`receive` владеет жизненным циклом входящих сообщений.

`send` владеет жизненным циклом исходящих сообщений.

`live` владеет состоянием предпросмотра, редактирования, прогресса и потока.

`state` владеет устойчивым хранением намерений, квитанциями, идемпотентностью, восстановлением, блокировками и
дедупликацией.

## Термины сообщений

### Сообщение

Нормализованное сообщение нейтрально к платформе:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type ChannelMessage = {
  id: string;
  channel: string;
  accountId?: string;
  direction: "inbound" | "outbound";
  target: MessageTarget;
  sender?: MessageActor;
  body?: MessageBody;
  attachments?: MessageAttachment[];
  relation?: MessageRelation;
  origin?: MessageOrigin;
  timestamp?: number;
  raw?: unknown;
};
```

### Цель

Цель описывает, где живет сообщение:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageTarget = {
  kind: "direct" | "group" | "channel" | "thread";
  id: string;
  label?: string;
  spaceId?: string;
  parentId?: string;
  threadId?: string;
  nativeChannelId?: string;
};
```

### Связь

Ответ — это связь, а не корень API:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageRelation =
  | {
      kind: "reply";
      inboundMessageId?: string;
      replyToId?: string;
      threadId?: string;
      quote?: MessageQuote;
    }
  | {
      kind: "followup";
      sessionKey?: string;
      previousMessageId?: string;
    }
  | {
      kind: "broadcast";
      reason?: string;
    }
  | {
      kind: "system";
      reason:
        | "approval"
        | "task"
        | "hook"
        | "cron"
        | "subagent"
        | "message_tool"
        | "cli"
        | "control_ui"
        | "automation"
        | "error";
    };
```

Это позволяет одному и тому же пути отправки обрабатывать обычные ответы, уведомления Cron, prompts approval,
завершения задач, отправки message-tool, отправки из CLI или Control UI, результаты subagent
и автоматические отправки.

### Происхождение

Происхождение описывает, кто создал сообщение и как OpenClaw должен обрабатывать эхо
этого сообщения. Оно отделено от связи: сообщение может быть ответом пользователю
и при этом быть операционным выводом, созданным OpenClaw.

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageOrigin =
  | {
      source: "openclaw";
      schemaVersion: 1;
      kind: "gateway_failure";
      code: "agent_failed_before_reply" | "missing_api_key" | "model_login_expired";
      echoPolicy: "drop_bot_room_echo";
    }
  | {
      source: "user" | "external_bot" | "platform" | "unknown";
    };
```

Ядро владеет смыслом вывода, созданного OpenClaw. Каналы владеют тем, как это
происхождение кодируется в их транспорт.

Первое обязательное использование — вывод сбоев Gateway. Люди все еще должны видеть
сообщения вроде "Agent failed before reply" или "Missing API key", но помеченный
операционный вывод OpenClaw не должен приниматься как ввод, созданный ботом, в общих
комнатах, когда включен `allowBots`.

### Квитанция

Квитанции являются первоклассными:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageReceipt = {
  primaryPlatformMessageId?: string;
  platformMessageIds: string[];
  parts: MessageReceiptPart[];
  threadId?: string;
  replyToId?: string;
  editToken?: string;
  deleteToken?: string;
  url?: string;
  sentAt: number;
  raw?: unknown;
};

type MessageReceiptPart = {
  platformMessageId: string;
  kind: "text" | "media" | "voice" | "card" | "preview" | "unknown";
  index: number;
  threadId?: string;
  replyToId?: string;
  editToken?: string;
  deleteToken?: string;
  url?: string;
  raw?: unknown;
};
```

Квитанции — это мост от устойчивого намерения к будущему редактированию, удалению, финализации
предпросмотра, подавлению дубликатов и восстановлению.

Квитанция может описывать одно сообщение платформы или доставку из нескольких частей. Разбитый на фрагменты
текст, медиа плюс текст, голос плюс текст и fallback карточек должны сохранять все
идентификаторы платформы, все еще предоставляя primary id для трединга и последующих редактирований.

## Контекст приема

Прием не должен быть простым вызовом помощника. Ядру нужен контекст, который знает
дедупликацию, маршрутизацию, запись сессии и политику подтверждения платформы.

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageReceiveContext = {
  id: string;
  channel: string;
  accountId?: string;
  input: ChannelMessage;
  ack: ReceiveAckController;
  route: MessageRouteController;
  session: MessageSessionController;
  log: MessageLifecycleLogger;

  dedupe(): Promise<ReceiveDedupeResult>;
  resolve(): Promise<ResolvedInboundMessage>;
  record(resolved: ResolvedInboundMessage): Promise<RecordResult>;
  dispatch(recorded: RecordResult): Promise<DispatchResult>;
  commit(result: DispatchResult): Promise<void>;
  fail(error: unknown): Promise<void>;
};
```

Поток приема:

```text theme={"theme":{"light":"min-light","dark":"min-dark"}}
platform event
  -> begin receive context
  -> normalize
  -> classify
  -> dedupe and self-echo gate
  -> route and authorize
  -> record inbound session metadata
  -> dispatch agent run
  -> durable outbound sends happen through send context
  -> commit receive
  -> ack platform when policy allows
```

Ack — это не одна вещь. Контракт приема должен держать эти сигналы раздельно:

* **Transport ack:** сообщает webhook или сокету платформы, что OpenClaw принял
  конверт события. Некоторым платформам это требуется до диспетчеризации.
* **Polling offset ack:** продвигает cursor, чтобы то же событие не было получено
  снова. Это не должно продвигаться дальше работы, которую нельзя восстановить.
* **Inbound record ack:** подтверждает, что OpenClaw сохранил достаточно входящих метаданных для
  дедупликации и маршрутизации повторной доставки.
* **User-visible receipt:** необязательное поведение чтения/статуса/typing; никогда не
  граница устойчивости.

`ReceiveAckPolicy` управляет только транспортным подтверждением или подтверждением polling. Он не должен
переиспользоваться для квитанций о прочтении или статусных реакций.

Перед авторизацией бота прием должен применять общую политику эха OpenClaw,
когда канал может декодировать метаданные происхождения сообщения:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
function shouldDropOpenClawEcho(params: {
  origin?: MessageOrigin;
  isBotAuthor: boolean;
  isRoomish: boolean;
}): boolean {
  return (
    params.isBotAuthor &&
    params.isRoomish &&
    params.origin?.source === "openclaw" &&
    params.origin.kind === "gateway_failure" &&
    params.origin.echoPolicy === "drop_bot_room_echo"
  );
}
```

Это отбрасывание основано на теге, а не на тексте. Сообщение в комнате, созданное ботом, с тем же
видимым текстом сбоя Gateway, но без метаданных происхождения OpenClaw, все равно
проходит обычную авторизацию `allowBots`.

Политика ack явная:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type ReceiveAckPolicy =
  | { kind: "immediate"; reason: "webhook-timeout" | "platform-contract" }
  | { kind: "after-record" }
  | { kind: "after-durable-send" }
  | { kind: "manual" };
```

Telegram polling теперь использует политику ack контекста приема для своего сохраняемого
watermark перезапуска. Tracker все еще наблюдает обновления grammY по мере их входа в
цепочку middleware, но OpenClaw сохраняет только безопасный завершенный update id после
успешной диспетчеризации, оставляя неудачные или более ранние ожидающие обновления доступными для повторного воспроизведения после
перезапуска. Вышестоящий `getUpdates` fetch offset Telegram по-прежнему контролируется
библиотекой polling, поэтому оставшаяся более глубокая доработка — полностью устойчивый источник polling,
если нам потребуется повторная доставка на уровне платформы за пределами restart
watermark OpenClaw. Webhook-платформам может требоваться немедленный HTTP ack, но им все равно нужны
входящая дедупликация и устойчивые намерения исходящей отправки, потому что webhooks могут доставляться повторно.

## Контекст отправки

Отправка также основана на контексте:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageSendContext = {
  id: string;
  channel: string;
  accountId?: string;
  message: ChannelMessage;
  intent: DurableSendIntent;
  attempt: number;
  signal: AbortSignal;
  previousReceipt?: MessageReceipt;
  preview?: LiveMessageState;
  log: MessageLifecycleLogger;

  render(): Promise<RenderedMessageBatch>;
  previewUpdate(rendered: RenderedMessageBatch): Promise<LiveMessageState>;
  send(rendered: RenderedMessageBatch): Promise<MessageReceipt>;
  edit(receipt: MessageReceipt, rendered: RenderedMessageBatch): Promise<MessageReceipt>;
  delete(receipt: MessageReceipt): Promise<void>;
  commit(receipt: MessageReceipt): Promise<void>;
  fail(error: unknown): Promise<void>;
};
```

Предпочтительная оркестрация:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
await core.messages.withSendContext(message, async (ctx) => {
  const rendered = await ctx.render();

  if (ctx.preview?.canFinalizeInPlace) {
    return await ctx.edit(ctx.preview.receipt, rendered);
  }

  return await ctx.send(rendered);
});
```

Вспомогательная функция разворачивается в:

```text theme={"theme":{"light":"min-light","dark":"min-dark"}}
begin durable intent
  -> render
  -> optional preview/edit/stream work
  -> mark sending
  -> final platform send or final edit
  -> mark committing with raw receipt
  -> commit receipt
  -> ack durable intent
  -> fail durable intent on classified failure
```

Намерение должно существовать до ввода-вывода транспорта. Перезапуск после начала, но до
фиксации, восстанавливаем.

Опасная граница находится после успешной отправки на платформе и до фиксации квитанции. Если
процесс завершается там, OpenClaw не может знать, существует ли сообщение на платформе,
если адаптер не предоставляет нативную идемпотентность или путь сверки квитанций.
Такие попытки должны возобновляться в `unknown_after_send`, а не слепо повторяться. Каналы
без сверки могут выбрать повтор по принципу «как минимум один раз» только если дублирующиеся видимые
сообщения являются приемлемым, задокументированным компромиссом для этого канала и связи.
Текущий мост сверки SDK требует, чтобы адаптер объявил
`reconcileUnknownSend`, затем просит `durableFinal.reconcileUnknownSend`
классифицировать неизвестную запись как `sent`, `not_sent` или `unresolved`; только `not_sent`
разрешает повтор, а неразрешенные записи остаются терминальными или повторяют только
проверку сверки.

Политика долговечности должна быть явной:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageDurabilityPolicy = "required" | "best_effort" | "disabled";
```

`required` означает, что ядро должно завершаться отказом, когда не может записать долговечное намерение.
`best_effort` может продолжить выполнение, когда постоянное хранение недоступно. `disabled` сохраняет
старое поведение прямой отправки. Во время миграции устаревшие обертки и публичные
вспомогательные функции совместимости по умолчанию используют `disabled`; они не должны выводить `required` из
того факта, что у канала есть общий исходящий адаптер.

Контексты отправки также владеют локальными для канала эффектами после отправки. Миграция небезопасна,
если долговечная доставка обходит локальное поведение, которое ранее было привязано к
пути прямой отправки канала. Примеры включают кэши подавления self-echo,
маркеры участия в треде, нативные якоря редактирования, рендеринг подписи модели
и специфичные для платформы защиты от дубликатов. Эти эффекты должны либо перейти в
адаптер отправки, адаптер рендеринга, либо именованный хук контекста отправки до того,
как этот канал сможет включить долговечную общую финальную доставку.

Вспомогательные функции отправки должны возвращать квитанции до самого вызывающего кода. Долговечные
обертки не могут проглатывать идентификаторы сообщений или заменять результат доставки канала на
`undefined`; буферизованные диспетчеры используют эти идентификаторы для якорей тредов, последующих правок,
финализации предпросмотра и подавления дубликатов.

Резервные отправки работают с пакетами, а не с одиночными полезными нагрузками. Переписывания silent-reply,
резервная отправка медиа, резервная отправка карточек и проекция фрагментов могут все создавать больше
одного доставляемого сообщения, поэтому контекст отправки должен либо доставить весь
спроецированный пакет, либо явно задокументировать, почему допустима только одна полезная нагрузка.

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type RenderedMessageBatch = {
  units: RenderedMessageUnit[];
  atomicity: "all_or_retry_remaining" | "best_effort_parts";
  idempotencyKey: string;
};

type RenderedMessageUnit = {
  index: number;
  kind: "text" | "media" | "voice" | "card" | "preview" | "unknown";
  payload: unknown;
  required: boolean;
};
```

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

## Живой контекст

Поведение предпросмотра, редактирования, прогресса и потока должно быть единым жизненным циклом с явным включением.

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageLiveAdapter = {
  begin?(ctx: MessageSendContext): Promise<LiveMessageState>;
  update?(
    ctx: MessageSendContext,
    state: LiveMessageState,
    update: LiveMessageUpdate,
  ): Promise<LiveMessageState>;
  finalize?(
    ctx: MessageSendContext,
    state: LiveMessageState,
    final: RenderedMessageBatch,
  ): Promise<MessageReceipt>;
  cancel?(
    ctx: MessageSendContext,
    state: LiveMessageState,
    reason: LiveCancelReason,
  ): Promise<void>;
};
```

Живое состояние достаточно долговечно для восстановления или подавления дубликатов:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type LiveMessageState = {
  mode: "partial" | "block" | "progress" | "native";
  receipt?: MessageReceipt;
  visibleSince?: number;
  canFinalizeInPlace: boolean;
  lastRenderedHash?: string;
  staleAfterMs?: number;
};
```

Это должно покрывать текущее поведение:

* Telegram отправляет и редактирует предпросмотр, со свежим финальным сообщением после устаревания предпросмотра.
* Discord отправляет и редактирует предпросмотр, отменяет при медиа/ошибке/явном ответе.
* Slack использует нативный поток или черновой предпросмотр в зависимости от формы треда.
* Финализация черновой публикации Mattermost.
* Финализация чернового события Matrix или редактирование с удалением при несоответствии.
* Нативный поток прогресса Microsoft Teams.
* Поток QQ Bot или накопленный резервный результат.

## Поверхность адаптера

Цель публичного SDK должна быть одним подпутем:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
import { defineChannelMessageAdapter } from "openclaw/plugin-sdk/channel-outbound";
```

Целевая форма:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type ChannelMessageAdapter = {
  receive?: MessageReceiveAdapter;
  send: MessageSendAdapter;
  live?: MessageLiveAdapter;
  origin?: MessageOriginAdapter;
  render?: MessageRenderAdapter;
  capabilities: MessageCapabilities;
};
```

Адаптер отправки:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageSendAdapter = {
  send(ctx: MessageSendContext, rendered: RenderedMessageBatch): Promise<MessageReceipt>;
  edit?(
    ctx: MessageSendContext,
    receipt: MessageReceipt,
    rendered: RenderedMessageBatch,
  ): Promise<MessageReceipt>;
  delete?(ctx: MessageSendContext, receipt: MessageReceipt): Promise<void>;
  classifyError?(ctx: MessageSendContext, error: unknown): DeliveryFailureKind;
  reconcileUnknownSend?(ctx: MessageSendContext): Promise<MessageReceipt | null>;
  afterSendSuccess?(ctx: MessageSendContext, receipt: MessageReceipt): Promise<void>;
  afterCommit?(ctx: MessageSendContext, receipt: MessageReceipt): Promise<void>;
};
```

Адаптер приема:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageReceiveAdapter<TRaw = unknown> = {
  normalize(raw: TRaw, ctx: MessageNormalizeContext): Promise<ChannelMessage>;
  classify?(message: ChannelMessage): Promise<MessageEventClass>;
  preflight?(message: ChannelMessage, event: MessageEventClass): Promise<MessagePreflightResult>;
  ackPolicy?(message: ChannelMessage, event: MessageEventClass): ReceiveAckPolicy;
};
```

До preflight-авторизации ядро должно запускать общий предикат OpenClaw для echo
всякий раз, когда `origin.decode` возвращает метаданные происхождения OpenClaw. Адаптер приема
предоставляет факты платформы, такие как автор-бот и форма комнаты; ядро владеет решением
об отбрасывании и порядком, чтобы каналы не реализовывали текстовые фильтры заново.

Адаптер происхождения:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageOriginAdapter<TRaw = unknown, TNative = unknown> = {
  encode?(origin: MessageOrigin): TNative | undefined;
  decode?(raw: TRaw): MessageOrigin | undefined;
};
```

Ядро устанавливает `MessageOrigin`. Каналы только преобразуют его в нативные
метаданные транспорта и обратно. Slack сопоставляет это с `chat.postMessage({ metadata })` и
входящим `message.metadata`; Matrix может сопоставлять это с дополнительным содержимым события; каналы
без нативных метаданных могут использовать реестр квитанций/исходящих сообщений, когда это
лучшее доступное приближение.

Возможности:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type MessageCapabilities = {
  text: { maxLength?: number; chunking?: boolean };
  attachments?: {
    upload: boolean;
    remoteUrl: boolean;
    voice?: boolean;
  };
  threads?: {
    reply: boolean;
    topic?: boolean;
    nativeThread?: boolean;
  };
  live?: {
    edit: boolean;
    delete: boolean;
    nativeStream?: boolean;
    progress?: boolean;
  };
  delivery?: {
    idempotencyKey?: boolean;
    retryAfter?: boolean;
    receiptRequired?: boolean;
  };
};
```

## Сокращение публичного SDK

Новая публичная поверхность должна поглотить или объявить устаревшими эти концептуальные области:

* `reply-runtime`
* `reply-dispatch-runtime`
* `reply-reference`
* `reply-chunking`
* `reply-payload`
* `inbound-reply-dispatch`
* `channel-reply-pipeline`
* большинство публичных использований `outbound-runtime`
* специальные вспомогательные функции жизненного цикла чернового потока

Подпути совместимости могут оставаться обертками, но новым сторонним plugins
они не должны быть нужны.

Встроенные plugins могут сохранять внутренние импорты вспомогательных функций через зарезервированные подпути
runtime во время миграции. Публичная документация должна направлять авторов plugins к
`plugin-sdk/channel-outbound`, когда он появится.

## Связь с входящим каналом

`runtime.channel.inbound.*` является runtime-мостом во время миграции.

Он должен стать адаптером совместимости:

```text theme={"theme":{"light":"min-light","dark":"min-dark"}}
channel.inbound.run
  -> messages.receive context
  -> session dispatch
  -> messages.send context for visible output
```

`channel.inbound.runPreparedReply` также должен сначала остаться:

```text theme={"theme":{"light":"min-light","dark":"min-dark"}}
channel-owned dispatcher
  -> messages.receive record/finalize bridge
  -> messages.live for preview/progress
  -> messages.send for final delivery
```

Старая runtime-поверхность `channel.turn` была удалена. Runtime-вызывающие используют
`channel.inbound.*`; документация каналов и подпути SDK используют существительные inbound/message.

## Ограничения совместимости

Во время миграции общая долговечная доставка включается явно для любого канала, чей
существующий callback доставки имеет побочные эффекты помимо «отправить эту полезную нагрузку».

Устаревшие точки входа по умолчанию недолговечны:

* `channel.inbound.run` и `dispatchChannelInboundReply` используют callback доставки канала,
  если этот канал явно не предоставляет проверенный объект политики/параметров долговечности.
* `channel.inbound.runPreparedReply` остается принадлежащим каналу, пока подготовленный диспетчер
  явно не вызовет контекст отправки.
* Публичные вспомогательные функции совместимости, такие как `recordInboundSessionAndDispatchReply`,
  `dispatchInboundReplyWithBase` и direct-DM helpers, никогда не внедряют общую
  долговечную доставку до предоставленного вызывающим кодом callback `deliver` или `reply`.

Для типов мостов миграции `durable: undefined` означает «не долговечно». Долговечный
путь включается только явным значением политики/параметров. `durable:
false` может оставаться совместимым написанием, но реализация не должна
требовать, чтобы каждый немигрированный канал добавлял его.

Текущий код моста должен сохранять явность решения о долговечности:

* Надежная финальная доставка возвращает дискриминированный статус. `handled_visible` и
  `handled_no_send` являются терминальными; `unsupported` и `not_applicable` могут
  откатиться к доставке, принадлежащей каналу; `failed` передает ошибку отправки.
* Универсальная надежная финальная доставка ограничивается возможностями адаптера, такими как
  тихая доставка, сохранение цели ответа, сохранение нативного цитирования и
  хуки отправки сообщений. При отсутствии паритета следует выбирать доставку,
  принадлежащую каналу, а не универсальную отправку, которая меняет видимое пользователю поведение.
* Надежные отправки на базе очереди предоставляют ссылку на намерение доставки. Существующие
  поля сеанса `pendingFinalDelivery*` могут переносить идентификатор намерения во время
  перехода; конечное состояние — хранилище `MessageSendIntent` вместо замороженного
  текста ответа плюс специальных полей контекста.

Не включайте универсальный надежный путь для канала, пока все перечисленное ниже не
станет истинным:

* Универсальный адаптер отправки выполняет тот же рендеринг и транспортное поведение, что и
  старый прямой путь.
* Локальные побочные эффекты после отправки сохраняются через контекст отправки.
* Адаптер возвращает квитанции или результаты доставки со всеми идентификаторами сообщений
  платформы.
* Подготовленные пути диспетчера либо вызывают новый контекст отправки, либо остаются задокументированными
  как находящиеся вне надежной гарантии.
* Резервная доставка обрабатывает каждый спроецированный payload, а не только первый.
* Надежная резервная доставка записывает весь массив спроецированных payload как одно
  воспроизводимое намерение или пакетный план.

Конкретные риски миграции, которые нужно сохранить:

* Доставка монитора iMessage записывает отправленные сообщения в echo cache после
  успешной отправки. Надежные финальные отправки все еще должны заполнять этот кэш, иначе
  OpenClaw может повторно принять собственные финальные ответы как входящие пользовательские сообщения.
* Tlon добавляет необязательную сигнатуру модели и записывает участвующие threads
  после групповых ответов. Универсальная надежная доставка не должна обходить эти эффекты;
  либо перенесите их в адаптеры рендеринга/отправки/финализации Tlon, либо оставьте Tlon на
  пути, принадлежащем каналу.
* Discord и другие подготовленные диспетчеры уже владеют прямой доставкой и поведением
  предпросмотра. На них не распространяется надежная гарантия assembled-turn, пока
  их подготовленные диспетчеры явно не направят финальные сообщения через контекст отправки.
* Тихая резервная доставка Telegram должна доставлять полный массив спроецированных payload.
  Упрощенный путь с одним payload может отбросить дополнительные резервные payload после
  проекции.
* LINE, Zalo, Nostr и другие существующие assembled/helper-пути могут
  иметь обработку reply-token, проксирование медиа, кэши отправленных сообщений, очистку loading/status
  или цели только для callback. Они остаются на доставке, принадлежащей каналу, пока
  эта семантика не будет представлена адаптером отправки и проверена тестами.
* Direct-DM helpers могут иметь callback ответа, который является единственной корректной транспортной
  целью. Универсальный исходящий путь не должен угадывать по `OriginatingTo` или `To` и пропускать
  этот callback.
* Вывод сбоя OpenClaw gateway должен оставаться видимым людям, но помеченные
  room echoes, созданные ботом, должны отбрасываться до авторизации `allowBots`.
  Каналы не должны реализовывать это через фильтры префиксов видимого текста, кроме как в качестве
  короткой экстренной временной меры; надежный контракт — структурированные метаданные origin.

## Внутреннее хранилище

Надежная очередь должна хранить намерения отправки сообщений, а не payload ответов.

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type DurableSendIntent = {
  id: string;
  idempotencyKey: string;
  channel: string;
  accountId?: string;
  message: ChannelMessage;
  batch?: RenderedMessageBatch;
  liveState?: LiveMessageState;
  status:
    | "pending"
    | "sending"
    | "committing"
    | "unknown_after_send"
    | "sent"
    | "failed"
    | "cancelled";
  attempt: number;
  nextAttemptAt?: number;
  receipt?: MessageReceipt;
  partialReceipt?: MessageReceipt;
  failure?: DeliveryFailure;
  createdAt: number;
  updatedAt: number;
};
```

Цикл восстановления:

```text theme={"theme":{"light":"min-light","dark":"min-dark"}}
load pending or sending intents
  -> acquire idempotency lock
  -> skip if receipt already committed
  -> reconstruct send context
  -> render if needed
  -> reconcile unknown_after_send if needed
  -> call adapter send/edit/finalize
  -> commit receipt, mark unknown_after_send, or schedule retry
```

Очередь должна хранить достаточно идентичности, чтобы после перезапуска воспроизвести отправку через тот же аккаунт,
thread, цель, политику форматирования и правила медиа.

## Классы сбоев

Адаптеры каналов классифицируют транспортные сбои по закрытым категориям:

```typescript theme={"theme":{"light":"min-light","dark":"min-dark"}}
type DeliveryFailureKind =
  | "transient"
  | "rate_limit"
  | "auth"
  | "permission"
  | "not_found"
  | "invalid_payload"
  | "conflict"
  | "cancelled"
  | "unknown";
```

Политика ядра:

* Повторять `transient` и `rate_limit`.
* Не повторять `invalid_payload`, если не существует резервного рендеринга.
* Не повторять `auth` или `permission` до изменения конфигурации.
* Для `not_found` разрешить live-финализации откатиться с редактирования к новой отправке, когда
  канал объявляет это безопасным.
* Для `conflict` использовать правила квитанции/идемпотентности, чтобы решить, существует ли сообщение
  уже.
* Любая ошибка после того, как адаптер мог завершить platform I/O, но до commit квитанции
  становится `unknown_after_send`, если адаптер не может доказать, что операция на платформе
  не произошла.

## Сопоставление каналов

| Канал           | Целевая миграция                                                                                                                                                                                                                                                                                                                                                                                                                                                           |
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Telegram        | Получение политики подтверждений плюс устойчивые финальные отправки. Рабочий адаптер владеет отправкой плюс редактированием предпросмотра, финальной отправкой устаревшего предпросмотра, темами, пропуском предпросмотра ответа с цитированием, резервной отправкой медиа и обработкой `retry-after`.                                                                                                                                                                     |
| Discord         | Адаптер отправки оборачивает существующую устойчивую доставку полезной нагрузки. Рабочий адаптер владеет редактированием черновика, черновиком прогресса, отменой предпросмотра медиа/ошибок, сохранением цели ответа и получением идентификаторов сообщений. Проверьте эхо сбоев Gateway, созданные ботом, в общих комнатах; используйте исходящий реестр или другой нативный эквивалент, если Discord не может переносить метаданные происхождения в обычных сообщениях. |
| Slack           | Адаптер отправки обрабатывает обычные сообщения чата. Рабочий адаптер выбирает нативный поток, когда форма треда это поддерживает, иначе использует предпросмотр черновика. Квитанции сохраняют временные метки тредов. Адаптер происхождения сопоставляет сбои Gateway OpenClaw с `chat.postMessage.metadata` Slack и отбрасывает помеченные эхо из комнат ботов до авторизации `allowBots`.                                                                              |
| WhatsApp        | Адаптер отправки владеет отправкой текста/медиа с устойчивыми финальными намерениями. Адаптер получения обрабатывает упоминание группы и личность отправителя. Рабочий адаптер может отсутствовать, пока у WhatsApp не появится редактируемый транспорт.                                                                                                                                                                                                                   |
| Matrix          | Рабочий адаптер владеет редактированием событий черновика, финализацией, редактированием с удалением, ограничениями зашифрованных медиа и резервным поведением при несовпадении цели ответа. Адаптер получения владеет гидратацией и дедупликацией зашифрованных событий. Адаптер происхождения должен кодировать происхождение сбоя Gateway OpenClaw в содержимое события Matrix и отбрасывать эхо комнат настроенных ботов до обработки `allowBots`.                     |
| Mattermost      | Рабочий адаптер владеет одним черновым постом, сворачиванием прогресса/инструментов, финализацией на месте и резервной новой отправкой.                                                                                                                                                                                                                                                                                                                                    |
| Microsoft Teams | Рабочий адаптер владеет нативным прогрессом и поведением блочного потока. Адаптер отправки владеет активностями и квитанциями вложений/карточек.                                                                                                                                                                                                                                                                                                                           |
| Feishu          | Адаптер рендеринга владеет рендерингом текста/карточек/сырого содержимого. Рабочий адаптер владеет потоковыми карточками и подавлением дублирующего финала. Адаптер отправки владеет комментариями, тематическими сессиями, медиа и подавлением голосовых сообщений.                                                                                                                                                                                                       |
| QQ Bot          | Рабочий адаптер владеет потоковой передачей C2C, тайм-аутом накопителя и резервной финальной отправкой. Адаптер рендеринга владеет медиа-тегами и текстом как голосом.                                                                                                                                                                                                                                                                                                     |
| Signal          | Простой адаптер получения плюс адаптер отправки. Без рабочего адаптера, если signal-cli не добавит надежную поддержку редактирования.                                                                                                                                                                                                                                                                                                                                      |
| iMessage        | Простой адаптер получения плюс адаптер отправки. Отправка iMessage должна сохранять заполнение эхо-кэша монитора, прежде чем устойчивые финалы смогут обходить доставку через монитор.                                                                                                                                                                                                                                                                                     |
| Google Chat     | Простой адаптер получения плюс адаптер отправки, где связь треда сопоставлена с пространствами и идентификаторами тредов. Проверьте поведение комнаты с `allowBots=true` для помеченных эхо сбоев Gateway OpenClaw.                                                                                                                                                                                                                                                        |
| LINE            | Простой адаптер получения плюс адаптер отправки, где ограничения токена ответа смоделированы как возможность цели/связи.                                                                                                                                                                                                                                                                                                                                                   |
| Nextcloud Talk  | Мост получения SDK плюс адаптер отправки.                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| IRC             | Простой адаптер получения плюс адаптер отправки, без устойчивых квитанций редактирования.                                                                                                                                                                                                                                                                                                                                                                                  |
| Nostr           | Адаптер получения плюс адаптер отправки для зашифрованных личных сообщений; квитанциями являются идентификаторы событий.                                                                                                                                                                                                                                                                                                                                                   |
| QA-канал        | Адаптер контрактных тестов для поведения получения, отправки, рабочего режима, повторов и восстановления.                                                                                                                                                                                                                                                                                                                                                                  |
| Synology Chat   | Простой адаптер получения плюс адаптер отправки.                                                                                                                                                                                                                                                                                                                                                                                                                           |
| Tlon            | Адаптер отправки должен сохранять рендеринг подписи модели и отслеживание тредов с участием, прежде чем будет включена универсальная устойчивая финальная доставка.                                                                                                                                                                                                                                                                                                        |
| Twitch          | Простой адаптер получения плюс адаптер отправки с классификацией ограничений частоты.                                                                                                                                                                                                                                                                                                                                                                                      |
| Zalo            | Простой адаптер получения плюс адаптер отправки.                                                                                                                                                                                                                                                                                                                                                                                                                           |
| Zalo Personal   | Простой адаптер получения плюс адаптер отправки.                                                                                                                                                                                                                                                                                                                                                                                                                           |

## План миграции

### Фаза 1: Внутренний домен сообщений

* Добавьте типы `src/channels/message/*` для сообщений, целей, связей,
  происхождений, квитанций, возможностей, устойчивых намерений, контекста
  получения, контекста отправки, рабочего контекста и классов сбоев.
* Добавьте `origin?: MessageOrigin` в тип полезной нагрузки миграционного моста,
  используемый текущей доставкой ответов, затем перенесите это поле в
  `ChannelMessage` и типы отрендеренных сообщений по мере того, как рефакторинг
  заменяет полезные нагрузки ответов.
* Держите это внутренним, пока адаптеры и тесты не подтвердят форму.
* Добавьте чистые модульные тесты для переходов состояний и сериализации.

### Фаза 2: Ядро устойчивой отправки

* Перенесите существующую исходящую очередь с устойчивости полезной нагрузки
  ответа на устойчивые намерения отправки сообщений.
* Позвольте устойчивому намерению отправки нести массив спроецированных полезных
  нагрузок или план пакета, а не только одну полезную нагрузку ответа.
* Сохраните текущее поведение восстановления очереди через совместимое преобразование.
* Сделайте так, чтобы `deliverOutboundPayloads` вызывал `messages.send`.
* Сделайте устойчивость финальной отправки поведением по умолчанию и закрывайте
  сбоем, когда устойчивое намерение нельзя записать в новом жизненном цикле
  сообщения, после того как адаптер объявит безопасность воспроизведения.
  Существующие пути входящего раннера и совместимости SDK остаются прямой
  отправкой по умолчанию на этой фазе.
* Последовательно записывайте квитанции.
* Возвращайте квитанции и результаты доставки исходному вызывающему диспетчера,
  вместо того чтобы рассматривать устойчивую отправку как конечный побочный эффект.
* Сохраняйте происхождение сообщения через устойчивые намерения отправки, чтобы
  восстановление, воспроизведение и отправки частями сохраняли операционное
  происхождение OpenClaw.

### Фаза 3: Мост входящих сообщений канала

* Повторно реализуйте `channel.inbound.run` и `dispatchChannelInboundReply` поверх
  `messages.receive` и `messages.send`.
* Сохраните стабильность текущих типов фактов.
* Сохраните прежнее поведение по умолчанию. Канал с собранным ходом становится
  устойчивым только тогда, когда его адаптер явно подключается с политикой
  устойчивости, безопасной для воспроизведения.
* Сохраните `durable: false` как совместимый аварийный выход для путей, которые
  финализируют нативные редактирования и пока не могут безопасно воспроизводиться,
  но не полагайтесь на маркеры `false` для защиты немигрированных каналов.
* Включайте устойчивость собранного хода по умолчанию только в новом жизненном
  цикле сообщений, после того как сопоставление канала докажет, что универсальный
  путь отправки сохраняет прежнюю семантику доставки канала.

### Фаза 4: Мост подготовленного диспетчера

* Заменить `deliverDurableInboundReplyPayload` мостом с контекстом отправки.
* Сохранить старый помощник как обертку.
* Сначала перенести Telegram, WhatsApp, Slack, Signal, iMessage и Discord, потому что
  у них уже есть работа с устойчивыми окончательными ответами или более простые пути отправки.
* Считать каждый подготовленный диспетчер непокрытым, пока он явно не подключится к
  контексту отправки. В документации и записях changelog нужно писать «собранные
  ходы канала» или называть перенесенные пути каналов, а не заявлять обо всех
  автоматических окончательных ответах.
* Сохранить поведение `recordInboundSessionAndDispatchReply`, помощников прямых DM
  и похожих публичных помощников совместимости. Позже они могут открыть явное
  подключение к контексту отправки, но не должны автоматически пытаться выполнить
  универсальную устойчивую доставку до callback доставки, которым владеет вызывающая сторона.

### Фаза 5: единый жизненный цикл live

* Построить `messages.live` с двумя адаптерами доказательства:
  * Telegram для отправки, редактирования и отправки устаревшего окончательного сообщения.
  * Matrix для финализации черновика и fallback редактирования.
* Затем перенести Discord, Slack, Mattermost, Teams, QQ Bot и Feishu.
* Удалять дублированный код финализации preview только после того, как у каждого канала
  появятся тесты паритета.

### Фаза 6: публичный SDK

* Добавить `openclaw/plugin-sdk/channel-outbound`.
* Задокументировать его как предпочтительный API plugin канала.
* Обновить экспорты пакета, инвентарь entrypoint, сгенерированные базовые линии API и
  документацию SDK plugin.
* Включить `MessageOrigin`, хуки кодирования/декодирования origin и общий
  предикат `shouldDropOpenClawEcho` в поверхность SDK channel-outbound.
* Сохранить обертки совместимости для старых подпутей.
* Пометить SDK-помощники с reply в названии как устаревшие в документации после миграции
  встроенных plugins.

### Фаза 7: все отправители

Перенести всех исходящих производителей не-ответов на `messages.send`:

* уведомления cron и heartbeat
* завершения задач
* результаты hook
* запросы подтверждения и результаты подтверждения
* отправки инструмента сообщений
* объявления о завершении subagent
* явные отправки CLI или Control UI
* пути автоматизации/рассылки

Здесь модель перестает быть «ответами агента» и становится «OpenClaw отправляет
сообщения».

### Фаза 8: удаление совместимости с названиями turn

* Сохранить обертки с inbound/message в названии как окно совместимости.
* Опубликовать заметки о миграции.
* Запустить тесты совместимости SDK plugin со старыми импортами.
* Удалять или скрывать старые внутренние помощники только после того, как они больше не нужны
  ни одному встроенному plugin и у сторонних контрактов есть стабильная замена.

## План тестирования

Модульные тесты:

* Сериализация и восстановление намерения устойчивой отправки.
* Повторное использование ключа идемпотентности и подавление дублей.
* Коммит receipt и пропуск replay.
* Восстановление `unknown_after_send`, которое выполняет сверку перед replay, когда адаптер
  поддерживает сверку.
* Политика классификации ошибок.
* Упорядочивание политики ack при получении.
* Сопоставление связей для отправок reply, followup, system и broadcast.
* Фабрика origin для ошибки Gateway и предикат `shouldDropOpenClawEcho`.
* Сохранение origin через нормализацию payload, chunking, сериализацию устойчивой очереди
  и восстановление.

Интеграционные тесты:

* Простой адаптер `channel.inbound.run` по-прежнему записывает и отправляет.
* Доставка legacy assembled-event не становится устойчивой, если канал явно не подключился.
* Мост `channel.inbound.runPreparedReply` по-прежнему записывает и финализирует.
* Публичные помощники совместимости по умолчанию вызывают callback доставки, которым владеет вызывающая сторона,
  и не выполняют generic-send до этих callback.
* Устойчивая fallback-доставка воспроизводит весь массив спроецированных payload после
  перезапуска и не может оставить поздние payload незаписанными после раннего сбоя.
* Устойчивая доставка assembled-event возвращает идентификаторы сообщений платформы буферизованному
  диспетчеру.
* Пользовательские delivery hooks по-прежнему возвращают идентификаторы сообщений платформы, когда устойчивая доставка
  отключена или недоступна.
* Окончательный ответ переживает перезапуск между завершением assistant и отправкой на платформу.
* Черновик preview финализируется на месте, когда это разрешено.
* Черновик preview отменяется или редактируется, когда media/error/несоответствие reply-target
  требует обычной доставки.
* Block streaming и preview streaming не доставляют один и тот же текст одновременно.
* Media, отправленные рано через streaming, не дублируются в окончательной доставке.

Тесты каналов:

* Ответ в теме Telegram с polling ack, отложенным до безопасной completed watermark контекста получения.
* Восстановление polling Telegram для принятых, но не доставленных updates, покрытое
  сохраненной моделью safe-completed offset.
* Устаревший preview Telegram отправляет свежий окончательный ответ и очищает preview.
* Silent fallback Telegram отправляет каждый спроецированный fallback payload.
* Устойчивость silent fallback Telegram атомарно записывает полный спроецированный fallback array,
  а не одно single-payload устойчивое намерение на каждую итерацию цикла.
* Discord отменяет preview при media/error/явном reply.
* Окончательные сообщения подготовленного диспетчера Discord проходят через контекст отправки до того,
  как документация или changelog заявят об устойчивости окончательного ответа Discord.
* Устойчивые окончательные отправки iMessage заполняют echo cache отправленного сообщения монитора.
* Legacy-пути доставки LINE, Zalo и Nostr не обходятся
  generic durable send, пока не появятся тесты паритета их адаптеров.
* Доставка callback Direct-DM/Nostr остается авторитетной, если она явно не
  перенесена на полный target сообщения и replay-safe адаптер отправки.
* Помеченные Slack сообщения сбоя Gateway OpenClaw остаются видимыми исходящими, помеченные
  echoes bot-room отбрасываются до `allowBots`, а непомеченные bot messages с тем же
  видимым текстом по-прежнему проходят обычную авторизацию bot.
* Fallback нативного stream Slack к draft preview в DM верхнего уровня.
* Финализация preview Matrix и fallback редактирования.
* Помеченные Matrix echoes room от настроенных bot
  accounts для сбоя Gateway OpenClaw отбрасываются до обработки `allowBots`.
* Аудиты cascade сбоя Gateway в общих комнатах Discord и Google Chat покрывают
  режимы `allowBots` до заявления об универсальной защите там.
* Финализация черновика Mattermost и fallback свежей отправки.
* Финализация нативного progress Teams.
* Подавление дублирующего окончательного сообщения Feishu.
* Fallback timeout аккумулятора QQ Bot.
* Устойчивые окончательные отправки Tlon сохраняют рендеринг model-signature и отслеживание
  участвовавшего thread.
* Простые устойчивые окончательные отправки WhatsApp, Signal, iMessage, Google Chat, LINE, IRC, Nostr, Nextcloud Talk,
  Synology Chat, Tlon, Twitch, Zalo и Zalo Personal.

Валидация:

* Целевые файлы Vitest во время разработки.
* `pnpm check:changed` в Testbox для всей измененной поверхности.
* Более широкий `pnpm check` в Testbox перед landing полного рефакторинга или после
  изменений публичного SDK/export.
* Live или qa-channel smoke минимум для одного канала с поддержкой редактирования и одного
  простого канала только с отправкой перед удалением оберток совместимости.

## Открытые вопросы

* Должен ли Telegram со временем заменить источник grammY runner полностью
  устойчивым polling source, который может управлять повторной доставкой на уровне платформы, а не
  только сохраненной watermark перезапуска OpenClaw.
* Должно ли состояние durable live preview храниться в той же записи очереди,
  что и намерение окончательной отправки, или в соседнем хранилище live-state.
* Как долго обертки совместимости остаются задокументированными после выпуска
  `plugin-sdk/channel-outbound`.
* Должны ли сторонние plugins реализовывать адаптеры получения напрямую или только
  предоставлять хуки normalize/send/live через `defineChannelMessageAdapter`.
* Какие поля receipt безопасно раскрывать в публичном SDK по сравнению с внутренним состоянием runtime.
* Следует ли моделировать побочные эффекты, такие как self-echo caches и маркеры participated-thread,
  как хуки контекста отправки, шаги finalize, принадлежащие адаптеру, или
  подписчиков receipt.
* У каких каналов есть нативные метаданные origin, каким нужны сохраненные исходящие
  registries, а какие не могут предоставить надежное подавление cross-bot echo.

## Критерии приемки

* Каждый встроенный канал сообщений отправляет окончательный видимый вывод через
  `messages.send`.
* Каждый входящий канал сообщений входит через `messages.receive` или
  задокументированную обертку совместимости.
* Каждый канал preview/edit/stream использует `messages.live` для состояния черновика и
  финализации.
* `channel.inbound` является только оберткой.
* SDK-помощники с reply в названии являются экспортами совместимости, а не рекомендуемым путем.
* Устойчивое восстановление может воспроизводить ожидающие окончательные отправки после перезапуска без потери
  окончательного ответа или дублирования уже закоммиченных отправок; отправки, чей
  результат платформы неизвестен, сверяются перед replay или документируются как
  at-least-once для этого адаптера.
* Устойчивые окончательные отправки закрываются с ошибкой, когда не удается записать durable intent,
  если вызывающая сторона явно не выбрала задокументированный non-durable режим.
* Legacy-помощники совместимости SDK по умолчанию используют прямую
  доставку, принадлежащую каналу; generic durable send доступен только по явному opt-in.
* Receipts сохраняют все идентификаторы сообщений платформы для multipart-доставок и
  primary id для удобства threading/edit.
* Устойчивые обертки сохраняют локальные для канала побочные эффекты до замены прямых
  delivery callbacks.
* Подготовленные диспетчеры не считаются устойчивыми, пока их путь окончательной доставки
  явно не использует контекст отправки.
* Fallback-доставка обрабатывает каждый спроецированный payload.
* Устойчивая fallback-доставка записывает каждый спроецированный payload в одно replayable
  intent или batch plan.
* Вывод сбоя Gateway, инициированный OpenClaw, виден людям, но помеченные
  echoes room от bot-authored отбрасываются до авторизации bot в каналах, которые
  объявляют поддержку контракта origin.
* Документация объясняет отправку, получение, live, состояние, receipts, relations, failure
  policy, migration и test coverage.

## Связанные материалы

* [Сообщения](/ru/concepts/messages)
* [Streaming и chunking](/ru/concepts/streaming)
* [Черновики progress](/ru/concepts/progress-drafts)
* [Политика retry](/ru/concepts/retry)
* [API входящих сообщений канала](/ru/plugins/sdk-channel-inbound)
