Перейти до основного вмісту

Documentation Index

Fetch the complete documentation index at: https://docs.openclaw.ai/llms.txt

Use this file to discover all available pages before exploring further.

Ядро обробки ходу каналу — це спільна вхідна машина станів, яка перетворює нормалізовану подію платформи на хід агента. Плагіни каналів надають факти платформи й callback доставлення. Core володіє оркестрацією: приймання, класифікація, попередня перевірка, розв’язання, авторизація, складання, запис, dispatch і фіналізація. Використовуйте це, коли ваш плагін перебуває на гарячому шляху вхідного повідомлення. Для подій, що не є повідомленнями (slash-команди, модальні вікна, взаємодії з кнопками, події життєвого циклу, реакції, стан голосу), залишайте їх локальними для плагіна. Ядро володіє лише подіями, які можуть стати текстовим ходом агента.
До ядра звертаються через інʼєктований runtime плагіна як runtime.channel.turn.*. Тип runtime плагіна експортується з openclaw/plugin-sdk/core, тож сторонні нативні плагіни можуть використовувати ці точки входу так само, як це роблять bundled-плагіни каналів.

Навіщо спільне ядро

Плагіни каналів повторюють той самий вхідний потік: нормалізувати, маршрутизувати, пропустити через gates, побудувати контекст, записати метадані сесії, dispatch ходу агента, фіналізувати стан доставлення. Без спільного ядра зміну в gating згадок, видимих відповідях лише для інструментів, метаданих сесії, pending-історії або фіналізації dispatch потрібно застосовувати окремо для кожного каналу. Ядро навмисно тримає чотири поняття окремими:
  • ConversationFacts: звідки надійшло повідомлення
  • RouteFacts: який агент і яка сесія мають його обробити
  • ReplyPlanFacts: куди мають іти видимі відповіді
  • MessageFacts: яке тіло й додатковий контекст має бачити агент
Slack DM, теми Telegram, потоки Matrix і topic-сесії Feishu на практиці розрізняють усе це. Якщо трактувати їх як один ідентифікатор, з часом виникає drift.

Життєвий цикл етапів

Ядро виконує той самий фіксований pipeline незалежно від каналу:
  1. ingest — адаптер перетворює raw-подію платформи на NormalizedTurnInput
  2. classify — адаптер оголошує, чи може ця подія почати хід агента
  3. preflight — адаптер виконує dedupe, self-echo, hydration, debounce, decryption, часткове попереднє заповнення фактів
  4. resolve — адаптер повертає повністю зібраний хід (route, reply plan, message, delivery)
  5. authorize — політика DM, group, mention і command застосовується до зібраних фактів
  6. assembleFinalizedMsgContext будується з фактів через buildContext
  7. record — зберігаються метадані вхідної сесії та останній route
  8. dispatch — хід агента виконується через buffered block dispatcher
  9. finalize — adapter onFinalize виконується навіть у разі помилки dispatch
Кожен етап emits структуровану подію журналу, коли надано callback log. Див. Спостережуваність.

Види допуску

Ядро не кидає помилку, коли хід відхиляється gate. Воно повертає ChannelTurnAdmission:
ВидКоли
dispatchХід допущено. Хід агента запускається, і шлях видимої відповіді виконується.
observeOnlyХід виконується end-to-end, але delivery-адаптер не надсилає нічого видимого. Використовується для broadcast observer-агентів та інших пасивних multi-agent потоків.
handledПодію платформи було оброблено локально (життєвий цикл, реакція, кнопка, модальне вікно). Ядро пропускає dispatch.
dropШлях пропуску. Опційно recordHistory: true зберігає повідомлення в pending group history, щоб майбутня згадка мала контекст.
Допуск може надходити з classify (клас події сказав, що вона не може почати хід), з preflight (dedupe, self-echo, відсутня згадка із записом історії) або безпосередньо з resolveTurn.

Точки входу

Runtime надає три бажані точки входу, щоб адаптери могли підключатися на рівні, який відповідає каналу.
runtime.channel.turn.run(...)             // adapter-driven full pipeline
runtime.channel.turn.runPrepared(...)     // channel owns dispatch; kernel runs record + finalize
runtime.channel.turn.buildContext(...)    // pure facts to FinalizedMsgContext mapping
Два старіші runtime helpers залишаються доступними для сумісності з Plugin SDK:
runtime.channel.turn.runResolved(...)      // deprecated compatibility alias; prefer run
runtime.channel.turn.dispatchAssembled(...) // deprecated compatibility alias; prefer run or runPrepared

run

Використовуйте, коли ваш канал може виразити свій вхідний потік як ChannelTurnAdapter<TRaw>. Адаптер має callbacks для ingest, необовʼязкового classify, необовʼязкового preflight, обовʼязкового resolveTurn і необовʼязкового onFinalize.
await runtime.channel.turn.run({
  channel: "tlon",
  accountId,
  raw: platformEvent,
  adapter: {
    ingest(raw) {
      return {
        id: raw.messageId,
        timestamp: raw.timestamp,
        rawText: raw.body,
        textForAgent: raw.body,
      };
    },
    classify(input) {
      return { kind: "message", canStartAgentTurn: input.rawText.length > 0 };
    },
    async preflight(input, eventClass) {
      if (await isDuplicate(input.id)) {
        return { admission: { kind: "drop", reason: "dedupe" } };
      }
      return {};
    },
    resolveTurn(input) {
      return buildAssembledTurn(input);
    },
    onFinalize(result) {
      clearPendingGroupHistory(result);
    },
  },
});
run — правильна форма, коли канал має невелику логіку адаптера й виграє від володіння життєвим циклом через hooks.

runPrepared

Використовуйте, коли канал має складний локальний dispatcher із previews, retries, edits або thread bootstrap, які мають залишатися у власності каналу. Ядро все одно записує вхідну сесію перед dispatch і повертає уніфікований DispatchedChannelTurnResult.
const { dispatchResult } = await runtime.channel.turn.runPrepared({
  channel: "matrix",
  accountId,
  routeSessionKey,
  storePath,
  ctxPayload,
  recordInboundSession,
  record: {
    onRecordError,
    updateLastRoute,
  },
  onPreDispatchFailure: async (err) => {
    await stopStatusReactions();
  },
  runDispatch: async () => {
    return await runMatrixOwnedDispatcher();
  },
});
Rich channels (Matrix, Mattermost, Microsoft Teams, Feishu, QQ Bot) використовують runPrepared, тому що їхній dispatcher оркеструє platform-specific поведінку, про яку ядро не повинно знати.

buildContext

Чиста функція, яка відображає bundles фактів у FinalizedMsgContext. Використовуйте її, коли ваш канал hand-rolls частину pipeline, але хоче узгоджену форму контексту.
const ctxPayload = runtime.channel.turn.buildContext({
  channel: "googlechat",
  accountId,
  messageId,
  timestamp,
  from,
  sender,
  conversation,
  route,
  reply,
  message,
  access,
  media,
  supplemental,
});
buildContext також корисний усередині callbacks resolveTurn під час складання ходу для run.
Застарілі SDK helpers, як-от dispatchInboundReplyWithBase, досі bridge through assembled-turn helper. Новий код плагіна має використовувати run або runPrepared.

Типи фактів

Факти, які ядро споживає з вашого адаптера, не залежать від платформи. Перекладайте обʼєкти платформи в ці форми, перш ніж передавати їх ядру.

NormalizedTurnInput

ПолеПризначення
idСтабільний message id, що використовується для dedupe і журналів
timestampНеобовʼязкові epoch ms
rawTextТіло в тому вигляді, у якому його отримано від платформи
textForAgentНеобовʼязкове очищене тіло для агента (mention strip, typing trim)
textForCommandsНеобовʼязкове тіло, що використовується для парсингу /command
rawНеобовʼязкове pass-through посилання для callbacks адаптера, яким потрібен оригінал

ChannelEventClass

ПолеПризначення
kindmessage, command, interaction, reaction, lifecycle, unknown
canStartAgentTurnЯкщо false, ядро повертає { kind: "handled" }
requiresImmediateAckПідказка для адаптерів, яким потрібно ACK перед dispatch

SenderFacts

ПолеПризначення
idСтабільний sender id платформи
nameВідображуване імʼя
usernameHandle, якщо відрізняється від name
tagДискримінатор у стилі Discord або тег платформи
rolesRole ids, що використовуються для зіставлення member-role allowlist
isBotTrue, коли відправник є відомим ботом (ядро використовує для dropping)
isSelfTrue, коли відправник є самим налаштованим агентом
displayLabelПопередньо відрендерений label для envelope-тексту

ConversationFacts

ПолеПризначення
kinddirect, group або channel
idConversation id, що використовується для routing
labelЛюдський label для envelope
spaceIdНеобовʼязковий outer space identifier (Slack workspace, Matrix homeserver)
parentIdOuter conversation id, коли це thread
threadIdThread id, коли це повідомлення всередині thread
nativeChannelIdPlatform-native channel id, коли відрізняється від routing id
routePeerPeer, що використовується для lookup resolveAgentRoute

RouteFacts

ПолеПризначення
agentIdАгент, який має обробити цей хід
accountIdНеобовʼязковий override (multi-account channels)
routeSessionKeySession key, що використовується для routing
dispatchSessionKeySession key, що використовується під час dispatch, коли відрізняється від route key
persistedSessionKeySession key, що записується в persisted session metadata
parentSessionKeyParent для branched/threaded sessions
modelParentSessionKeyModel-side parent для branched sessions
mainSessionKeyMain DM owner pin для direct conversations
createIfMissingДозволити етапу record створити відсутній session row

ReplyPlanFacts

ПолеПризначення
toЛогічна ціль відповіді, записана в контекст To
originatingToПочаткова ціль контексту (OriginatingTo)
nativeChannelIdНативний для платформи ідентифікатор каналу для доставлення
replyTargetКінцеве місце призначення видимої відповіді, якщо воно відрізняється від to
deliveryTargetНижчорівневе перевизначення доставлення
replyToIdІдентифікатор процитованого/прив’язаного повідомлення
replyToIdFullПовна форма процитованого ідентифікатора, коли платформа має обидві
messageThreadIdІдентифікатор треду на момент доставлення
threadParentIdІдентифікатор батьківського повідомлення треду
sourceReplyDeliveryModethread, reply, channel, direct або none

AccessFacts

AccessFacts містить булеві значення, потрібні етапу авторизації. Зіставлення ідентичності залишається в каналі: ядро лише споживає результат.
ПолеПризначення
dmРішення дозволити/спарувати/заборонити DM і список allowFrom
groupПолітика групи, дозвіл маршруту, дозвіл відправника, allowlist, вимога згадки
commandsАвторизація команд у налаштованих авторизаторах
mentionsЧи можливе виявлення згадки та чи було згадано агента

MessageFacts

ПолеПризначення
bodyКінцеве тіло конверта (відформатоване)
rawBodyСире вхідне тіло
bodyForAgentТіло, яке бачить агент
commandBodyТіло, використане для розбору команд
envelopeFromПопередньо відрендерена мітка відправника для конверта
senderLabelНеобов’язкове перевизначення для відрендереного відправника
previewКороткий редагований попередній перегляд для журналів
inboundHistoryОстанні записи вхідної історії, коли канал зберігає буфер

SupplementalContextFacts

Додатковий контекст охоплює контекст цитати, пересланого повідомлення та початкового завантаження треду. Ядро застосовує налаштовану політику contextVisibility. Адаптер каналу надає лише факти та прапорці senderAllowed, щоб політика між каналами залишалася узгодженою.

InboundMediaFacts

Медіа мають форму фактів. Завантаження з платформи, авторизація, політика SSRF, правила CDN і розшифрування залишаються локальними для каналу. Ядро перетворює факти на MediaPath, MediaUrl, MediaType, MediaPaths, MediaUrls, MediaTypes і MediaTranscribedIndexes.

Контракт адаптера

Для повного run форма адаптера така:
type ChannelTurnAdapter<TRaw> = {
  ingest(raw: TRaw): Promise<NormalizedTurnInput | null> | NormalizedTurnInput | null;
  classify?(input: NormalizedTurnInput): Promise<ChannelEventClass> | ChannelEventClass;
  preflight?(
    input: NormalizedTurnInput,
    eventClass: ChannelEventClass,
  ): Promise<PreflightFacts | ChannelTurnAdmission | null | undefined>;
  resolveTurn(
    input: NormalizedTurnInput,
    eventClass: ChannelEventClass,
    preflight: PreflightFacts,
  ): Promise<ChannelTurnResolved> | ChannelTurnResolved;
  onFinalize?(result: ChannelTurnResult): Promise<void> | void;
};
resolveTurn повертає ChannelTurnResolved, тобто AssembledChannelTurn з необов’язковим типом допуску. Повернення { admission: { kind: "observeOnly" } } запускає хід без створення видимого виводу. Адаптер усе ще володіє callback доставлення; для цього ходу він просто стає no-op. onFinalize виконується для кожного результату, включно з помилками диспетчеризації. Використовуйте його, щоб очищати очікувану групову історію, прибирати реакції підтвердження, зупиняти індикатори стану та скидати локальний стан.

Адаптер доставлення

Ядро не викликає платформу напряму. Канал передає ядру ChannelTurnDeliveryAdapter:
type ChannelTurnDeliveryAdapter = {
  deliver(payload: ReplyPayload, info: ChannelDeliveryInfo): Promise<ChannelDeliveryResult | void>;
  onError?(err: unknown, info: { kind: string }): void;
};

type ChannelDeliveryResult = {
  messageIds?: string[];
  threadId?: string;
  replyToId?: string;
  visibleReplySent?: boolean;
};
deliver викликається один раз для кожного буферизованого фрагмента відповіді. Повертайте ідентифікатори повідомлень платформи, коли канал їх має, щоб диспетчер міг зберігати прив’язки тредів і редагувати пізніші фрагменти. Для ходів лише зі спостереженням повертайте { visibleReplySent: false } або використовуйте createNoopChannelTurnDeliveryAdapter().

Параметри запису

Етап запису обгортає recordInboundSession. Більшість каналів можуть використовувати типові значення. Перевизначайте через record:
record: {
  groupResolution,
  createIfMissing: true,
  updateLastRoute,
  onRecordError: (err) => log.warn("record failed", err),
  trackSessionMetaTask: (task) => pendingTasks.push(task),
}
Диспетчер очікує завершення етапу запису. Якщо запис викидає помилку, ядро запускає onPreDispatchFailure (коли його передано до runPrepared) і повторно викидає помилку.

Спостережуваність

Кожен етап випромінює структуровану подію, коли надано callback log:
await runtime.channel.turn.run({
  channel: "twitch",
  accountId,
  raw,
  adapter,
  log: (event) => {
    runtime.log?.debug?.(`turn.${event.stage}:${event.event}`, {
      channel: event.channel,
      accountId: event.accountId,
      messageId: event.messageId,
      sessionKey: event.sessionKey,
      admission: event.admission,
      reason: event.reason,
    });
  },
});
Етапи журналювання: ingest, classify, preflight, resolve, authorize, assemble, record, dispatch, finalize. Уникайте журналювання сирих тіл; використовуйте MessageFacts.preview для коротких редагованих попередніх переглядів.

Що залишається локальним для каналу

Ядро володіє оркестрацією. Канал усе ще володіє:
  • Транспортами платформи (gateway, REST, websocket, polling, webhooks)
  • Розв’язанням ідентичності та зіставленням відображуваних імен
  • Нативними командами, slash-командами, автодоповненням, модальними вікнами, кнопками, голосовим станом
  • Рендерингом карток, модальних вікон і adaptive-card
  • Авторизацією медіа, правилами CDN, зашифрованими медіа, транскрипцією
  • API редагування, реакцій, редагування/приховування та присутності
  • Backfill і отриманням історії на боці платформи
  • Потоками спарування, які потребують специфічної для платформи перевірки
Якщо двом каналам починає бути потрібен той самий helper для одного з цих пунктів, винесіть спільний SDK helper замість проштовхування його в ядро.

Стабільність

runtime.channel.turn.* є частиною публічної поверхні runtime Plugin. Типи фактів (SenderFacts, ConversationFacts, RouteFacts, ReplyPlanFacts, AccessFacts, MessageFacts, SupplementalContextFacts, InboundMediaFacts) і форми допуску (ChannelTurnAdmission, ChannelEventClass) доступні через PluginRuntime з openclaw/plugin-sdk/core. Застосовуються правила зворотної сумісності: нові поля фактів є додатковими, типи допуску не перейменовуються, а назви точок входу залишаються стабільними. Нові потреби каналу, які вимагають неадитивної зміни, мають проходити через процес міграції SDK Plugin.

Пов’язане