From 12b0f4c6cc0439380921512f79bb98469c1e63b1 Mon Sep 17 00:00:00 2001 From: orion Date: Sun, 8 Mar 2026 06:13:38 +0000 Subject: [PATCH] feat(turn-init): persist channel bot membership cache to local file --- plugin/core/turn-bootstrap.ts | 47 ++++++++++++++++++++++++++++++-- plugin/hooks/message-received.ts | 4 +-- 2 files changed, 47 insertions(+), 4 deletions(-) diff --git a/plugin/core/turn-bootstrap.ts b/plugin/core/turn-bootstrap.ts index 960eb9f..a73128e 100644 --- a/plugin/core/turn-bootstrap.ts +++ b/plugin/core/turn-bootstrap.ts @@ -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>(); const channelBootstrapTried = new Set(); +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; + 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 = {}; + 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) || {}; @@ -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 { + 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); } diff --git a/plugin/hooks/message-received.ts b/plugin/hooks/message-received.ts index 584f58d..3629a15 100644 --- a/plugin/hooks/message-received.ts +++ b/plugin/hooks/message-received.ts @@ -16,7 +16,7 @@ type MessageReceivedDeps = { debugCtxSummary: (ctx: Record, event: Record) => Record; ensureTurnOrder: (api: OpenClawPluginApi, channelId: string) => Promise | 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; }; @@ -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`);