Messages and delivery
Herstructurering van de berichtlevenscyclus
Waarom deze refactor is uitgevoerd
De kanaalstack groeide vanuit verschillende lokale oplossingen: afzonderlijke helpers voor inkomende berichten per
volwassenheidsniveau (runtime.channel.inbound.run voor eenvoudige adapters,
runtime.channel.inbound.runPreparedReply voor uitgebreide adapters), verouderde helpers voor het doorsturen van antwoorden
(dispatchInboundReplyWithBase, recordInboundSessionAndDispatchReply),
kanaalspecifieke previewstreaming en duurzaamheid van de uiteindelijke aflevering die achteraf werd toegevoegd aan
bestaande paden voor antwoordpayloads. Die structuur leidde tot te veel openbare concepten en
te veel plaatsen waar de afleveringssemantiek uiteen kon lopen.
Het betrouwbaarheidsprobleem dat het herontwerp noodzakelijk maakte:
Telegram-pollingupdate bevestigd -> definitieve tekst van de assistent bestaat -> proces wordt opnieuw gestart voordat sendMessage slaagt -> definitief antwoord gaat verlorenBeoogde invariant: zodra de kern bepaalt dat een zichtbaar uitgaand bericht moet bestaan, moet de verzendintentie duurzaam zijn vastgelegd voordat de platformaanroep wordt geprobeerd, en moet het platformontvangstbewijs na succes worden vastgelegd. Dit levert standaard herstel met ten minste één aflevering op. Gedrag met exact één aflevering bestaat alleen wanneer een adapter native idempotentie aantoont of een poging met een onbekende uitkomst na verzending vergelijkt met de platformstatus voordat deze opnieuw wordt uitgevoerd.
Wat is uitgebracht
Het interne domein bevindt zich in src/channels/message/*:
| Bestand | Verantwoordelijk voor |
|---|---|
types.ts |
Typecontracten voor adapters, verzendcontexten, ontvangstbewijzen en duurzame intenties |
send.ts |
withDurableMessageSendContext / sendDurableMessageBatch — de duurzame verzendcontext |
receive.ts |
createMessageReceiveContext — toestandsmachine voor beleid rond bevestiging van inkomende berichten |
live.ts |
Status van live previews en logica voor ter plaatse afronden of terugvallen |
state.ts |
classifyDurableSendRecoveryState — herstelclassificatie na een onderbreking |
receipt.ts |
Normaliseert platformresultaten van verzending naar MessageReceipt |
capabilities.ts |
Leidt de vereiste mogelijkheden voor duurzame definitieve aflevering af uit een payload |
contracts.ts |
Verificatie van contractbewijs voor gedeclareerde adaptermogelijkheden |
adapter.ts |
defineChannelMessageAdapter |
outbound-bridge.ts |
createChannelMessageAdapterFromOutbound — omhult verouderde functies sendText/sendMedia/sendPayload/sendPoll |
ingress-queue.ts |
createChannelIngressQueue — duurzame wachtrij voor inkomende gebeurtenissen |
durable-receive.ts |
createDurableInboundReceiveJournal — journaal voor accepteren/in behandeling/voltooien/vrijgeven bij deduplicatie van inkomende berichten |
inbound-reply-dispatch.ts |
dispatchChannelInboundReply en wrappers met verouderde namen |
reply-pipeline.ts |
createChannelReplyPipeline, helpers voor antwoordvoorvoegsels en callbacks voor type-indicaties |
Openbaar oppervlak: openclaw/plugin-sdk/channel-outbound (helpers voor verzending/ontvangstbewijzen/duurzaamheid/live previews/antwoordpijplijnen)
en openclaw/plugin-sdk/channel-inbound (context voor inkomende berichten, runChannelInboundEvent,
dispatchChannelInboundReply). Raadpleeg die pagina's voor adaptervoorbeelden, huidige
typenamen en migratieopmerkingen — zij zijn de gezaghebbende bron voor de API-structuur,
niet de onderstaande schetsen.
Verzendcontext
withDurableMessageSendContext biedt kanaalcode de stappen render, previewUpdate,
send, edit, delete, commit en fail rond één uitgaand
bericht. sendDurableMessageBatch is de wrapper voor het gebruikelijke geval: renderen, verzenden
en vervolgens vastleggen bij sent/suppressed, of als mislukt markeren bij een fout.
sendDurableMessageBatch retourneert één gediscrimineerd resultaat:
| Status | Betekenis |
|---|---|
sent |
Ten minste één zichtbaar platformbericht is afgeleverd |
suppressed |
Geen enkel platformbericht moet als ontbrekend worden beschouwd (geannuleerd door hook, simulatie enzovoort) |
partial_failed |
Ten minste één bericht is afgeleverd voordat een latere payload of bijwerking mislukte |
failed |
Er is geen platformontvangstbewijs geproduceerd |
Duurzaamheid is required, best_effort of disabled
(MessageDurabilityPolicy in src/channels/message/types.ts). required
stopt veilig wanneer de duurzame intentie niet kan worden geschreven; best_effort valt
terug op rechtstreekse verzending wanneer persistentie niet beschikbaar is; disabled behoudt het
gedrag van rechtstreekse verzending van vóór de refactor. Verouderde compatibiliteitshelpers gebruiken standaard
disabled en leiden niet automatisch required af enkel omdat een kanaal een generieke
adapter voor uitgaande berichten heeft.
De grens die gevaarlijk blijft: nadat de platformaanroep slaagt en voordat
het ontvangstbewijs wordt vastgelegd. Als het proces daar stopt, kan de kern niet weten of het
platformbericht bestaat, tenzij de adapter reconcileUnknownSend declareert.
Die hook classificeert een onderbroken verzending als sent, not_sent of
unresolved; alleen not_sent staat opnieuw uitvoeren toe. Kanalen zonder reconciliatie
vallen terug op de status unknown_after_send (src/channels/message/state.ts,
src/infra/outbound/delivery-queue-recovery.ts) en mogen alleen kiezen voor opnieuw uitvoeren met
ten minste één aflevering als dubbele zichtbare berichten een aanvaardbare, gedocumenteerde
afweging voor dat kanaal zijn.
Ontvangstcontext
createMessageReceiveContext houdt de bevestigings-/afwijzingsstatus per inkomende gebeurtenis bij met een
idempotente ack() en expliciete nack(error). Het bevestigingsbeleid
(ChannelMessageReceiveAckPolicy) is een van:
| Beleid | Bevestigt wanneer |
|---|---|
after_receive_record |
De kern voldoende metadata van het inkomende bericht heeft opgeslagen om een herlevering te dedupliceren/routeren |
after_agent_dispatch |
De agentuitvoering is gestart |
after_durable_send |
De duurzame uitgaande verzending voor deze beurt is vastgelegd |
manual |
De aanroeper bepaalt expliciet het bevestigingsmoment (standaard voor adapters die geen beleid declareren) |
Telegram-polling gebruikt dit om een watermerk voor veilig voltooide updates op te slaan
(safeCompletedUpdateId in extensions/telegram/src/bot-update-tracker.ts):
grammY observeert nog steeds elke update wanneer deze de middlewareketen binnenkomt, maar
OpenClaw verplaatst het opgeslagen watermerk voor herstarts alleen voorbij updates waarvan
het doorsturen is voltooid, zodat mislukte of nog in behandeling zijnde updates na een herstart opnieuw worden uitgevoerd.
De upstream-offset van Telegram voor getUpdates blijft eigendom van grammY; een volledig
duurzame pollingbron die herlevering op platformniveau voorbij dit
watermerk beheert, is niet gebouwd (zie Openstaande vragen).
Live preview
src/channels/message/live.ts modelleert preview/bewerken/afronden als één levenscyclus:
createLiveMessageState, markLiveMessagePreviewUpdated,
markLiveMessageFinalized, markLiveMessageCancelled en
deliverFinalizableLivePreviewAdapter (een definitieve bewerking opbouwen vanuit een concept, deze
toepassen en terugvallen op een normale verzending wanneer bewerken niet mogelijk is of mislukt).
LiveMessageState.phase is idle | previewing | finalizing | finalized | cancelled; canFinalizeInPlace bepaalt of een preview via een bewerking het definitieve
bericht kan worden in plaats van via een nieuwe verzending.
Duurzame ontvangstbewijzen
MessageReceipt (src/channels/message/types.ts) normaliseert een of meer
platformbericht-id's van één logische verzending naar platformMessageIds plus
parts per onderdeel (soort, index, thread-id, antwoord-op-id). Een primaire id blijft behouden
voor threads en latere bewerkingen. Hierdoor kunnen afleveringen met meerdere onderdelen (tekst
plus media, opgesplitste tekst, terugval voor kaarten) na een herstart opnieuw worden uitgevoerd en
gededupliceerd.
Verkleining van de openbare SDK
De refactor heeft het volgende opgenomen of afgeschaft: reply-runtime, reply-dispatch-runtime,
reply-reference, reply-chunking, reply-payload-helpers die als openbare
API werden aangeboden, inbound-reply-dispatch, channel-reply-pipeline en het merendeel van de openbare toepassingen
van outbound-runtime. src/plugin-sdk/channel-message.ts is nu een
@deprecated barrelbestand voor herexports dat verwijst naar channel-outbound /
channel-inbound; runtime-aliassen van channel.turn zijn verwijderd en de oude
documentatiepagina /plugins/sdk-channel-turn verwijst door naar
API voor inkomende kanalen. Nieuwe Plugincode moet
rechtstreeks gericht zijn op channel-outbound en channel-inbound.
Waar de implementatie afweek van het oorspronkelijke ontwerp
De onderstaande ontwerpschets is nooit letterlijk zoals beschreven uitgebracht. Deze wordt bewaard voor historische nauwkeurigheid; beschouw deze typenamen niet als de huidige API.
- Geen
MessageOrigin/shouldDropOpenClawEcho. Het oorspronkelijke plan voorzag in een oorsprongstagsource: "openclaw"op berichten over Gateway-fouten, plus een gedeeld predicaat dat getagde, door bots geschreven echo's in gedeelde ruimten verwijdert vóórallowBots-autorisatie. Dat type en predicaat bestaan niet in de codebase.allowBotszelf is een echte configuratiesleutel per kanaal (Slack, Discord, Google Chat en andere), maar het mechanisme voor oorsprongstags dat deze moest beschermen, is nooit gebouwd. Onderdrukking van echo's van Gateway-fouten in ruimten waarin bots zijn ingeschakeld, blijft een openstaand tekort en is geen uitgebrachte garantie. - Geen uniforme naamruimte
core.messages.receive/send/live/state. De uitgebrachte functies bevinden zich rechtstreeks insrc/channels/message/*(withDurableMessageSendContext,createMessageReceiveContext,createLiveMessageState,classifyDurableSendRecoveryState) in plaats van achter een facadecore.messages.*. - Geen generiek genormaliseerd berichttype
ChannelMessage/MessageTarget/MessageRelation. De kern geeft nog steeds concrete antwoordpayloads (ReplyPayload) en kanaalspecifieke contexten door aan de verzendadapters, in plaats van één platformneutrale berichtstructuur met een relatiekind: "reply" | "followup" | "broadcast" | "system". - De namen van het bevestigingsbeleid wijken af van de schets. Uitgebracht:
after_receive_record | after_agent_dispatch | after_durable_send | manual. De oorspronkelijke schets gebruikteimmediate | after-record | after-durable-send | manualmet een redenveld voor webhooktime-outs; die structuur is niet gebouwd. - Mogelijkheidssleutels van
DurableFinalDeliveryRequirementMapvervingen het geschetste objectMessageCapabilities. Mogelijkheden zijn platte booleaanse vlaggen (text,media,poll,payload,silent,replyTo,thread,nativeQuote,messageSendingHooks,batch,reconcileUnknownSend,afterSendSuccess,afterCommit) die worden geverifieerd viaverifyDurableFinalCapabilityProofs, in plaats van een geneste structuur in de stijl vantext.chunking/attachments.voice.
Concrete migratierisico's (nog steeds relevant)
Deze kanaalspecifieke neveneffecten bestonden al vóór de refactor en moeten via de nieuwe verzendpaden blijven werken. Ze zijn niet hypothetisch: elk ervan is momenteel geïmplementeerd en essentieel voor de werking.
- iMessage (
extensions/imessage/src/monitor/echo-cache.ts,persisted-echo-cache.ts): de monitor registreert verzonden berichten na een geslaagde verzending in een echo- cache. Permanente definitieve verzendingen moeten die cache nog steeds vullen, anders kan OpenClaw zijn eigen antwoorden opnieuw verwerken als inkomende gebruikersberichten. - Tlon (
extensions/tlon/src/monitor/index.ts): voegt een optionele model- handtekening toe en registreert threads waaraan is deelgenomen na antwoorden in groepen. Permanente bezorging mag deze effecten niet omzeilen. - Discord en andere voorbereide dispatchers beheren al rechtstreekse bezorging en voorbeeldgedrag. Een kanaal is pas end-to-end permanent wanneer de voorbereide dispatcher definitieve berichten expliciet via de verzendcontext routeert; ga niet uit van dekking door alleen de generieke adapter.
- Stille fallbackbezorging van Telegram moet de volledige geprojecteerde payload-array bezorgen, niet alleen de eerste payload, na opdeling/fallback- projectie.
- LINE, Zalo, Nostr en vergelijkbare hulppaden kunnen verwerking van antwoordtokens, mediaproxying, caches voor verzonden berichten of doelen die alleen via callbacks bereikbaar zijn bevatten. Ze blijven kanaalgestuurde bezorging gebruiken totdat deze semantiek door de verzendadapter wordt ondersteund en door tests wordt gedekt.
- Hulpfuncties voor rechtstreekse privéberichten kunnen een antwoordcallback hebben die het enige juiste transportdoel is. Generieke uitgaande verzending mag geen doel afleiden uit ruwe platformvelden en die callback overslaan.
Classificatie van fouten
Adapters classificeren transportfouten in gesloten categorieën in de stijl van
DeliveryFailureKind (tijdelijk, snelheidslimiet, authenticatie, toestemming, niet gevonden, ongeldige
payload, conflict, geannuleerd, onbekend). Kernbeleid:
- Probeer tijdelijke fouten en fouten door snelheidslimieten opnieuw.
- Probeer fouten door een ongeldige payload niet opnieuw, tenzij er een renderfallback bestaat.
- Probeer authenticatie- of toestemmingsfouten niet opnieuw totdat de configuratie verandert.
- Laat bij niet-gevonden de livefinalisatie terugvallen van bewerken naar een nieuwe verzending wanneer het kanaal aangeeft dat dit veilig is.
- Gebruik bij een conflict ontvangstbewijs-/idempotentiestatus om te bepalen of het bericht al bestaat.
- Elke fout nadat de platformaanroep mogelijk is geslaagd, maar voordat het ontvangstbewijs is
vastgelegd, wordt
unknown_after_send, tenzij de adapter bewijst dat de platformbewerking niet heeft plaatsgevonden.
Openstaande vragen
- Of Telegram uiteindelijk de polling-
runner van grammY (
1.43.0) moet vervangen door een volledig permanente pollingbron die herbezorging op platformniveau beheert, en niet alleen OpenClaws blijvende herstartwatermerk (safeCompletedUpdateId). - Of de status van het livevoorbeeld in dezelfde record als de intentie voor de definitieve verzending moet worden opgeslagen, of in een afzonderlijke opslag voor livestatus.
- Of echo-onderdrukking bij Gateway-fouten in gedeelde ruimten met bots het oorspronkelijk geplande mechanisme voor oorsprongstags nodig heeft, een eenvoudiger kanaalspecifiek contract, of buiten het bereik valt.
- Welke kanalen systeemeigen ondersteuning voor oorsprong/metadata hebben voor echo- onderdrukking tussen bots en welke een permanent uitgaand register nodig hebben.