185209242e
The union agent roster (host.agents) enumerates EVERY registered connection, including the active gateway that already answered profiles.list. The plugin merger treated the active gateway's own agents as rows from other sources because a remote-primary desktop reports them with connectionKind 'remote', so every bot appeared twice (baseline) and kept growing with each refetch. Match union agents to the active gateway via the new primaryConnectionId field on the roster RPC response and annotate the local rows in place instead of appending phantom copies. Same-named profiles on genuinely separate sources (This device, other remotes) still get their own tagged rows, preserving the @name-device disambiguation rule. Fall back to the legacy connectionKind==='local' rule when primaryConnectionId is absent (older Electron builds), so single-source behavior is byte-identical. Fixes #88344
Bundled plugins
Drop a <name>/plugin.{ts,tsx} here that default-exports a HermesPlugin and
it registers automatically at boot (vite glob in ../contrib/plugins.ts), with
the same inventory + live enable/disable contract as runtime plugins.
Keep this tree for real shipped plugins (and the small authoring fixtures that
dogfood the SDK). One-off demos that rebuild a core chrome piece 1:1 do not
belong here — they double the UI and confuse Settings ▸ Plugins. Publish those
in the companion
hermes-example-plugins
repo instead.
User- and agent-authored plugins load at runtime from
$HERMES_HOME/desktop-plugins/<name>/plugin.js (the disk door) — see the
hermes-desktop-plugins skill.