From 85cd80f166a2046105c9e8b26f90fa8b7939e401 Mon Sep 17 00:00:00 2001 From: ekko Date: Wed, 29 Jul 2026 09:06:27 +0800 Subject: [PATCH 1/2] fix desktop group chat agent list click --- ...026-07-29-group-chat-desktop-agent-list.md | 10 ++++ .../hermes/group-chat/GroupChatPanel.vue | 17 +++++- tests/e2e/group-chat-room-deeplink.spec.ts | 53 +++++++++++++++++-- 3 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 docs/chat-chain-changes/2026-07-29-group-chat-desktop-agent-list.md diff --git a/docs/chat-chain-changes/2026-07-29-group-chat-desktop-agent-list.md b/docs/chat-chain-changes/2026-07-29-group-chat-desktop-agent-list.md new file mode 100644 index 000000000..c9b92f45c --- /dev/null +++ b/docs/chat-chain-changes/2026-07-29-group-chat-desktop-agent-list.md @@ -0,0 +1,10 @@ +--- +date: 2026-07-29 +pr: pending +feature: Desktop Group Chat Agent list +impact: Group Chat Agent avatars can open their Agent list from draggable macOS and Windows desktop headers. +--- + +The Agent avatar stack is now a semantic button and explicitly opts out of the +Electron drag region, preserving the draggable header around it while restoring +pointer and keyboard interaction. diff --git a/packages/client/src/components/hermes/group-chat/GroupChatPanel.vue b/packages/client/src/components/hermes/group-chat/GroupChatPanel.vue index 6bb5c1976..0a8531123 100644 --- a/packages/client/src/components/hermes/group-chat/GroupChatPanel.vue +++ b/packages/client/src/components/hermes/group-chat/GroupChatPanel.vue @@ -804,7 +804,11 @@ async function handleApproval(choice: 'once' | 'session' | 'always' | 'deny') {
@@ -2081,6 +2085,15 @@ export default defineComponent({ components: { CreateRoomForm } }) align-items: center; } +.avatar-stack-trigger { + padding: 0; + border: 0; + color: inherit; + background: transparent; + cursor: pointer; + -webkit-app-region: no-drag; +} + .avatar-stack-item { width: 28px; height: 28px; diff --git a/tests/e2e/group-chat-room-deeplink.spec.ts b/tests/e2e/group-chat-room-deeplink.spec.ts index 2e2f65bb1..39f2b766d 100644 --- a/tests/e2e/group-chat-room-deeplink.spec.ts +++ b/tests/e2e/group-chat-room-deeplink.spec.ts @@ -1,6 +1,8 @@ import { expect, test, type Page, type Route } from '@playwright/test' import { authenticate, TEST_MODEL_GROUP } from './fixtures' +type DesktopPlatform = 'darwin' | 'win32' + const baseRooms = [ { id: 'room-alpha', name: 'Alpha Room', inviteCode: 'ALPHA1', canManage: true, workspace: '/tmp/alpha', triggerTokens: 100000, maxHistoryTokens: 32000, tailMessageCount: 10, totalTokens: 123 }, { id: 'room-beta', name: 'Beta Room', inviteCode: 'BETA22', canManage: true, workspace: '/tmp/beta', triggerTokens: 100000, maxHistoryTokens: 32000, tailMessageCount: 10, totalTokens: 456 }, @@ -39,6 +41,20 @@ const messagesByRoom: Record = { ], } +const agentsByRoom: Record = { + 'room-alpha': [ + { + id: 'agent-row-1', + roomId: 'room-alpha', + agentId: 'agent-1', + profile: 'default', + name: 'Worker', + description: 'Group agent', + invited: 1, + }, + ], +} + async function mockGroupChatApi(page: Page) { const rooms = baseRooms.map(room => ({ ...room })) const inviteCodeUpdates: Array<{ roomId: string, body: unknown }> = [] @@ -105,7 +121,7 @@ async function mockGroupChatApi(page: Page) { const roomId = decodeURIComponent(detailMatch[1]) const room = rooms.find(r => r.id === roomId) return room - ? json({ room, messages: messagesByRoom[roomId] || [], agents: [], members: [{ id: 'member-1', userId: 'user-1', name: 'User One', description: '', joinedAt: 1_790_000_000 }] }) + ? json({ room, messages: messagesByRoom[roomId] || [], agents: agentsByRoom[roomId] || [], members: [{ id: 'member-1', userId: 'user-1', name: 'User One', description: '', joinedAt: 1_790_000_000 }] }) : json({ error: 'Room not found' }, 404) } @@ -123,6 +139,7 @@ async function mockGroupChatSocket(page: Page) { body: ` const state = window.__PW_GROUP_SOCKET__ || (window.__PW_GROUP_SOCKET__ = { sockets: [], emitted: [] }) const roomMessages = ${JSON.stringify(messagesByRoom)} +const roomAgents = ${JSON.stringify(agentsByRoom)} function makeSocket(url, options) { const listeners = new Map() const socket = { @@ -139,7 +156,7 @@ function makeSocket(url, options) { state.emitted.push({ event, payload }) if (event === 'join' && typeof ack === 'function') { const roomId = payload && payload.roomId - setTimeout(() => ack({ roomId, roomName: roomId, members: [], messages: roomMessages[roomId] || [], agents: [], rooms: [], typingUsers: [], contextStatuses: [] }), 0) + setTimeout(() => ack({ roomId, roomName: roomId, members: [], messages: roomMessages[roomId] || [], agents: roomAgents[roomId] || [], rooms: [], typingUsers: [], contextStatuses: [] }), 0) } if (event === 'message' && typeof ack === 'function') { setTimeout(() => ack({ id: payload && payload.id }), 0) @@ -171,7 +188,22 @@ export default { io } }) } -async function setup(page: Page, path: string) { +async function installDesktopBridge(page: Page, platform: DesktopPlatform) { + await page.addInitScript((desktopPlatform) => { + Object.defineProperty(window, 'hermesDesktop', { + configurable: true, + value: { + isDesktop: true, + platform: desktopPlatform, + getWindowState: async () => ({ isMaximized: false }), + windowControl: async () => ({ isMaximized: false }), + }, + }) + }, platform) +} + +async function setup(page: Page, path: string, platform?: DesktopPlatform) { + if (platform) await installDesktopBridge(page, platform) await authenticate(page) await mockGroupChatSocket(page) const api = await mockGroupChatApi(page) @@ -222,6 +254,21 @@ test.describe('group chat room deep links', () => { await expect(page.locator('.group-workspace-panel')).toHaveCount(0) }) + for (const platform of ['darwin', 'win32'] as const) { + test(`opens the Agent list from the ${platform} desktop drag header`, async ({ page }) => { + await setup(page, '/#/hermes/group-chat/room/room-alpha', platform) + + const trigger = page.getByRole('button', { name: 'Agents (1)' }) + await expect(trigger).toBeVisible() + await expect(trigger).toHaveCSS('-webkit-app-region', 'no-drag') + await trigger.click() + + const popover = page.locator('.n-popover .agent-popover') + await expect(popover).toBeVisible() + await expect(popover.locator('.agent-popover-name', { hasText: 'Worker' })).toBeVisible() + }) + } + test('room settings rotate invite codes only after the update API succeeds', async ({ page }) => { const api = await setup(page, '/#/hermes/group-chat/room/room-alpha') From 99eae3e3e18148abc41f99dd6b0a530ee0e6b3f0 Mon Sep 17 00:00:00 2001 From: ekko Date: Wed, 29 Jul 2026 09:07:58 +0800 Subject: [PATCH 2/2] link group chat change to PR 2259 --- .../2026-07-29-group-chat-desktop-agent-list.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/chat-chain-changes/2026-07-29-group-chat-desktop-agent-list.md b/docs/chat-chain-changes/2026-07-29-group-chat-desktop-agent-list.md index c9b92f45c..e6bc831c4 100644 --- a/docs/chat-chain-changes/2026-07-29-group-chat-desktop-agent-list.md +++ b/docs/chat-chain-changes/2026-07-29-group-chat-desktop-agent-list.md @@ -1,6 +1,6 @@ --- date: 2026-07-29 -pr: pending +pr: 2259 feature: Desktop Group Chat Agent list impact: Group Chat Agent avatars can open their Agent list from draggable macOS and Windows desktop headers. ---