Plugins
API پیام کانال
Pluginهای کانال باید یک آداپتور message از
openclaw/plugin-sdk/channel-message ارائه کنند. آداپتور، چرخهی عمر پیام بومی
را که پلتفرم پشتیبانی میکند توصیف میکند:
receive -> route and record -> agent turn -> durable final send
send -> render batch -> platform I/O -> receipt -> lifecycle side effects
live preview -> final edit or fallback -> receipt
هسته مالک صفبندی، دوام، سیاست تلاش دوبارهی عمومی، hookها، رسیدها و ابزار
مشترک message است. Plugin مالک فراخوانیهای بومی ارسال/ویرایش/حذف، نرمالسازی
مقصد، رشتهبندی پلتفرم، نقلقولهای انتخابشده، پرچمهای اعلان، وضعیت حساب و
عوارض جانبی ویژهی پلتفرم است.
این صفحه را همراه با ساخت Pluginهای کانال استفاده کنید.
زیرمسیر channel-message عمدا آنقدر سبک است که برای فایلهای راهاندازی داغ
Plugin مانند channel.ts مناسب باشد: قراردادهای آداپتور، اثباتهای قابلیت،
رسیدها و نماهای سازگاری را بدون بارگذاری تحویل خروجی ارائه میکند. ابزارهای
کمکی تحویل زمان اجرا از
openclaw/plugin-sdk/channel-message-runtime برای مسیرهای کد پایش/ارسال که
همین حالا I/O پیام ناهمگام انجام میدهند در دسترس هستند.
آداپتور حداقلی
بیشتر Pluginهای کانال جدید میتوانند با یک آداپتور کوچک شروع کنند:
defineChannelMessageAdapter,
createMessageReceiptFromOutboundResults,
} from "openclaw/plugin-sdk/channel-message";
export const demoMessageAdapter = defineChannelMessageAdapter({
id: "demo",
durableFinal: {
capabilities: {
text: true,
replyTo: true,
thread: true,
messageSendingHooks: true,
},
},
send: {
text: async ({ cfg, to, text, accountId, replyToId, threadId, signal }) => {
const sent = await sendDemoMessage({
cfg,
to,
text,
accountId: accountId ?? undefined,
replyToId: replyToId ?? undefined,
threadId: threadId == null ? undefined : String(threadId),
signal,
});
return {
receipt: createMessageReceiptFromOutboundResults({
results: [{ channel: "demo", messageId: sent.id, conversationId: to }],
kind: "text",
threadId: threadId == null ? undefined : String(threadId),
replyToId: replyToId ?? undefined,
}),
};
},
},
});
سپس آن را به Plugin کانال متصل کنید:
export const demoPlugin = createChatChannelPlugin({
base: {
id: "demo",
message: demoMessageAdapter,
// other channel plugin fields
},
});
فقط قابلیتهایی را اعلام کنید که آداپتور واقعا حفظ میکند. هر قابلیت اعلامشده باید یک آزمون قرارداد داشته باشد.
پل خروجی
اگر کانال از قبل یک آداپتور سازگار outbound دارد، بهجای تکرار کد ارسال،
آداپتور پیام را از آن مشتق کنید:
const demoMessageAdapter = createChannelMessageAdapterFromOutbound({
id: "demo",
outbound: demoOutboundAdapter,
});
پل، نتایج ارسال خروجی قدیمی را به مقدارهای MessageReceipt تبدیل میکند. کد
جدید باید رسیدها را از ابتدا تا انتها عبور دهد و شناسههای قدیمی را فقط در
لبههای سازگاری با listMessageReceiptPlatformIds(...) یا
resolveMessageReceiptPrimaryId(...) مشتق کند.
اگر سیاست دریافتی ارائه نشود، createChannelMessageAdapterFromOutbound(...)
از سیاست تایید دریافت manual استفاده میکند. این کار تایید دریافتِ پلتفرمِ
تحت مالکیت Plugin را بدون تغییر کانالهایی که Webhookها، سوکتها یا offsetهای
polling را خارج از زمینهی دریافت عمومی تایید میکنند، صریح میسازد.
ارسالهای ابزار پیام
مسیر مشترک message(action="send") باید از همان چرخهی عمر تحویل هستهای
استفاده کند که پاسخهای نهایی استفاده میکنند. اگر یک کانال برای ارسال ابزار
به شکلدهی ویژهی ارائهدهنده نیاز دارد، بهجای ارسال از
actions.handleAction(...)، actions.prepareSendPayload(...) را پیادهسازی کنید.
prepareSendPayload(...)، ReplyPayload نرمالشدهی هسته را همراه با زمینهی
کامل کنش دریافت میکند. یک payload با دادههای ویژهی کانال در
payload.channelData.<channel> برگردانید و اجازه دهید هسته sendMessage(...)،
deliverOutboundPayloads(...)، صف write-ahead، hookهای ارسال پیام، تلاش دوباره،
بازیابی و پاکسازی ack را فراخوانی کند.
فقط وقتی null برگردانید که ارسال نتواند بهصورت payload بادوام نمایش داده
شود، برای مثال چون شامل یک کارخانهی مولفهی غیرقابلسریالسازی است. هسته برای
سازگاری، fallback کنش Plugin قدیمی را نگه میدارد، اما ویژگیهای جدید ارسال
کانال باید بهصورت دادهی payload بادوام قابل بیان باشند.
export const demoActions: ChannelMessageActionAdapter = {
describeMessageTool: () => ({ actions: ["send"], capabilities: ["presentation"] }),
prepareSendPayload: ({ ctx, payload }) => {
if (ctx.action !== "send") {
return null;
}
return {
...payload,
channelData: {
...payload.channelData,
demo: {
...(payload.channelData?.demo as object | undefined),
nativeCard: ctx.params.card,
},
},
};
},
};
سپس آداپتور خروجی payload.channelData.demo را داخل sendPayload میخواند.
این کار رندرینگ ویژهی پلتفرم را در Plugin نگه میدارد، در حالی که هسته همچنان
مالک ماندگاری، تلاش دوباره، بازیابی، hookها و ack است.
payloadهای آمادهشدهی message(action="send") و تحویل عمومی پاسخ نهایی بهطور
پیشفرض از تحویل هستهای با صفبندی best-effort استفاده میکنند. صفبندی بادوام
الزامی فقط پس از آن معتبر است که هسته تایید کند کانال میتواند ارسالی را که پس
از crash نتیجهاش نامعلوم است بازآشتی دهد. اگر آداپتور نتواند
reconcileUnknownSend را پیادهسازی کند، مسیر ارسال آمادهشده را best-effort
نگه دارید؛ هسته همچنان صف write-ahead را امتحان میکند، اما ماندگاری صف یا
بازیابی نامطمئن پس از crash بخشی از قرارداد تحویل الزامی نیست.
قابلیتهای نهایی بادوام
تحویل نهایی بادوام برای هر عارضهی جانبی بهصورت اختیاری فعال میشود. هسته فقط وقتی از تحویل بادوام عمومی استفاده میکند که آداپتور همهی قابلیتهای موردنیاز payload و گزینههای تحویل را اعلام کند.
| قابلیت | چه زمانی اعلام شود |
|---|---|
text |
آداپتور میتواند متن ارسال کند و رسید برگرداند. |
media |
ارسالهای رسانه برای هر پیام قابلمشاهدهی پلتفرم رسید برمیگردانند. |
payload |
آداپتور معناشناسی payload پاسخ غنی را حفظ میکند، نه فقط متن و یک URL رسانه. |
replyTo |
مقصدهای پاسخ بومی به پلتفرم میرسند. |
thread |
مقصدهای رشته، موضوع یا رشتهی کانال بومی به پلتفرم میرسند. |
silent |
سرکوب اعلان به پلتفرم میرسد. |
nativeQuote |
فرادادهی نقلقول انتخابشده به پلتفرم میرسد. |
messageSendingHooks |
hookهای ارسال پیام هسته میتوانند پیش از I/O پلتفرم محتوا را لغو یا بازنویسی کنند. |
batch |
batchهای رندرشدهی چندبخشی بهعنوان یک طرح بادوام قابل بازپخش هستند. |
reconcileUnknownSend |
آداپتور میتواند بازیابی unknown_after_send را بدون بازپخش کور حل کند. |
afterSendSuccess |
عوارض جانبی after-send محلی کانال یکبار اجرا میشوند. |
afterCommit |
عوارض جانبی after-commit محلی کانال یکبار اجرا میشوند. |
تحویل نهایی best-effort به reconcileUnknownSend نیاز ندارد؛ وقتی آداپتور
معناشناسی قابلمشاهدهی payload را حفظ میکند، از چرخهی عمر مشترک استفاده
میکند و اگر ماندگاری صف در دسترس نباشد به I/O مستقیم پلتفرم fallback میکند.
تحویل نهایی بادوام الزامی باید صراحتا reconcileUnknownSend را لازم بداند. اگر
آداپتور نتواند تعیین کند یک ارسال شروعشده/نامعلوم به پلتفرم رسیده است یا نه،
آن قابلیت را اعلام نکنید؛ هسته پیش از صفبندی، تحویل بادوام الزامی را رد میکند.
وقتی یک فراخواننده به تحویل بادوام نیاز دارد، بهجای ساختن دستی mapها، نیازمندیها را مشتق کنید:
const requiredCapabilities = deriveDurableFinalDeliveryRequirements({
payload,
replyToId,
threadId,
silent,
payloadTransport: true,
extraCapabilities: {
nativeQuote: hasSelectedQuote(payload),
},
});
messageSendingHooks بهطور پیشفرض الزامی است. فقط برای مسیری که عمدا نمیتواند
hookهای ارسال پیام سراسری را اجرا کند، messageSendingHooks: false را تنظیم کنید.
قرارداد ارسال بادوام
یک ارسال نهایی بادوام معناشناسی سختگیرانهتری نسبت به تحویل قدیمیِ تحت مالکیت کانال دارد:
- intent بادوام را پیش از I/O پلتفرم ایجاد کنید.
- اگر تحویل بادوام نتیجهی handled برگرداند، به ارسال قدیمی fallback نکنید.
- لغو hook و نتایج no-send را نهایی در نظر بگیرید.
unsupportedرا فقط بهعنوان نتیجهی پیش از intent در نظر بگیرید.- برای دوام الزامی، اگر صف نتواند ثبت کند که ارسال پلتفرم شروع شده است، پیش از I/O پلتفرم شکست بخورید.
- برای تحویل نهایی الزامی و ارسالهای آمادهشدهی الزامی ابزار پیام،
reconcileUnknownSendرا preflight کنید؛ بازیابی باید بتواند پیام ازپیشارسالشده را ack کند یا فقط پس از آن بازپخش کند که آداپتور ثابت کند ارسال اصلی رخ نداده است. - برای
best_effort، شکستهای نوشتن صف میتوانند به I/O مستقیم پلتفرم fallback کنند. - سیگنالهای abort را به بارگذاری رسانه و ارسالهای پلتفرم منتقل کنید.
- hookهای after-commit را پس از ack صف اجرا کنید؛ fallback مستقیم best-effort آنها را پس از I/O موفق پلتفرم اجرا میکند، چون commit صف بادوام وجود ندارد.
- برای هر شناسهی پیام قابلمشاهدهی پلتفرم رسید برگردانید.
- وقتی پلتفرم میتواند بررسی کند که یک ارسال نامطمئن از قبل به کاربر رسیده است یا نه، از
reconcileUnknownSendاستفاده کنید.
این قرارداد از ارسالهای تکراری پس از crash جلوگیری میکند و مانع دور زدن hookهای لغو ارسال پیام میشود.
رسیدها
MessageReceipt رکورد داخلی جدید از چیزی است که پلتفرم پذیرفته است:
type MessageReceipt = {
primaryPlatformMessageId?: string;
platformMessageIds: string[];
parts: MessageReceiptPart[];
threadId?: string;
replyToId?: string;
editToken?: string;
deleteToken?: string;
sentAt: number;
raw?: readonly MessageReceiptSourceResult[];
};
هنگام سازگار کردن یک نتیجهی ارسال موجود، از
createMessageReceiptFromOutboundResults(...) استفاده کنید. وقتی یک پیام
پیشنمایش زنده به رسید نهایی تبدیل میشود، از createPreviewMessageReceipt(...)
استفاده کنید. از افزودن فیلدهای messageIds محلیِ مالک جدید خودداری کنید.
ChannelDeliveryResult.messageIds قدیمی همچنان در لبههای سازگاری تولید میشود.
پیشنمایش زنده
کانالهایی که پیشنمایشهای پیشنویس یا بهروزرسانیهای پیشرفت را stream میکنند باید قابلیتهای زنده را اعلام کنند:
const demoMessageAdapter = defineChannelMessageAdapter({
id: "demo",
live: {
capabilities: {
draftPreview: true,
previewFinalization: true,
progressUpdates: true,
quietFinalization: true,
},
finalizer: {
capabilities: {
finalEdit: true,
normalFallback: true,
discardPending: true,
previewReceipt: true,
retainOnAmbiguousFailure: true,
},
},
},
});
برای نهاییسازی زمان اجرا از defineFinalizableLivePreviewAdapter(...) و
deliverWithFinalizableLivePreviewAdapter(...) استفاده کنید. نهاییساز تصمیم
میگیرد که پاسخ نهایی پیشنمایش را درجا ویرایش کند، یک fallback عادی بفرستد،
وضعیت پیشنمایش در انتظار را دور بیندازد، ویرایش شکستخوردهی مبهم را بدون
تکرار پیام نگه دارد، و رسید نهایی را برگرداند.
سیاست ack دریافت
دریافتکنندههای ورودی که زمانبندی تایید دریافت پلتفرم را کنترل میکنند باید سیاست دریافت را اعلام کنند:
const demoMessageAdapter = defineChannelMessageAdapter({
id: "demo",
receive: {
defaultAckPolicy: "after_agent_dispatch",
supportedAckPolicies: ["after_receive_record", "after_agent_dispatch"],
},
});
آداپتورهایی که سیاست دریافت اعلام نمیکنند بهطور پیشفرض این مقدار را دارند:
{
receive: {
defaultAckPolicy: "manual",
supportedAckPolicies: ["manual"],
},
}
هنگامی از پیشفرض استفاده کنید که پلتفرم هیچ تأییدی برای بهتعویقانداختن ندارد، پیشاپیش قبل از پردازش ناهمگام تأیید میکند، یا به معناشناسی پاسخ مختص پروتکل نیاز دارد. یکی از سیاستهای مرحلهای را فقط زمانی اعلام کنید که گیرنده واقعاً از زمینه دریافت برای بهتعویقانداختن تأیید پلتفرم استفاده میکند.
سیاستها:
| سیاست | زمان استفاده |
|---|---|
after_receive_record |
پلتفرم میتواند پس از تجزیه و ثبت رویداد ورودی تأیید شود. |
after_agent_dispatch |
پلتفرم باید تا زمانی منتظر بماند که اعزام عامل پذیرفته شده باشد. |
after_durable_send |
پلتفرم باید تا زمانی منتظر بماند که تحویل نهایی تصمیمی پایدار داشته باشد. |
manual |
Plugin مالک تأیید است، چون معناشناسی پلتفرم با یک مرحله عمومی سازگار نیست. |
در گیرندههایی که وضعیت ack را به تعویق میاندازند از createMessageReceiveContext(...) استفاده کنید، و زمانی که گیرنده باید بررسی کند آیا یک مرحله سیاست پیکربندیشده را برآورده کرده است، از shouldAckMessageAfterStage(...) استفاده کنید.
آزمونهای قرارداد
اعلامیههای قابلیت بخشی از قرارداد Plugin هستند. آنها را با آزمونها پشتیبانی کنید:
verifyChannelMessageAdapterCapabilityProofs,
verifyChannelMessageLiveCapabilityAdapterProofs,
verifyChannelMessageLiveFinalizerProofs,
verifyChannelMessageReceiveAckPolicyAdapterProofs,
} from "openclaw/plugin-sdk/channel-message";
it("backs declared message capabilities", async () => {
await expect(
verifyChannelMessageAdapterCapabilityProofs({
adapterName: "demo",
adapter: demoMessageAdapter,
proofs: {
text: async () => {
const result = await demoMessageAdapter.send!.text!(textCtx);
expect(result.receipt.platformMessageIds).toContain("msg-1");
},
replyTo: async () => {
await demoMessageAdapter.send!.text!({ ...textCtx, replyToId: "parent-1" });
expect(sendDemoMessage).toHaveBeenCalledWith(
expect.objectContaining({
replyToId: "parent-1",
}),
);
},
messageSendingHooks: () => {
expect(demoMessageAdapter.durableFinal!.capabilities!.messageSendingHooks).toBe(true);
},
},
}),
).resolves.toContainEqual({ capability: "text", status: "verified" });
});
وقتی آداپتر آن ویژگیها را اعلام میکند، مجموعههای اثبات زنده و دریافت را اضافه کنید. نبود یک اثبات باید آزمون را ناموفق کند، نه اینکه سطح پایدار را بیسروصدا گستردهتر کند.
APIهای سازگاری منسوخشده
این APIها برای سازگاری با طرفهای ثالث همچنان قابل import هستند. از آنها برای کد کانال جدید استفاده نکنید.
| API منسوخشده | جایگزین |
|---|---|
openclaw/plugin-sdk/channel-reply-pipeline |
openclaw/plugin-sdk/channel-message |
createChannelTurnReplyPipeline(...) |
createChannelMessageReplyPipeline(...) برای توزیعکنندههای سازگاری، یا یک آداپتر message برای کد کانال جدید |
deliverDurableInboundReplyPayload(...) |
deliverInboundReplyWithMessageSendContext(...) از openclaw/plugin-sdk/channel-message-runtime |
dispatchInboundReplyWithBase(...) |
dispatchChannelMessageReplyWithBase(...) فقط برای توزیعکنندههای سازگاری |
recordInboundSessionAndDispatchReply(...) |
recordChannelMessageReplyDispatch(...) فقط برای توزیعکنندههای سازگاری |
resolveChannelSourceReplyDeliveryMode(...) |
resolveChannelMessageSourceReplyDeliveryMode(...) |
deliverFinalizableDraftPreview(...) |
defineFinalizableLivePreviewAdapter(...) بههمراه deliverWithFinalizableLivePreviewAdapter(...) |
DraftPreviewFinalizerDraft |
LivePreviewFinalizerDraft |
DraftPreviewFinalizerResult |
LivePreviewFinalizerResult |
توزیعکنندههای سازگاری همچنان میتوانند از طریق نمای پیام از createReplyPrefixContext(...)، createReplyPrefixOptions(...)، و createTypingCallbacks(...) استفاده کنند. کد چرخه عمر جدید باید از زیرمسیر قدیمی channel-reply-pipeline پرهیز کند.
چکلیست مهاجرت
message: defineChannelMessageAdapter(...)یاmessage: createChannelMessageAdapterFromOutbound(...)را به Plugin کانال اضافه کنید.- از ارسالهای متن، رسانه، و payload مقدار
MessageReceiptبرگردانید. - فقط قابلیتهایی را اعلام کنید که با رفتار بومی و آزمونها پشتیبانی میشوند.
- نگاشتهای دستنویس الزامات پایدار را با
deriveDurableFinalDeliveryRequirements(...)جایگزین کنید. - هنگامی که کانال پیامهای پیشنویس را درجا ویرایش میکند، نهاییسازی پیشنمایش را از طریق کمککنندههای پیشنمایش زنده منتقل کنید.
- سیاست ack دریافت را فقط زمانی اعلام کنید که گیرنده واقعاً بتواند تأیید پلتفرم را به تعویق بیندازد.
- کمککنندههای قدیمی اعزام پاسخ را فقط در مرزهای سازگاری نگه دارید.