feat: implement multi-message mode and shuffle mode features
- Add multi-message mode with start/end/prompt markers - Implement turn order shuffling with /turn-shuffling command - Add channel mode state management - Update hooks to handle multi-message mode behavior - Update plugin config with new markers - Update TASKLIST.md with completed tasks
This commit is contained in:
@@ -11,6 +11,8 @@
|
||||
* - If sender IS in turn order → current = next after sender
|
||||
*/
|
||||
|
||||
import { isMultiMessageMode, exitMultiMessageMode } from "./core/channel-modes.js";
|
||||
|
||||
export type ChannelTurnState = {
|
||||
/** Ordered accountIds for this channel (auto-populated, shuffled) */
|
||||
turnOrder: string[];
|
||||
@@ -46,6 +48,26 @@ function shuffleArray<T>(arr: T[]): T[] {
|
||||
return a;
|
||||
}
|
||||
|
||||
function reshuffleTurnOrder(channelId: string, currentOrder: string[], lastSpeaker?: string): string[] {
|
||||
const shufflingEnabled = getChannelShuffling(channelId);
|
||||
if (!shufflingEnabled) return currentOrder;
|
||||
|
||||
const shuffled = shuffleArray(currentOrder);
|
||||
|
||||
// If there's a last speaker and they're in the order, ensure they're not first
|
||||
if (lastSpeaker && shuffled.length > 1 && shuffled[0] === lastSpeaker) {
|
||||
// Find another speaker to swap with
|
||||
for (let i = 1; i < shuffled.length; i++) {
|
||||
if (shuffled[i] !== lastSpeaker) {
|
||||
[shuffled[0], shuffled[i]] = [shuffled[i], shuffled[0]];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
// --- public API ---
|
||||
|
||||
/**
|
||||
@@ -176,6 +198,13 @@ export function onNewMessage(channelId: string, senderAccountId: string | undefi
|
||||
const state = channelTurns.get(channelId);
|
||||
if (!state || state.turnOrder.length === 0) return;
|
||||
|
||||
// Check for multi-message mode exit condition
|
||||
if (isMultiMessageMode(channelId) && isHuman) {
|
||||
// In multi-message mode, human messages don't trigger turn activation
|
||||
// We only exit multi-message mode if the end marker is detected in a higher-level hook
|
||||
return;
|
||||
}
|
||||
|
||||
if (isHuman) {
|
||||
// Human message: clear wait-for-human, restore original order if overridden, activate from first
|
||||
state.waitingForHuman = false;
|
||||
@@ -330,6 +359,7 @@ export function onSpeakerDone(channelId: string, accountId: string, wasNoReply:
|
||||
state.noRepliedThisCycle = new Set();
|
||||
}
|
||||
|
||||
const prevSpeaker = state.currentSpeaker;
|
||||
const next = advanceTurn(channelId);
|
||||
|
||||
// Check if override cycle completed (returned to first agent)
|
||||
@@ -341,6 +371,18 @@ export function onSpeakerDone(channelId: string, accountId: string, wasNoReply:
|
||||
return null; // go dormant after override cycle completes
|
||||
}
|
||||
|
||||
// Check if we've completed a full cycle (all agents spoke once)
|
||||
// This happens when we're back to the first agent in the turn order
|
||||
const isFirstSpeakerAgain = next === state.turnOrder[0];
|
||||
if (!wasNoReply && !state.overrideFirstAgent && next && isFirstSpeakerAgain && state.noRepliedThisCycle.size === 0) {
|
||||
// Completed a full cycle without anyone NO_REPLYing - reshuffle if enabled
|
||||
const newOrder = reshuffleTurnOrder(channelId, state.turnOrder, prevSpeaker);
|
||||
if (newOrder !== state.turnOrder) {
|
||||
state.turnOrder = newOrder;
|
||||
console.log(`[dirigent][turn-debug] reshuffled turn order for channel=${channelId} newOrder=${JSON.stringify(newOrder)}`);
|
||||
}
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user