118 lines
4.5 KiB
TypeScript
118 lines
4.5 KiB
TypeScript
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
import { initTurnOrder } from "../turn-manager.js";
|
|
import { fetchVisibleChannelBotAccountIds } from "./channel-members.js";
|
|
|
|
const channelSeenAccounts = new Map<string, Set<string>>();
|
|
const channelBootstrapTried = new Set<string>();
|
|
let cacheLoaded = false;
|
|
|
|
function cachePath(api: OpenClawPluginApi): string {
|
|
return api.resolvePath("~/.openclaw/dirigent-channel-members.json");
|
|
}
|
|
|
|
function loadCache(api: OpenClawPluginApi): void {
|
|
if (cacheLoaded) return;
|
|
cacheLoaded = true;
|
|
const p = cachePath(api);
|
|
try {
|
|
if (!fs.existsSync(p)) return;
|
|
const raw = fs.readFileSync(p, "utf8");
|
|
const parsed = JSON.parse(raw) as Record<string, { botAccountIds?: string[]; source?: string; guildId?: string; updatedAt?: string }>;
|
|
for (const [channelId, rec] of Object.entries(parsed || {})) {
|
|
const ids = Array.isArray(rec?.botAccountIds) ? rec.botAccountIds.filter(Boolean) : [];
|
|
if (ids.length > 0) channelSeenAccounts.set(channelId, new Set(ids));
|
|
}
|
|
} catch (err) {
|
|
api.logger.warn?.(`dirigent: failed loading channel member cache: ${String(err)}`);
|
|
}
|
|
}
|
|
|
|
function inferGuildIdFromChannelId(api: OpenClawPluginApi, channelId: string): string | undefined {
|
|
const root = (api.config as Record<string, unknown>) || {};
|
|
const channels = (root.channels as Record<string, unknown>) || {};
|
|
const discord = (channels.discord as Record<string, unknown>) || {};
|
|
const accounts = (discord.accounts as Record<string, Record<string, unknown>>) || {};
|
|
for (const rec of Object.values(accounts)) {
|
|
const chMap = (rec?.channels as Record<string, Record<string, unknown>> | undefined) || undefined;
|
|
if (!chMap) continue;
|
|
const direct = chMap[channelId];
|
|
const prefixed = chMap[`channel:${channelId}`];
|
|
const found = (direct || prefixed) as Record<string, unknown> | undefined;
|
|
if (found && typeof found.guildId === "string" && found.guildId.trim()) return found.guildId.trim();
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function persistCache(api: OpenClawPluginApi): void {
|
|
const p = cachePath(api);
|
|
const out: Record<string, { botAccountIds: string[]; updatedAt: string; source: string; guildId?: string }> = {};
|
|
for (const [channelId, set] of channelSeenAccounts.entries()) {
|
|
out[channelId] = {
|
|
botAccountIds: [...set],
|
|
updatedAt: new Date().toISOString(),
|
|
source: "dirigent/turn-bootstrap",
|
|
guildId: inferGuildIdFromChannelId(api, channelId),
|
|
};
|
|
}
|
|
try {
|
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
const tmp = `${p}.tmp`;
|
|
fs.writeFileSync(tmp, `${JSON.stringify(out, null, 2)}\n`, "utf8");
|
|
fs.renameSync(tmp, p);
|
|
} catch (err) {
|
|
api.logger.warn?.(`dirigent: failed persisting channel member cache: ${String(err)}`);
|
|
}
|
|
}
|
|
|
|
function getAllBotAccountIds(api: OpenClawPluginApi): string[] {
|
|
const root = (api.config as Record<string, unknown>) || {};
|
|
const bindings = root.bindings as Array<Record<string, unknown>> | undefined;
|
|
if (!Array.isArray(bindings)) return [];
|
|
const ids: string[] = [];
|
|
for (const b of bindings) {
|
|
const match = b.match as Record<string, unknown> | 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(api: OpenClawPluginApi, channelId: string, accountId: string): boolean {
|
|
loadCache(api);
|
|
let seen = channelSeenAccounts.get(channelId);
|
|
if (!seen) {
|
|
seen = new Set();
|
|
channelSeenAccounts.set(channelId, seen);
|
|
}
|
|
if (seen.has(accountId)) return false;
|
|
seen.add(accountId);
|
|
persistCache(api);
|
|
return true;
|
|
}
|
|
|
|
export async function ensureTurnOrder(api: OpenClawPluginApi, channelId: string): Promise<void> {
|
|
loadCache(api);
|
|
let botAccounts = getChannelBotAccountIds(api, channelId);
|
|
|
|
if (botAccounts.length === 0 && !channelBootstrapTried.has(channelId)) {
|
|
channelBootstrapTried.add(channelId);
|
|
const discovered = await fetchVisibleChannelBotAccountIds(api, channelId).catch(() => [] as string[]);
|
|
for (const aid of discovered) recordChannelAccount(api, channelId, aid);
|
|
botAccounts = getChannelBotAccountIds(api, channelId);
|
|
}
|
|
|
|
if (botAccounts.length > 0) {
|
|
initTurnOrder(channelId, botAccounts);
|
|
}
|
|
}
|