Pular para o conteúdo principal

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.

O kernel de turno de canal é a máquina de estados de entrada compartilhada que transforma um evento de plataforma normalizado em um turno de agente. Plugins de canal fornecem os fatos da plataforma e o callback de entrega. O core é dono da orquestração: ingerir, classificar, fazer preflight, resolver, autorizar, montar, registrar, despachar e finalizar. Use isto quando seu plugin estiver no caminho crítico de mensagens de entrada. Para eventos que não são mensagens (comandos de barra, modais, interações de botão, eventos de ciclo de vida, reações, estado de voz), mantenha-os locais ao plugin. O kernel só é dono de eventos que podem se tornar um turno de texto de agente.
O kernel é acessado pelo runtime de plugin injetado como runtime.channel.turn.*. O tipo do runtime de plugin é exportado de openclaw/plugin-sdk/core, portanto plugins nativos de terceiros podem usar esses pontos de entrada da mesma forma que plugins de canal incluídos fazem.

Por que um kernel compartilhado

Plugins de canal repetem o mesmo fluxo de entrada: normalizar, rotear, bloquear, construir um contexto, registrar metadados de sessão, despachar o turno do agente, finalizar o estado de entrega. Sem um kernel compartilhado, uma alteração no bloqueio por menção, em respostas visíveis somente de ferramentas, metadados de sessão, histórico pendente ou finalização de despacho precisa ser aplicada por canal. O kernel mantém quatro conceitos deliberadamente separados:
  • ConversationFacts: de onde a mensagem veio
  • RouteFacts: qual agente e sessão devem processá-la
  • ReplyPlanFacts: para onde respostas visíveis devem ir
  • MessageFacts: qual corpo e contexto suplementar o agente deve ver
DMs do Slack, tópicos do Telegram, threads do Matrix e sessões de tópico do Feishu distinguem todos esses na prática. Tratá-los como um único identificador causa divergência ao longo do tempo.

Ciclo de vida dos estágios

O kernel executa o mesmo pipeline fixo independentemente do canal:
  1. ingest — o adaptador converte um evento bruto da plataforma em NormalizedTurnInput
  2. classify — o adaptador declara se este evento pode iniciar um turno de agente
  3. preflight — o adaptador faz deduplicação, eco próprio, hidratação, debounce, descriptografia, pré-preenchimento parcial de fatos
  4. resolve — o adaptador retorna um turno totalmente montado (rota, plano de resposta, mensagem, entrega)
  5. authorize — políticas de DM, grupo, menção e comando aplicadas aos fatos montados
  6. assembleFinalizedMsgContext construído a partir dos fatos via buildContext
  7. record — metadados de sessão de entrada e última rota persistidos
  8. dispatch — turno do agente executado pelo despachante de blocos em buffer
  9. finalizeonFinalize do adaptador executa mesmo em erro de despacho
Cada estágio emite um evento de log estruturado quando um callback log é fornecido. Consulte Observabilidade.

Tipos de admissão

O kernel não lança erro quando um turno é bloqueado. Ele retorna um ChannelTurnAdmission:
TipoQuando
dispatchO turno é admitido. O turno do agente executa e o caminho de resposta visível é exercitado.
observeOnlyO turno executa de ponta a ponta, mas o adaptador de entrega não envia nada visível. Usado para agentes observadores de broadcast e outros fluxos passivos multiagente.
handledUm evento de plataforma foi consumido localmente (ciclo de vida, reação, botão, modal). O kernel pula o despacho.
dropCaminho de salto. Opcionalmente, recordHistory: true mantém a mensagem no histórico pendente do grupo para que uma menção futura tenha contexto.
A admissão pode vir de classify (a classe do evento disse que ele não pode iniciar um turno), de preflight (deduplicação, eco próprio, menção ausente com registro de histórico) ou do próprio resolveTurn.

Pontos de entrada

O runtime expõe três pontos de entrada preferenciais para que adaptadores possam optar pelo nível que corresponde ao canal.
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
Dois helpers de runtime mais antigos continuam disponíveis para compatibilidade com o Plugin SDK:
runtime.channel.turn.runResolved(...)      // deprecated compatibility alias; prefer run
runtime.channel.turn.dispatchAssembled(...) // deprecated compatibility alias; prefer run or runPrepared

run

Use quando seu canal puder expressar seu fluxo de entrada como um ChannelTurnAdapter<TRaw>. O adaptador tem callbacks para ingest, classify opcional, preflight opcional, resolveTurn obrigatório e onFinalize opcional.
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 é o formato certo quando o canal tem uma lógica pequena de adaptador e se beneficia de ser dono do ciclo de vida por meio de hooks.

runPrepared

Use quando o canal tiver um despachante local complexo com pré-visualizações, tentativas, edições ou bootstrap de thread que deve permanecer de propriedade do canal. O kernel ainda registra a sessão de entrada antes do despacho e expõe um DispatchedChannelTurnResult uniforme.
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();
  },
});
Canais ricos (Matrix, Mattermost, Microsoft Teams, Feishu, QQ Bot) usam runPrepared porque seu despachante orquestra comportamento específico da plataforma que o kernel não deve aprender.

buildContext

Uma função pura que mapeia pacotes de fatos para FinalizedMsgContext. Use-a quando seu canal implementar manualmente parte do pipeline, mas quiser um formato de contexto consistente.
const ctxPayload = runtime.channel.turn.buildContext({
  channel: "googlechat",
  accountId,
  messageId,
  timestamp,
  from,
  sender,
  conversation,
  route,
  reply,
  message,
  access,
  media,
  supplemental,
});
buildContext também é útil dentro de callbacks resolveTurn ao montar um turno para run.
Helpers obsoletos do SDK, como dispatchInboundReplyWithBase, ainda fazem ponte por meio de um helper de turno montado. Novo código de plugin deve usar run ou runPrepared.

Tipos de fatos

Os fatos que o kernel consome do seu adaptador são agnósticos à plataforma. Traduza objetos da plataforma para estes formatos antes de entregá-los ao kernel.

NormalizedTurnInput

CampoFinalidade
idID de mensagem estável usado para deduplicação e logs
timestampEpoch opcional em ms
rawTextCorpo como recebido da plataforma
textForAgentCorpo limpo opcional para o agente (remoção de menção, ajuste de digitação)
textForCommandsCorpo opcional usado para análise de /command
rawReferência opcional de passagem para callbacks de adaptador que precisam do original

ChannelEventClass

CampoFinalidade
kindmessage, command, interaction, reaction, lifecycle, unknown
canStartAgentTurnSe falso, o kernel retorna { kind: "handled" }
requiresImmediateAckDica para adaptadores que precisam confirmar com ACK antes do despacho

SenderFacts

CampoFinalidade
idID estável do remetente na plataforma
nameNome de exibição
usernameHandle se distinto de name
tagDiscriminador no estilo Discord ou tag da plataforma
rolesIDs de função, usados para correspondência de lista de permissões por função de membro
isBotVerdadeiro quando o remetente é um bot conhecido (o kernel usa para descartar)
isSelfVerdadeiro quando o remetente é o próprio agente configurado
displayLabelRótulo pré-renderizado para texto de envelope

ConversationFacts

CampoFinalidade
kinddirect, group ou channel
idID da conversa usado para roteamento
labelRótulo humano para o envelope
spaceIdIdentificador opcional do espaço externo (workspace do Slack, homeserver do Matrix)
parentIdID da conversa externa quando isto é uma thread
threadIdID da thread quando esta mensagem está dentro de uma thread
nativeChannelIdID de canal nativo da plataforma quando diferente do ID de roteamento
routePeerPeer usado para consulta resolveAgentRoute

RouteFacts

CampoFinalidade
agentIdAgente que deve tratar este turno
accountIdSobrescrita opcional (canais com várias contas)
routeSessionKeyChave de sessão usada para roteamento
dispatchSessionKeyChave de sessão usada no despacho quando diferente da chave de rota
persistedSessionKeyChave de sessão gravada nos metadados de sessão persistidos
parentSessionKeyPai para sessões ramificadas/em thread
modelParentSessionKeyPai no lado do modelo para sessões ramificadas
mainSessionKeyFixação do proprietário da DM principal para conversas diretas
createIfMissingPermite que a etapa de registro crie uma linha de sessão ausente

ReplyPlanFacts

CampoFinalidade
toDestino lógico de resposta gravado no contexto To
originatingToDestino de contexto de origem (OriginatingTo)
nativeChannelIdID de canal nativo da plataforma para entrega
replyTargetDestino final da resposta visível se diferir de to
deliveryTargetSobrescrita de entrega de nível inferior
replyToIdID da mensagem citada/ancorada
replyToIdFullID citado em formato completo quando a plataforma tem ambos
messageThreadIdID da thread no momento da entrega
threadParentIdID da mensagem pai da thread
sourceReplyDeliveryModethread, reply, channel, direct ou none

AccessFacts

AccessFacts carrega os booleanos de que o estágio de autorização precisa. A correspondência de identidade permanece no canal: o kernel consome apenas o resultado.
CampoFinalidade
dmDecisão de permissão/pareamento/negação de DM e lista allowFrom
groupPolítica de grupo, permissão de rota, permissão de remetente, allowlist, exigência de menção
commandsAutorização de comandos entre autorizadores configurados
mentionsSe a detecção de menção é possível e se o agente foi mencionado

MessageFacts

CampoFinalidade
bodyCorpo final do envelope (formatado)
rawBodyCorpo bruto de entrada
bodyForAgentCorpo que o agente vê
commandBodyCorpo usado para análise de comandos
envelopeFromRótulo de remetente pré-renderizado para o envelope
senderLabelSobrescrita opcional para o remetente renderizado
previewPrévia curta redigida para logs
inboundHistoryEntradas recentes do histórico de entrada quando o canal mantém um buffer

SupplementalContextFacts

O contexto suplementar abrange contexto de citação, encaminhamento e inicialização de thread. O kernel aplica a política contextVisibility configurada. O adaptador de canal fornece apenas fatos e sinalizadores senderAllowed, para que a política entre canais permaneça consistente.

InboundMediaFacts

Mídia é modelada como fatos. Download da plataforma, autenticação, política de SSRF, regras de CDN e descriptografia permanecem locais ao canal. O kernel mapeia fatos para MediaPath, MediaUrl, MediaType, MediaPaths, MediaUrls, MediaTypes e MediaTranscribedIndexes.

Contrato do adaptador

Para run completo, o formato do adaptador é:
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 retorna um ChannelTurnResolved, que é um AssembledChannelTurn com um tipo de admissão opcional. Retornar { admission: { kind: "observeOnly" } } executa o turno sem produzir saída visível. O adaptador ainda é dono do callback de entrega; ele apenas se torna um no-op para esse turno. onFinalize é executado em todo resultado, incluindo erros de despacho. Use-o para limpar histórico de grupo pendente, remover reações de confirmação, parar indicadores de status e liberar estado local.

Adaptador de entrega

O kernel não chama a plataforma diretamente. O canal entrega ao kernel um 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 é chamado uma vez por chunk de resposta em buffer. Retorne IDs de mensagens da plataforma quando o canal os tiver, para que o despachante possa preservar âncoras de thread e editar chunks posteriores. Para turnos apenas de observação, retorne { visibleReplySent: false } ou use createNoopChannelTurnDeliveryAdapter().

Opções de registro

O estágio de registro envolve recordInboundSession. A maioria dos canais pode usar os padrões. Sobrescreva via record:
record: {
  groupResolution,
  createIfMissing: true,
  updateLastRoute,
  onRecordError: (err) => log.warn("record failed", err),
  trackSessionMetaTask: (task) => pendingTasks.push(task),
}
O despachante aguarda o estágio de registro. Se o registro lançar erro, o kernel executa onPreDispatchFailure (quando fornecido a runPrepared) e relança.

Observabilidade

Cada estágio emite um evento estruturado quando um callback log é fornecido:
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,
    });
  },
});
Estágios registrados: ingest, classify, preflight, resolve, authorize, assemble, record, dispatch, finalize. Evite registrar corpos brutos; use MessageFacts.preview para prévias curtas redigidas.

O que permanece local ao canal

O kernel é dono da orquestração. O canal ainda é dono de:
  • Transportes da plataforma (Gateway, REST, websocket, polling, Webhooks)
  • Resolução de identidade e correspondência de nome de exibição
  • Comandos nativos, comandos de barra, preenchimento automático, modais, botões, estado de voz
  • Renderização de cards, modais e adaptive cards
  • Autenticação de mídia, regras de CDN, mídia criptografada, transcrição
  • APIs de edição, reação, redação e presença
  • Backfill e busca de histórico do lado da plataforma
  • Fluxos de pareamento que exigem verificação específica da plataforma
Se dois canais começarem a precisar do mesmo auxiliar para um destes, extraia um auxiliar compartilhado do SDK em vez de colocá-lo no kernel.

Estabilidade

runtime.channel.turn.* faz parte da superfície pública de runtime de Plugin. Os tipos de fatos (SenderFacts, ConversationFacts, RouteFacts, ReplyPlanFacts, AccessFacts, MessageFacts, SupplementalContextFacts, InboundMediaFacts) e os formatos de admissão (ChannelTurnAdmission, ChannelEventClass) são acessíveis por meio de PluginRuntime a partir de openclaw/plugin-sdk/core. Aplicam-se regras de compatibilidade retroativa: novos campos de fatos são aditivos, tipos de admissão não são renomeados e os nomes dos pontos de entrada permanecem estáveis. Novas necessidades de canal que exigirem uma alteração não aditiva devem passar pelo processo de migração do SDK de Plugin.

Relacionados