Skip to content

Commit 8dd18b8

Browse files
Joelclaude
andcommitted
DM command: Two-phase room lookup to handle UUID changes
When user UUIDs change (e.g., after data reseed), the uniqueId-based lookup fails because uniqueId uses last 6 chars of UUIDs. Two-phase lookup now: 1. Fast path: Try uniqueId (deterministic, handles most cases) 2. Fallback: Search direct/private rooms and match by member set When fallback finds a match, it updates the room's uniqueId to the current format for future fast lookups. This prevents duplicate DM rooms after reseed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 00a1400 commit 8dd18b8

File tree

1 file changed

+52
-7
lines changed

1 file changed

+52
-7
lines changed

src/debug/jtag/commands/collaboration/dm/server/DmServerCommand.ts

Lines changed: 52 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@ export class DmServerCommand extends DmCommand {
4040
// 5. Generate deterministic uniqueId from sorted set
4141
const uniqueId = this.generateDmUniqueId(allParticipantIds);
4242

43-
// 6. Try to find existing room
44-
const existingRoom = await this.findRoomByUniqueId(uniqueId, params);
43+
// 6. Try to find existing room (by uniqueId or member set)
44+
const existingRoom = await this.findExistingDmRoom(uniqueId, allParticipantIds, params);
4545
if (existingRoom) {
4646
return transformPayload(params, {
4747
success: true,
@@ -156,10 +156,19 @@ export class DmServerCommand extends DmCommand {
156156
}
157157

158158
/**
159-
* Find existing room by uniqueId
159+
* Find existing DM room by uniqueId OR by exact member set
160+
*
161+
* Two-phase lookup:
162+
* 1. Try uniqueId (fast, deterministic)
163+
* 2. Fall back to member matching (handles UUID changes after reseed)
160164
*/
161-
private async findRoomByUniqueId(uniqueId: string, params: DmParams): Promise<RoomEntity | null> {
162-
const result = await Commands.execute<DataListParams<RoomEntity>, DataListResult<RoomEntity>>(
165+
private async findExistingDmRoom(
166+
uniqueId: string,
167+
participantIds: UUID[],
168+
params: DmParams
169+
): Promise<RoomEntity | null> {
170+
// Phase 1: Try by uniqueId (fast path)
171+
const byUniqueId = await Commands.execute<DataListParams<RoomEntity>, DataListResult<RoomEntity>>(
163172
DATA_COMMANDS.LIST,
164173
{
165174
collection: RoomEntity.collection,
@@ -170,8 +179,44 @@ export class DmServerCommand extends DmCommand {
170179
}
171180
);
172181

173-
if (result.success && result.items && result.items.length > 0) {
174-
return result.items[0];
182+
if (byUniqueId.success && byUniqueId.items && byUniqueId.items.length > 0) {
183+
return byUniqueId.items[0];
184+
}
185+
186+
// Phase 2: Search direct/private rooms and match by member set
187+
// This handles cases where user UUIDs changed (e.g., after reseed)
188+
const directRooms = await Commands.execute<DataListParams<RoomEntity>, DataListResult<RoomEntity>>(
189+
DATA_COMMANDS.LIST,
190+
{
191+
collection: RoomEntity.collection,
192+
filter: { type: participantIds.length === 2 ? 'direct' : 'private' },
193+
limit: 100,
194+
context: params.context,
195+
sessionId: params.sessionId
196+
}
197+
);
198+
199+
if (directRooms.success && directRooms.items) {
200+
const sortedParticipants = [...participantIds].sort();
201+
202+
for (const room of directRooms.items) {
203+
if (!room.members || room.members.length !== participantIds.length) continue;
204+
205+
const roomMemberIds = room.members.map(m => m.userId).sort();
206+
const isMatch = sortedParticipants.every((id, i) => id === roomMemberIds[i]);
207+
208+
if (isMatch) {
209+
// Found matching room - update its uniqueId to current format for future lookups
210+
await Commands.execute('data/update', {
211+
collection: RoomEntity.collection,
212+
id: room.id,
213+
data: { uniqueId },
214+
context: params.context,
215+
sessionId: params.sessionId
216+
});
217+
return room;
218+
}
219+
}
175220
}
176221

177222
return null;

0 commit comments

Comments
 (0)