From 0a99abc7e3d838f5f2eab3c633532438a4cf6e0b Mon Sep 17 00:00:00 2001 From: orion Date: Wed, 1 Apr 2026 19:33:00 +0000 Subject: [PATCH] Use pluginConfig directly for Dirigent runtime config --- plugin/commands/dirigent-command.ts | 5 +-- plugin/core/live-config.ts | 23 ----------- plugin/hooks/before-message-write.ts | 4 +- plugin/hooks/before-model-resolve.ts | 54 +++++-------------------- plugin/hooks/before-prompt-build.ts | 18 +++------ plugin/hooks/message-received.ts | 4 +- plugin/hooks/message-sent.ts | 4 +- plugin/index.ts | 59 +++++++++++++--------------- plugin/tools/register-tools.ts | 39 +++++------------- 9 files changed, 59 insertions(+), 151 deletions(-) delete mode 100644 plugin/core/live-config.ts diff --git a/plugin/commands/dirigent-command.ts b/plugin/commands/dirigent-command.ts index 7232816..7c6cb29 100644 --- a/plugin/commands/dirigent-command.ts +++ b/plugin/commands/dirigent-command.ts @@ -8,11 +8,10 @@ type CommandDeps = { policyState: { filePath: string; channelPolicies: Record }; persistPolicies: (api: OpenClawPluginApi) => void; ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void; - getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig; }; export function registerDirigentCommand(deps: CommandDeps): void { - const { api, baseConfig, policyState, persistPolicies, ensurePolicyStateLoaded, getLivePluginConfig } = deps; + const { api, baseConfig, policyState, persistPolicies, ensurePolicyStateLoaded } = deps; api.registerCommand({ name: "dirigent", @@ -70,7 +69,7 @@ export function registerDirigentCommand(deps: CommandDeps): void { description: "Dirigent channel policy CRUD", acceptsArgs: true, handler: async (cmdCtx) => { - const live = getLivePluginConfig(api, baseConfig); + const live = baseConfig; ensurePolicyStateLoaded(api, live); const args = (cmdCtx.args || "").trim(); diff --git a/plugin/core/live-config.ts b/plugin/core/live-config.ts deleted file mode 100644 index 8da04af..0000000 --- a/plugin/core/live-config.ts +++ /dev/null @@ -1,23 +0,0 @@ -import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; -import type { DirigentConfig } from "../rules.js"; - -export function getLivePluginConfig(api: OpenClawPluginApi, fallback: DirigentConfig): DirigentConfig { - const root = (api.config as Record) || {}; - const plugins = (root.plugins as Record) || {}; - const entries = (plugins.entries as Record) || {}; - const entry = (entries.dirigent as Record) || (entries.whispergate as Record) || {}; - const cfg = (entry.config as Record) || {}; - if (Object.keys(cfg).length > 0) { - return { - enableDiscordControlTool: true, - enableDirigentPolicyTool: true, - enableDebugLogs: false, - debugLogChannelIds: [], - noReplyPort: 8787, - schedulingIdentifier: "➡️", - waitIdentifier: "👤", - ...cfg, - } as DirigentConfig; - } - return fallback; -} diff --git a/plugin/hooks/before-message-write.ts b/plugin/hooks/before-message-write.ts index 6cc93c0..6c2cf63 100644 --- a/plugin/hooks/before-message-write.ts +++ b/plugin/hooks/before-message-write.ts @@ -16,7 +16,6 @@ type BeforeMessageWriteDeps = { sessionAccountId: Map; sessionTurnHandled: Set; ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void; - getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig; shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean; ensureTurnOrder: (api: OpenClawPluginApi, channelId: string) => Promise | void; resolveDiscordUserId: (api: OpenClawPluginApi, accountId: string) => string | undefined; @@ -38,7 +37,6 @@ export function registerBeforeMessageWriteHook(deps: BeforeMessageWriteDeps): vo sessionAccountId, sessionTurnHandled, ensurePolicyStateLoaded, - getLivePluginConfig, shouldDebugLog, ensureTurnOrder, resolveDiscordUserId, @@ -110,7 +108,7 @@ export function registerBeforeMessageWriteHook(deps: BeforeMessageWriteDeps): vo return; } - const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & DebugConfig; + const live = baseConfig as DirigentConfig & DebugConfig; ensurePolicyStateLoaded(api, live); const policy = resolvePolicy(live, channelId, policyState.channelPolicies as Record); diff --git a/plugin/hooks/before-model-resolve.ts b/plugin/hooks/before-model-resolve.ts index 85447e5..2021f47 100644 --- a/plugin/hooks/before-model-resolve.ts +++ b/plugin/hooks/before-model-resolve.ts @@ -24,7 +24,6 @@ type BeforeModelResolveDeps = { policyState: { channelPolicies: Record }; DECISION_TTL_MS: number; ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void; - getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig; resolveAccountId: (api: OpenClawPluginApi, agentId: string) => string | undefined; pruneDecisionMap: () => void; shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean; @@ -42,7 +41,6 @@ export function registerBeforeModelResolveHook(deps: BeforeModelResolveDeps): vo policyState, DECISION_TTL_MS, ensurePolicyStateLoaded, - getLivePluginConfig, resolveAccountId, pruneDecisionMap, shouldDebugLog, @@ -53,7 +51,7 @@ export function registerBeforeModelResolveHook(deps: BeforeModelResolveDeps): vo const key = ctx.sessionKey; if (!key) return; - const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & DebugConfig; + const live = baseConfig as DirigentConfig & DebugConfig; ensurePolicyStateLoaded(api, live); const prompt = ((event as Record).prompt as string) || ""; @@ -118,55 +116,23 @@ export function registerBeforeModelResolveHook(deps: BeforeModelResolveDeps): vo if (!turnCheck.allowed) { sessionAllowed.set(key, false); api.logger.info( - `dirigent: turn gate blocked session=${key} accountId=${accountId} currentSpeaker=${turnCheck.currentSpeaker} reason=${turnCheck.reason}`, + `dirigent: before_model_resolve blocking out-of-turn speaker session=${key} channel=${derived.channelId} accountId=${accountId} currentSpeaker=${turnCheck.currentSpeaker}`, ); return { - providerOverride: live.noReplyProvider, - modelOverride: live.noReplyModel, + model: ctx.model, + provider: ctx.provider, + noReply: true, }; } sessionAllowed.set(key, true); - api.logger.info( - `dirigent: turn allowed, skipping rules override session=${key} accountId=${accountId}`, - ); - return; } } - if (!rec.decision.shouldUseNoReply) { - if (rec.needsRestore) { - sessionDecision.delete(key); - return { - providerOverride: undefined, - modelOverride: undefined, - }; - } - return; - } + if (!rec.decision.shouldUseNoReply) return; - rec.needsRestore = true; - sessionDecision.set(key, rec); - - if (live.enableDebugLogs) { - const hasConvMarker2 = prompt.includes("Conversation info (untrusted metadata):"); - api.logger.info( - `dirigent: DEBUG_NO_REPLY_TRIGGER session=${key} ` + - `channel=${derived.channel} channelId=${derived.channelId ?? ""} senderId=${derived.senderId ?? ""} ` + - `convSenderId=${String((derived.conv as Record).sender_id ?? "")} ` + - `convSender=${String((derived.conv as Record).sender ?? "")} ` + - `decision=${rec.decision.reason} ` + - `shouldNoReply=${rec.decision.shouldUseNoReply} shouldInject=${rec.decision.shouldInjectEndMarkerPrompt} ` + - `hasConvMarker=${hasConvMarker2} promptLen=${prompt.length}`, - ); - } - - api.logger.info( - `dirigent: override model for session=${key}, provider=${live.noReplyProvider}, model=${live.noReplyModel}, reason=${rec.decision.reason}`, - ); - - return { - providerOverride: live.noReplyProvider, - modelOverride: live.noReplyModel, - }; + const out: Record = { noReply: true }; + if (rec.decision.provider) out.provider = rec.decision.provider; + if (rec.decision.model) out.model = rec.decision.model; + return out; }); } diff --git a/plugin/hooks/before-prompt-build.ts b/plugin/hooks/before-prompt-build.ts index 2c8331e..3f75a83 100644 --- a/plugin/hooks/before-prompt-build.ts +++ b/plugin/hooks/before-prompt-build.ts @@ -21,7 +21,6 @@ type BeforePromptBuildDeps = { policyState: { channelPolicies: Record }; DECISION_TTL_MS: number; ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void; - getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig; shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean; buildEndMarkerInstruction: (endSymbols: string[], isGroupChat: boolean, schedulingIdentifier: string, waitIdentifier: string) => string; buildSchedulingIdentifierInstruction: (schedulingIdentifier: string) => string; @@ -37,7 +36,6 @@ export function registerBeforePromptBuildHook(deps: BeforePromptBuildDeps): void policyState, DECISION_TTL_MS, ensurePolicyStateLoaded, - getLivePluginConfig, shouldDebugLog, buildEndMarkerInstruction, buildSchedulingIdentifierInstruction, @@ -48,7 +46,7 @@ export function registerBeforePromptBuildHook(deps: BeforePromptBuildDeps): void const key = ctx.sessionKey; if (!key) return; - const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & DebugConfig; + const live = baseConfig as DirigentConfig & DebugConfig; ensurePolicyStateLoaded(api, live); let rec = sessionDecision.get(key); @@ -118,17 +116,13 @@ export function registerBeforePromptBuildHook(deps: BeforePromptBuildDeps): void let identity = ""; if (isGroupChat && ctx.agentId) { const idStr = buildAgentIdentity(api, ctx.agentId); - if (idStr) identity = idStr + "\n\n"; - } - - let schedulingInstruction = ""; - if (isGroupChat) { - schedulingInstruction = buildSchedulingIdentifierInstruction(schedulingId); + if (idStr) { + identity = `\n\nYour agent identity: ${idStr}.`; + } } + const schedulingInstruction = isGroupChat ? buildSchedulingIdentifierInstruction(schedulingId) : ""; + (event as Record).prompt = `${prompt}\n\n${instruction}${identity}${schedulingInstruction}`; sessionInjected.add(key); - - api.logger.info(`dirigent: prepend end marker instruction for session=${key}, reason=${rec.decision.reason} isGroupChat=${isGroupChat}`); - return { prependContext: identity + instruction + schedulingInstruction }; }); } diff --git a/plugin/hooks/message-received.ts b/plugin/hooks/message-received.ts index 3629a15..01d253b 100644 --- a/plugin/hooks/message-received.ts +++ b/plugin/hooks/message-received.ts @@ -11,7 +11,6 @@ type DebugConfig = { type MessageReceivedDeps = { api: OpenClawPluginApi; baseConfig: DirigentConfig; - getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig; shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean; debugCtxSummary: (ctx: Record, event: Record) => Record; ensureTurnOrder: (api: OpenClawPluginApi, channelId: string) => Promise | void; @@ -25,7 +24,6 @@ export function registerMessageReceivedHook(deps: MessageReceivedDeps): void { const { api, baseConfig, - getLivePluginConfig, shouldDebugLog, debugCtxSummary, ensureTurnOrder, @@ -40,7 +38,7 @@ export function registerMessageReceivedHook(deps: MessageReceivedDeps): void { const c = (ctx || {}) as Record; const e = (event || {}) as Record; const preChannelId = extractDiscordChannelId(c, e); - const livePre = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & DebugConfig; + const livePre = baseConfig as DirigentConfig & DebugConfig; if (shouldDebugLog(livePre, preChannelId)) { api.logger.info(`dirigent: debug message_received preflight ctx=${JSON.stringify(debugCtxSummary(c, e))}`); } diff --git a/plugin/hooks/message-sent.ts b/plugin/hooks/message-sent.ts index b9703bc..1449ece 100644 --- a/plugin/hooks/message-sent.ts +++ b/plugin/hooks/message-sent.ts @@ -16,7 +16,6 @@ type MessageSentDeps = { sessionAccountId: Map; sessionTurnHandled: Set; ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void; - getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig; resolveDiscordUserId: (api: OpenClawPluginApi, accountId: string) => string | undefined; sendModeratorMessage: ( botToken: string, @@ -35,7 +34,6 @@ export function registerMessageSentHook(deps: MessageSentDeps): void { sessionAccountId, sessionTurnHandled, ensurePolicyStateLoaded, - getLivePluginConfig, resolveDiscordUserId, sendModeratorMessage, } = deps; @@ -69,7 +67,7 @@ export function registerMessageSentHook(deps: MessageSentDeps): void { if (!channelId || !accountId) return; - const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & DebugConfig; + const live = baseConfig as DirigentConfig & DebugConfig; ensurePolicyStateLoaded(api, live); const policy = resolvePolicy(live, channelId, policyState.channelPolicies as Record); diff --git a/plugin/index.ts b/plugin/index.ts index d02108b..7c8598a 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -10,7 +10,6 @@ import { registerBeforeMessageWriteHook } from "./hooks/before-message-write.js" import { registerMessageSentHook } from "./hooks/message-sent.js"; 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"; @@ -34,6 +33,24 @@ type DebugConfig = { debugLogChannelIds?: string[]; }; +type NormalizedDirigentConfig = DirigentConfig & { + enableDiscordControlTool: boolean; + enableDirigentPolicyTool: boolean; +}; + +function normalizePluginConfig(api: OpenClawPluginApi): NormalizedDirigentConfig { + return { + enableDiscordControlTool: true, + enableDirigentPolicyTool: true, + enableDebugLogs: false, + debugLogChannelIds: [], + noReplyPort: 8787, + schedulingIdentifier: "➡️", + waitIdentifier: "👤", + ...(api.pluginConfig || {}), + } as NormalizedDirigentConfig; +} + function buildEndMarkerInstruction(endSymbols: string[], isGroupChat: boolean, schedulingIdentifier: string, waitIdentifier: string): string { const symbols = endSymbols.length > 0 ? endSymbols.join("") : "🔚"; let instruction = `Your response MUST end with ${symbols}. Exception: gateway keywords (e.g. NO_REPLY, HEARTBEAT_OK, NO, or an empty response) must NOT include ${symbols}.`; @@ -52,21 +69,8 @@ export default { id: "dirigent", name: "Dirigent", register(api: OpenClawPluginApi) { - // Merge pluginConfig with defaults (in case config is missing from openclaw.json) - const baseConfig = { - enableDiscordControlTool: true, - enableDirigentPolicyTool: true, - schedulingIdentifier: "➡️", - waitIdentifier: "👤", - noReplyPort: 8787, - ...(api.pluginConfig || {}), - } as DirigentConfig & { - enableDiscordControlTool: boolean; - enableDirigentPolicyTool: boolean; - }; - - const liveAtRegister = getLivePluginConfig(api, baseConfig as DirigentConfig); - ensurePolicyStateLoaded(api, liveAtRegister); + const baseConfig = normalizePluginConfig(api); + ensurePolicyStateLoaded(api, baseConfig); // Resolve plugin directory for locating sibling modules (no-reply-api/) // Note: api.resolvePath(".") returns cwd, not script directory. Use import.meta.url instead. @@ -77,7 +81,7 @@ export default { api.on("gateway_start", () => { api.logger.info(`dirigent: gateway_start event received`); - const live = getLivePluginConfig(api, baseConfig as DirigentConfig); + const live = normalizePluginConfig(api); // Check no-reply-api server file exists const serverPath = path.resolve(pluginDir, "no-reply-api", "server.mjs"); @@ -112,9 +116,8 @@ export default { // Register tools registerDirigentTools({ api, - baseConfig: baseConfig as DirigentConfig, + baseConfig, pickDefined, - getLivePluginConfig, }); // Turn management is handled internally by the plugin (not exposed as tools). @@ -122,8 +125,7 @@ export default { registerMessageReceivedHook({ api, - baseConfig: baseConfig as DirigentConfig, - getLivePluginConfig, + baseConfig, shouldDebugLog, debugCtxSummary, ensureTurnOrder, @@ -135,7 +137,7 @@ export default { registerBeforeModelResolveHook({ api, - baseConfig: baseConfig as DirigentConfig, + baseConfig, sessionDecision, sessionAllowed, sessionChannelId, @@ -143,7 +145,6 @@ export default { policyState, DECISION_TTL_MS, ensurePolicyStateLoaded, - getLivePluginConfig, resolveAccountId, pruneDecisionMap, shouldDebugLog, @@ -152,13 +153,12 @@ export default { registerBeforePromptBuildHook({ api, - baseConfig: baseConfig as DirigentConfig, + baseConfig, sessionDecision, sessionInjected, policyState, DECISION_TTL_MS, ensurePolicyStateLoaded, - getLivePluginConfig, shouldDebugLog, buildEndMarkerInstruction, buildSchedulingIdentifierInstruction, @@ -168,24 +168,22 @@ export default { // Register slash commands for Discord registerDirigentCommand({ api, - baseConfig: baseConfig as DirigentConfig, + baseConfig, policyState, persistPolicies, ensurePolicyStateLoaded, - getLivePluginConfig, }); // Handle NO_REPLY detection before message write registerBeforeMessageWriteHook({ api, - baseConfig: baseConfig as DirigentConfig, + baseConfig, policyState, sessionAllowed, sessionChannelId, sessionAccountId, sessionTurnHandled, ensurePolicyStateLoaded, - getLivePluginConfig, shouldDebugLog, ensureTurnOrder, resolveDiscordUserId, @@ -195,13 +193,12 @@ export default { // Turn advance: when an agent sends a message, check if it signals end of turn registerMessageSentHook({ api, - baseConfig: baseConfig as DirigentConfig, + baseConfig, policyState, sessionChannelId, sessionAccountId, sessionTurnHandled, ensurePolicyStateLoaded, - getLivePluginConfig, resolveDiscordUserId, sendModeratorMessage, }); diff --git a/plugin/tools/register-tools.ts b/plugin/tools/register-tools.ts index 4180ada..99f1edf 100644 --- a/plugin/tools/register-tools.ts +++ b/plugin/tools/register-tools.ts @@ -7,7 +7,6 @@ type ToolDeps = { api: OpenClawPluginApi; baseConfig: DirigentConfig; pickDefined: (obj: Record) => Record; - getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig; }; function parseAccountToken(api: OpenClawPluginApi, accountId?: string): { accountId: string; token: string } | null { @@ -47,10 +46,10 @@ function roleOrMemberType(v: unknown): number { } export function registerDirigentTools(deps: ToolDeps): void { - const { api, baseConfig, pickDefined, getLivePluginConfig } = deps; + const { api, baseConfig, pickDefined } = deps; async function executeDiscordAction(action: DiscordControlAction, params: Record) { - const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & { + const live = baseConfig as DirigentConfig & { enableDiscordControlTool?: boolean; discordControlAccountId?: string; }; @@ -128,14 +127,16 @@ export function registerDirigentTools(deps: ToolDeps): void { } api.registerTool({ - name: "discord_channel_create", - description: "Create a private Discord channel with specific user/role permissions.", - parameters: { + name: "dirigent_discord_control", + description: "Create/update Discord private channels using the configured Discord bot token", + inputSchema: { type: "object", additionalProperties: false, properties: { + action: { type: "string", enum: ["channel-private-create", "channel-private-update"] }, accountId: { type: "string" }, guildId: { type: "string" }, + channelId: { type: "string" }, name: { type: "string" }, type: { type: "number" }, parentId: { type: "string" }, @@ -146,34 +147,14 @@ export function registerDirigentTools(deps: ToolDeps): void { 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: "discord_channel_update", - description: "Update permissions on an existing private Discord channel.", - parameters: { - type: "object", - additionalProperties: false, - properties: { - accountId: { type: "string" }, - 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: [], + required: ["action"], }, - async execute(_id: string, params: Record) { - return executeDiscordAction("channel-private-update", params); - }, - }, { optional: false }); + handler: async (params) => executeDiscordAction(params.action as DiscordControlAction, params as Record), + }); }