refactor(plugin): extract identity and mention helpers into core modules

This commit is contained in:
2026-03-08 05:19:40 +00:00
parent e258e99ed2
commit 05b902c0b2
3 changed files with 112 additions and 99 deletions

79
plugin/core/identity.ts Normal file
View File

@@ -0,0 +1,79 @@
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
function userIdFromToken(token: string): string | undefined {
try {
const segment = token.split(".")[0];
const padded = segment + "=".repeat((4 - (segment.length % 4)) % 4);
return Buffer.from(padded, "base64").toString("utf8");
} catch {
return undefined;
}
}
function resolveDiscordUserIdFromAccount(api: OpenClawPluginApi, accountId: 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>>) || {};
const acct = accounts[accountId];
if (!acct?.token || typeof acct.token !== "string") return undefined;
return userIdFromToken(acct.token);
}
export function resolveAccountId(api: OpenClawPluginApi, agentId: string): string | undefined {
const root = (api.config as Record<string, unknown>) || {};
const bindings = root.bindings as Array<Record<string, unknown>> | undefined;
if (!Array.isArray(bindings)) return undefined;
for (const b of bindings) {
if (b.agentId === agentId) {
const match = b.match as Record<string, unknown> | undefined;
if (match?.channel === "discord" && typeof match.accountId === "string") {
return match.accountId;
}
}
}
return undefined;
}
export function buildAgentIdentity(api: OpenClawPluginApi, agentId: string): string | undefined {
const root = (api.config as Record<string, unknown>) || {};
const bindings = root.bindings as Array<Record<string, unknown>> | undefined;
const agents = ((root.agents as Record<string, unknown>)?.list as Array<Record<string, unknown>>) || [];
if (!Array.isArray(bindings)) return undefined;
let accountId: string | undefined;
for (const b of bindings) {
if (b.agentId === agentId) {
const match = b.match as Record<string, unknown> | undefined;
if (match?.channel === "discord" && typeof match.accountId === "string") {
accountId = match.accountId;
break;
}
}
}
if (!accountId) return undefined;
const agent = agents.find((a: Record<string, unknown>) => a.id === agentId);
const name = (agent?.name as string) || agentId;
const discordUserId = resolveDiscordUserIdFromAccount(api, accountId);
let identity = `You are ${name} (Discord account: ${accountId}`;
if (discordUserId) identity += `, Discord userId: ${discordUserId}`;
identity += `).`;
return identity;
}
export function buildUserIdToAccountIdMap(api: OpenClawPluginApi): Map<string, string> {
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>>) || {};
const map = new Map<string, string>();
for (const [accountId, acct] of Object.entries(accounts)) {
if (typeof acct.token === "string") {
const userId = userIdFromToken(acct.token);
if (userId) map.set(userId, accountId);
}
}
return map;
}

31
plugin/core/mentions.ts Normal file
View File

@@ -0,0 +1,31 @@
import type { DirigentConfig } from "../rules.js";
function userIdFromToken(token: string): string | undefined {
try {
const segment = token.split(".")[0];
const padded = segment + "=".repeat((4 - (segment.length % 4)) % 4);
return Buffer.from(padded, "base64").toString("utf8");
} catch {
return undefined;
}
}
export function extractMentionedUserIds(content: string): string[] {
const regex = /<@!?(\d+)>/g;
const ids: string[] = [];
const seen = new Set<string>();
let match: RegExpExecArray | null;
while ((match = regex.exec(content)) !== null) {
const id = match[1];
if (!seen.has(id)) {
seen.add(id);
ids.push(id);
}
}
return ids;
}
export function getModeratorUserId(config: DirigentConfig): string | undefined {
if (!config.moderatorBotToken) return undefined;
return userIdFromToken(config.moderatorBotToken);
}

View File

@@ -14,6 +14,8 @@ import { registerDirigentCommand } from "./commands/dirigent-command.js";
import { registerDirigentTools } from "./tools/register-tools.js";
import { getLivePluginConfig } from "./core/live-config.js";
import { ensurePolicyStateLoaded, persistPolicies, policyState } from "./policy/store.js";
import { buildAgentIdentity, buildUserIdToAccountIdMap, resolveAccountId } from "./core/identity.js";
import { extractMentionedUserIds, getModeratorUserId } from "./core/mentions.js";
// ── No-Reply API child process lifecycle ──────────────────────────────
let noReplyProcess: ChildProcess | null = null;
@@ -108,22 +110,6 @@ function pruneDecisionMap(now = Date.now()) {
}
}
/** Resolve agentId → Discord accountId from config bindings */
function resolveAccountId(api: OpenClawPluginApi, agentId: string): string | undefined {
const root = (api.config as Record<string, unknown>) || {};
const bindings = root.bindings as Array<Record<string, unknown>> | undefined;
if (!Array.isArray(bindings)) return undefined;
for (const b of bindings) {
if (b.agentId === agentId) {
const match = b.match as Record<string, unknown> | undefined;
if (match?.channel === "discord" && typeof match.accountId === "string") {
return match.accountId;
}
}
}
return undefined;
}
/**
* Get all Discord bot accountIds from config bindings.
*/
@@ -184,45 +170,6 @@ function ensureTurnOrder(api: OpenClawPluginApi, channelId: string): void {
}
}
/**
* Build agent identity string for injection into group chat prompts.
* Includes agent name, Discord accountId, and Discord userId.
*/
function buildAgentIdentity(api: OpenClawPluginApi, agentId: string): string | undefined {
const root = (api.config as Record<string, unknown>) || {};
const bindings = root.bindings as Array<Record<string, unknown>> | undefined;
const agents = ((root.agents as Record<string, unknown>)?.list as Array<Record<string, unknown>>) || [];
if (!Array.isArray(bindings)) return undefined;
// Find accountId for this agent
let accountId: string | undefined;
for (const b of bindings) {
if (b.agentId === agentId) {
const match = b.match as Record<string, unknown> | undefined;
if (match?.channel === "discord" && typeof match.accountId === "string") {
accountId = match.accountId;
break;
}
}
}
if (!accountId) return undefined;
// Find agent name
const agent = agents.find((a: Record<string, unknown>) => a.id === agentId);
const name = (agent?.name as string) || agentId;
// Resolve Discord userId from bot token
const discordUserId = resolveDiscordUserId(api, accountId);
let identity = `You are ${name} (Discord account: ${accountId}`;
if (discordUserId) {
identity += `, Discord userId: ${discordUserId}`;
}
identity += `).`;
return identity;
}
// --- Moderator bot helpers ---
/** Extract Discord user ID from a bot token (base64-encoded in first segment) */
@@ -248,50 +195,6 @@ function resolveDiscordUserId(api: OpenClawPluginApi, accountId: string): string
return userIdFromToken(acct.token);
}
/**
* Build a reverse map: Discord userId → accountId for all configured Discord accounts.
*/
function buildUserIdToAccountIdMap(api: OpenClawPluginApi): Map<string, string> {
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>>) || {};
const map = new Map<string, string>();
for (const [accountId, acct] of Object.entries(accounts)) {
if (typeof acct.token === "string") {
const userId = userIdFromToken(acct.token);
if (userId) map.set(userId, accountId);
}
}
return map;
}
/**
* Extract Discord @mention user IDs from message content.
* Matches <@USER_ID> and <@!USER_ID> patterns.
* Returns user IDs in the order they appear.
*/
function extractMentionedUserIds(content: string): string[] {
const regex = /<@!?(\d+)>/g;
const ids: string[] = [];
const seen = new Set<string>();
let match;
while ((match = regex.exec(content)) !== null) {
const id = match[1];
if (!seen.has(id)) {
seen.add(id);
ids.push(id);
}
}
return ids;
}
/** Get the moderator bot's Discord user ID from its token */
function getModeratorUserId(config: DirigentConfig): string | undefined {
if (!config.moderatorBotToken) return undefined;
return userIdFromToken(config.moderatorBotToken);
}
/** Send a message as the moderator bot via Discord REST API */
async function sendModeratorMessage(token: string, channelId: string, content: string, logger: { info: (msg: string) => void; warn: (msg: string) => void }): Promise<boolean> {
try {