渠道呈现重构计划
已针对共享代理、CLI、插件功能和出站交付表面实现:
ReplyPayload.presentation承载语义消息 UI。ReplyPayload.delivery.pin承载已发送消息的固定请求。- 共享消息操作暴露
presentation、delivery和pin,而不是提供商原生的components、blocks、buttons或card。 - 核心通过插件声明的出站功能来渲染或自动降级呈现。
- Discord、Slack、Telegram、Mattermost、MS Teams 和 Feishu 渲染器使用通用合约。
- Discord 渠道控制平面代码不再导入基于 Carbon 的 UI 容器。
权威文档现位于 Message Presentation。 请将此计划保留为历史实现上下文;如需更改契约、渲染器或回退行为,请更新权威指南。
渠道 UI 目前分散在几个不兼容的表面上:
- 核心通过
buildCrossContextComponents拥有一个 Discord 形状的跨上下文渲染器钩子。 - Discord
channel.ts可以通过DiscordUiContainer导入原生 Carbon UI,这会将运行时 UI 依赖项引入渠道插件控制平面。 - 代理和 CLI 暴露了原生负载的逃生舱口,例如 Discord
components、Slackblocks、Telegram 或 Mattermostbuttons,以及 Teams 或 Feishucard。 ReplyPayload.channelData同时承载传输提示和原生 UI 信封。- 通用
interactive模型存在,但它比 Discord、Slack、Teams、Feishu、LINE、Telegram 和 Mattermost 已经使用的更丰富的布局要窄。
这使得核心能够感知原生 UI 形状,削弱了插件运行时的延迟加载,并为代理提供了太多特定于提供商的方式来表达相同的消息意图。
- 核心根据声明的功能决定消息的最佳语义呈现。
- 扩展声明能力并将语义呈现渲染为原生传输负载。
- Web 控制界面与聊天原生界面保持分离。
- 原生渠道负载不会通过共享代理或 CLI 消息表面暴露。
- 不支持的呈现功能会自动降级为最佳文本表示形式。
- 置入已发送消息等传递行为属于通用传递元数据,而非呈现。
- 不为
buildCrossContextComponents提供向后兼容的填充层。 - 不为
components、blocks、buttons或card提供公共的原生逃生舱。 - 核心不导入渠道原生的 UI 库。
- 没有针对打包渠道的特定于提供商的 SDK 缝隙。
在 ReplyPayload 中添加一个核心拥有的 presentation 字段。
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"。interactive选择块映射到presentation.blocks[].type = "select"。
外部代理和 CLI 模式现在使用 presentation;interactive 仍然是现有回复生成器的内部旧版解析/渲染辅助工具。
面向生成器的公共 API 将 interactive 视为已弃用。运行时
支持仍然保留,以便现有的审批辅助工具和旧版插件继续
工作,同时新代码发出 presentation。
为核心拥有的 delivery 字段添加非 UI 的发送行为。
type ReplyPayloadDelivery = { pin?: | boolean | { enabled: boolean; notify?: boolean; required?: boolean; };};语义:
delivery.pin = true意味着固定第一条成功传递的消息。notify默认为false。required默认为false;不支持的渠道或固定失败会通过继续传递自动降级。- 针对现有消息保留手动
pin、unpin和list-pins消息操作。
当前的 Telegram ACP 主题绑定应从 channelData.telegram.pin = true 移至 delivery.pin = true。
运行时能力契约
Section titled “运行时能力契约”将呈现和交付渲染挂钩添加到运行时出站适配器,而不是控制平面渠道插件。
type ChannelPresentationCapabilities = { supported: boolean; buttons?: boolean; selects?: boolean; context?: boolean; divider?: boolean; tones?: MessagePresentationTone[]; 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; }; };};
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>;};核心行为:
- 解析目标渠道和运行时适配器。
- 请求呈现能力。
- 在 渲染之前降级不支持的块并应用通用功能限制。
- 调用
renderPresentation。 - 如果不存在渲染器,则将呈现转换为文本后备。
- 成功发送后,当请求并支持
delivery.pin时,调用pinDeliveredMessage。
Discord:
- 在仅运行时模块中将
presentation渲染为 components v2 和 Carbon 容器。 - 将强调色辅助工具保留在轻量级模块中。
- 从渠道插件控制平面代码中移除
DiscordUiContainer导入。
Slack:
- 将
presentation渲染为 Block Kit。 - 移除代理和 CLI
blocks输入。
Telegram:
- 将文本、上下文和分隔线渲染为文本。
- 在为目标界面配置并允许的情况下,将操作和选择渲染为内联键盘。
- 当禁用内联按钮时使用文本后备。
- 将 ACP 主题固定移动到
delivery.pin。
Mattermost:
- 在配置的位置将操作渲染为交互式按钮。
- 将其他块渲染为文本后备。
MS Teams:
- 将
presentation渲染为 Adaptive Cards。 - 保留手动固定/取消固定/列出固定操作。
- 如果 Graph 支持对目标对话可靠,则可选择实现
pinDeliveredMessage。
Feishu:
- 将
presentation渲染为交互式卡片。 - 保留手动固定/取消固定/列出固定操作。
- 如果 API 行为可靠,可选择实现
pinDeliveredMessage以进行发送消息的固定。
LINE:
- 尽可能将
presentation渲染为 Flex 或模板消息。 - 不支持的块回退到文本。
- 从
channelData中移除 LINE UI 负载。
纯文本或受限渠道:
- 将呈现转换为具有保守格式的文本。
- 重新应用 Discord 发布修复程序,将
ui-colors.ts与基于 Carbon 的 UI 分离,并从extensions/discord/src/channel.ts中移除DiscordUiContainer。 - 将
presentation和delivery添加到ReplyPayload、出站负载规范化、传递摘要和 hook 负载中。 - 在狭窄的 SDK/运行时子路径中添加
MessagePresentationschema 和解析器辅助程序。 - 用语义展示能力替换消息能力
buttons、cards、components和blocks。 - 添加用于呈现渲染和传递固定的运行时出站适配器钩子。
- 用
buildCrossContextPresentation替换跨上下文组件构造。 - 删除
src/infra/outbound/channel-adapters.ts并从渠道插件类型中移除buildCrossContextComponents。 - 更改
maybeApplyCrossContextMarker以附加presentation而不是原生参数。 - 更新插件分派发送路径,使其仅消耗语义呈现和传递元数据。
- 移除 agent 和 CLI 原生负载参数:
components、blocks、buttons和card。 - 移除创建原生消息工具架构的 SDK 辅助函数,用呈现架构辅助函数替换它们。
- 从
channelData中移除 UI/原生信封;在审查每个剩余字段之前,仅保留传输元数据。 - 迁移 Discord、Slack、Telegram、Mattermost、MS Teams、飞书和 LINE 渲染器。
- 更新消息 CLI、渠道页面、插件 SDK 和能力手册的文档。
- 针对 Discord 和受影响的渠道入口点运行导入扩展分析。
步骤 1-11 和 13-14 已在此重构中为共享 agent、CLI、插件能力和出站适配器合同实现。步骤 12 仍然是对提供商私有 channelData 传输信封的更深层内部清理传递。步骤 15 仍然是后续验证,如果我们想要超出类型/测试范围的量化导入扇出数字。
添加或更新:
- 呈现规范化测试。
- 针对不受支持块的呈现自动降级测试。
- 针对插件分派和核心传递路径的跨上下文标记测试。
- Discord、Slack、Telegram、Mattermost、MS Teams、Feishu、LINE 和文本回退的通道渲染矩阵测试。
- 消息工具 schema 测试,证明原生字段已移除。
- CLI 测试,证明原生标志已移除。
- Discord 入口点导入惰性回归,覆盖 Carbon。
- 传递固定 测试,覆盖 Telegram 和通用回退。
- 在第一阶段,是否应该为 Discord、Slack、MS Teams 和飞书实现
delivery.pin,还是仅先为 Telegram 实现? delivery最终是否应包含replyToId、replyToCurrent、silent和audioAsVoice等现有字段,还是应专注于发送后的行为?- 演示是否应该直接支持图片或文件引用,或者媒体目前是否应与 UI 布局保持分离?