fix(bot-mode): group room preview shows the bot handle, not @default

`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 `🤖 @<name>` 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
This commit is contained in:
Jay
2026-08-18 22:48:58 +01:00
committed by Teknium
parent cb0fd836ac
commit 104ec97595
4 changed files with 30 additions and 2 deletions

View File

@@ -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)

View File

@@ -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'\}`/)
})

View File

@@ -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')
})

View File

@@ -0,0 +1,2 @@
johnsonAyo
# PR for #89484 (desktop: group room preview uses botHandle)