feat(turn-init): persist channel bot membership cache to local file

This commit is contained in:
2026-03-08 06:13:38 +00:00
parent 307235e207
commit 12b0f4c6cc
2 changed files with 47 additions and 4 deletions

View File

@@ -1,9 +1,49 @@
import fs from "node:fs";
import path from "node:path";
import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
import { initTurnOrder } from "../turn-manager.js"; import { initTurnOrder } from "../turn-manager.js";
import { fetchVisibleChannelBotAccountIds } from "./channel-members.js"; import { fetchVisibleChannelBotAccountIds } from "./channel-members.js";
const channelSeenAccounts = new Map<string, Set<string>>(); const channelSeenAccounts = new Map<string, Set<string>>();
const channelBootstrapTried = new 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[] { function getAllBotAccountIds(api: OpenClawPluginApi): string[] {
const root = (api.config as Record<string, unknown>) || {}; 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)); 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); let seen = channelSeenAccounts.get(channelId);
if (!seen) { if (!seen) {
seen = new Set(); seen = new Set();
@@ -34,16 +75,18 @@ export function recordChannelAccount(channelId: string, accountId: string): bool
} }
if (seen.has(accountId)) return false; if (seen.has(accountId)) return false;
seen.add(accountId); seen.add(accountId);
persistCache(api);
return true; return true;
} }
export async function ensureTurnOrder(api: OpenClawPluginApi, channelId: string): Promise<void> { export async function ensureTurnOrder(api: OpenClawPluginApi, channelId: string): Promise<void> {
loadCache(api);
let botAccounts = getChannelBotAccountIds(api, channelId); let botAccounts = getChannelBotAccountIds(api, channelId);
if (botAccounts.length === 0 && !channelBootstrapTried.has(channelId)) { if (botAccounts.length === 0 && !channelBootstrapTried.has(channelId)) {
channelBootstrapTried.add(channelId); channelBootstrapTried.add(channelId);
const discovered = await fetchVisibleChannelBotAccountIds(api, channelId).catch(() => [] as string[]); 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); botAccounts = getChannelBotAccountIds(api, channelId);
} }

View File

@@ -16,7 +16,7 @@ type MessageReceivedDeps = {
debugCtxSummary: (ctx: Record<string, unknown>, event: Record<string, unknown>) => Record<string, unknown>; debugCtxSummary: (ctx: Record<string, unknown>, event: Record<string, unknown>) => Record<string, unknown>;
ensureTurnOrder: (api: OpenClawPluginApi, channelId: string) => Promise<void> | void; ensureTurnOrder: (api: OpenClawPluginApi, channelId: string) => Promise<void> | void;
getModeratorUserId: (cfg: DirigentConfig) => string | undefined; getModeratorUserId: (cfg: DirigentConfig) => string | undefined;
recordChannelAccount: (channelId: string, accountId: string) => boolean; recordChannelAccount: (api: OpenClawPluginApi, channelId: string, accountId: string) => boolean;
extractMentionedUserIds: (content: string) => string[]; extractMentionedUserIds: (content: string) => string[];
buildUserIdToAccountIdMap: (api: OpenClawPluginApi) => Map<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; const senderAccountId = typeof c.accountId === "string" ? c.accountId : undefined;
if (senderAccountId && senderAccountId !== "default") { if (senderAccountId && senderAccountId !== "default") {
const isNew = recordChannelAccount(preChannelId, senderAccountId); const isNew = recordChannelAccount(api, preChannelId, senderAccountId);
if (isNew) { if (isNew) {
await ensureTurnOrder(api, preChannelId); await ensureTurnOrder(api, preChannelId);
api.logger.info(`dirigent: new account ${senderAccountId} seen in channel=${preChannelId}, turn order updated`); api.logger.info(`dirigent: new account ${senderAccountId} seen in channel=${preChannelId}, turn order updated`);