Skip to content

[FEATURE]: TUI plugin API: render synthetic messages with a plugin component #59432

Open

Description

@iceteaSA

Feature hasn't been suggested before.

  • I have verified this feature I'm about to request hasn't been suggested before.

Describe the enhancement you want to request

Plugins can add synthetic messages with session.synthetic({ text, description, metadata }). The V2 TUI shows each synthetic message as one notice row: an icon and the description, cut to one line (SessionNoticeMessageV2 in packages/tui/src/routes/session/index.tsx). A plugin cannot change how its own synthetic messages look.

Example: a plugin sends messages between sessions. It puts the sender, the intent and the send time in metadata, and the message body in text. The user wants a small box that shows the sender, the time and the first lines of the body. Today the plugin can only set a one-line description.

Proposal: a TUI plugin registration for synthetic messages, selected by a metadata field. For example:

context.ui.message.register("my-plugin.mail", (props) => <MailCard message={props.message} />)

The host calls this renderer when metadata.renderer is "my-plugin.mail". If no plugin gives a renderer for that name, or if the renderer has an error, the host shows the current notice row. The model context does not change. Only the TUI view changes.

This is the same pattern as the markdown code-block renderer registration and the per-message footer slot in #46268. Related: #48644 (UI-only notices).

I can write the change after design approval.

Activity

  1. opencode-agent commented on Oct 26, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed proposal, @iceteaSA! You're asking for a TUI plugin registration that lets a plugin draw its own synthetic messages: the host would pick the renderer by a metadata field such as metadata.renderer, fall back to the current one-line notice row if no renderer matches or the renderer fails, and leave model context unchanged.

    I checked the current code. SessionNoticeMessageV2 always draws one truncated line from description, and the TUI plugin API has a code-block renderer, routes and slots but no hook for message rendering. So this can't be done from a plugin today and would need a new API. Maintainers will look at the design; please wait for their feedback before you start the change.

    Related issues:

  2. cevr commented on Nov 6, 2026

    @cevr

    A smaller step that would cover a lot of this: let a synthetic message pick its notice icon, e.g. metadata.icon. Today SessionNoticeMessageV2 hard-codes ◎ for the goal source and ◈ for everything else, so a plugin's notices all look alike and the only way to tell a plugin's own kind apart is a host special case. On my fork I have exactly that kind of special case for a plugin that delivers messages between sessions (✉, cevr@509357d); a generic field would replace it:

    ctx.session.synthetic({ sessionID, text, description: "from planner: review ready", metadata: { icon: "✉" } })
    // SessionNoticeMessageV2
    const icon = () => (typeof metadata()?.icon === "string" ? metadata().icon : source() === "goal" ? "◎" : "◈")

    The full renderer registration proposed here would still be the way to draw a card; the icon is a one-line, TUI-only change that needs no new API surface.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions