Skip to main content

Estado

Implementado para las superficies compartidas de agente, CLI, capacidad de plugin y entrega saliente:
  • ReplyPayload.presentation transporta UI semántica de mensajes.
  • ReplyPayload.delivery.pin transporta solicitudes para fijar mensajes enviados.
  • Las acciones de mensajes compartidas exponen presentation, delivery y pin en lugar de components, blocks, buttons o card nativos del proveedor.
  • El núcleo renderiza o degrada automáticamente la presentación mediante capacidades salientes declaradas por el plugin.
  • Los renderizadores de Discord, Slack, Telegram, Mattermost, MS Teams y Feishu consumen el contrato genérico.
  • El código del plano de control del canal Discord ya no importa contenedores de UI respaldados por Carbon.
La documentación canónica ahora vive en Presentación de mensajes. Mantén este plan como contexto histórico de implementación; actualiza la guía canónica para cambios de contrato, renderizador o comportamiento de degradación.

Problema

La UI de canales está actualmente dividida entre varias superficies incompatibles:
  • El núcleo posee un hook de renderizador entre contextos con forma de Discord mediante buildCrossContextComponents.
  • channel.ts de Discord puede importar UI nativa de Carbon mediante DiscordUiContainer, lo que arrastra dependencias de UI en tiempo de ejecución al plano de control del plugin de canal.
  • El agente y la CLI exponen escapes de payload nativos como components de Discord, blocks de Slack, buttons de Telegram o Mattermost, y card de Teams o Feishu.
  • ReplyPayload.channelData transporta tanto indicaciones de transporte como sobres de UI nativa.
  • El modelo genérico interactive existe, pero es más limitado que los diseños más ricos que ya usan Discord, Slack, Teams, Feishu, LINE, Telegram y Mattermost.
Esto hace que el núcleo conozca formas de UI nativas, debilita la carga diferida del runtime de plugins y da a los agentes demasiadas formas específicas de proveedor para expresar la misma intención de mensaje.

Objetivos

  • El núcleo decide la mejor presentación semántica para un mensaje a partir de las capacidades declaradas.
  • Las extensiones declaran capacidades y renderizan la presentación semántica en payloads de transporte nativos.
  • La Web Control UI permanece separada de la UI nativa de chat.
  • Los payloads nativos de canal no se exponen mediante la superficie compartida de mensajes del agente o la CLI.
  • Las características de presentación no admitidas se degradan automáticamente a la mejor representación de texto.
  • El comportamiento de entrega, como fijar un mensaje enviado, es metadato genérico de entrega, no presentación.

No objetivos

  • Sin shim de compatibilidad hacia atrás para buildCrossContextComponents.
  • Sin escapes nativos públicos para components, blocks, buttons o card.
  • Sin importaciones del núcleo de bibliotecas de UI nativas de canal.
  • Sin seams de SDK específicos de proveedor para canales incluidos.

Modelo objetivo

Añadir un campo presentation propiedad del núcleo a 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 se convierte en un subconjunto de presentation durante la migración:
  • El bloque de texto interactive se asigna a presentation.blocks[].type = "text".
  • El bloque de botones interactive se asigna a presentation.blocks[].type = "buttons".
  • El bloque de selección interactive se asigna a presentation.blocks[].type = "select".
Los esquemas externos del agente y la CLI ahora usan presentation; interactive permanece como un helper interno heredado de análisis/renderizado para productores de respuestas existentes. La API pública orientada a productores trata interactive como obsoleto. El soporte en runtime permanece para que los helpers de aprobación existentes y los plugins antiguos sigan funcionando mientras el código nuevo emite presentation.

Metadatos de entrega

Añadir un campo delivery propiedad del núcleo para comportamiento de envío que no es UI.
type ReplyPayloadDelivery = {
  pin?:
    | boolean
    | {
        enabled: boolean;
        notify?: boolean;
        required?: boolean;
      };
};
Semántica:
  • delivery.pin = true significa fijar el primer mensaje entregado correctamente.
  • notify tiene false como valor predeterminado.
  • required tiene false como valor predeterminado; los canales no admitidos o los fallos al fijar se degradan automáticamente continuando la entrega.
  • Las acciones manuales de mensaje pin, unpin y list-pins permanecen para mensajes existentes.
La vinculación actual de temas ACP de Telegram debe pasar de channelData.telegram.pin = true a delivery.pin = true.

Contrato de capacidades del runtime

Añadir hooks de renderizado de presentación y entrega al adaptador saliente del runtime, no al plugin de canal del plano de control.
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>;
};
Comportamiento del núcleo:
  • Resolver el canal objetivo y el adaptador de runtime.
  • Pedir las capacidades de presentación.
  • Degradar bloques no admitidos y aplicar límites genéricos de capacidades antes de renderizar.
  • Llamar a renderPresentation.
  • Si no existe renderizador, convertir la presentación a una alternativa de texto.
  • Después de un envío correcto, llamar a pinDeliveredMessage cuando delivery.pin se solicite y esté admitido.

Mapeo de canales

Discord:
  • Renderizar presentation a componentes v2 y contenedores Carbon en módulos solo de runtime.
  • Mantener helpers de color de acento en módulos ligeros.
  • Eliminar importaciones de DiscordUiContainer del código del plano de control del plugin de canal.
Slack:
  • Renderizar presentation a Block Kit.
  • Eliminar la entrada blocks del agente y la CLI.
Telegram:
  • Renderizar texto, contexto y divisores como texto.
  • Renderizar acciones y selección como teclados en línea cuando esté configurado y permitido para la superficie objetivo.
  • Usar alternativa de texto cuando los botones en línea estén deshabilitados.
  • Mover el fijado de temas ACP a delivery.pin.
Mattermost:
  • Renderizar acciones como botones interactivos donde esté configurado.
  • Renderizar otros bloques como alternativa de texto.
MS Teams:
  • Renderizar presentation a Adaptive Cards.
  • Mantener las acciones manuales pin/unpin/list-pins.
  • Implementar opcionalmente pinDeliveredMessage si el soporte de Graph es fiable para la conversación objetivo.
Feishu:
  • Renderizar presentation a tarjetas interactivas.
  • Mantener las acciones manuales pin/unpin/list-pins.
  • Implementar opcionalmente pinDeliveredMessage para fijar mensajes enviados si el comportamiento de la API es fiable.
LINE:
  • Renderizar presentation a mensajes Flex o de plantilla cuando sea posible.
  • Recurrir a texto para bloques no admitidos.
  • Eliminar payloads de UI de LINE de channelData.
Canales simples o limitados:
  • Convertir la presentación a texto con formato conservador.

Pasos de refactorización

  1. Volver a aplicar la corrección de lanzamiento de Discord que separa ui-colors.ts de la UI respaldada por Carbon y elimina DiscordUiContainer de extensions/discord/src/channel.ts.
  2. Añadir presentation y delivery a ReplyPayload, la normalización de payloads salientes, los resúmenes de entrega y los payloads de hooks.
  3. Añadir el esquema MessagePresentation y helpers de análisis en una subruta estrecha de SDK/runtime.
  4. Reemplazar las capacidades de mensaje buttons, cards, components y blocks por capacidades de presentación semántica.
  5. Añadir hooks de adaptador saliente de runtime para renderizado de presentación y fijado de entrega.
  6. Reemplazar la construcción de componentes entre contextos por buildCrossContextPresentation.
  7. Eliminar src/infra/outbound/channel-adapters.ts y quitar buildCrossContextComponents de los tipos de plugin de canal.
  8. Cambiar maybeApplyCrossContextMarker para adjuntar presentation en lugar de parámetros nativos.
  9. Actualizar las rutas de envío de plugin-dispatch para consumir solo presentación semántica y metadatos de entrega.
  10. Eliminar parámetros de payload nativo del agente y la CLI: components, blocks, buttons y card.
  11. Eliminar helpers del SDK que crean esquemas nativos de herramientas de mensaje, reemplazándolos por helpers de esquema de presentación.
  12. Eliminar sobres UI/nativos de channelData; conservar solo metadatos de transporte hasta que se revise cada campo restante.
  13. Migrar los renderizadores de Discord, Slack, Telegram, Mattermost, MS Teams, Feishu y LINE.
  14. Actualizar la documentación para la CLI de mensajes, páginas de canales, SDK de plugins y recetario de capacidades.
  15. Ejecutar perfilado de fanout de importaciones para Discord y los entrypoints de canal afectados.
Los pasos 1-11 y 13-14 están implementados en esta refactorización para los contratos compartidos de agente, CLI, capacidad de plugin y adaptador saliente. El paso 12 sigue siendo una limpieza interna más profunda para sobres de transporte channelData privados del proveedor. El paso 15 queda como validación de seguimiento si queremos números cuantificados de fanout de importaciones más allá de la barrera de tipos/pruebas.

Pruebas

Añadir o actualizar:
  • Pruebas de normalización de presentación.
  • Pruebas de degradación automática de presentación para bloques no admitidos.
  • Pruebas de marcador entre contextos para rutas de plugin dispatch y entrega del núcleo.
  • Pruebas de matriz de renderizado de canales para Discord, Slack, Telegram, Mattermost, MS Teams, Feishu, LINE y alternativa de texto.
  • Pruebas de esquema de herramientas de mensaje que demuestren que los campos nativos ya no están.
  • Pruebas de CLI que demuestren que las flags nativas ya no están.
  • Regresión de carga diferida de importaciones del entrypoint de Discord que cubra Carbon.
  • Pruebas de fijado de entrega que cubran Telegram y la alternativa genérica.

Preguntas abiertas

  • ¿Debe implementarse delivery.pin para Discord, Slack, MS Teams y Feishu en la primera pasada, o solo Telegram primero?
  • ¿Debe delivery absorber eventualmente campos existentes como replyToId, replyToCurrent, silent y audioAsVoice, o mantenerse centrado en comportamientos posteriores al envío?
  • ¿Debe la presentación admitir imágenes o referencias a archivos directamente, o los medios deben permanecer separados del diseño de UI por ahora?

Relacionado