---
read_when:
    - بازآرایی رفتار ارسال یا دریافت کانال
    - تغییر APIهای پیام مربوط به ورودی کانال، ارسال پاسخ، صف خروجی، استریم پیش‌نمایش یا SDK افزونه
    - طراحی یک Plugin کانال جدید که به ارسال‌های پایدار، رسیدها، پیش‌نمایش‌ها، ویرایش‌ها یا تلاش‌های مجدد نیاز دارد
summary: 'وضعیت چرخهٔ عمر پایدار دریافت/ارسال پیام: چه چیزهایی منتشر شده‌اند، چه تغییراتی نسبت به طراحی اولیه ایجاد شده‌اند، و چه مواردی همچنان باز مانده‌اند'
title: بازآرایی چرخه عمر پیام
x-i18n:
    generated_at: "2026-07-27T16:28:39Z"
    model: gpt-5.6
    postprocess_version: locale-links-v1
    prompt_version: 32
    provider: openai
    source_hash: d21eda70b8be0de78677f4ff6d7547317112731d9e86a5bef58eac0268899818
    source_path: concepts/message-lifecycle-refactor.md
    workflow: 16
---

<Note>
این صفحه در ابتدا به‌عنوان یک پیشنهاد طراحی آینده‌نگر ایجاد شد. بخش اصلی آن
طراحی از آن زمان در `src/channels/message/*` و زیرمسیرهای عمومی
`openclaw/plugin-sdk/channel-outbound` / `channel-inbound` منتشر شده است. برای
API فعلی، از [API خروجی کانال](/fa/plugins/sdk-channel-outbound) و
[API ورودی کانال](/fa/plugins/sdk-channel-inbound) استفاده کنید. این صفحه موارد
منتشرشده، نقاطی که پیاده‌سازی از طرح اولیه فاصله گرفته است، و مواردی را که
هنوز باز هستند پیگیری می‌کند.
</Note>

## چرا این بازآرایی انجام شد

پشته کانال از چندین اصلاح محلی رشد کرد: یاریگرهای ورودی جداگانه برای هر
سطح بلوغ (`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 ورودی کانال](/fa/plugins/sdk-channel-inbound) هدایت می‌شود. کد 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 در اتاق‌های مشترک دارای ربات به
  سازوکار برچسب‌گذاری مبدأ که در ابتدا برنامه‌ریزی شده بود، یک قرارداد ساده‌تر
  برای هر کانال نیاز دارد، یا خارج از محدوده است.
- کدام کانال‌ها برای سرکوب پژواک میان‌رباتی از مبدأ/فرادادهٔ بومی
  پشتیبانی می‌کنند و کدام‌یک به یک دفتر ثبت خروجی پایدار نیاز دارند.

## مرتبط

- [پیام‌ها](/fa/concepts/messages)
- [جریان‌دهی و قطعه‌بندی](/fa/concepts/streaming)
- [پیش‌نویس‌های پیشرفت](/fa/concepts/progress-drafts)
- [سیاست تلاش مجدد](/fa/concepts/retry)
- [API خروجی کانال](/fa/plugins/sdk-channel-outbound)
- [API ورودی کانال](/fa/plugins/sdk-channel-inbound)
