From 7e0f187f3401ca7f84c7ee8b2bf61a058137236a Mon Sep 17 00:00:00 2001 From: orion Date: Sun, 8 Mar 2026 07:56:26 +0000 Subject: [PATCH] fix(turn): keep mention override speaker/order when membership refresh runs --- plugin/turn-manager.ts | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/plugin/turn-manager.ts b/plugin/turn-manager.ts index d00015d..121d68a 100644 --- a/plugin/turn-manager.ts +++ b/plugin/turn-manager.ts @@ -55,8 +55,10 @@ function shuffleArray(arr: T[]): T[] { export function initTurnOrder(channelId: string, botAccountIds: string[]): void { const existing = channelTurns.get(channelId); if (existing) { - // Check if membership changed - const oldSet = new Set(existing.turnOrder); + // Compare membership against base order. + // If mention override is active, turnOrder is temporary; use savedTurnOrder for stable comparison. + const baseOrder = existing.savedTurnOrder || existing.turnOrder; + const oldSet = new Set(baseOrder); const newSet = new Set(botAccountIds); const same = oldSet.size === newSet.size && [...oldSet].every(id => newSet.has(id)); if (same) return; // no change @@ -66,12 +68,41 @@ export function initTurnOrder(channelId: string, botAccountIds: string[]): void `oldOrder=${JSON.stringify(existing.turnOrder)} oldCurrent=${existing.currentSpeaker} ` + `oldOverride=${JSON.stringify(existing.savedTurnOrder || null)} newMembers=${JSON.stringify(botAccountIds)}`, ); - } else { + + const nextOrder = shuffleArray(botAccountIds); + + // Mention override active: update only the saved base order. + // Keep temporary turnOrder/currentSpeaker intact so @mention routing is not clobbered. + if (existing.savedTurnOrder) { + existing.savedTurnOrder = nextOrder; + existing.lastChangedAt = Date.now(); + console.log( + `[dirigent][turn-debug] initTurnOrder applied-base-only channel=${channelId} ` + + `savedOrder=${JSON.stringify(nextOrder)} keptOverrideOrder=${JSON.stringify(existing.turnOrder)} ` + + `keptCurrent=${existing.currentSpeaker}`, + ); + return; + } + + // Non-mention flow: preserve previous behavior (re-init to dormant). + channelTurns.set(channelId, { + turnOrder: nextOrder, + currentSpeaker: null, // start dormant + noRepliedThisCycle: new Set(), + lastChangedAt: Date.now(), + waitingForHuman: false, + }); + console.log( - `[dirigent][turn-debug] initTurnOrder first-init channel=${channelId} members=${JSON.stringify(botAccountIds)}`, + `[dirigent][turn-debug] initTurnOrder applied channel=${channelId} newOrder=${JSON.stringify(nextOrder)} newCurrent=null`, ); + return; } + console.log( + `[dirigent][turn-debug] initTurnOrder first-init channel=${channelId} members=${JSON.stringify(botAccountIds)}`, + ); + const nextOrder = shuffleArray(botAccountIds); channelTurns.set(channelId, { turnOrder: nextOrder,