Plugin maintainer reference
การนำเสนอข้อความ
การนำเสนอข้อความคือสัญญาร่วมของ OpenClaw สำหรับ UI แชตขาออกแบบสมบูรณ์ ช่วยให้เอเจนต์ คำสั่ง CLI โฟลว์การอนุมัติ และ Plugin ต่างๆ อธิบายเจตนาของข้อความ เพียงครั้งเดียว ขณะที่ Plugin ช่องทางแต่ละตัวเรนเดอร์รูปแบบเนทีฟที่ดีที่สุดเท่าที่ทำได้
ใช้การนำเสนอสำหรับ UI ข้อความแบบพกพา:
- ส่วนข้อความ
- ข้อความบริบท/ส่วนท้ายขนาดเล็ก
- เส้นคั่น
- ปุ่ม
- เมนูเลือก
- ชื่อการ์ดและโทน
อย่าเพิ่มฟิลด์เนทีฟของผู้ให้บริการใหม่ เช่น 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 แบบทึบผ่านเส้นทางการโต้ตอบของช่องทาง Plugin ช่องทางต้องไม่ตีความข้อมูลคอลแบ็กใหม่เป็นคำสั่งสแลชvalueคือค่าคอลแบ็กแบบทึบเดิม ตัวควบคุมใหม่ควรใช้actionเพื่อให้ Plugin ช่องทางสามารถแมปคำสั่งและคอลแบ็กได้โดยไม่ต้องเดาจากข้อความurlคือปุ่มลิงก์ สามารถมีอยู่ได้โดยไม่มีvaluewebAppอธิบายปุ่มเว็บแอปเนทีฟของช่องทาง Telegram เรนเดอร์สิ่งนี้ เป็นweb_appและรองรับเฉพาะในแชตส่วนตัวเท่านั้นweb_appยังคง ถูกยอมรับในเพย์โหลด JSON แบบหลวมเพื่อความเข้ากันได้ แต่ผู้ผลิต TypeScript ควรใช้webApp- ต้องมี
labelและยังใช้ในข้อความสำรองด้วย styleเป็นคำแนะนำ ตัวเรนเดอร์ควรแมปสไตล์ที่ไม่รองรับไปยังค่าเริ่มต้น ที่ปลอดภัย ไม่ใช่ทำให้การส่งล้มเหลวpriorityเป็นทางเลือก เมื่อช่องทางประกาศขีดจำกัดแอ็กชันและต้องตัด ตัวควบคุม แกนหลักจะเก็บปุ่มที่มีลำดับความสำคัญสูงกว่าก่อน และคงลำดับเดิม ของปุ่มที่มีลำดับความสำคัญเท่ากัน เมื่อควบคุมทั้งหมดพอดี จะคงลำดับที่ผู้เขียนกำหนดไว้disabledเป็นทางเลือก ช่องทางต้องเลือกใช้ด้วยsupportsDisabled; มิฉะนั้น แกนหลักจะลดระดับตัวควบคุมที่ปิดใช้งานเป็นข้อความสำรองที่ไม่โต้ตอบreusableเป็นทางเลือก ช่องทางที่รองรับคอลแบ็กเนทีฟแบบใช้ซ้ำได้อาจ คงแอ็กชันไว้หลังการโต้ตอบสำเร็จ ใช้สำหรับแอ็กชันที่ทำซ้ำได้หรือเป็น idempotent เช่น รีเฟรช ตรวจสอบ หรือดูรายละเอียดเพิ่มเติม; ปล่อยว่างไว้สำหรับการอนุมัติ แบบครั้งเดียวปกติและแอ็กชันที่ทำลายข้อมูล
ความหมายของการเลือก:
options[].actionมีความหมายของคำสั่ง/คอลแบ็กเหมือนกับ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 Mini App:
{ "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 }}สัญญาของตัวเรนเดอร์
Plugin ช่องทางประกาศการรองรับการเรนเดอร์บนอะแดปเตอร์ขาออกของตน:
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หลังจากส่งข้อความแรกสำเร็จ
แกนหลักเป็นเจ้าของพฤติกรรมสำรอง เพื่อให้ผู้ผลิตคงความไม่ขึ้นกับช่องทางได้ Plugin ช่องทางเป็นเจ้าของการเรนเดอร์เนทีฟและการจัดการการโต้ตอบ
กฎการลดระดับ
การนำเสนอต้องปลอดภัยสำหรับการส่งบนช่องทางที่มีข้อจำกัด
ข้อความสำรองประกอบด้วย:
titleเป็นบรรทัดแรก- บล็อก
textเป็นย่อหน้าปกติ - บล็อก
contextเป็นบรรทัดบริบทแบบกะทัดรัด - บล็อก
dividerเป็นตัวคั่นเชิงภาพ - ป้ายกำกับปุ่ม รวมถึง URL สำหรับปุ่มลิงก์
- ป้ายกำกับตัวเลือกการเลือก
ตัวควบคุมเนทีฟที่ไม่รองรับควรลดระดับแทนที่จะทำให้การส่งทั้งหมดล้มเหลว ตัวอย่าง:
- 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; เนื้อหาหลีกเลี่ยงการทำซ้ำชื่อนั้น |
| ช่องทางธรรมดา | ข้อความสำรอง | ช่องทางที่ไม่มีตัวเรนเดอร์ยังคงได้เอาต์พุตที่อ่านได้ |
ความเข้ากันได้ของเพย์โหลดแบบเนทีฟของผู้ให้บริการเป็นตัวช่วยช่วงเปลี่ยนผ่านสำหรับตัวสร้างการตอบกลับที่มีอยู่ ไม่ใช่เหตุผลในการเพิ่มฟิลด์เนทีฟที่ใช้ร่วมกันใหม่
การนำเสนอเทียบกับ InteractiveReply
InteractiveReply คือชุดย่อยภายในแบบเก่าที่ตัวช่วยการอนุมัติและการโต้ตอบใช้ รองรับ:
- ข้อความ
- ปุ่ม
- ตัวเลือก
MessagePresentation คือสัญญาการส่งที่ใช้ร่วมกันแบบมาตรฐาน เพิ่มสิ่งต่อไปนี้:
- ชื่อเรื่อง
- โทน
- บริบท
- เส้นแบ่ง
- ปุ่มเฉพาะ URL
- เมตาดาต้าการส่งมอบทั่วไปผ่าน
ReplyPayload.delivery
ใช้ตัวช่วยจาก openclaw/plugin-sdk/interactive-runtime เมื่อเชื่อมโค้ดเก่า:
adaptMessagePresentationForChannel, applyPresentationActionLimits, interactiveReplyToPresentation, normalizeMessagePresentation, presentationPageSize, presentationToInteractiveControlsReply, presentationToInteractiveReply, renderMessagePresentationFallbackText,} from "openclaw/plugin-sdk/interactive-runtime";โค้ดใหม่ควรรับหรือสร้าง MessagePresentation โดยตรง เพย์โหลด interactive ที่มีอยู่เป็นชุดย่อยที่เลิกใช้แล้วของ presentation; การรองรับในรันไทม์ยังคงอยู่สำหรับตัวสร้างแบบเก่า
ชนิด InteractiveReply* ดั้งเดิมและตัวช่วยแปลงถูกทำเครื่องหมาย
@deprecated ใน SDK:
InteractiveReply,InteractiveReplyBlock,InteractiveReplyButton,InteractiveReplyOption,InteractiveReplySelectBlockและInteractiveReplyTextBlocknormalizeInteractiveReply(...)hasInteractiveReplyBlocks(...)interactiveReplyToPresentation(...)presentationToInteractiveReply(...)presentationToInteractiveControlsReply(...)resolveInteractiveTextFallback(...)reduceInteractiveReply(...)
presentationToInteractiveReply(...) และ
presentationToInteractiveControlsReply(...) ยังคงพร้อมใช้งานเป็นสะพานเรนเดอร์สำหรับการใช้งานช่องทางแบบเดิม โค้ดตัวสร้างใหม่ไม่ควรเรียกใช้สิ่งเหล่านี้ ให้ส่ง presentation และปล่อยให้การปรับ core/channel จัดการการเรนเดอร์
ตัวช่วยการอนุมัติก็มีตัวทดแทนที่ยึดการนำเสนอเป็นหลักเช่นกัน:
- ใช้
buildApprovalPresentationFromActionDescriptors(...)แทนbuildApprovalInteractiveReplyFromActionDescriptors(...) - ใช้
buildApprovalPresentation(...)แทนbuildApprovalInteractiveReply(...) - ใช้
buildExecApprovalPresentation(...)แทนbuildExecApprovalInteractiveReply(...)
renderMessagePresentationFallbackText(...) ส่งคืนสตริงว่างสำหรับบล็อกการนำเสนอที่ไม่มีข้อความสำรอง เช่น การนำเสนอที่มีเพียงเส้นแบ่ง ทรานสปอร์ตที่ต้องการเนื้อหาการส่งที่ไม่ว่างสามารถส่ง
emptyFallback เพื่อเลือกใช้เนื้อหาขั้นต่ำโดยไม่เปลี่ยนสัญญาข้อความสำรองเริ่มต้น
การปักหมุดการส่งมอบ
การปักหมุดเป็นพฤติกรรมการส่งมอบ ไม่ใช่การนำเสนอ ใช้ delivery.pin แทนฟิลด์เนทีฟของผู้ให้บริการ เช่น channelData.telegram.pin
ความหมาย:
pin: trueปักหมุดข้อความแรกที่ส่งสำเร็จpin.notifyมีค่าเริ่มต้นเป็นfalsepin.requiredมีค่าเริ่มต้นเป็นfalse- ความล้มเหลวในการปักหมุดที่เป็นทางเลือกจะลดระดับลงและปล่อยให้ข้อความที่ส่งแล้วยังคงอยู่
- ความล้มเหลวในการปักหมุดที่จำเป็นจะทำให้การส่งมอบล้มเหลว
- ข้อความที่แบ่งเป็นชิ้นจะปักหมุดชิ้นแรกที่ส่งแล้ว ไม่ใช่ชิ้นสุดท้าย
แอ็กชันข้อความ pin, unpin และ pins แบบแมนนวลยังคงมีอยู่สำหรับข้อความเดิมที่ผู้ให้บริการรองรับการดำเนินการเหล่านั้น
รายการตรวจสอบสำหรับผู้เขียน Plugin
- ประกาศ
presentationจากdescribeMessageTool(...)เมื่อช่องทางสามารถเรนเดอร์หรือลดระดับการนำเสนอเชิงความหมายได้อย่างปลอดภัย - เพิ่ม
presentationCapabilitiesไปยังอะแดปเตอร์ขาออกของรันไทม์ - ติดตั้ง
renderPresentationในโค้ดรันไทม์ ไม่ใช่โค้ดตั้งค่า Plugin ของ control-plane - แยกไลบรารี UI เนทีฟออกจากเส้นทางตั้งค่า/แค็ตตาล็อกที่ร้อน
- ประกาศขีดจำกัดความสามารถทั่วไปบน
presentationCapabilities.limitsเมื่อทราบค่าเหล่านั้น - รักษาขีดจำกัดขั้นสุดท้ายของแพลตฟอร์มไว้ในตัวเรนเดอร์และการทดสอบ
- เพิ่มการทดสอบสำรองสำหรับปุ่มที่ไม่รองรับ ตัวเลือก ปุ่ม URL การซ้ำกันของชื่อเรื่อง/ข้อความ และการส่งแบบผสมระหว่าง
messageกับpresentation - เพิ่มการรองรับการปักหมุดการส่งมอบผ่าน
deliveryCapabilities.pinและpinDeliveredMessageเฉพาะเมื่อผู้ให้บริการสามารถปักหมุดรหัสข้อความที่ส่งแล้วได้ - อย่าเปิดเผยฟิลด์การ์ด/บล็อก/คอมโพเนนต์/ปุ่มเนทีฟของผู้ให้บริการใหม่ผ่านสคีมาแอ็กชันข้อความที่ใช้ร่วมกัน