Get started
План рефакторингу представлення каналу
Статус
Реалізовано для спільного агента, CLI, можливостей plugin і поверхонь вихідної доставки:
ReplyPayload.presentationмістить семантичний UI повідомлення.ReplyPayload.delivery.pinмістить запити на закріплення надісланого повідомлення.- Спільні дії повідомлень надають
presentation,deliveryіpinзамість нативних для провайдераcomponents,blocks,buttonsабоcard. - Core рендерить або автоматично деградує presentation через оголошені plugin можливості вихідної доставки.
- Рендерери Discord, Slack, Telegram, Mattermost, MS Teams і Feishu використовують узагальнений контракт.
- Код control plane каналу Discord більше не імпортує UI-контейнери на базі Carbon.
Канонічна документація тепер міститься в Представлення повідомлень. Зберігайте цей план як історичний контекст реалізації; оновлюйте канонічний посібник у разі змін контракту, рендерера або поведінки fallback.
Проблема
Наразі UI каналу розділений між кількома несумісними поверхнями:
- Core володіє Discord-подібним хуком рендерингу між контекстами через
buildCrossContextComponents. channel.tsу Discord може імпортувати нативний Carbon UI черезDiscordUiContainer, що підтягує залежності UI середовища виконання в control plane plugin каналу.- Агент і CLI надають обхідні шляхи для нативних payload-об’єктів, такі як Discord
components, Slackblocks, Telegram або Mattermostbuttons, а також Teams або Feishucard. ReplyPayload.channelDataмістить і підказки транспорту, і нативні UI-обгортки.- Узагальнена модель
interactiveіснує, але вона вужча за багатші макети, які вже використовуються в Discord, Slack, Teams, Feishu, LINE, Telegram і Mattermost.
Через це core знає про нативні UI-форми, послаблюється лінивість середовища виконання plugin, а агенти отримують надто багато специфічних для провайдера способів вираження одного й того самого наміру повідомлення.
Цілі
- Core визначає найкраще семантичне представлення повідомлення на основі оголошених можливостей.
- Розширення оголошують можливості та рендерять семантичне представлення в нативні transport payload-об’єкти.
- Web Control UI залишається окремим від нативного UI чату.
- Нативні payload-об’єкти каналу не надаються через спільну поверхню повідомлень агента або CLI.
- Непідтримувані можливості presentation автоматично деградують до найкращого текстового представлення.
- Поведінка доставки, як-от закріплення надісланого повідомлення, є узагальненими метаданими доставки, а не presentation.
Не цілі
- Жодного shim зворотної сумісності для
buildCrossContextComponents. - Жодних публічних нативних обхідних шляхів для
components,blocks,buttonsабоcard. - Жодних імпортів у core нативних для каналу UI-бібліотек.
- Жодних seams SDK, специфічних для провайдера, для вбудованих каналів.
Цільова модель
Додайте поле presentation, яким володіє core, до ReplyPayload.
type MessagePresentationTone = "neutral" | "info" | "success" | "warning" | "danger";
type MessagePresentation = {
tone?: MessagePresentationTone;
title?: string;
blocks: MessagePresentationBlock[];
};
type MessagePresentationBlock =
| { type: "text"; text: string }
| { type: "context"; text: string }
| { type: "divider" }
| { type: "buttons"; buttons: MessagePresentationButton[] }
| { type: "select"; placeholder?: string; options: MessagePresentationOption[] };
type MessagePresentationButton = {
label: string;
value?: string;
url?: string;
style?: "primary" | "secondary" | "success" | "danger";
};
type MessagePresentationOption = {
label: string;
value: string;
};
Під час міграції interactive стає підмножиною presentation:
- Блок тексту
interactiveмапиться наpresentation.blocks[].type = "text". - Блок кнопок
interactiveмапиться наpresentation.blocks[].type = "buttons". - Блок вибору
interactiveмапиться наpresentation.blocks[].type = "select".
Зовнішні схеми агента і CLI тепер використовують presentation; interactive залишається внутрішнім legacy-хелпером для парсингу/рендерингу для наявних продуцентів відповідей.
Метадані доставки
Додайте поле delivery, яким володіє core, для поведінки надсилання, що не є UI.
type ReplyPayloadDelivery = {
pin?:
| boolean
| {
enabled: boolean;
notify?: boolean;
required?: boolean;
};
};
Семантика:
delivery.pin = trueозначає закріпити перше успішно доставлене повідомлення.- Значення
notifyза замовчуванням дорівнюєfalse. - Значення
requiredза замовчуванням дорівнюєfalse; непідтримувані канали або невдале закріплення автоматично деградують шляхом продовження доставки. - Ручні дії повідомлень
pin,unpinіlist-pinsзалишаються для наявних повідомлень.
Поточну прив’язку теми Telegram ACP слід перенести з channelData.telegram.pin = true до delivery.pin = true.
Контракт можливостей середовища виконання
Додайте хуки рендерингу presentation і доставки до адаптера вихідної доставки середовища виконання, а не до plugin каналу control plane.
type ChannelPresentationCapabilities = {
supported: boolean;
buttons?: boolean;
selects?: boolean;
context?: boolean;
divider?: boolean;
tones?: MessagePresentationTone[];
};
type ChannelDeliveryCapabilities = {
pinSentMessage?: boolean;
};
type ChannelOutboundAdapter = {
presentationCapabilities?: ChannelPresentationCapabilities;
renderPresentation?: (params: {
payload: ReplyPayload;
presentation: MessagePresentation;
ctx: ChannelOutboundSendContext;
}) => ReplyPayload | null;
deliveryCapabilities?: ChannelDeliveryCapabilities;
pinDeliveredMessage?: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
to: string;
threadId?: string | number | null;
messageId: string;
notify: boolean;
}) => Promise<void>;
};
Поведінка core:
- Визначити цільовий канал і адаптер середовища виконання.
- Запитати можливості presentation.
- Деградувати непідтримувані блоки перед рендерингом.
- Викликати
renderPresentation. - Якщо рендерера немає, перетворити presentation на текстовий fallback.
- Після успішного надсилання викликати
pinDeliveredMessage, якщо запитаноdelivery.pinі це підтримується.
Мапінг каналів
Discord:
- Рендерити
presentationу components v2 і контейнери Carbon у модулях лише для runtime. - Зберегти хелпери accent color у легких модулях.
- Видалити імпорти
DiscordUiContainerз коду control plane plugin каналу.
Slack:
- Рендерити
presentationу Block Kit. - Видалити вхід
blocksз агента і CLI.
Telegram:
- Рендерити text, context і divider як текст.
- Рендерити actions і select як inline keyboards, коли це налаштовано і дозволено для цільової поверхні.
- Використовувати текстовий fallback, коли inline-кнопки вимкнено.
- Перенести закріплення теми ACP до
delivery.pin.
Mattermost:
- Рендерити actions як інтерактивні кнопки, коли це налаштовано.
- Рендерити інші блоки як текстовий fallback.
MS Teams:
- Рендерити
presentationу Adaptive Cards. - Зберегти ручні дії pin/unpin/list-pins.
- За потреби реалізувати
pinDeliveredMessage, якщо підтримка Graph є надійною для цільової розмови.
Feishu:
- Рендерити
presentationв interactive cards. - Зберегти ручні дії pin/unpin/list-pins.
- За потреби реалізувати
pinDeliveredMessageдля закріплення надісланого повідомлення, якщо поведінка API є надійною.
LINE:
- Рендерити
presentationу повідомлення Flex або template, де це можливо. - Для непідтримуваних блоків використовувати текстовий fallback.
- Видалити LINE UI payload-об’єкти з
channelData.
Звичайні або обмежені канали:
- Перетворювати presentation на текст із консервативним форматуванням.
Кроки рефакторингу
- Повторно застосувати виправлення релізу Discord, яке відокремлює
ui-colors.tsвід UI на базі Carbon і видаляєDiscordUiContainerзextensions/discord/src/channel.ts. - Додати
presentationіdeliveryдоReplyPayload, нормалізації вихідного payload-об’єкта, підсумків доставки та payload-об’єктів hook. - Додати схему
MessagePresentationі хелпери парсера у вузький підшлях SDK/runtime. - Замінити можливості повідомлень
buttons,cards,componentsіblocksна семантичні можливості presentation. - Додати хуки адаптера вихідної доставки runtime для рендерингу presentation і закріплення доставки.
- Замінити побудову components між контекстами на
buildCrossContextPresentation. - Видалити
src/infra/outbound/channel-adapters.tsі прибратиbuildCrossContextComponentsз типів plugin каналу. - Змінити
maybeApplyCrossContextMarker, щоб він прикріплювавpresentationзамість нативних параметрів. - Оновити шляхи надсилання plugin-dispatch так, щоб вони використовували лише семантичне presentation і метадані delivery.
- Видалити нативні параметри payload-об’єктів агента і CLI:
components,blocks,buttonsіcard. - Видалити хелпери SDK, які створюють нативні схеми message-tool, замінивши їх на хелпери схем presentation.
- Видалити UI/нативні обгортки з
channelData; залишити лише транспортні метадані, доки не буде переглянуто кожне інше поле. - Мігрувати рендерери Discord, Slack, Telegram, Mattermost, MS Teams, Feishu і LINE.
- Оновити документацію для CLI повідомлень, сторінок каналів, SDK plugin і cookbook можливостей.
- Запустити профілювання fanout імпорту для Discord і пов’язаних entrypoint каналів.
Кроки 1-11 і 13-14 реалізовано в цьому рефакторингу для контрактів спільного агента, CLI, можливостей plugin і адаптера вихідної доставки. Крок 12 залишається глибшим внутрішнім етапом очищення для приватних для провайдера транспортних обгорток channelData. Крок 15 залишається подальшою валідацією, якщо нам потрібні кількісні показники fanout імпорту понад бар’єр типів/тестів.
Тести
Додати або оновити:
- Тести нормалізації presentation.
- Тести автоматичної деградації presentation для непідтримуваних блоків.
- Тести маркерів між контекстами для шляхів plugin dispatch і доставки core.
- Тести матриці рендерингу каналів для Discord, Slack, Telegram, Mattermost, MS Teams, Feishu, LINE і текстового fallback.
- Тести схеми message tool, що доводять відсутність нативних полів.
- Тести CLI, що доводять відсутність нативних прапорців.
- Регресійний тест лінивості імпорту entrypoint Discord, що покриває Carbon.
- Тести закріплення delivery, що покривають Telegram і узагальнений fallback.
Відкриті питання
- Чи слід реалізувати
delivery.pinдля Discord, Slack, MS Teams і Feishu у першому проході, чи спочатку лише для Telegram? - Чи має
deliveryзрештою поглинути наявні поля, такі якreplyToId,replyToCurrent,silentіaudioAsVoice, чи залишатися зосередженим на поведінці після надсилання? - Чи має presentation безпосередньо підтримувати зображення або посилання на файли, чи медіа поки що слід тримати окремо від UI-макета?