Messages and delivery

การปรับโครงสร้างวงจรชีวิตของข้อความ

เหตุผลที่เกิดการปรับโครงสร้างนี้

สแต็กช่องทางเติบโตมาจากการแก้ไขเฉพาะจุดหลายครั้ง ได้แก่ ตัวช่วยขาเข้าที่แยกกันตาม ระดับความสมบูรณ์ (runtime.channel.inbound.run สำหรับอะแดปเตอร์แบบเรียบง่าย และ runtime.channel.inbound.runPreparedReply สำหรับอะแดปเตอร์ที่มีความสามารถสูง) ตัวช่วยจัดส่งการตอบกลับแบบเดิม (dispatchInboundReplyWithBase, recordInboundSessionAndDispatchReply) การสตรีมตัวอย่างแสดงผลล่วงหน้าเฉพาะช่องทาง และความทนทานของการส่งขั้นสุดท้ายที่ถูกต่อเพิ่มเข้ากับพาธเพย์โหลดการตอบกลับเดิม โครงสร้างดังกล่าวทำให้มีแนวคิดสาธารณะมากเกินไป และมีจุดที่ความหมายเชิงการส่ง อาจคลาดเคลื่อนจากกันมากเกินไป

ช่องว่างด้านความน่าเชื่อถือที่บังคับให้ต้องออกแบบใหม่:

text
รับทราบอัปเดตจากการโพล Telegram แล้ว  -> มีข้อความสุดท้ายจากผู้ช่วย  -> กระบวนการเริ่มใหม่ก่อนที่ sendMessage จะสำเร็จ  -> การตอบกลับสุดท้ายสูญหาย

ข้อกำหนดคงสภาพเป้าหมาย: เมื่อ core ตัดสินใจว่าควรมีข้อความขาออกที่มองเห็นได้ เจตนาในการส่งต้องถูกจัดเก็บอย่างคงทนก่อนพยายามเรียกแพลตฟอร์ม และต้องบันทึก ใบตอบรับจากแพลตฟอร์มหลังส่งสำเร็จ วิธีนี้ทำให้กู้คืนแบบอย่างน้อยหนึ่งครั้งได้ โดยปริยาย พฤติกรรมแบบหนึ่งครั้งพอดีจะมีเฉพาะเมื่ออะแดปเตอร์พิสูจน์ได้ว่า แพลตฟอร์มรองรับการทำงานซ้ำโดยไม่เกิดผลซ้ำโดยกำเนิด หรือกระทบยอดความพยายาม ที่ไม่ทราบผลหลังส่งกับสถานะแพลตฟอร์มก่อนเล่นซ้ำ

สิ่งที่เผยแพร่แล้ว

โดเมนภายในอยู่ใน src/channels/message/*:

ไฟล์ ความรับผิดชอบ
types.ts สัญญาชนิดข้อมูลของอะแดปเตอร์ บริบทการส่ง ใบตอบรับ และเจตนาแบบคงทน
send.ts withDurableMessageSendContext / sendDurableMessageBatch — บริบทการส่งแบบคงทน
receive.ts createMessageReceiveContext — กลไกสถานะของนโยบายการรับทราบขาเข้า
live.ts สถานะตัวอย่างแสดงผลล่วงหน้าแบบสด และตรรกะสรุปผลในตำแหน่งเดิมหรือเปลี่ยนไปใช้วิธีสำรอง
state.ts classifyDurableSendRecoveryState — การจำแนกการกู้คืนหลังการหยุดชะงัก
receipt.ts ปรับผลลัพธ์การส่งของแพลตฟอร์มให้อยู่ในรูปแบบมาตรฐาน MessageReceipt
capabilities.ts อนุมานความสามารถที่จำเป็นสำหรับการส่งขั้นสุดท้ายแบบคงทนจากเพย์โหลด
contracts.ts ตรวจสอบหลักฐานตามสัญญาสำหรับความสามารถที่อะแดปเตอร์ประกาศ
adapter.ts defineChannelMessageAdapter
outbound-bridge.ts createChannelMessageAdapterFromOutbound — ครอบฟังก์ชันเดิม sendText/sendMedia/sendPayload/sendPoll
ingress-queue.ts createChannelIngressQueue — คิวเหตุการณ์ขาเข้าแบบคงทน
durable-receive.ts createDurableInboundReceiveJournal — บันทึกสถานะรับ/รอดำเนินการ/เสร็จสิ้น/ปล่อยสำหรับการขจัดข้อมูลขาเข้าซ้ำ
inbound-reply-dispatch.ts dispatchChannelInboundReply และตัวครอบที่ใช้ชื่อแบบเดิม
reply-pipeline.ts createChannelReplyPipeline ตัวช่วยคำนำหน้าการตอบกลับและคอลแบ็กสถานะกำลังพิมพ์

พื้นผิวสาธารณะ: openclaw/plugin-sdk/channel-outbound (ตัวช่วยการส่ง/ใบตอบรับ/ความคงทน/การทำงานสด/ไปป์ไลน์การตอบกลับ) และ openclaw/plugin-sdk/channel-inbound (บริบทขาเข้า runChannelInboundEvent และ dispatchChannelInboundReply) โปรดดูตัวอย่างอะแดปเตอร์ ชื่อชนิดข้อมูลปัจจุบัน และหมายเหตุการย้ายระบบในหน้าเหล่านั้น เพราะหน้าเหล่านั้นคือแหล่งข้อมูลจริงของ รูปแบบ API ไม่ใช่โครงร่างด้านล่าง

บริบทการส่ง

withDurableMessageSendContext มอบขั้นตอน render, previewUpdate, send, edit, delete, commit และ fail ให้โค้ดช่องทางสำหรับข้อความขาออก หนึ่งข้อความ sendDurableMessageBatch คือตัวครอบสำหรับกรณีทั่วไป: เรนเดอร์ ส่ง จากนั้นบันทึกเมื่อเป็น sent/suppressed หรือระบุว่าล้มเหลวเมื่อเกิดข้อผิดพลาด

sendDurableMessageBatch ส่งคืนผลลัพธ์แบบจำแนกได้หนึ่งรายการ:

สถานะ ความหมาย
sent ส่งข้อความบนแพลตฟอร์มที่มองเห็นได้อย่างน้อยหนึ่งข้อความสำเร็จ
suppressed ไม่ควรถือว่าข้อความบนแพลตฟอร์มหายไป (ถูกฮุกยกเลิก การทดลองทำงานโดยไม่ส่งจริง เป็นต้น)
partial_failed ส่งอย่างน้อยหนึ่งข้อความสำเร็จก่อนที่เพย์โหลดหรือผลข้างเคียงภายหลังจะล้มเหลว
failed ไม่มีใบตอบรับจากแพลตฟอร์ม

ระดับความคงทนเป็นค่าใดค่าหนึ่งระหว่าง required, best_effort หรือ disabled (MessageDurabilityPolicy ใน src/channels/message/types.ts) ค่า required จะหยุดอย่างปลอดภัยเมื่อลงบันทึกเจตนาแบบคงทนไม่ได้ ส่วน best_effort จะ ดำเนินการส่งโดยตรงเมื่อพื้นที่จัดเก็บถาวรใช้งานไม่ได้ และ disabled จะคง พฤติกรรมส่งโดยตรงก่อนการปรับโครงสร้างไว้ ตัวช่วยความเข้ากันได้แบบเดิมมีค่าเริ่มต้นเป็น disabled และจะไม่อนุมานเป็น required เพียงเพราะช่องทางมีอะแดปเตอร์ ขาออกทั่วไป

ขอบเขตที่ยังคงอันตรายคือช่วงหลังการเรียกแพลตฟอร์มสำเร็จแต่ก่อนบันทึก ใบตอบรับ หากกระบวนการหยุดทำงานในช่วงนั้น core จะไม่ทราบว่ามีข้อความอยู่บน แพลตฟอร์มหรือไม่ เว้นแต่อะแดปเตอร์จะประกาศ reconcileUnknownSend ฮุกดังกล่าวจำแนกการส่งที่ถูกขัดจังหวะเป็น sent, not_sent หรือ unresolved โดยมีเพียง not_sent เท่านั้นที่อนุญาตให้เล่นซ้ำ ช่องทางที่ไม่มี การกระทบยอดจะเปลี่ยนไปใช้สถานะ unknown_after_send (src/channels/message/state.ts, src/infra/outbound/delivery-queue-recovery.ts) และอาจเลือกเล่นซ้ำแบบ อย่างน้อยหนึ่งครั้งได้เฉพาะเมื่อข้อความที่มองเห็นได้ซ้ำกันเป็นข้อแลกเปลี่ยน ที่ยอมรับได้และมีการบันทึกไว้สำหรับช่องทางนั้น

บริบทการรับ

createMessageReceiveContext ติดตามสถานะการรับทราบ/ปฏิเสธการรับทราบต่อเหตุการณ์ขาเข้า แต่ละรายการด้วย ack() ที่เรียกซ้ำได้โดยไม่เกิดผลซ้ำ และ nack(error) แบบชัดเจน นโยบายการรับทราบ (ChannelMessageReceiveAckPolicy) เป็นค่าใดค่าหนึ่งต่อไปนี้:

นโยบาย รับทราบเมื่อ
after_receive_record Core จัดเก็บข้อมูลเมตาขาเข้าเพียงพอสำหรับขจัดการส่งซ้ำหรือกำหนดเส้นทางการส่งซ้ำ
after_agent_dispatch ส่งมอบการทำงานของเอเจนต์แล้ว
after_durable_send บันทึกการส่งขาออกแบบคงทนสำหรับรอบนี้แล้ว
manual ผู้เรียกควบคุมจังหวะการรับทราบอย่างชัดเจน (ค่าเริ่มต้นสำหรับอะแดปเตอร์ที่ไม่ได้ประกาศนโยบาย)

การโพลของ Telegram ใช้ส่วนนี้เพื่อจัดเก็บลายน้ำของอัปเดตที่เสร็จสมบูรณ์อย่างปลอดภัย (safeCompletedUpdateId ใน extensions/telegram/src/bot-update-tracker.ts): grammY ยังคงตรวจพบทุกอัปเดตเมื่อเข้าสู่สายโซ่มิดเดิลแวร์ แต่ OpenClaw จะเลื่อนลายน้ำสำหรับเริ่มใหม่ที่จัดเก็บไว้ให้ผ่านเฉพาะอัปเดตที่ ส่งมอบเสร็จแล้วเท่านั้น ดังนั้นอัปเดตที่ล้มเหลวหรือยังรอดำเนินการจะถูกเล่นซ้ำ หลังการเริ่มใหม่ ออฟเซ็ต getUpdates ต้นทางของ Telegram ยังคงอยู่ภายใต้ การควบคุมของ grammY และยังไม่มีการสร้างแหล่งการโพลแบบคงทนเต็มรูปแบบที่ควบคุม การส่งซ้ำระดับแพลตฟอร์มนอกเหนือจากลายน้ำนี้ (ดูคำถามที่ยังเปิดอยู่)

ตัวอย่างแสดงผลล่วงหน้าแบบสด

src/channels/message/live.ts จำลองตัวอย่างแสดงผลล่วงหน้า/การแก้ไข/การสรุปผล เป็นวงจรชีวิตเดียว ได้แก่ createLiveMessageState, markLiveMessagePreviewUpdated, markLiveMessageFinalized, markLiveMessageCancelled และ deliverFinalizableLivePreviewAdapter (สร้างการแก้ไขขั้นสุดท้ายจากฉบับร่าง นำการแก้ไขไปใช้ และเปลี่ยนไปส่งตามปกติ เมื่อแก้ไขไม่ได้หรือการแก้ไขล้มเหลว) LiveMessageState.phase เป็น idle | previewing | finalizing | finalized | cancelled ส่วน canFinalizeInPlace ควบคุมว่าตัวอย่างแสดงผลล่วงหน้าจะกลายเป็น ข้อความสุดท้ายด้วยการแก้ไขแทนการส่งข้อความใหม่ได้หรือไม่

ใบตอบรับแบบคงทน

MessageReceipt (src/channels/message/types.ts) ปรับรหัสข้อความของแพลตฟอร์ม หนึ่งรายการขึ้นไปจากการส่งเชิงตรรกะครั้งเดียวให้อยู่ในรูปแบบมาตรฐานเป็น platformMessageIds พร้อม parts แยกตามส่วน (ชนิด ดัชนี รหัสเธรด และรหัสข้อความที่ตอบกลับ) มีการเก็บรหัสหลักไว้สำหรับการจัดเธรดและการแก้ไขภายหลัง สิ่งนี้ทำให้การส่งแบบหลายส่วน (ข้อความพร้อมสื่อ ข้อความที่แบ่งเป็นช่วง และวิธีสำรองสำหรับการ์ด) สามารถเล่นซ้ำและขจัดรายการซ้ำได้หลังการเริ่มใหม่

การลดพื้นผิว SDK สาธารณะ

การปรับโครงสร้างนี้ได้รวมเข้าหรือเลิกใช้สิ่งต่อไปนี้: ตัวช่วย reply-runtime, reply-dispatch-runtime, reply-reference, reply-chunking, reply-payload ที่เปิดเผยเป็น API สาธารณะ, inbound-reply-dispatch, channel-reply-pipeline และการใช้งาน outbound-runtime สาธารณะส่วนใหญ่ ปัจจุบัน src/plugin-sdk/channel-message.ts เป็นบาร์เรลส่งออกซ้ำที่มีสถานะ @deprecated และชี้ไปยัง channel-outbound / channel-inbound นามแฝงรันไทม์ channel.turn ถูกนำออกแล้ว และหน้าเอกสารเดิม /plugins/sdk-channel-turn จะเปลี่ยนเส้นทางไปยัง API ขาเข้าของช่องทาง โค้ด Plugin ใหม่ควร อ้างอิง channel-outbound และ channel-inbound โดยตรง

จุดที่การนำไปใช้จริงแตกต่างจากการออกแบบเดิม

โครงร่างการออกแบบด้านล่างไม่เคยเผยแพร่ตามคำอธิบายอย่างตรงตัว เก็บบันทึกนี้ไว้ เพื่อความถูกต้องทางประวัติศาสตร์ อย่าถือว่าชื่อชนิดข้อมูลเหล่านี้เป็น API ปัจจุบัน

  • ไม่มี MessageOrigin / shouldDropOpenClawEcho แผนเดิมกำหนดให้ เพิ่มแท็กต้นทาง source: "openclaw" ในข้อความความล้มเหลวของ Gateway พร้อม เพรดิเคตร่วมที่ทิ้งเสียงสะท้อนซึ่งสร้างโดยบอตและมีแท็กในห้องที่ใช้ร่วมกัน ก่อนการอนุญาต allowBots ชนิดข้อมูลและเพรดิเคตดังกล่าวไม่มีอยู่ใน ฐานโค้ด ตัว allowBots เป็นคีย์การกำหนดค่าแยกตามช่องทางที่มีอยู่จริง (Slack, Discord, Google Chat และช่องทางอื่น) แต่กลไกติดแท็กต้นทางซึ่ง ตั้งใจใช้ปกป้องคีย์นี้ไม่เคยถูกสร้างขึ้น การระงับเสียงสะท้อนจากความล้มเหลว ของ Gateway ในห้องที่เปิดใช้บอตยังคงเป็นช่องว่างที่ค้างอยู่ ไม่ใช่ การรับประกันที่เผยแพร่แล้ว
  • ไม่มีเนมสเปซรวม core.messages.receive/send/live/state ฟังก์ชันที่ เผยแพร่แล้วอยู่โดยตรงใน src/channels/message/* (withDurableMessageSendContext, createMessageReceiveContext, createLiveMessageState, classifyDurableSendRecoveryState) แทนที่จะ อยู่เบื้องหลังฟาซาด core.messages.*
  • ไม่มีชนิดข้อความมาตรฐานทั่วไป ChannelMessage / MessageTarget / MessageRelation Core ยังคงส่งเพย์โหลดการตอบกลับที่เป็นรูปธรรม (ReplyPayload) และบริบทเฉพาะช่องทางผ่านอะแดปเตอร์การส่ง แทนที่จะใช้ รูปแบบข้อความเดียวที่ไม่ขึ้นกับแพลตฟอร์มพร้อมความสัมพันธ์ kind: "reply" | "followup" | "broadcast" | "system"
  • ชื่อนโยบายการรับทราบต่างจากโครงร่าง ค่าที่เผยแพร่แล้วคือ after_receive_record | after_agent_dispatch | after_durable_send | manual โครงร่างเดิมใช้ immediate | after-record | after-durable-send | manual พร้อมช่องข้อมูลเหตุผลการหมดเวลาของ Webhook แต่รูปแบบดังกล่าวไม่ได้ ถูกสร้างขึ้น
  • คีย์ความสามารถ DurableFinalDeliveryRequirementMap แทนที่ออบเจ็กต์ MessageCapabilities ในโครงร่าง ความสามารถเป็นแฟล็กบูลีนแบบแบน (text, media, poll, payload, silent, replyTo, thread, nativeQuote, messageSendingHooks, batch, reconcileUnknownSend, afterSendSuccess, afterCommit) ซึ่งตรวจสอบผ่าน verifyDurableFinalCapabilityProofs แทนโครงสร้างซ้อนแบบ text.chunking / attachments.voice

ความเสี่ยงที่เป็นรูปธรรมในการย้ายระบบ (ยังคงเกี่ยวข้อง)

ผลข้างเคียงเฉพาะช่องทางเหล่านี้มีอยู่ก่อนการปรับโครงสร้างและต้องยังคง ทำงานผ่านเส้นทางการส่งแบบใหม่ ผลเหล่านี้ไม่ใช่สมมติฐาน แต่ละรายการ ถูกนำไปใช้จริงและเป็นส่วนสำคัญต่อการทำงานในปัจจุบัน

  • iMessage (extensions/imessage/src/monitor/echo-cache.ts, persisted-echo-cache.ts): ตัวเฝ้าติดตามจะบันทึกข้อความที่ส่งแล้วลงในแคช การสะท้อนกลับหลังจากส่งสำเร็จ การส่งข้อความสุดท้ายแบบคงทนต้องยังคงเติมข้อมูลลงใน แคชดังกล่าว มิฉะนั้น OpenClaw อาจนำคำตอบของตัวเองกลับเข้ามาเป็นข้อความขาเข้าจากผู้ใช้
  • Tlon (extensions/tlon/src/monitor/index.ts): เพิ่มลายเซ็นของโมเดลซึ่งเป็นทางเลือก และบันทึกเธรดที่เข้าร่วมหลังจากตอบกลับในกลุ่ม การส่งมอบแบบคงทน ต้องไม่ข้ามผลเหล่านั้น
  • Discord และตัวจัดส่งที่เตรียมไว้รายการอื่นๆ เป็นเจ้าของพฤติกรรมการส่งโดยตรงและ การแสดงตัวอย่างอยู่แล้ว ช่องทางจะยังไม่คงทนตลอดทั้งกระบวนการจนกว่าตัวจัดส่งที่เตรียมไว้ ของช่องทางนั้นจะกำหนดเส้นทางข้อความสุดท้ายผ่านบริบทการส่งอย่างชัดเจน อย่าสันนิษฐานว่า อะแดปเตอร์ทั่วไปเพียงอย่างเดียวครอบคลุมแล้ว
  • การส่งมอบทางเลือกแบบเงียบของ Telegram ต้องส่งมอบอาร์เรย์เพย์โหลดที่ฉายผลแล้ว ทั้งหมด ไม่ใช่เฉพาะเพย์โหลดแรก หลังจากการแบ่งส่วน/การฉายผลทางเลือก
  • LINE, Zalo, Nostr และเส้นทางตัวช่วยที่คล้ายกันอาจมีการจัดการโทเค็นตอบกลับ การพร็อกซีสื่อ แคชข้อความที่ส่งแล้ว หรือเป้าหมายที่ใช้ได้เฉพาะคอลแบ็ก เส้นทางเหล่านี้ยังคงใช้การส่งมอบที่ช่องทางเป็นเจ้าของ จนกว่าความหมายเชิงพฤติกรรมเหล่านั้นจะถูกแทนด้วย อะแดปเตอร์การส่งและครอบคลุมด้วยการทดสอบ
  • ตัวช่วย DM โดยตรง อาจมีคอลแบ็กการตอบกลับซึ่งเป็นเป้าหมายการขนส่งที่ถูกต้อง เพียงเป้าหมายเดียว ระบบขาออกทั่วไปต้องไม่คาดเดาเป้าหมายจากฟิลด์ดิบของ แพลตฟอร์มและข้ามคอลแบ็กดังกล่าว

การจำแนกความล้มเหลว

อะแดปเตอร์จำแนกความล้มเหลวของการขนส่งเป็นหมวดหมู่แบบปิดในลักษณะ DeliveryFailureKind (ชั่วคราว, เกินขีดจำกัดอัตรา, การยืนยันตัวตน, สิทธิ์, ไม่พบ, เพย์โหลดไม่ถูกต้อง, ข้อขัดแย้ง, ยกเลิกแล้ว, ไม่ทราบ) นโยบายหลัก:

  • ลองใหม่เมื่อเกิดความล้มเหลวชั่วคราวและความล้มเหลวจากการเกินขีดจำกัดอัตรา
  • อย่าลองใหม่เมื่อเพย์โหลดไม่ถูกต้อง เว้นแต่จะมีทางเลือกสำรองในการเรนเดอร์
  • อย่าลองใหม่เมื่อการยืนยันตัวตนหรือสิทธิ์ล้มเหลว จนกว่าการกำหนดค่าจะเปลี่ยนแปลง
  • เมื่อไม่พบ ให้การสรุปผลแบบสดเปลี่ยนจากการแก้ไขเป็นการส่งใหม่ เมื่อ ช่องทางประกาศว่าสามารถทำเช่นนั้นได้อย่างปลอดภัย
  • เมื่อเกิดข้อขัดแย้ง ให้ใช้สถานะใบรับรอง/การทำงานซ้ำได้โดยไม่เกิดผลเพิ่ม เพื่อตัดสินว่าข้อความ มีอยู่แล้วหรือไม่
  • ข้อผิดพลาดใดๆ หลังจากการเรียกแพลตฟอร์มอาจหมายถึงการดำเนินการสำเร็จแล้ว แต่เกิดขึ้นก่อน การยืนยันใบรับรอง จะกลายเป็น unknown_after_send เว้นแต่อะแดปเตอร์จะพิสูจน์ได้ว่าการดำเนินการ บนแพลตฟอร์มไม่ได้เกิดขึ้น

คำถามที่ยังไม่มีข้อสรุป

  • ในท้ายที่สุด Telegram ควรแทนที่ตัวรันการสำรวจของ grammY (1.43.0) ด้วยแหล่งการสำรวจที่คงทนอย่างสมบูรณ์ ซึ่งควบคุมการส่งมอบซ้ำในระดับแพลตฟอร์ม ไม่ใช่เฉพาะจุดอ้างอิงการเริ่มทำงานใหม่ที่ OpenClaw เก็บไว้อย่างคงทน (safeCompletedUpdateId) หรือไม่
  • สถานะการแสดงตัวอย่างแบบสดควรอยู่ในเรคคอร์ดเดียวกับเจตนาการส่งข้อความสุดท้าย หรืออยู่ในพื้นที่จัดเก็บสถานะแบบสดที่อยู่คู่กัน
  • การระงับการสะท้อนกลับเมื่อ Gateway ล้มเหลวในห้องที่ใช้บอตร่วมกันจำเป็นต้องใช้ กลไกการติดแท็กต้นทางที่วางแผนไว้เดิม สัญญารายช่องทางที่เรียบง่ายกว่า หรืออยู่นอกขอบเขต
  • ช่องทางใดรองรับต้นทาง/เมทาดาทาโดยกำเนิดสำหรับการระงับการสะท้อนกลับข้ามบอต และช่องทางใดจำเป็นต้องใช้รีจิสทรีขาออกที่จัดเก็บอย่างคงทน

ที่เกี่ยวข้อง

Was this useful?
On this page

On this page