feat(turn-init): persist channel bot membership cache to local file
This commit is contained in:
@@ -1,9 +1,49 @@
|
||||
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[] }>;
|
||||
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 persistCache(api: OpenClawPluginApi): void {
|
||||
const p = cachePath(api);
|
||||
const out: Record<string, { botAccountIds: string[]; updatedAt: string }> = {};
|
||||
for (const [channelId, set] of channelSeenAccounts.entries()) {
|
||||
out[channelId] = { botAccountIds: [...set], updatedAt: new Date().toISOString() };
|
||||
}
|
||||
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>) || {};
|
||||
@@ -26,7 +66,8 @@ function getChannelBotAccountIds(api: OpenClawPluginApi, channelId: string): str
|
||||
return [...seen].filter((id) => allBots.has(id));
|
||||
}
|
||||
|
||||
export function recordChannelAccount(channelId: string, accountId: string): boolean {
|
||||
export function recordChannelAccount(api: OpenClawPluginApi, channelId: string, accountId: string): boolean {
|
||||
loadCache(api);
|
||||
let seen = channelSeenAccounts.get(channelId);
|
||||
if (!seen) {
|
||||
seen = new Set();
|
||||
@@ -34,16 +75,18 @@ export function recordChannelAccount(channelId: string, accountId: string): bool
|
||||
}
|
||||
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(channelId, aid);
|
||||
for (const aid of discovered) recordChannelAccount(api, channelId, aid);
|
||||
botAccounts = getChannelBotAccountIds(api, channelId);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ type MessageReceivedDeps = {
|
||||
debugCtxSummary: (ctx: Record<string, unknown>, event: Record<string, unknown>) => Record<string, unknown>;
|
||||
ensureTurnOrder: (api: OpenClawPluginApi, channelId: string) => Promise<void> | void;
|
||||
getModeratorUserId: (cfg: DirigentConfig) => string | undefined;
|
||||
recordChannelAccount: (channelId: string, accountId: string) => boolean;
|
||||
recordChannelAccount: (api: OpenClawPluginApi, channelId: string, accountId: string) => boolean;
|
||||
extractMentionedUserIds: (content: string) => string[];
|
||||
buildUserIdToAccountIdMap: (api: OpenClawPluginApi) => Map<string, string>;
|
||||
};
|
||||
@@ -63,7 +63,7 @@ export function registerMessageReceivedHook(deps: MessageReceivedDeps): void {
|
||||
const senderAccountId = typeof c.accountId === "string" ? c.accountId : undefined;
|
||||
|
||||
if (senderAccountId && senderAccountId !== "default") {
|
||||
const isNew = recordChannelAccount(preChannelId, senderAccountId);
|
||||
const isNew = recordChannelAccount(api, preChannelId, senderAccountId);
|
||||
if (isNew) {
|
||||
await ensureTurnOrder(api, preChannelId);
|
||||
api.logger.info(`dirigent: new account ${senderAccountId} seen in channel=${preChannelId}, turn order updated`);
|
||||
|
||||
Reference in New Issue
Block a user