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 و بیشتر کاربردهای عمومی نمای خروجی قدیمی. 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