import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import type { ChannelPolicy, DirigentConfig } from "../rules.js"; type DiscordControlAction = "channel-private-create" | "channel-private-update" | "member-list"; type ToolDeps = { api: OpenClawPluginApi; baseConfig: DirigentConfig; policyState: { filePath: string; channelPolicies: Record }; pickDefined: (obj: Record) => Record; persistPolicies: (api: OpenClawPluginApi) => void; ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void; getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig; }; export function registerDirigentTools(deps: ToolDeps): void { const { api, baseConfig, policyState, pickDefined, persistPolicies, ensurePolicyStateLoaded, getLivePluginConfig } = deps; async function executeDiscordAction(action: DiscordControlAction, params: Record) { const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & { discordControlApiBaseUrl?: string; discordControlApiToken?: string; discordControlCallerId?: string; enableDiscordControlTool?: boolean; }; if (live.enableDiscordControlTool === false) { return { content: [{ type: "text", text: "discord actions disabled by config" }], isError: true }; } const baseUrl = (live.discordControlApiBaseUrl || "http://127.0.0.1:8790").replace(/\/$/, ""); const body = pickDefined({ ...params, action }); const headers: Record = { "Content-Type": "application/json" }; if (live.discordControlApiToken) headers.Authorization = `Bearer ${live.discordControlApiToken}`; if (live.discordControlCallerId) headers["X-OpenClaw-Caller-Id"] = live.discordControlCallerId; const r = await fetch(`${baseUrl}/v1/discord/action`, { method: "POST", headers, body: JSON.stringify(body), }); const text = await r.text(); if (!r.ok) { return { content: [{ type: "text", text: `discord action failed (${r.status}): ${text}` }], isError: true }; } return { content: [{ type: "text", text }] }; } api.registerTool( { name: "dirigent_discord_channel_create", description: "Create a private Discord channel with specific user/role permissions.", parameters: { type: "object", additionalProperties: false, properties: { guildId: { type: "string" }, name: { type: "string" }, type: { type: "number" }, parentId: { type: "string" }, topic: { type: "string" }, position: { type: "number" }, nsfw: { type: "boolean" }, allowedUserIds: { type: "array", items: { type: "string" } }, allowedRoleIds: { type: "array", items: { type: "string" } }, allowMask: { type: "string" }, denyEveryoneMask: { type: "string" }, }, required: [], }, async execute(_id: string, params: Record) { return executeDiscordAction("channel-private-create", params); }, }, { optional: false }, ); api.registerTool( { name: "dirigent_discord_channel_update", description: "Update permissions on an existing private Discord channel.", parameters: { type: "object", additionalProperties: false, properties: { channelId: { type: "string" }, mode: { type: "string", enum: ["merge", "replace"] }, addUserIds: { type: "array", items: { type: "string" } }, addRoleIds: { type: "array", items: { type: "string" } }, removeTargetIds: { type: "array", items: { type: "string" } }, allowMask: { type: "string" }, denyMask: { type: "string" }, }, required: [], }, async execute(_id: string, params: Record) { return executeDiscordAction("channel-private-update", params); }, }, { optional: false }, ); api.registerTool( { name: "dirigent_discord_member_list", description: "List members of a Discord guild.", parameters: { type: "object", additionalProperties: false, properties: { guildId: { type: "string" }, limit: { type: "number" }, after: { type: "string" }, fields: { anyOf: [{ type: "string" }, { type: "array", items: { type: "string" } }] }, }, required: [], }, async execute(_id: string, params: Record) { return executeDiscordAction("member-list", params); }, }, { optional: false }, ); api.registerTool( { name: "dirigent_policy_get", description: "Get all Dirigent channel policies.", parameters: { type: "object", additionalProperties: false, properties: {}, required: [] }, async execute() { const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & { enableDirigentPolicyTool?: boolean }; if (live.enableDirigentPolicyTool === false) { return { content: [{ type: "text", text: "policy actions disabled by config" }], isError: true }; } ensurePolicyStateLoaded(api, live); return { content: [{ type: "text", text: JSON.stringify({ file: policyState.filePath, policies: policyState.channelPolicies }, null, 2) }], }; }, }, { optional: false }, ); api.registerTool( { name: "dirigent_policy_set", description: "Set or update a Dirigent channel policy.", parameters: { type: "object", additionalProperties: false, properties: { channelId: { type: "string" }, listMode: { type: "string", enum: ["human-list", "agent-list"] }, humanList: { type: "array", items: { type: "string" } }, agentList: { type: "array", items: { type: "string" } }, endSymbols: { type: "array", items: { type: "string" } }, }, required: ["channelId"], }, async execute(_id: string, params: Record) { const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & { enableDirigentPolicyTool?: boolean }; if (live.enableDirigentPolicyTool === false) { return { content: [{ type: "text", text: "policy actions disabled by config" }], isError: true }; } ensurePolicyStateLoaded(api, live); const channelId = String(params.channelId || "").trim(); if (!channelId) return { content: [{ type: "text", text: "channelId is required" }], isError: true }; const prev = JSON.parse(JSON.stringify(policyState.channelPolicies)); try { const next: ChannelPolicy = { listMode: (params.listMode as "human-list" | "agent-list" | undefined) || undefined, humanList: Array.isArray(params.humanList) ? (params.humanList as string[]) : undefined, agentList: Array.isArray(params.agentList) ? (params.agentList as string[]) : undefined, endSymbols: Array.isArray(params.endSymbols) ? (params.endSymbols as string[]) : undefined, }; policyState.channelPolicies[channelId] = pickDefined(next as unknown as Record) as ChannelPolicy; persistPolicies(api); return { content: [{ type: "text", text: JSON.stringify({ ok: true, channelId, policy: policyState.channelPolicies[channelId] }) }] }; } catch (err) { policyState.channelPolicies = prev; return { content: [{ type: "text", text: `persist failed: ${String(err)}` }], isError: true }; } }, }, { optional: false }, ); api.registerTool( { name: "dirigent_policy_delete", description: "Delete a Dirigent channel policy.", parameters: { type: "object", additionalProperties: false, properties: { channelId: { type: "string" }, }, required: ["channelId"], }, async execute(_id: string, params: Record) { const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & { enableDirigentPolicyTool?: boolean }; if (live.enableDirigentPolicyTool === false) { return { content: [{ type: "text", text: "policy actions disabled by config" }], isError: true }; } ensurePolicyStateLoaded(api, live); const channelId = String(params.channelId || "").trim(); if (!channelId) return { content: [{ type: "text", text: "channelId is required" }], isError: true }; const prev = JSON.parse(JSON.stringify(policyState.channelPolicies)); try { delete policyState.channelPolicies[channelId]; persistPolicies(api); return { content: [{ type: "text", text: JSON.stringify({ ok: true, channelId, deleted: true }) }] }; } catch (err) { policyState.channelPolicies = prev; return { content: [{ type: "text", text: `persist failed: ${String(err)}` }], isError: true }; } }, }, { optional: false }, ); }