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)