3 Commits

Author SHA1 Message Date
teknium1
be59f0411e fix(plugins): broadcast_plugin_event from the turn-isolation child reaches the Desktop
Review finding (MAJOR-2): with dashboard.turn_isolation the plugin's agent-side
code (tools, hooks) runs in the compute-host child, where _live_transports is
empty and _broadcast_global_event dropped the frame at logger.debug. The child
now registers its _HostTransport as a live transport for the life of run_host,
so a session-less broadcast rides the existing host pipe; the parent bridge
(_relay_compute_host_rpc) recognises an event frame with no session_id and
fans it out via _broadcast_global_event instead of write_json, which would
have dropped it on hermes serve's stdio.

Minor: the late `from tui_gateway.server import …` could raise ImportError in
a plugin-only process despite the "safe from any handler" docstring — caught
and logged as a warning.

Docs: the SDK page now tables exactly which process each call site runs in
and what it reaches (hermes serve / compute-host child / stdio TUI / gateway
run + chat + cron = nobody).
2026-09-24 02:03:03 -07:00
teknium1
5fa1402819 feat(plugins): plugin event names take a dotted hierarchy; ids are catalog names
broadcast_plugin_event('rss-reader', 'feed.updated', …) is how the consumer
(rss-reader #115972) and core's own names (display.install.done) spell
events, so the bare event accepts dot-separated segments — with every
segment non-empty, so '../x', '.x' and 'a..b' still raise. The plugin id
drops the dot instead and matches plugin_catalog._NAME_RE: 'plugin.<id>.'
must be an unambiguous prefix, or 'other.x' would spell plugin 'other'.

Tests trimmed to two invariants: the frame reaching a REAL registered
transport (name, session_id '', payload) and the refusal matrix proven to
raise before anything is emitted.

Part of #116305 (item 8), salvage of #116419.
2026-09-24 02:03:03 -07:00
tobenwarrior
8034d493dc feat(plugins): public event bridge for plugin backends
A plugin backend pushing an update to its own desktop half imports
tui_gateway.server._broadcast_global_event — a core-private whose signature
is not a plugin contract. Give plugin authors a public, documented door.

hermes_cli.plugin_events.broadcast_plugin_event(plugin_id, event, payload)
emits plugin.<plugin_id>.<event> on the app's global event stream (the one
host.onEvent subscribes to), with the plugin id forced into the namespace and
the bare event name validated — a mangled name would strand the desktop half
waiting on a name nobody emits. Fire-and-forget, safe from any request
handler.

Wishlist item 8 of #116305; unblocks rss-reader (#115972).
2026-09-24 02:03:03 -07:00