From 104ec97595567466170e2727cc8ec2f6ed382db2 Mon Sep 17 00:00:00 2001 From: Jay Date: Tue, 18 Aug 2026 22:48:58 +0100 Subject: [PATCH] fix(bot-mode): group room preview shows the bot handle, not @default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `botHandle()` exists so that, per its own comment, "the word 'default' never surfaces in the UI" — it presents the primary profile as `hermes`. The roster rows, mention resolution and the group-chat prompt all route through it. Two preview paths did not, and rendered the raw profile name: - `GroupRow`'s room preview line built `@${last.from?.name}`, so a group room read `@default: …` while the bot answers to `@hermes`. - `previewKind()` returned the raw captured name from the bot-to-bot delivery prefix, so the `🤖 @` badge and its tooltip could show `@default` too. The mismatch is presentation-only, but it reads as a routing bug: the room says the message came from `@default` while `@default` is not a handle the mention resolver accepts, so users reasonably conclude bot-to-bot addressing is broken when it is working correctly. Both paths now map through `botHandle()`. `GroupRow` passes the matching member so a bot with a custom handle keeps it; `previewKind` maps the lowercased sender name, which leaves every non-primary profile unchanged. Tests: the primary profile resolves to `hermes` and a named profile keeps its own handle (behavioural, in the existing previewKind suite), plus a source-shape assertion for the render path matching that file's convention. Both new assertions fail against the pre-fix source. Fixes #89484 --- apps/desktop/src/plugins/hermes-bots/plugin.js | 12 ++++++++++-- .../plugins/hermes-bots/tests/group-chat.test.mjs | 8 ++++++++ .../hermes-bots/tests/roster-preview.test.mjs | 10 ++++++++++ contributors/emails/johnsonafuye@gmail.com | 2 ++ 4 files changed, 30 insertions(+), 2 deletions(-) create mode 100644 contributors/emails/johnsonafuye@gmail.com diff --git a/apps/desktop/src/plugins/hermes-bots/plugin.js b/apps/desktop/src/plugins/hermes-bots/plugin.js index e8c709bbae..c39ab1a385 100644 --- a/apps/desktop/src/plugins/hermes-bots/plugin.js +++ b/apps/desktop/src/plugins/hermes-bots/plugin.js @@ -4600,7 +4600,11 @@ function previewKind(preview) { } const match = text.match(A2A_RE) if (match) { - return { fromBot: (match[1] || match[2] || '').trim().toLowerCase() || null } + // The captured name is whatever the delivery prefix carried — a raw + // profile name. Map it the way every other surface does so the primary + // profile reads @hermes, never @default (#89484). + const sender = (match[1] || match[2] || '').trim().toLowerCase() + return { fromBot: sender ? botHandle(sender) : null } } return { fromBot: null } } @@ -9175,8 +9179,12 @@ function GroupRow({ group, members, needsYou, onOpen }) { const log = Array.isArray(room.log) ? room.log : [] const last = log.length ? log[log.length - 1] : null const lastAt = groupLastActivity(room) + // Room previews speak the same handle vocabulary as the roster, mentions + // and the group prompt: the primary profile is @hermes, not @default. + const lastFrom = last?.from?.name || '' + const lastHandle = botHandle(lastFrom || 'bot', members.find(member => member?.name === lastFrom)) const preview = last - ? `${last.from?.kind === 'user' ? 'You' : `@${last.from?.name || 'bot'}`}: ${stripPreviewMarkdown(last.text) || '…'}` + ? `${last.from?.kind === 'user' ? 'You' : `@${lastHandle}`}: ${stripPreviewMarkdown(last.text) || '…'}` : 'No messages yet — say hi to the room' const faces = members.slice(0, 3) diff --git a/apps/desktop/src/plugins/hermes-bots/tests/group-chat.test.mjs b/apps/desktop/src/plugins/hermes-bots/tests/group-chat.test.mjs index d61e04560a..8f0e3f9741 100644 --- a/apps/desktop/src/plugins/hermes-bots/tests/group-chat.test.mjs +++ b/apps/desktop/src/plugins/hermes-bots/tests/group-chat.test.mjs @@ -714,3 +714,11 @@ test('source contract: group chat message bodies opt back into selectable text', const src = groupChatWorkspaceSource() assert.match(src, /'data-selectable-text':\s*'true'/) }) + +test('group room preview renders the bot HANDLE, not the raw profile name', () => { + // #89484: the room line read "@default: …" while the bot answers to + // @hermes, so users concluded mention routing was broken. + assert.match(pluginSource, /const lastHandle = botHandle\(lastFrom \|\| 'bot', members\.find\(/) + assert.match(pluginSource, /\? `\$\{last\.from\?\.kind === 'user' \? 'You' : `@\$\{lastHandle\}`\}/) + assert.doesNotMatch(pluginSource, /`@\$\{last\.from\?\.name \|\| 'bot'\}`/) +}) diff --git a/apps/desktop/src/plugins/hermes-bots/tests/roster-preview.test.mjs b/apps/desktop/src/plugins/hermes-bots/tests/roster-preview.test.mjs index a91d9513e3..7b32d664ce 100644 --- a/apps/desktop/src/plugins/hermes-bots/tests/roster-preview.test.mjs +++ b/apps/desktop/src/plugins/hermes-bots/tests/roster-preview.test.mjs @@ -209,3 +209,13 @@ test('render: BotRow previews the pinned canonical chat, not an unrelated latest assert.match(text, /pinned chat content/) assert.doesNotMatch(text, /unrelated scratch content/) }) + +test('previewKind: the primary profile surfaces as @hermes, never @default', () => { + // botHandle() exists so "the word 'default' never surfaces in the UI"; the + // bot-to-bot badge was rendering the raw captured profile name (#89484). + assert.equal(fromBotOf("Message from agent 'default': deploy is green"), 'hermes') +}) + +test('previewKind: a named profile keeps its own handle', () => { + assert.equal(fromBotOf("Message from agent 'ops': deploy is green"), 'ops') +}) diff --git a/contributors/emails/johnsonafuye@gmail.com b/contributors/emails/johnsonafuye@gmail.com new file mode 100644 index 0000000000..455905ad08 --- /dev/null +++ b/contributors/emails/johnsonafuye@gmail.com @@ -0,0 +1,2 @@ +johnsonAyo +# PR for #89484 (desktop: group room preview uses botHandle)