Plugin maintainer reference
عرض الرسائل
عرض الرسائل هو العقد المشترك في OpenClaw لواجهة مستخدم الدردشة الصادرة الغنية. يتيح للوكلاء، وأوامر CLI، وتدفقات الموافقة، وPlugins وصف قصد الرسالة مرة واحدة، بينما يعرض كل Plugin قناة أفضل هيئة أصلية يستطيعها.
استخدم العرض لواجهة مستخدم الرسائل المحمولة:
- أقسام النص
- نص سياق/تذييل صغير
- فواصل
- أزرار
- قوائم اختيار
- عنوان البطاقة ونبرتها
لا تضف حقولًا أصلية جديدة خاصة بالموفّر مثل Discord components، أو Slack
blocks، أو Telegram buttons، أو Teams card، أو Feishu card إلى أداة
الرسائل المشتركة. هذه مخرجات عرض يملكها Plugin القناة.
العقد
يستورد مؤلفو Plugin العقد العام من:
MessagePresentation, ReplyPayloadDelivery,} from "openclaw/plugin-sdk/interactive-runtime";الشكل:
type MessagePresentation = { title?: string; tone?: "neutral" | "info" | "success" | "warning" | "danger"; blocks: MessagePresentationBlock[];}; type MessagePresentationBlock = | { type: "text"; text: string } | { type: "context"; text: string } | { type: "divider" } | { type: "buttons"; buttons: MessagePresentationButton[] } | { type: "select"; placeholder?: string; options: MessagePresentationOption[] }; type MessagePresentationAction = | { type: "command"; command: string } | { type: "callback"; value: string }; type MessagePresentationButton = { label: string; action?: MessagePresentationAction; /** Legacy callback value. Prefer action for new controls. */ value?: string; url?: string; webApp?: { url: string }; /** @deprecated Use webApp. Accepted for legacy JSON payloads only. */ web_app?: { url: string }; priority?: number; disabled?: boolean; reusable?: boolean; style?: "primary" | "secondary" | "success" | "danger";}; type MessagePresentationOption = { label: string; action?: MessagePresentationAction; /** Legacy callback value. Prefer action for new controls. */ value?: string;}; type ReplyPayloadDelivery = { pin?: | boolean | { enabled: boolean; notify?: boolean; required?: boolean; };};دلالات الأزرار:
- يشغّل
action.type: "command"أمر شرطة مائلة أصليًا عبر مسار أوامر النواة. استخدم هذا لأزرار الأوامر والقوائم المضمنة. - يحمل
action.type: "callback"بيانات Plugin مبهمة عبر مسار تفاعل القناة. يجب ألا تعيد Plugins القنوات تفسير بيانات callback كأوامر شرطة مائلة. valueهي قيمة callback مبهمة قديمة. ينبغي أن تستخدم عناصر التحكم الجديدةactionحتى تتمكن Plugins القنوات من ربط الأوامر وcallbacks دون التخمين من النص.urlهو زر رابط. يمكن أن يوجد دونvalue.- يصف
webAppزر تطبيق ويب أصليًا للقناة. يعرض Telegram هذا على أنهweb_appولا يدعمه إلا في الدردشات الخاصة. لا يزالweb_appمقبولًا في حمولات JSON المرنة للتوافق، لكن ينبغي أن يستخدم منتجو TypeScriptwebApp. labelمطلوب ويُستخدم أيضًا في احتياطي النص.styleإرشادي. ينبغي أن تربط العارضات الأنماط غير المدعومة بقيمة افتراضية آمنة، لا أن تُفشل الإرسال.priorityاختياري. عندما تعلن قناة حدود الإجراءات ويجب إسقاط عناصر تحكم، تحتفظ النواة بالأزرار الأعلى أولوية أولًا وتحافظ على الترتيب الأصلي بين الأزرار ذات الأولوية المتساوية. عندما تتسع كل عناصر التحكم، يُحفظ الترتيب المؤلّف.disabledاختياري. يجب أن تشترك القنوات صراحةً عبرsupportsDisabled؛ وإلا تخفض النواة عنصر التحكم المعطل إلى نص احتياطي غير تفاعلي.reusableاختياري. قد تبقي القنوات التي تدعم callbacks أصلية قابلة لإعادة الاستخدام الإجراء متاحًا بعد تفاعل ناجح. استخدمه للإجراءات القابلة للتكرار أو idempotent مثل التحديث أو الفحص أو مزيد من التفاصيل؛ واتركه غير مضبوط للموافقات العادية ذات الاستخدام الواحد والإجراءات التدميرية.
دلالات الاختيار:
- يحمل
options[].actionمعنى الأمر/callback نفسه مثل زرaction. options[].valueهي قيمة التطبيق المحددة القديمة.placeholderإرشادي وقد تتجاهله القنوات التي لا تدعم الاختيار الأصلي.- إذا لم تدعم قناة الاختيارات، يسرد النص الاحتياطي التسميات.
أمثلة المنتجين
بطاقة بسيطة:
{ "title": "Deploy approval", "tone": "warning", "blocks": [ { "type": "text", "text": "Canary is ready to promote." }, { "type": "context", "text": "Build 1234, staging passed." }, { "type": "buttons", "buttons": [ { "label": "Approve", "value": "deploy:approve", "style": "success" }, { "label": "Decline", "value": "deploy:decline", "style": "danger" } ] } ]}زر رابط URL فقط:
{ "blocks": [ { "type": "text", "text": "Release notes are ready." }, { "type": "buttons", "buttons": [{ "label": "Open notes", "url": "https://example.com/release" }] } ]}زر تطبيق Telegram المصغر:
{ "blocks": [ { "type": "buttons", "buttons": [{ "label": "Launch", "web_app": { "url": "https://example.com/app" } }] } ]}قائمة اختيار:
{ "title": "Choose environment", "blocks": [ { "type": "select", "placeholder": "Environment", "options": [ { "label": "Canary", "value": "env:canary" }, { "label": "Production", "value": "env:prod" } ] } ]}إرسال CLI:
openclaw message send --channel slack \ --target channel:C123 \ --message "Deploy approval" \ --presentation '{"title":"Deploy approval","tone":"warning","blocks":[{"type":"text","text":"Canary is ready."},{"type":"buttons","buttons":[{"label":"Approve","value":"deploy:approve","style":"success"},{"label":"Decline","value":"deploy:decline","style":"danger"}]}]}'تسليم مثبت:
openclaw message send --channel telegram \ --target -1001234567890 \ --message "Topic opened" \ --pinتسليم مثبت مع JSON صريح:
{ "pin": { "enabled": true, "notify": true, "required": false }}عقد العارض
تعلن Plugins القنوات دعم العرض على محوّلها الصادر:
const adapter: ChannelOutboundAdapter = { deliveryMode: "direct", presentationCapabilities: { supported: true, buttons: true, selects: true, context: true, divider: true, limits: { actions: { maxActions: 25, maxActionsPerRow: 5, maxRows: 5, maxLabelLength: 80, maxValueBytes: 100, supportsStyles: true, supportsDisabled: false, }, selects: { maxOptions: 25, maxLabelLength: 100, maxValueBytes: 100, }, text: { maxLength: 2000, encoding: "characters", markdownDialect: "discord-markdown", }, }, }, deliveryCapabilities: { pin: true, }, renderPresentation({ payload, presentation, ctx }) { return renderNativePayload(payload, presentation, ctx); }, async pinDeliveredMessage({ target, messageId, pin }) { await pinNativeMessage(target, messageId, { notify: pin.notify === true }); },};تصف قيم الإمكانات المنطقية ما يمكن للعارض جعله تفاعليًا. تصف limits الاختيارية
الغلاف العام الذي يمكن للنواة تكييفه قبل استدعاء العارض:
type ChannelPresentationCapabilities = { supported?: boolean; buttons?: boolean; selects?: boolean; context?: boolean; divider?: boolean; limits?: { actions?: { maxActions?: number; maxActionsPerRow?: number; maxRows?: number; maxLabelLength?: number; maxValueBytes?: number; supportsStyles?: boolean; supportsDisabled?: boolean; supportsLayoutHints?: boolean; }; selects?: { maxOptions?: number; maxLabelLength?: number; maxValueBytes?: number; }; text?: { maxLength?: number; encoding?: "characters" | "utf8-bytes" | "utf16-units"; markdownDialect?: "plain" | "markdown" | "html" | "slack-mrkdwn" | "discord-markdown"; supportsEdit?: boolean; }; };};تطبّق النواة الحدود العامة على عناصر التحكم الدلالية قبل العرض. لا يزال العارضون يملكون التحقق والقص النهائيين الخاصين بالموفّر لعدد الكتل الأصلية، وحجم البطاقة، وحدود URL، وخصوصيات الموفّر التي لا يمكن التعبير عنها في العقد العام. إذا أزالت الحدود كل عنصر تحكم من كتلة ما، تبقي النواة التسميات كنص سياق غير تفاعلي حتى تظل الرسالة المسلّمة تحتوي على احتياطي مرئي.
تدفق عرض النواة
عندما يتضمن ReplyPayload أو إجراء رسالة presentation، تقوم النواة بما يلي:
- تطبّع حمولة العرض.
- تحل محوّل الخروج للقناة الهدف.
- تقرأ
presentationCapabilities. - تطبّق حدود الإمكانات العامة مثل عدد الإجراءات، وطول التسمية، وعدد خيارات الاختيار عندما يعلنها المحوّل.
- تستدعي
renderPresentationعندما يستطيع المحوّل عرض الحمولة. - تعود إلى نص محافظ عندما يكون المحوّل غائبًا أو لا يستطيع العرض.
- ترسل الحمولة الناتجة عبر مسار تسليم القناة العادي.
- تطبّق بيانات تعريف التسليم مثل
delivery.pinبعد أول رسالة مُرسلة بنجاح.
تملك النواة سلوك الاحتياطي حتى يبقى المنتجون غير مرتبطين بقناة معينة. وتملك Plugins القنوات العرض الأصلي ومعالجة التفاعل.
قواعد التدهور
يجب أن يكون العرض آمنًا للإرسال على القنوات المحدودة.
يتضمن النص الاحتياطي:
titleكسطر أول- كتل
textكفقرات عادية - كتل
contextكأسطر سياق مدمجة - كتل
dividerكفاصل مرئي - تسميات الأزرار، بما في ذلك URLs لأزرار الروابط
- تسميات خيارات الاختيار
ظهور احتياطي قيمة الزر
عندما لا تستطيع قناة عرض عناصر التحكم التفاعلية، تعود قيم الأزرار والاختيارات إلى نص عادي. يحافظ سلوك الاحتياطي على قابلية الاستخدام مع إبقاء بيانات callback المبهمة خاصة:
- تعرض الإجراءات ذات النوع
commandبصيغةlabel: \command`` حتى يستطيع المستخدمون نسخ الأمر وتشغيله يدويًا في إدخال القناة. - تعرض الإجراءات ذات النوع
callbackوحقولvalueالقديمة كتسمية فقط. لا تُكشف قيمة callback المبهمة في النص الاحتياطي. - تعرض أزرار
url/webAppنص URL بجانب تسمية الزر، لأن URL موجّه للمستخدم. - تعرض خيارات الاختيار كتسمية فقط. لا تُكشف قيمة الخيار الأساسية في النص الاحتياطي.
يجب أن تشتق محوّلات القنوات التي تضيف إرشادات أوامر يدوية في واجهة الاحتياطي الخاصة بها (مثل تعليمات تعليق مستند Feishu) فحص وجود الأمر من كتل العرض نفسها التي يستخدمها عارض الاحتياطي، حتى لا يظهر نص الإرشادات إلا عندما يكون أمر يدوي معروضًا فعليًا.
ينبغي أن تتدهور عناصر التحكم الأصلية غير المدعومة بدلًا من إفشال الإرسال كله. أمثلة:
- يرسل Telegram مع تعطيل الأزرار المضمنة احتياطيًا نصيًا.
- تسرد القناة التي لا تدعم الاختيار خيارات الاختيار كنص.
- يتحول زر URL فقط إما إلى زر رابط أصلي أو سطر URL احتياطي.
- لا تُفشل إخفاقات التثبيت الاختيارية الرسالة المسلّمة.
الاستثناء الرئيسي هو delivery.pin.required: true؛ إذا طُلب التثبيت باعتباره
مطلوبًا ولم تستطع القناة تثبيت الرسالة المُرسلة، يبلّغ التسليم عن فشل.
ربط الموفّر
العارضون المضمنون حاليًا:
| القناة | هدف العرض الأصلي | ملاحظات |
|---|---|---|
| Discord | المكوّنات وحاويات المكوّنات | يحافظ على channelData.discord.components القديم لمنتجي الحمولات الأصلية للمزوّد الحاليين، لكن يجب أن تستخدم عمليات الإرسال المشتركة الجديدة presentation. |
| Slack | Block Kit | يحافظ على channelData.slack.blocks القديم لمنتجي الحمولات الأصلية للمزوّد الحاليين، لكن يجب أن تستخدم عمليات الإرسال المشتركة الجديدة presentation. |
| Telegram | نص مع لوحات مفاتيح مضمنة | تتطلب الأزرار/عناصر التحديد قدرة الأزرار المضمنة للسطح المستهدف؛ وإلا يُستخدم الرجوع إلى النص. |
| Mattermost | نص مع خصائص تفاعلية | تتحول الكتل الأخرى إلى نص. |
| Microsoft Teams | Adaptive Cards | يُضمّن نص message العادي مع البطاقة عندما يُقدَّمان معًا. |
| Feishu | بطاقات تفاعلية | يمكن أن يستخدم رأس البطاقة title؛ ويتجنب المتن تكرار ذلك العنوان. |
| القنوات النصية البسيطة | الرجوع إلى النص | تظل القنوات التي لا تحتوي على عارض تحصل على مخرج قابل للقراءة. |
توافق الحمولات الأصلية للمزوّد هو تسهيل انتقالي لمنتجي الردود الحاليين. وليس سببًا لإضافة حقول أصلية مشتركة جديدة.
Presentation مقابل InteractiveReply
InteractiveReply هو المجموعة الفرعية الداخلية الأقدم التي تستخدمها أدوات الموافقة والتفاعل المساعدة. ويدعم:
- النص
- الأزرار
- عناصر التحديد
MessagePresentation هو عقد الإرسال المشترك المعتمد. ويضيف:
- العنوان
- النبرة
- السياق
- الفاصل
- أزرار URL فقط
- بيانات تعريف تسليم عامة عبر
ReplyPayload.delivery
استخدم الأدوات المساعدة من openclaw/plugin-sdk/interactive-runtime عند ربط الكود الأقدم:
OC_I18N_900011
يجب أن يقبل الكود الجديد MessagePresentation أو ينتجه مباشرة. حمولات interactive الحالية هي مجموعة فرعية مهملة من presentation؛ ويظل دعم وقت التشغيل متاحًا للمنتجين الأقدم.
أنواع InteractiveReply* القديمة وأدوات التحويل المساعدة موسومة بـ @deprecated في SDK:
InteractiveReply,InteractiveReplyBlock,InteractiveReplyButton,InteractiveReplyOption,InteractiveReplySelectBlock, وInteractiveReplyTextBlocknormalizeInteractiveReply(...)hasInteractiveReplyBlocks(...)interactiveReplyToPresentation(...)presentationToInteractiveReply(...)presentationToInteractiveControlsReply(...)resolveInteractiveTextFallback(...)reduceInteractiveReply(...)
يظل presentationToInteractiveReply(...) و
presentationToInteractiveControlsReply(...) متاحين كجسور عارض لتطبيقات القنوات القديمة. يجب ألا يستدعيهما كود المنتج الجديد؛ أرسل presentation ودع تكييف النواة/القناة يتولى العرض.
تتوفر أيضًا بدائل لأدوات الموافقة المساعدة تعتمد على العرض أولًا:
- استخدم
buildApprovalPresentationFromActionDescriptors(...)بدلًا منbuildApprovalInteractiveReplyFromActionDescriptors(...) - استخدم
buildApprovalPresentation(...)بدلًا منbuildApprovalInteractiveReply(...) - استخدم
buildExecApprovalPresentation(...)بدلًا منbuildExecApprovalInteractiveReply(...)
يعيد renderMessagePresentationFallbackText(...) سلسلة فارغة لكتل العرض التي لا تملك رجوعًا نصيًا، مثل عرض يحتوي على فاصل فقط. يمكن لوسائل النقل التي تتطلب متن إرسال غير فارغ تمرير emptyFallback لاختيار متن أدنى دون تغيير عقد الرجوع الافتراضي.
تثبيت التسليم
التثبيت سلوك تسليم، وليس عرضًا. استخدم delivery.pin بدلًا من الحقول الأصلية للمزوّد مثل channelData.telegram.pin.
الدلالات:
pin: trueيثبّت أول رسالة يتم تسليمها بنجاح.- القيمة الافتراضية لـ
pin.notifyهيfalse. - القيمة الافتراضية لـ
pin.requiredهيfalse. - تتدهور حالات فشل التثبيت الاختيارية وتترك الرسالة المرسلة سليمة.
- حالات فشل التثبيت المطلوبة تفشل التسليم.
- الرسائل المجزأة تثبّت أول جزء مُسلَّم، وليس الجزء الأخير.
ما زالت إجراءات رسائل pin وunpin وpins اليدوية موجودة للرسائل الحالية حيث يدعم المزوّد تلك العمليات.
قائمة تحقق مؤلف Plugin
- أعلن عن
presentationمنdescribeMessageTool(...)عندما تستطيع القناة عرض العرض الدلالي أو تدهوره بأمان. - أضف
presentationCapabilitiesإلى محوّل وقت التشغيل الصادر. - نفّذ
renderPresentationفي كود وقت التشغيل، وليس في كود إعداد Plugin في مستوى التحكم. - أبقِ مكتبات الواجهة الأصلية خارج مسارات الإعداد/الفهرس الساخنة.
- أعلن حدود القدرة العامة على
presentationCapabilities.limitsعندما تكون معروفة. - حافظ على حدود المنصة النهائية في العارض والاختبارات.
- أضف اختبارات رجوع للأزرار غير المدعومة، وعناصر التحديد، وأزرار URL، وتكرار العنوان/النص، وعمليات الإرسال المختلطة التي تجمع
messageمعpresentation. - أضف دعم تثبيت التسليم عبر
deliveryCapabilities.pinوpinDeliveredMessageفقط عندما يستطيع المزوّد تثبيت معرّف الرسالة المرسلة. - لا تكشف حقول بطاقة/كتلة/مكوّن/زر أصلية جديدة خاصة بالمزوّد عبر مخطط إجراء الرسائل المشترك.