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) یک یا چند شناسهٔ پیام پلتفرم از یک ارسال منطقی را به 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 وجود ندارد. هسته همچنان بارهای پاسخ مشخص (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 و مسیرهای کمکی مشابه می‌توانند دارای مدیریت توکن پاسخ، پراکسی‌کردن رسانه، حافظه‌های نهان پیام‌های ارسال‌شده یا مقصدهای صرفاً مبتنی بر فراخوانی برگشتی باشند. آن‌ها تا زمانی که این معناشناسی‌ها در آداپتور ارسال بازنمایی و با آزمون‌ها پوشش داده شوند، تحت تحویل متعلق به کانال باقی می‌مانند.
  • کمک‌تابع‌های پیام مستقیم می‌توانند دارای یک فراخوانی برگشتی پاسخ باشند که تنها مقصد انتقال صحیح است. خروجی عمومی نباید مقصدی را از فیلدهای خام پلتفرم حدس بزند و آن فراخوانی برگشتی را نادیده بگیرد.

طبقه‌بندی شکست‌ها

آداپتورها شکست‌های انتقال را در دسته‌های بسته‌ای مشابه DeliveryFailureKind طبقه‌بندی می‌کنند (موقت، محدودیت نرخ، احراز هویت، مجوز، یافت‌نشدن، بار دادهٔ نامعتبر، تعارض، لغوشده، ناشناخته). سیاست هسته:

  • شکست‌های موقت و محدودیت نرخ را دوباره امتحان کنید.
  • شکست‌های بار دادهٔ نامعتبر را دوباره امتحان نکنید، مگر اینکه یک روش جایگزین برای رندر وجود داشته باشد.
  • شکست‌های احراز هویت یا مجوز را تا زمان تغییر پیکربندی دوباره امتحان نکنید.
  • در حالت یافت‌نشدن، هنگامی که کانال ایمن‌بودن این کار را اعلام می‌کند، اجازه دهید نهایی‌سازی زنده از ویرایش به یک ارسال تازه بازگردد.
  • در حالت تعارض، برای تشخیص اینکه پیام از قبل وجود دارد یا نه، از وضعیت رسید/هم‌توانی استفاده کنید.
  • هر خطایی پس از احتمال موفقیت فراخوانی پلتفرم اما پیش از ثبت رسید، به unknown_after_send تبدیل می‌شود، مگر اینکه آداپتور ثابت کند عملیات پلتفرم انجام نشده است.

پرسش‌های باز

  • آیا Telegram باید در نهایت اجراکنندهٔ نظرسنجی grammY (1.43.0) را با یک منبع نظرسنجی کاملاً ماندگار جایگزین کند که تحویل مجدد در سطح پلتفرم را کنترل کند، نه فقط نشان‌وارهٔ ماندگار بازراه‌اندازی OpenClaw (safeCompletedUpdateId).
  • آیا وضعیت پیش‌نمایش زنده باید در همان رکورد قصد ارسال نهایی نگهداری شود یا در یک مخزن وضعیت زندهٔ همتا.
  • آیا جلوگیری از پژواک هنگام شکست Gateway در اتاق‌های مشترکِ دارای ربات، به سازوکار برچسب‌گذاری مبدأ که در ابتدا برنامه‌ریزی شده بود نیاز دارد، به یک قرارداد ساده‌تر برای هر کانال، یا خارج از دامنه است.
  • کدام کانال‌ها برای جلوگیری از پژواک میان‌رباتی از مبدأ/فرادادهٔ بومی پشتیبانی می‌کنند و کدام‌یک به یک دفتر ثبت خروجی ماندگار نیاز دارند.

مرتبط

Was this useful?
On this page

On this page