Messages and delivery

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

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

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

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

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

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

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

โดเมนภายในอยู่ใน 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 เพียงเพราะช่องทางมี อะแดปเตอร์ขาออกแบบทั่วไป

ขอบเขตที่ยังคงอันตรายคือ หลังการเรียกแพลตฟอร์มสำเร็จและก่อน บันทึกการตอบรับ หากกระบวนการหยุดทำงาน ณ จุดนั้น แกนหลักจะไม่ทราบว่า มีข้อความบนแพลตฟอร์มหรือไม่ เว้นแต่อะแดปเตอร์จะประกาศ 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 แกนหลักจัดเก็บเมทาดาทาขาเข้าเพียงพอสำหรับขจัดรายการซ้ำ/กำหนดเส้นทางการส่งใหม่แล้ว
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) ทำให้ ID ข้อความบนแพลตฟอร์มตั้งแต่หนึ่งรายการขึ้นไป จากการส่งเชิงตรรกะครั้งเดียวอยู่ในรูปแบบมาตรฐานเป็น platformMessageIds พร้อม parts ต่อส่วน (ชนิด ดัชนี ID เธรด ID ข้อความที่ตอบกลับ) โดยเก็บ ID หลักไว้ สำหรับการสร้างเธรดและการแก้ไขภายหลัง นี่คือสิ่งที่ทำให้การส่งหลายส่วน (ข้อความ พร้อมสื่อ ข้อความที่แบ่งเป็นส่วน การย้อนกลับไปใช้การ์ด) สามารถเล่นซ้ำและขจัดรายการซ้ำได้หลัง เริ่มใหม่

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

การปรับโครงสร้างได้รวมเข้าหรือเลิกใช้: reply-runtime, reply-dispatch-runtime, reply-reference, reply-chunking, ตัวช่วย reply-payload ที่เปิดเผยเป็น API สาธารณะ, inbound-reply-dispatch, channel-reply-pipeline และการใช้งานสาธารณะส่วนใหญ่ ของส่วนหน้าขาออกแบบเดิม ปัจจุบัน 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 แกนหลักยังคงส่งเพย์โหลดการตอบกลับที่เป็นรูปธรรม (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