Vai al contenuto principale

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.

I plugin di canale dovrebbero esporre un adapter message da openclaw/plugin-sdk/channel-message. L’adapter descrive il ciclo di vita dei messaggi nativo supportato dalla piattaforma:
receive -> route and record -> agent turn -> durable final send
send -> render batch -> platform I/O -> receipt -> lifecycle side effects
live preview -> final edit or fallback -> receipt
Il core possiede accodamento, durabilità, criteri generici di retry, hook, ricevute e lo strumento message condiviso. Il plugin possiede chiamate native di invio/modifica/eliminazione, normalizzazione del target, threading della piattaforma, citazioni selezionate, flag di notifica, stato dell’account ed effetti collaterali specifici della piattaforma. Usa questa pagina insieme a Creare plugin di canale. Il sottopercorso channel-message è intenzionalmente abbastanza leggero per i file di bootstrap dei plugin a caldo, come channel.ts: espone contratti degli adapter, prove di capability, ricevute e facciate di compatibilità senza caricare la consegna in uscita. Gli helper di consegna runtime sono disponibili da openclaw/plugin-sdk/channel-message-runtime per percorsi di monitoraggio/invio che stanno già eseguendo I/O di messaggi asincrono.

Adapter minimale

La maggior parte dei nuovi plugin di canale può iniziare con un piccolo adapter:
import {
  defineChannelMessageAdapter,
  createMessageReceiptFromOutboundResults,
} from "openclaw/plugin-sdk/channel-message";

export const demoMessageAdapter = defineChannelMessageAdapter({
  id: "demo",
  durableFinal: {
    capabilities: {
      text: true,
      replyTo: true,
      thread: true,
      messageSendingHooks: true,
    },
  },
  send: {
    text: async ({ cfg, to, text, accountId, replyToId, threadId, signal }) => {
      const sent = await sendDemoMessage({
        cfg,
        to,
        text,
        accountId: accountId ?? undefined,
        replyToId: replyToId ?? undefined,
        threadId: threadId == null ? undefined : String(threadId),
        signal,
      });

      return {
        receipt: createMessageReceiptFromOutboundResults({
          results: [{ channel: "demo", messageId: sent.id, conversationId: to }],
          kind: "text",
          threadId: threadId == null ? undefined : String(threadId),
          replyToId: replyToId ?? undefined,
        }),
      };
    },
  },
});
Poi collegalo al plugin di canale:
export const demoPlugin = createChatChannelPlugin({
  base: {
    id: "demo",
    message: demoMessageAdapter,
    // other channel plugin fields
  },
});
Dichiara solo le capability che l’adapter preserva davvero. Ogni capability dichiarata dovrebbe avere un test di contratto.

Bridge in uscita

Se il canale ha già un adapter outbound compatibile, preferisci derivare l’adapter dei messaggi invece di duplicare il codice di invio:
import { createChannelMessageAdapterFromOutbound } from "openclaw/plugin-sdk/channel-message";

const demoMessageAdapter = createChannelMessageAdapterFromOutbound({
  id: "demo",
  outbound: demoOutboundAdapter,
});
Il bridge converte i vecchi risultati di invio outbound in valori MessageReceipt. Il nuovo codice dovrebbe passare le ricevute end to end e derivare gli ID legacy solo ai margini di compatibilità con listMessageReceiptPlatformIds(...) o resolveMessageReceiptPrimaryId(...). Se non viene fornito alcun criterio di ricezione, createChannelMessageAdapterFromOutbound(...) usa il criterio di conferma ricezione manual. Questo rende esplicita la conferma della piattaforma posseduta dal plugin senza modificare i canali che confermano webhook, socket o offset di polling fuori dal contesto di ricezione generico.

Invii dello strumento Message

Il percorso condiviso message(action="send") dovrebbe usare lo stesso ciclo di vita di consegna del core delle risposte finali. Se un canale richiede una modellazione specifica del provider per l’invio dello strumento, implementa actions.prepareSendPayload(...) invece di inviare da actions.handleAction(...). prepareSendPayload(...) riceve il ReplyPayload normalizzato dal core più il contesto completo dell’azione. Restituisci un payload con dati specifici del canale in payload.channelData.<channel> e lascia che il core chiami sendMessage(...), deliverOutboundPayloads(...), la coda write-ahead, gli hook di invio messaggi, retry, recovery e pulizia dell’ack. Restituisci null solo quando l’invio non può essere rappresentato come payload durabile, per esempio perché contiene una factory di componenti non serializzabile. Il core manterrà il fallback dell’azione plugin legacy per compatibilità, ma le nuove funzionalità di invio del canale dovrebbero essere esprimibili come dati di payload durabili.
export const demoActions: ChannelMessageActionAdapter = {
  describeMessageTool: () => ({ actions: ["send"], capabilities: ["presentation"] }),
  prepareSendPayload: ({ ctx, payload }) => {
    if (ctx.action !== "send") {
      return null;
    }
    return {
      ...payload,
      channelData: {
        ...payload.channelData,
        demo: {
          ...(payload.channelData?.demo as object | undefined),
          nativeCard: ctx.params.card,
        },
      },
    };
  },
};
L’adapter outbound legge quindi payload.channelData.demo dentro sendPayload. Questo mantiene il rendering specifico della piattaforma nel plugin mentre il core continua a possedere persistenza, retry, recovery, hook e ack. I payload preparati message(action="send") e la consegna generica della risposta finale usano la consegna del core con accodamento best-effort per impostazione predefinita. L’accodamento durabile richiesto è valido solo dopo che il core verifica che il canale possa riconciliare un invio il cui esito è ignoto dopo un crash. Se l’adapter non può implementare reconcileUnknownSend, mantieni il percorso di invio preparato best-effort; il core tenterà comunque la coda write-ahead, ma la persistenza della coda o il recovery incerto dopo crash non fa parte del contratto di consegna richiesto.

Capability finali durabili

La consegna finale durabile è opt-in per effetto collaterale. Il core userà la consegna durabile generica solo quando l’adapter dichiara ogni capability necessaria per il payload e per le opzioni di consegna.
CapabilityDichiara quando
textL’adapter può inviare testo e restituire una ricevuta.
mediaGli invii multimediali restituiscono ricevute per ogni messaggio visibile della piattaforma.
payloadL’adapter preserva la semantica del payload di risposta ricco, non solo testo e un URL multimediale.
replyToI target di risposta nativi raggiungono la piattaforma.
threadI target nativi di thread, argomento o thread di canale raggiungono la piattaforma.
silentLa soppressione delle notifiche raggiunge la piattaforma.
nativeQuoteI metadati della citazione selezionata raggiungono la piattaforma.
messageSendingHooksGli hook di invio messaggi del core possono annullare o riscrivere il contenuto prima dell’I/O della piattaforma.
batchI batch renderizzati in più parti sono riproducibili come un unico piano durabile.
reconcileUnknownSendL’adapter può risolvere il recovery unknown_after_send senza replay cieco.
afterSendSuccessGli effetti collaterali after-send locali del canale vengono eseguiti una volta.
afterCommitGli effetti collaterali after-commit locali del canale vengono eseguiti una volta.
La consegna finale best-effort non richiede reconcileUnknownSend; usa il ciclo di vita condiviso quando l’adapter preserva la semantica visibile del payload e ripiega su I/O diretto della piattaforma se la persistenza della coda non è disponibile. La consegna finale durabile richiesta deve richiedere esplicitamente reconcileUnknownSend. Se l’adapter non può determinare se un invio avviato/ignoto abbia raggiunto la piattaforma, non dichiarare quella capability; il core rifiuterà la consegna durabile richiesta prima dell’accodamento. Quando un chiamante richiede consegna durabile, deriva i requisiti invece di creare mappe manualmente:
import { deriveDurableFinalDeliveryRequirements } from "openclaw/plugin-sdk/channel-message";

const requiredCapabilities = deriveDurableFinalDeliveryRequirements({
  payload,
  replyToId,
  threadId,
  silent,
  payloadTransport: true,
  extraCapabilities: {
    nativeQuote: hasSelectedQuote(payload),
  },
});
messageSendingHooks è richiesto per impostazione predefinita. Imposta messageSendingHooks: false solo per un percorso che intenzionalmente non può eseguire hook globali di invio messaggi.

Contratto di invio durabile

Un invio finale durabile ha semantiche più rigorose rispetto alla consegna legacy posseduta dal canale:
  • Crea l’intento durabile prima dell’I/O della piattaforma.
  • Se la consegna durabile restituisce un risultato gestito, non ripiegare sull’invio legacy.
  • Tratta l’annullamento da hook e i risultati no-send come terminali.
  • Tratta unsupported come risultato pre-intento soltanto.
  • Per la durabilità richiesta, fallisci prima dell’I/O della piattaforma se la coda non può registrare che l’invio alla piattaforma è iniziato.
  • Per la consegna finale richiesta e gli invii preparati richiesti dello strumento message, esegui il preflight di reconcileUnknownSend; il recovery deve poter confermare un messaggio già inviato o rieseguire solo dopo che l’adapter prova che l’invio originale non è avvenuto.
  • Per best_effort, gli errori di scrittura in coda possono ripiegare su I/O diretto della piattaforma.
  • Inoltra i segnali di abort al caricamento dei media e agli invii della piattaforma.
  • Esegui gli hook after-commit dopo l’ack della coda; il fallback diretto best-effort li esegue dopo I/O della piattaforma riuscito perché non esiste alcun commit di coda durabile.
  • Restituisci ricevute per ogni ID di messaggio visibile della piattaforma.
  • Usa reconcileUnknownSend quando una piattaforma può controllare se un invio incerto abbia già raggiunto l’utente.
Questo contratto evita invii duplicati dopo crash ed evita di bypassare gli hook di annullamento dell’invio messaggi.

Ricevute

MessageReceipt è il nuovo record interno di ciò che la piattaforma ha accettato:
type MessageReceipt = {
  primaryPlatformMessageId?: string;
  platformMessageIds: string[];
  parts: MessageReceiptPart[];
  threadId?: string;
  replyToId?: string;
  editToken?: string;
  deleteToken?: string;
  sentAt: number;
  raw?: readonly MessageReceiptSourceResult[];
};
Usa createMessageReceiptFromOutboundResults(...) quando adatti un risultato di invio esistente. Usa createPreviewMessageReceipt(...) quando un messaggio di anteprima live diventa la ricevuta finale. Evita di aggiungere nuovi campi messageIds locali al proprietario. Il legacy ChannelDeliveryResult.messageIds viene ancora prodotto ai margini di compatibilità.

Anteprima live

I canali che trasmettono anteprime bozza o aggiornamenti di avanzamento dovrebbero dichiarare capability live:
const demoMessageAdapter = defineChannelMessageAdapter({
  id: "demo",
  live: {
    capabilities: {
      draftPreview: true,
      previewFinalization: true,
      progressUpdates: true,
      quietFinalization: true,
    },
    finalizer: {
      capabilities: {
        finalEdit: true,
        normalFallback: true,
        discardPending: true,
        previewReceipt: true,
        retainOnAmbiguousFailure: true,
      },
    },
  },
});
Usa defineFinalizableLivePreviewAdapter(...) e deliverWithFinalizableLivePreviewAdapter(...) per la finalizzazione runtime. Il finalizzatore decide se la risposta finale modifica l’anteprima sul posto, invia un fallback normale, elimina lo stato di anteprima in sospeso, mantiene una modifica fallita ambigua senza duplicare il messaggio e restituisce la ricevuta finale.

Criterio di ack in ricezione

I receiver inbound che controllano la tempistica della conferma della piattaforma dovrebbero dichiarare il criterio di ricezione:
const demoMessageAdapter = defineChannelMessageAdapter({
  id: "demo",
  receive: {
    defaultAckPolicy: "after_agent_dispatch",
    supportedAckPolicies: ["after_receive_record", "after_agent_dispatch"],
  },
});
Gli adapter che non dichiarano un criterio di ricezione usano per impostazione predefinita:
{
  receive: {
    defaultAckPolicy: "manual",
    supportedAckPolicies: ["manual"],
  },
}
Usa il valore predefinito quando la piattaforma non ha alcuna conferma da differire, conferma già prima dell’elaborazione asincrona o richiede semantiche di risposta specifiche del protocollo. Dichiara una delle policy a fasi solo quando il receiver usa effettivamente il contesto di ricezione per spostare più avanti la conferma della piattaforma. Policy:
PolicyUsare quando
after_receive_recordLa piattaforma può essere confermata dopo che l’evento in ingresso è stato analizzato e registrato.
after_agent_dispatchLa piattaforma deve attendere finché il dispatch dell’agent non è stato accettato.
after_durable_sendLa piattaforma deve attendere finché la consegna finale non ha una decisione durevole.
manualIl Plugin possiede la conferma perché le semantiche della piattaforma non corrispondono a una fase generica.
Usa createMessageReceiveContext(...) nei receiver che differiscono lo stato dell’ack, e shouldAckMessageAfterStage(...) quando il receiver deve verificare se una fase ha soddisfatto la policy configurata.

Test di contratto

Le dichiarazioni di capacità fanno parte del contratto del Plugin. Supportale con test:
import {
  verifyChannelMessageAdapterCapabilityProofs,
  verifyChannelMessageLiveCapabilityAdapterProofs,
  verifyChannelMessageLiveFinalizerProofs,
  verifyChannelMessageReceiveAckPolicyAdapterProofs,
} from "openclaw/plugin-sdk/channel-message";

it("backs declared message capabilities", async () => {
  await expect(
    verifyChannelMessageAdapterCapabilityProofs({
      adapterName: "demo",
      adapter: demoMessageAdapter,
      proofs: {
        text: async () => {
          const result = await demoMessageAdapter.send!.text!(textCtx);
          expect(result.receipt.platformMessageIds).toContain("msg-1");
        },
        replyTo: async () => {
          await demoMessageAdapter.send!.text!({ ...textCtx, replyToId: "parent-1" });
          expect(sendDemoMessage).toHaveBeenCalledWith(
            expect.objectContaining({
              replyToId: "parent-1",
            }),
          );
        },
        messageSendingHooks: () => {
          expect(demoMessageAdapter.durableFinal!.capabilities!.messageSendingHooks).toBe(true);
        },
      },
    }),
  ).resolves.toContainEqual({ capability: "text", status: "verified" });
});
Aggiungi suite di prova live e di ricezione quando l’adapter dichiara queste funzionalità. Una prova mancante dovrebbe far fallire il test invece di ampliare silenziosamente la superficie durevole.

API di compatibilità deprecate

Queste API restano importabili per la compatibilità con terze parti. Non usarle per nuovo codice di canale.
API deprecataSostituzione
openclaw/plugin-sdk/channel-reply-pipelineopenclaw/plugin-sdk/channel-message
createChannelTurnReplyPipeline(...)createChannelMessageReplyPipeline(...) per dispatcher di compatibilità, oppure un adapter message per nuovo codice di canale
deliverDurableInboundReplyPayload(...)deliverInboundReplyWithMessageSendContext(...) da openclaw/plugin-sdk/channel-message-runtime
dispatchInboundReplyWithBase(...)dispatchChannelMessageReplyWithBase(...) solo per dispatcher di compatibilità
recordInboundSessionAndDispatchReply(...)recordChannelMessageReplyDispatch(...) solo per dispatcher di compatibilità
resolveChannelSourceReplyDeliveryMode(...)resolveChannelMessageSourceReplyDeliveryMode(...)
deliverFinalizableDraftPreview(...)defineFinalizableLivePreviewAdapter(...) più deliverWithFinalizableLivePreviewAdapter(...)
DraftPreviewFinalizerDraftLivePreviewFinalizerDraft
DraftPreviewFinalizerResultLivePreviewFinalizerResult
I dispatcher di compatibilità possono ancora usare createReplyPrefixContext(...), createReplyPrefixOptions(...) e createTypingCallbacks(...) tramite la facade dei messaggi. Il nuovo codice di lifecycle dovrebbe evitare il vecchio sottopercorso channel-reply-pipeline.

Checklist di migrazione

  1. Aggiungi message: defineChannelMessageAdapter(...) o message: createChannelMessageAdapterFromOutbound(...) al Plugin di canale.
  2. Restituisci MessageReceipt dagli invii di testo, media e payload.
  3. Dichiara solo capacità supportate da comportamento nativo e test.
  4. Sostituisci le mappe dei requisiti durevoli scritte a mano con deriveDurableFinalDeliveryRequirements(...).
  5. Sposta la finalizzazione dell’anteprima tramite gli helper di anteprima live quando il canale modifica i messaggi bozza sul posto.
  6. Dichiara la policy di ack di ricezione solo quando il receiver può davvero differire la conferma della piattaforma.
  7. Mantieni gli helper di dispatch delle risposte legacy solo ai confini di compatibilità.