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 =