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 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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`);
|
||||||
|
|||||||
Reference in New Issue
Block a user