Files
Dirigent/plugin/core/turn-bootstrap.ts

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);
}
}