diff --git a/plugin/core/turn-bootstrap.ts b/plugin/core/turn-bootstrap.ts new file mode 100644 index 0000000..601a63f --- /dev/null +++ b/plugin/core/turn-bootstrap.ts @@ -0,0 +1,43 @@ +import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; +import { initTurnOrder } from "../turn-manager.js"; + +const channelSeenAccounts = new Map>(); + +function getAllBotAccountIds(api: OpenClawPluginApi): string[] { + const root = (api.config as Record) || {}; + const bindings = root.bindings as Array> | undefined; + if (!Array.isArray(bindings)) return []; + const ids: string[] = []; + for (const b of bindings) { + const match = b.match as Record | undefined; + if (match?.channel === "discord" && typeof match.accountId === "string") { + ids.push(match.accountId); + } + } + return ids; +} + +function getChannelBotAccountIds(api: OpenClawPluginApi, channelId: string): string[] { + const allBots = new Set(getAllBotAccountIds(api)); + const seen = channelSeenAccounts.get(channelId); + if (!seen) return []; + return [...seen].filter((id) => allBots.has(id)); +} + +export function recordChannelAccount(channelId: string, accountId: string): boolean { + let seen = channelSeenAccounts.get(channelId); + if (!seen) { + seen = new Set(); + channelSeenAccounts.set(channelId, seen); + } + if (seen.has(accountId)) return false; + seen.add(accountId); + return true; +} + +export function ensureTurnOrder(api: OpenClawPluginApi, channelId: string): void { + const botAccounts = getChannelBotAccountIds(api, channelId); + if (botAccounts.length > 0) { + initTurnOrder(channelId, botAccounts); + } +} diff --git a/plugin/index.ts b/plugin/index.ts index 8556d83..3e7a679 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -3,7 +3,6 @@ import path from "node:path"; import { spawn, type ChildProcess } from "node:child_process"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import type { Decision, DirigentConfig } from "./rules.js"; -import { initTurnOrder } from "./turn-manager.js"; import { startModeratorPresence, stopModeratorPresence } from "./moderator-presence.js"; import { registerMessageReceivedHook } from "./hooks/message-received.js"; import { registerBeforeModelResolveHook } from "./hooks/before-model-resolve.js"; @@ -16,6 +15,7 @@ import { getLivePluginConfig } from "./core/live-config.js"; import { ensurePolicyStateLoaded, persistPolicies, policyState } from "./policy/store.js"; import { buildAgentIdentity, buildUserIdToAccountIdMap, resolveAccountId } from "./core/identity.js"; import { extractMentionedUserIds, getModeratorUserId } from "./core/mentions.js"; +import { ensureTurnOrder, recordChannelAccount } from "./core/turn-bootstrap.js"; // ── No-Reply API child process lifecycle ────────────────────────────── let noReplyProcess: ChildProcess | null = null; @@ -110,66 +110,6 @@ function pruneDecisionMap(now = Date.now()) { } } -/** - * Get all Discord bot accountIds from config bindings. - */ -function getAllBotAccountIds(api: OpenClawPluginApi): string[] { - const root = (api.config as Record) || {}; - const bindings = root.bindings as Array> | undefined; - if (!Array.isArray(bindings)) return []; - const ids: string[] = []; - for (const b of bindings) { - const match = b.match as Record | undefined; - if (match?.channel === "discord" && typeof match.accountId === "string") { - ids.push(match.accountId); - } - } - return ids; -} - -/** - * Track which bot accountIds have been seen in each channel via message_received. - * Key: channelId, Value: Set of accountIds seen. - */ -const channelSeenAccounts = new Map>(); - -/** - * Record a bot accountId seen in a channel. - * Returns true if this is a new account for this channel (turn order should be updated). - */ -function recordChannelAccount(channelId: string, accountId: string): boolean { - let seen = channelSeenAccounts.get(channelId); - if (!seen) { - seen = new Set(); - channelSeenAccounts.set(channelId, seen); - } - if (seen.has(accountId)) return false; - seen.add(accountId); - return true; -} - -/** - * Get the list of bot accountIds seen in a channel. - * Only returns accounts that are also in the global bindings (actual bots). - */ -function getChannelBotAccountIds(api: OpenClawPluginApi, channelId: string): string[] { - const allBots = new Set(getAllBotAccountIds(api)); - const seen = channelSeenAccounts.get(channelId); - if (!seen) return []; - return [...seen].filter(id => allBots.has(id)); -} - -/** - * Ensure turn order is initialized for a channel. - * Uses only bot accounts that have been seen in this channel. - */ -function ensureTurnOrder(api: OpenClawPluginApi, channelId: string): void { - const botAccounts = getChannelBotAccountIds(api, channelId); - if (botAccounts.length > 0) { - initTurnOrder(channelId, botAccounts); - } -} - // --- Moderator bot helpers --- /** Extract Discord user ID from a bot token (base64-encoded in first segment) */