feat: split dirigent_tools into individual tools + human @mention override
Feature 1: Split dirigent_tools - Replace monolithic dirigent_tools (9 actions) with 9 individual tools - Discord: dirigent_channel_create, dirigent_channel_update, dirigent_member_list - Policy: dirigent_policy_get, dirigent_policy_set, dirigent_policy_delete - Turn: dirigent_turn_status, dirigent_turn_advance, dirigent_turn_reset - Extract shared executeDiscordAction() helper Feature 2: Human @mention override - When humanList user @mentions agents, temporarily override turn order - Only mentioned agents cycle, ordered by their turn order position - Original order restores when cycle returns to first agent or all NO_REPLY - New: setMentionOverride(), hasMentionOverride(), extractMentionedUserIds() - New: buildUserIdToAccountIdMap() for reverse userId→accountId resolution Bump version to 0.3.0
This commit is contained in:
@@ -20,6 +20,11 @@ export type ChannelTurnState = {
|
||||
noRepliedThisCycle: Set<string>;
|
||||
/** Timestamp of last state change */
|
||||
lastChangedAt: number;
|
||||
// ── Mention override state ──
|
||||
/** Original turn order saved when override is active */
|
||||
savedTurnOrder?: string[];
|
||||
/** First agent in override cycle; used to detect cycle completion */
|
||||
overrideFirstAgent?: string;
|
||||
};
|
||||
|
||||
const channelTurns = new Map<string, ChannelTurnState>();
|
||||
@@ -107,6 +112,8 @@ export function checkTurn(channelId: string, accountId: string): {
|
||||
* Called when a new message arrives in the channel.
|
||||
* Handles reactivation from dormant state and human-triggered resets.
|
||||
*
|
||||
* NOTE: For human messages with @mentions, call setMentionOverride() instead.
|
||||
*
|
||||
* @param senderAccountId - the accountId of the message sender (could be human/bot/unknown)
|
||||
* @param isHuman - whether the sender is in the humanList
|
||||
*/
|
||||
@@ -115,7 +122,8 @@ export function onNewMessage(channelId: string, senderAccountId: string | undefi
|
||||
if (!state || state.turnOrder.length === 0) return;
|
||||
|
||||
if (isHuman) {
|
||||
// Human message: activate, start from first in order
|
||||
// Human message without @mentions: restore original order if overridden, activate from first
|
||||
restoreOriginalOrder(state);
|
||||
state.currentSpeaker = state.turnOrder[0];
|
||||
state.noRepliedThisCycle = new Set();
|
||||
state.lastChangedAt = Date.now();
|
||||
@@ -141,6 +149,61 @@ export function onNewMessage(channelId: string, senderAccountId: string | undefi
|
||||
state.lastChangedAt = Date.now();
|
||||
}
|
||||
|
||||
/**
|
||||
* Restore original turn order if an override is active.
|
||||
*/
|
||||
function restoreOriginalOrder(state: ChannelTurnState): void {
|
||||
if (state.savedTurnOrder) {
|
||||
state.turnOrder = state.savedTurnOrder;
|
||||
state.savedTurnOrder = undefined;
|
||||
state.overrideFirstAgent = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a temporary mention override for the turn order.
|
||||
* When a human @mentions specific agents, only those agents speak (in their
|
||||
* relative order from the current turn order). After the cycle returns to the
|
||||
* first agent, the original order is restored.
|
||||
*
|
||||
* @param channelId - Discord channel ID
|
||||
* @param mentionedAccountIds - accountIds of @mentioned agents, ordered by
|
||||
* their position in the current turn order
|
||||
* @returns true if override was set, false if no valid agents
|
||||
*/
|
||||
export function setMentionOverride(channelId: string, mentionedAccountIds: string[]): boolean {
|
||||
const state = channelTurns.get(channelId);
|
||||
if (!state || mentionedAccountIds.length === 0) return false;
|
||||
|
||||
// Restore any existing override first
|
||||
restoreOriginalOrder(state);
|
||||
|
||||
// Filter to agents actually in the turn order
|
||||
const validIds = mentionedAccountIds.filter(id => state.turnOrder.includes(id));
|
||||
if (validIds.length === 0) return false;
|
||||
|
||||
// Order by their position in the current turn order
|
||||
validIds.sort((a, b) => state.turnOrder.indexOf(a) - state.turnOrder.indexOf(b));
|
||||
|
||||
// Save original and apply override
|
||||
state.savedTurnOrder = [...state.turnOrder];
|
||||
state.turnOrder = validIds;
|
||||
state.overrideFirstAgent = validIds[0];
|
||||
state.currentSpeaker = validIds[0];
|
||||
state.noRepliedThisCycle = new Set();
|
||||
state.lastChangedAt = Date.now();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a mention override is currently active.
|
||||
*/
|
||||
export function hasMentionOverride(channelId: string): boolean {
|
||||
const state = channelTurns.get(channelId);
|
||||
return !!state?.savedTurnOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when the current speaker finishes (end symbol detected) or says NO_REPLY.
|
||||
* @param wasNoReply - true if the speaker said NO_REPLY (empty/silent)
|
||||
@@ -157,6 +220,8 @@ export function onSpeakerDone(channelId: string, accountId: string, wasNoReply:
|
||||
// Check if ALL agents have NO_REPLY'd this cycle
|
||||
const allNoReplied = state.turnOrder.every(id => state.noRepliedThisCycle.has(id));
|
||||
if (allNoReplied) {
|
||||
// If override active, restore original order before going dormant
|
||||
restoreOriginalOrder(state);
|
||||
// Go dormant
|
||||
state.currentSpeaker = null;
|
||||
state.noRepliedThisCycle = new Set();
|
||||
@@ -168,7 +233,18 @@ export function onSpeakerDone(channelId: string, accountId: string, wasNoReply:
|
||||
state.noRepliedThisCycle = new Set();
|
||||
}
|
||||
|
||||
return advanceTurn(channelId);
|
||||
const next = advanceTurn(channelId);
|
||||
|
||||
// Check if override cycle completed (returned to first agent)
|
||||
if (state.overrideFirstAgent && next === state.overrideFirstAgent) {
|
||||
restoreOriginalOrder(state);
|
||||
state.currentSpeaker = null;
|
||||
state.noRepliedThisCycle = new Set();
|
||||
state.lastChangedAt = Date.now();
|
||||
return null; // go dormant after override cycle completes
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,6 +285,7 @@ export function advanceTurn(channelId: string): string | null {
|
||||
export function resetTurn(channelId: string): void {
|
||||
const state = channelTurns.get(channelId);
|
||||
if (state) {
|
||||
restoreOriginalOrder(state);
|
||||
state.currentSpeaker = null;
|
||||
state.noRepliedThisCycle = new Set();
|
||||
state.lastChangedAt = Date.now();
|
||||
@@ -229,5 +306,8 @@ export function getTurnDebugInfo(channelId: string): Record<string, unknown> {
|
||||
noRepliedThisCycle: [...state.noRepliedThisCycle],
|
||||
lastChangedAt: state.lastChangedAt,
|
||||
dormant: state.currentSpeaker === null,
|
||||
hasOverride: !!state.savedTurnOrder,
|
||||
overrideFirstAgent: state.overrideFirstAgent || null,
|
||||
savedTurnOrder: state.savedTurnOrder || null,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user