Files
hermes-agent/apps/desktop/src/lib/chat-empty.ts
T
Brooklyn Nicholson 832ec82f75 fix(desktop): mount every chat.empty contributor, not just the first
Ownership of an empty transcript is per session and is not known until each
plugin has loaded its own data — which is why the slot mounts contributors
rather than resolving a claim up front. Taking only contributions[0] then let
a plugin that DECLINES a session suppress the one that owns it: silently,
permanently, and only for the users whose installed plugins happen to
register in that order.

Every registration is mounted and answers for itself. Declining is free; two
claiming one session render both, a visible conflict instead of a silent drop.
2026-08-27 23:23:47 -05:00

39 lines
1.7 KiB
TypeScript

import type { ReactNode } from 'react'
/**
* CHAT EMPTY STATE — the blank transcript as a contribution area.
*
* Core owns exactly one empty state: the intro splash, which belongs to a
* fresh draft with no session selected. A session that exists but has nothing
* in it yet falls outside that — and whoever opened it is the only one who
* knows what should stand in the gap. A bot's chat wants the bot's face and
* name; core has neither, and should not learn them.
*
* So the slot is contributed. A plugin claims the sessions it owns by
* returning an element for them and `null` for everything else, which is also
* how it stands down while the transcript is still hydrating.
*
* Ownership is per session, so EVERY registration is mounted and each answers
* for itself. Declining is free; two plugins claiming one session render both,
* which is the visible cost of never letting registration order silently
* suppress the plugin that actually owns the chat.
*/
export const CHAT_EMPTY_AREA = 'chat.empty'
/** Props handed to a chat-empty contribution's `render`. */
export interface ChatEmptyProps {
/** The live session whose transcript is empty. */
sessionId: string
}
/** Payload of a `chat.empty` contribution's `data`. */
export interface ChatEmptyContribution {
/** Renders the empty state, or returns `null` to decline the session and
* leave the transcript blank. Mounted as a component inside the
* contribution error boundary, so it can subscribe to its own stores and
* appear once they load — the roster a bot chat needs arrives after the
* transcript does. A throw degrades to an inline error, not a dead chat. */
render: (props: ChatEmptyProps) => ReactNode
}