diff --git a/apps/desktop/src/plugins/hermes-bots/group-chat-view.tsx b/apps/desktop/src/plugins/hermes-bots/group-chat-view.tsx index e0c3442f43..d550afb05f 100644 --- a/apps/desktop/src/plugins/hermes-bots/group-chat-view.tsx +++ b/apps/desktop/src/plugins/hermes-bots/group-chat-view.tsx @@ -1163,7 +1163,11 @@ export function GroupChatWorkspace({ group, members, onBack, visible = true }: G ]} {roomClarifies.map(entry => ( - + ))} {room.running ? (
diff --git a/apps/desktop/src/plugins/hermes-bots/group-rounds.ts b/apps/desktop/src/plugins/hermes-bots/group-rounds.ts index a6cad78c98..437d2ee708 100644 --- a/apps/desktop/src/plugins/hermes-bots/group-rounds.ts +++ b/apps/desktop/src/plugins/hermes-bots/group-rounds.ts @@ -18,7 +18,13 @@ import { updateGroupChat } from './group-chat' import type { GroupChatRoom, GroupHoldStamp } from './group-chat' -import { durableGroupChatMembers, followGroupChat, groupMemberKey } from './group-membership' +import { + durableGroupChatMembers, + followGroupChat, + groupMemberKey, + groupSessionKey, + hasThreadScopedGroupSession +} from './group-membership' import { runGroupContinuationMembers, runGroupRoundMember } from './group-round-members' import { rejectGroupSlashCommand } from './group-slash' import { GROUP_TURN_HARD_CAP_MS, harvestStrandedGroupReply } from './group-turns' @@ -399,7 +405,15 @@ export async function stopGroupThread(group: string, thread: null | string, memb }) // The captured descriptor owns routing even if the roster has changed. - const sessionId = onTurn ? (room.sessions || {})[groupMemberKey(onTurn)] : null + // Sessions are per thread, so a stop targets the session of the thread it + // was issued from; an unmigrated room still answers on its bare pointer. + const sessions = room.sessions || {} + const onTurnKey = onTurn ? groupMemberKey(onTurn) : '' + + const sessionId = onTurn + ? sessions[groupSessionKey(thread || 'legacy', onTurn)] || + (hasThreadScopedGroupSession(sessions, onTurnKey) ? null : sessions[onTurnKey]) + : null if (onTurn && sessionId) { try { diff --git a/apps/desktop/src/plugins/hermes-bots/session-sweep.ts b/apps/desktop/src/plugins/hermes-bots/session-sweep.ts index 2a5cc0e1c2..23bb8464de 100644 --- a/apps/desktop/src/plugins/hermes-bots/session-sweep.ts +++ b/apps/desktop/src/plugins/hermes-bots/session-sweep.ts @@ -12,7 +12,7 @@ import { host } from '@hermes/plugin-sdk' import { PROFILE_SESSION_LIST_LIMIT } from './canonical-chat' import { $lastRoster } from './data' import { $groupChats } from './group-chat' -import { groupMemberKey } from './group-membership' +import { groupMemberKey, groupSessionMemberKey } from './group-membership' import { backendTargetProfile, botConnectionRoute, requestForBot } from './routing' import type { GroupMember, RosterRow } from './types' @@ -121,25 +121,28 @@ function hideOwnedBotSessions() { } const persisted = room?.sessionOwners?.[key] - const derived = (room?.members || []).find((member: GroupMember) => groupMemberKey(member) === key) + // Sessions are keyed per thread (`thread:::`); the + // owner lookup is a MEMBER question, so derive from the member half. + const memberKey = groupSessionMemberKey(key) + const derived = (room?.members || []).find((member: GroupMember) => groupMemberKey(member) === memberKey) // Bare keys are legacy local rooms. A source-qualified key without its // immutable owner is unsafe: never let it fall through ambient routing. const owner = persisted || derived || - (!key.includes('::') + (!memberKey.includes('::') ? { - name: key + name: memberKey } : null) - if (key.includes('::')) { + if (memberKey.includes('::')) { const route = owner?.route const sourceMarked = owner?.sourceScoped || owner?.remoteSource const routeKey = route?.connectionId && route?.profile ? `${route.connectionId}::${route.profile}` : '' - if (!sourceMarked || !route?.targetProfile || routeKey !== key) { + if (!sourceMarked || !route?.targetProfile || routeKey !== memberKey) { return null } } diff --git a/apps/desktop/src/plugins/hermes-bots/types.ts b/apps/desktop/src/plugins/hermes-bots/types.ts index 5b9ad0cc00..e4adfbce70 100644 --- a/apps/desktop/src/plugins/hermes-bots/types.ts +++ b/apps/desktop/src/plugins/hermes-bots/types.ts @@ -216,6 +216,9 @@ export interface GroupPrompt { questions?: GroupPromptQuestion[] | null requestId: string sessionId?: null | string + /** The thread the blocking question belongs to — part of the mirror key, + * since a member can be blocked in two threads at once. */ + thread?: string } export type GroupActivityKind =