Messages and delivery
การปรับโครงสร้างวงจรชีวิตของข้อความ
เหตุผลที่เกิดการปรับโครงสร้างนี้
สแต็กช่องทางเติบโตมาจากการแก้ไขเฉพาะจุดหลายครั้ง ได้แก่ ตัวช่วยขาเข้าที่แยกกันตาม
ระดับความสมบูรณ์ (runtime.channel.inbound.run สำหรับอะแดปเตอร์แบบเรียบง่าย
และ runtime.channel.inbound.runPreparedReply สำหรับอะแดปเตอร์ที่มีความสามารถสูง)
ตัวช่วยจัดส่งการตอบกลับแบบเดิม (dispatchInboundReplyWithBase,
recordInboundSessionAndDispatchReply) การสตรีมตัวอย่างแสดงผลล่วงหน้าเฉพาะช่องทาง
และความทนทานของการส่งขั้นสุดท้ายที่ถูกต่อเพิ่มเข้ากับพาธเพย์โหลดการตอบกลับเดิม
โครงสร้างดังกล่าวทำให้มีแนวคิดสาธารณะมากเกินไป และมีจุดที่ความหมายเชิงการส่ง
อาจคลาดเคลื่อนจากกันมากเกินไป
ช่องว่างด้านความน่าเชื่อถือที่บังคับให้ต้องออกแบบใหม่:
รับทราบอัปเดตจากการโพล 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/MessageRelationCore ยังคงส่งเพย์โหลดการตอบกลับที่เป็นรูปธรรม (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 ล้มเหลวในห้องที่ใช้บอตร่วมกันจำเป็นต้องใช้ กลไกการติดแท็กต้นทางที่วางแผนไว้เดิม สัญญารายช่องทางที่เรียบง่ายกว่า หรืออยู่นอกขอบเขต
- ช่องทางใดรองรับต้นทาง/เมทาดาทาโดยกำเนิดสำหรับการระงับการสะท้อนกลับข้ามบอต และช่องทางใดจำเป็นต้องใช้รีจิสทรีขาออกที่จัดเก็บอย่างคงทน