Get started
แผนการปรับโครงสร้างส่วนการแสดงผลของช่องทาง
สถานะ
นำไปใช้แล้วสำหรับพื้นผิวของเอเจนต์ที่ใช้ร่วมกัน, CLI, ความสามารถของ Plugin และการส่งออก:
ReplyPayload.presentationเก็บ UI ข้อความเชิงความหมายReplyPayload.delivery.pinเก็บคำขอปักหมุดข้อความที่ส่งแล้ว- แอ็กชันข้อความที่ใช้ร่วมกันเปิดเผย
presentation,deliveryและpinแทนcomponents,blocks,buttonsหรือcardแบบเนทีฟของผู้ให้บริการ - Core เรนเดอร์หรือปรับลดระดับ presentation อัตโนมัติผ่านความสามารถการส่งออกที่ Plugin ประกาศไว้
- ตัวเรนเดอร์ของ Discord, Slack, Telegram, Mattermost, MS Teams และ Feishu ใช้สัญญาทั่วไป
- โค้ด control-plane ของช่อง Discord ไม่ได้นำเข้าคอนเทนเนอร์ UI ที่อิง Carbon อีกต่อไป
เอกสารหลักตอนนี้อยู่ที่ Message Presentation เก็บแผนนี้ไว้เป็นบริบทการนำไปใช้ในอดีต; อัปเดตคู่มือหลัก เมื่อมีการเปลี่ยนแปลงสัญญา, ตัวเรนเดอร์ หรือพฤติกรรม fallback
ปัญหา
ปัจจุบัน UI ของช่องถูกแบ่งอยู่บนพื้นผิวหลายแบบที่เข้ากันไม่ได้:
- Core เป็นเจ้าของ hook ตัวเรนเดอร์ข้ามบริบทที่มีรูปทรงแบบ Discord ผ่าน
buildCrossContextComponents channel.tsของ Discord สามารถนำเข้า UI Carbon แบบเนทีฟผ่านDiscordUiContainerซึ่งดึง dependency ของ UI runtime เข้าไปใน control plane ของ Plugin ช่อง- เอเจนต์และ CLI เปิดเผยช่องทางเลี่ยง payload แบบเนทีฟ เช่น Discord
components, Slackblocks, Telegram หรือ Mattermostbuttonsและ Teams หรือ Feishucard ReplyPayload.channelDataเก็บทั้ง hint ของการขนส่งและ envelope ของ UI แบบเนทีฟ- โมเดล
interactiveทั่วไปมีอยู่แล้ว แต่แคบกว่าเลย์เอาต์ที่สมบูรณ์กว่าซึ่งใช้อยู่แล้วโดย Discord, Slack, Teams, Feishu, LINE, Telegram และ Mattermost
สิ่งนี้ทำให้ core รับรู้รูปทรง UI แบบเนทีฟ ทำให้ความ lazy ของ Plugin runtime อ่อนลง และเปิดช่องทางเฉพาะผู้ให้บริการมากเกินไปให้เอเจนต์ใช้สื่อเจตนาของข้อความเดียวกัน
เป้าหมาย
- Core ตัดสินใจเลือก presentation เชิงความหมายที่ดีที่สุดสำหรับข้อความจากความสามารถที่ประกาศไว้
- Extension ประกาศความสามารถและเรนเดอร์ presentation เชิงความหมายเป็น payload การขนส่งแบบเนทีฟ
- Web Control UI แยกจาก UI เนทีฟของแชท
- ไม่เปิดเผย payload ช่องแบบเนทีฟผ่านพื้นผิวข้อความของเอเจนต์ที่ใช้ร่วมกันหรือ CLI
- ฟีเจอร์ presentation ที่ไม่รองรับจะปรับลดระดับอัตโนมัติเป็นการแทนค่าด้วยข้อความที่ดีที่สุด
- พฤติกรรมการส่ง เช่น การปักหมุดข้อความที่ส่งแล้ว เป็น metadata การส่งทั่วไป ไม่ใช่ presentation
สิ่งที่ไม่ใช่เป้าหมาย
- ไม่มี shim ความเข้ากันได้ย้อนหลังสำหรับ
buildCrossContextComponents - ไม่มีช่องทางเลี่ยงแบบเนทีฟสาธารณะสำหรับ
components,blocks,buttonsหรือcard - ไม่มีการนำเข้าไลบรารี UI เนทีฟของช่องใน core
- ไม่มี seam ของ SDK เฉพาะผู้ให้บริการสำหรับช่องที่ bundled มา
โมเดลเป้าหมาย
เพิ่มฟิลด์ presentation ที่ core เป็นเจ้าของลงใน ReplyPayload
type MessagePresentationTone = "neutral" | "info" | "success" | "warning" | "danger";
type MessagePresentation = {
tone?: MessagePresentationTone;
title?: string;
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 MessagePresentationButton = {
label: string;
value?: string;
url?: string;
style?: "primary" | "secondary" | "success" | "danger";
};
type MessagePresentationOption = {
label: string;
value: string;
};
interactive กลายเป็นชุดย่อยของ presentation ระหว่างการย้ายระบบ:
- บล็อกข้อความ
interactiveแมปไปยังpresentation.blocks[].type = "text" - บล็อกปุ่ม
interactiveแมปไปยังpresentation.blocks[].type = "buttons" - บล็อก select
interactiveแมปไปยังpresentation.blocks[].type = "select"
สคีมาของเอเจนต์ภายนอกและ CLI ตอนนี้ใช้ presentation; interactive ยังคงเป็นตัวช่วย parser/renderer legacy ภายในสำหรับตัวผลิต reply ที่มีอยู่
Metadata การส่ง
เพิ่มฟิลด์ delivery ที่ core เป็นเจ้าของสำหรับพฤติกรรมการส่งที่ไม่ใช่ UI
type ReplyPayloadDelivery = {
pin?:
| boolean
| {
enabled: boolean;
notify?: boolean;
required?: boolean;
};
};
ความหมาย:
delivery.pin = trueหมายถึงปักหมุดข้อความแรกที่ส่งสำเร็จnotifyมีค่าเริ่มต้นเป็นfalserequiredมีค่าเริ่มต้นเป็นfalse; ช่องที่ไม่รองรับหรือการปักหมุดที่ล้มเหลวจะปรับลดระดับอัตโนมัติโดยส่งต่อไป- แอ็กชันข้อความ
pin,unpinและlist-pinsแบบ manual ยังคงอยู่สำหรับข้อความที่มีอยู่
การผูก topic ของ Telegram ACP ปัจจุบันควรย้ายจาก channelData.telegram.pin = true ไปเป็น delivery.pin = true
สัญญาความสามารถ runtime
เพิ่ม hook เรนเดอร์ presentation และ delivery ลงใน adapter การส่งออกของ runtime ไม่ใช่ Plugin ช่อง control-plane
type ChannelPresentationCapabilities = {
supported: boolean;
buttons?: boolean;
selects?: boolean;
context?: boolean;
divider?: boolean;
tones?: MessagePresentationTone[];
};
type ChannelDeliveryCapabilities = {
pinSentMessage?: boolean;
};
type ChannelOutboundAdapter = {
presentationCapabilities?: ChannelPresentationCapabilities;
renderPresentation?: (params: {
payload: ReplyPayload;
presentation: MessagePresentation;
ctx: ChannelOutboundSendContext;
}) => ReplyPayload | null;
deliveryCapabilities?: ChannelDeliveryCapabilities;
pinDeliveredMessage?: (params: {
cfg: OpenClawConfig;
accountId?: string | null;
to: string;
threadId?: string | number | null;
messageId: string;
notify: boolean;
}) => Promise<void>;
};
พฤติกรรมของ core:
- Resolve ช่องเป้าหมายและ adapter runtime
- ขอความสามารถของ presentation
- ปรับลดระดับบล็อกที่ไม่รองรับก่อนเรนเดอร์
- เรียก
renderPresentation - หากไม่มี renderer ให้แปลง presentation เป็น text fallback
- หลังส่งสำเร็จ เรียก
pinDeliveredMessageเมื่อมีการร้องขอdelivery.pinและรองรับ
การแมปช่อง
Discord:
- เรนเดอร์
presentationเป็น components v2 และคอนเทนเนอร์ Carbon ในโมดูล runtime-only - เก็บตัวช่วยสี accent ไว้ในโมดูลเบา
- ลบการนำเข้า
DiscordUiContainerออกจากโค้ด control-plane ของ Plugin ช่อง
Slack:
- เรนเดอร์
presentationเป็น Block Kit - ลบ input
blocksของเอเจนต์และ CLI
Telegram:
- เรนเดอร์ข้อความ, context และ divider เป็นข้อความ
- เรนเดอร์แอ็กชันและ select เป็น inline keyboard เมื่อกำหนดค่าและอนุญาตสำหรับพื้นผิวเป้าหมาย
- ใช้ text fallback เมื่อปิดใช้งาน inline buttons
- ย้ายการปักหมุด topic ของ ACP ไปที่
delivery.pin
Mattermost:
- เรนเดอร์แอ็กชันเป็นปุ่ม interactive เมื่อกำหนดค่าไว้
- เรนเดอร์บล็อกอื่นเป็น text fallback
MS Teams:
- เรนเดอร์
presentationเป็น Adaptive Cards - เก็บแอ็กชัน manual pin/unpin/list-pins ไว้
- อาจนำ
pinDeliveredMessageไปใช้ถ้า Graph รองรับอย่างเชื่อถือได้สำหรับการสนทนาเป้าหมาย
Feishu:
- เรนเดอร์
presentationเป็น interactive cards - เก็บแอ็กชัน manual pin/unpin/list-pins ไว้
- อาจนำ
pinDeliveredMessageไปใช้สำหรับการปักหมุดข้อความที่ส่งแล้วถ้าพฤติกรรม API เชื่อถือได้
LINE:
- เรนเดอร์
presentationเป็น Flex หรือ template messages เมื่อเป็นไปได้ - fallback เป็นข้อความสำหรับบล็อกที่ไม่รองรับ
- ลบ payload UI ของ LINE ออกจาก
channelData
ช่องธรรมดาหรือมีข้อจำกัด:
- แปลง presentation เป็นข้อความด้วยการจัดรูปแบบแบบอนุรักษ์นิยม
ขั้นตอน refactor
- ใช้ซ้ำ fix release ของ Discord ที่แยก
ui-colors.tsออกจาก UI ที่อิง Carbon และลบDiscordUiContainerออกจากextensions/discord/src/channel.ts - เพิ่ม
presentationและdeliveryลงในReplyPayload, การ normalize payload การส่งออก, สรุปการส่ง และ hook payload - เพิ่มสคีมา
MessagePresentationและตัวช่วย parser ใน subpath ของ SDK/runtime ที่แคบ - แทนที่ความสามารถของข้อความ
buttons,cards,componentsและblocksด้วยความสามารถ presentation เชิงความหมาย - เพิ่ม hook ของ adapter การส่งออก runtime สำหรับเรนเดอร์ presentation และการปักหมุด delivery
- แทนที่การสร้าง component ข้ามบริบทด้วย
buildCrossContextPresentation - ลบ
src/infra/outbound/channel-adapters.tsและลบbuildCrossContextComponentsออกจาก type ของ Plugin ช่อง - เปลี่ยน
maybeApplyCrossContextMarkerให้แนบpresentationแทน native params - อัปเดตเส้นทางส่งของ plugin-dispatch ให้ใช้เฉพาะ presentation เชิงความหมายและ metadata การส่ง
- ลบ native payload params ของเอเจนต์และ CLI:
components,blocks,buttonsและcard - ลบตัวช่วย SDK ที่สร้างสคีมา message-tool แบบเนทีฟ และแทนที่ด้วยตัวช่วยสคีมา presentation
- ลบ UI/native envelopes ออกจาก
channelData; เก็บไว้เฉพาะ metadata การขนส่งจนกว่าจะตรวจทานฟิลด์ที่เหลือแต่ละรายการ - ย้ายตัวเรนเดอร์ Discord, Slack, Telegram, Mattermost, MS Teams, Feishu และ LINE
- อัปเดตเอกสารสำหรับ message CLI, หน้าช่อง, Plugin SDK และ capability cookbook
- รัน import fanout profiling สำหรับ Discord และ entrypoint ของช่องที่ได้รับผลกระทบ
ขั้นตอน 1-11 และ 13-14 ถูกนำไปใช้แล้วใน refactor นี้สำหรับเอเจนต์ที่ใช้ร่วมกัน, CLI, ความสามารถของ Plugin และสัญญา outbound adapter ขั้นตอน 12 ยังคงเป็นรอบ cleanup ภายในที่ลึกกว่า สำหรับ envelope การขนส่ง channelData แบบ provider-private ขั้นตอน 15 ยังคงเป็นการตรวจสอบติดตามผลหากเราต้องการตัวเลข import-fanout เชิงปริมาณนอกเหนือจาก type/test gate
การทดสอบ
เพิ่มหรืออัปเดต:
- การทดสอบ normalization ของ presentation
- การทดสอบการปรับลดระดับอัตโนมัติของ presentation สำหรับบล็อกที่ไม่รองรับ
- การทดสอบ marker ข้ามบริบทสำหรับเส้นทาง plugin dispatch และ core delivery
- การทดสอบเมทริกซ์ตัวเรนเดอร์ช่องสำหรับ Discord, Slack, Telegram, Mattermost, MS Teams, Feishu, LINE และ text fallback
- การทดสอบสคีมา message tool ที่พิสูจน์ว่าฟิลด์เนทีฟหายไปแล้ว
- การทดสอบ CLI ที่พิสูจน์ว่า flag เนทีฟหายไปแล้ว
- regression ความ lazy ของการนำเข้า entrypoint ของ Discord ครอบคลุม Carbon
- การทดสอบ delivery pin ครอบคลุม Telegram และ fallback ทั่วไป
คำถามที่เปิดอยู่
- ควรนำ
delivery.pinไปใช้กับ Discord, Slack, MS Teams และ Feishu ในรอบแรก หรือเฉพาะ Telegram ก่อน? - ในท้ายที่สุด
deliveryควรดูดซับฟิลด์ที่มีอยู่ เช่นreplyToId,replyToCurrent,silentและaudioAsVoiceหรือควรมุ่งเน้นที่พฤติกรรมหลังส่งเท่านั้น? - presentation ควรรองรับรูปภาพหรือ file references โดยตรง หรือสื่อต่าง ๆ ควรแยกจากเลย์เอาต์ UI ไว้ก่อนในตอนนี้?