Merge pull request 'Use api.pluginConfig directly for Dirigent runtime config' (#20) from fix/use-pluginconfig-runtime-config into main
Reviewed-on: #20
This commit was merged in pull request #20.
This commit is contained in:
@@ -8,11 +8,10 @@ type CommandDeps = {
|
|||||||
policyState: { filePath: string; channelPolicies: Record<string, unknown> };
|
policyState: { filePath: string; channelPolicies: Record<string, unknown> };
|
||||||
persistPolicies: (api: OpenClawPluginApi) => void;
|
persistPolicies: (api: OpenClawPluginApi) => void;
|
||||||
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
|
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
|
||||||
getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export function registerDirigentCommand(deps: CommandDeps): void {
|
export function registerDirigentCommand(deps: CommandDeps): void {
|
||||||
const { api, baseConfig, policyState, persistPolicies, ensurePolicyStateLoaded, getLivePluginConfig } = deps;
|
const { api, baseConfig, policyState, persistPolicies, ensurePolicyStateLoaded } = deps;
|
||||||
|
|
||||||
api.registerCommand({
|
api.registerCommand({
|
||||||
name: "dirigent",
|
name: "dirigent",
|
||||||
@@ -70,7 +69,7 @@ export function registerDirigentCommand(deps: CommandDeps): void {
|
|||||||
description: "Dirigent channel policy CRUD",
|
description: "Dirigent channel policy CRUD",
|
||||||
acceptsArgs: true,
|
acceptsArgs: true,
|
||||||
handler: async (cmdCtx) => {
|
handler: async (cmdCtx) => {
|
||||||
const live = getLivePluginConfig(api, baseConfig);
|
const live = baseConfig;
|
||||||
ensurePolicyStateLoaded(api, live);
|
ensurePolicyStateLoaded(api, live);
|
||||||
|
|
||||||
const args = (cmdCtx.args || "").trim();
|
const args = (cmdCtx.args || "").trim();
|
||||||
|
|||||||
@@ -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<string, unknown>) || {};
|
|
||||||
const plugins = (root.plugins as Record<string, unknown>) || {};
|
|
||||||
const entries = (plugins.entries as Record<string, unknown>) || {};
|
|
||||||
const entry = (entries.dirigent as Record<string, unknown>) || (entries.whispergate as Record<string, unknown>) || {};
|
|
||||||
const cfg = (entry.config as Record<string, unknown>) || {};
|
|
||||||
if (Object.keys(cfg).length > 0) {
|
|
||||||
return {
|
|
||||||
enableDiscordControlTool: true,
|
|
||||||
enableDirigentPolicyTool: true,
|
|
||||||
enableDebugLogs: false,
|
|
||||||
debugLogChannelIds: [],
|
|
||||||
noReplyPort: 8787,
|
|
||||||
schedulingIdentifier: "➡️",
|
|
||||||
waitIdentifier: "👤",
|
|
||||||
...cfg,
|
|
||||||
} as DirigentConfig;
|
|
||||||
}
|
|
||||||
return fallback;
|
|
||||||
}
|
|
||||||
@@ -16,7 +16,6 @@ type BeforeMessageWriteDeps = {
|
|||||||
sessionAccountId: Map<string, string>;
|
sessionAccountId: Map<string, string>;
|
||||||
sessionTurnHandled: Set<string>;
|
sessionTurnHandled: Set<string>;
|
||||||
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
|
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
|
||||||
getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig;
|
|
||||||
shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean;
|
shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean;
|
||||||
ensureTurnOrder: (api: OpenClawPluginApi, channelId: string) => Promise<void> | void;
|
ensureTurnOrder: (api: OpenClawPluginApi, channelId: string) => Promise<void> | void;
|
||||||
resolveDiscordUserId: (api: OpenClawPluginApi, accountId: string) => string | undefined;
|
resolveDiscordUserId: (api: OpenClawPluginApi, accountId: string) => string | undefined;
|
||||||
@@ -38,7 +37,6 @@ export function registerBeforeMessageWriteHook(deps: BeforeMessageWriteDeps): vo
|
|||||||
sessionAccountId,
|
sessionAccountId,
|
||||||
sessionTurnHandled,
|
sessionTurnHandled,
|
||||||
ensurePolicyStateLoaded,
|
ensurePolicyStateLoaded,
|
||||||
getLivePluginConfig,
|
|
||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
ensureTurnOrder,
|
ensureTurnOrder,
|
||||||
resolveDiscordUserId,
|
resolveDiscordUserId,
|
||||||
@@ -110,7 +108,7 @@ export function registerBeforeMessageWriteHook(deps: BeforeMessageWriteDeps): vo
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & DebugConfig;
|
const live = baseConfig as DirigentConfig & DebugConfig;
|
||||||
ensurePolicyStateLoaded(api, live);
|
ensurePolicyStateLoaded(api, live);
|
||||||
const policy = resolvePolicy(live, channelId, policyState.channelPolicies as Record<string, any>);
|
const policy = resolvePolicy(live, channelId, policyState.channelPolicies as Record<string, any>);
|
||||||
|
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ type BeforeModelResolveDeps = {
|
|||||||
policyState: { channelPolicies: Record<string, unknown> };
|
policyState: { channelPolicies: Record<string, unknown> };
|
||||||
DECISION_TTL_MS: number;
|
DECISION_TTL_MS: number;
|
||||||
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
|
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
|
||||||
getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig;
|
|
||||||
resolveAccountId: (api: OpenClawPluginApi, agentId: string) => string | undefined;
|
resolveAccountId: (api: OpenClawPluginApi, agentId: string) => string | undefined;
|
||||||
pruneDecisionMap: () => void;
|
pruneDecisionMap: () => void;
|
||||||
shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean;
|
shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean;
|
||||||
@@ -42,7 +41,6 @@ export function registerBeforeModelResolveHook(deps: BeforeModelResolveDeps): vo
|
|||||||
policyState,
|
policyState,
|
||||||
DECISION_TTL_MS,
|
DECISION_TTL_MS,
|
||||||
ensurePolicyStateLoaded,
|
ensurePolicyStateLoaded,
|
||||||
getLivePluginConfig,
|
|
||||||
resolveAccountId,
|
resolveAccountId,
|
||||||
pruneDecisionMap,
|
pruneDecisionMap,
|
||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
@@ -53,7 +51,7 @@ export function registerBeforeModelResolveHook(deps: BeforeModelResolveDeps): vo
|
|||||||
const key = ctx.sessionKey;
|
const key = ctx.sessionKey;
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
|
|
||||||
const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & DebugConfig;
|
const live = baseConfig as DirigentConfig & DebugConfig;
|
||||||
ensurePolicyStateLoaded(api, live);
|
ensurePolicyStateLoaded(api, live);
|
||||||
|
|
||||||
const prompt = ((event as Record<string, unknown>).prompt as string) || "";
|
const prompt = ((event as Record<string, unknown>).prompt as string) || "";
|
||||||
@@ -118,55 +116,23 @@ export function registerBeforeModelResolveHook(deps: BeforeModelResolveDeps): vo
|
|||||||
if (!turnCheck.allowed) {
|
if (!turnCheck.allowed) {
|
||||||
sessionAllowed.set(key, false);
|
sessionAllowed.set(key, false);
|
||||||
api.logger.info(
|
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 {
|
return {
|
||||||
providerOverride: live.noReplyProvider,
|
model: ctx.model,
|
||||||
modelOverride: live.noReplyModel,
|
provider: ctx.provider,
|
||||||
|
noReply: true,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
sessionAllowed.set(key, 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.decision.shouldUseNoReply) return;
|
||||||
if (rec.needsRestore) {
|
|
||||||
sessionDecision.delete(key);
|
|
||||||
return {
|
|
||||||
providerOverride: undefined,
|
|
||||||
modelOverride: undefined,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rec.needsRestore = true;
|
const out: Record<string, unknown> = { noReply: true };
|
||||||
sessionDecision.set(key, rec);
|
if (rec.decision.provider) out.provider = rec.decision.provider;
|
||||||
|
if (rec.decision.model) out.model = rec.decision.model;
|
||||||
if (live.enableDebugLogs) {
|
return out;
|
||||||
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<string, unknown>).sender_id ?? "")} ` +
|
|
||||||
`convSender=${String((derived.conv as Record<string, unknown>).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,
|
|
||||||
};
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ type BeforePromptBuildDeps = {
|
|||||||
policyState: { channelPolicies: Record<string, unknown> };
|
policyState: { channelPolicies: Record<string, unknown> };
|
||||||
DECISION_TTL_MS: number;
|
DECISION_TTL_MS: number;
|
||||||
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
|
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
|
||||||
getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig;
|
|
||||||
shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean;
|
shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean;
|
||||||
buildEndMarkerInstruction: (endSymbols: string[], isGroupChat: boolean, schedulingIdentifier: string, waitIdentifier: string) => string;
|
buildEndMarkerInstruction: (endSymbols: string[], isGroupChat: boolean, schedulingIdentifier: string, waitIdentifier: string) => string;
|
||||||
buildSchedulingIdentifierInstruction: (schedulingIdentifier: string) => string;
|
buildSchedulingIdentifierInstruction: (schedulingIdentifier: string) => string;
|
||||||
@@ -37,7 +36,6 @@ export function registerBeforePromptBuildHook(deps: BeforePromptBuildDeps): void
|
|||||||
policyState,
|
policyState,
|
||||||
DECISION_TTL_MS,
|
DECISION_TTL_MS,
|
||||||
ensurePolicyStateLoaded,
|
ensurePolicyStateLoaded,
|
||||||
getLivePluginConfig,
|
|
||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
buildEndMarkerInstruction,
|
buildEndMarkerInstruction,
|
||||||
buildSchedulingIdentifierInstruction,
|
buildSchedulingIdentifierInstruction,
|
||||||
@@ -48,7 +46,7 @@ export function registerBeforePromptBuildHook(deps: BeforePromptBuildDeps): void
|
|||||||
const key = ctx.sessionKey;
|
const key = ctx.sessionKey;
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
|
|
||||||
const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & DebugConfig;
|
const live = baseConfig as DirigentConfig & DebugConfig;
|
||||||
ensurePolicyStateLoaded(api, live);
|
ensurePolicyStateLoaded(api, live);
|
||||||
|
|
||||||
let rec = sessionDecision.get(key);
|
let rec = sessionDecision.get(key);
|
||||||
@@ -118,17 +116,13 @@ export function registerBeforePromptBuildHook(deps: BeforePromptBuildDeps): void
|
|||||||
let identity = "";
|
let identity = "";
|
||||||
if (isGroupChat && ctx.agentId) {
|
if (isGroupChat && ctx.agentId) {
|
||||||
const idStr = buildAgentIdentity(api, ctx.agentId);
|
const idStr = buildAgentIdentity(api, ctx.agentId);
|
||||||
if (idStr) identity = idStr + "\n\n";
|
if (idStr) {
|
||||||
}
|
identity = `\n\nYour agent identity: ${idStr}.`;
|
||||||
|
}
|
||||||
let schedulingInstruction = "";
|
|
||||||
if (isGroupChat) {
|
|
||||||
schedulingInstruction = buildSchedulingIdentifierInstruction(schedulingId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const schedulingInstruction = isGroupChat ? buildSchedulingIdentifierInstruction(schedulingId) : "";
|
||||||
|
(event as Record<string, unknown>).prompt = `${prompt}\n\n${instruction}${identity}${schedulingInstruction}`;
|
||||||
sessionInjected.add(key);
|
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 };
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ type DebugConfig = {
|
|||||||
type MessageReceivedDeps = {
|
type MessageReceivedDeps = {
|
||||||
api: OpenClawPluginApi;
|
api: OpenClawPluginApi;
|
||||||
baseConfig: DirigentConfig;
|
baseConfig: DirigentConfig;
|
||||||
getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig;
|
|
||||||
shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean;
|
shouldDebugLog: (config: DirigentConfig & DebugConfig, channelId?: string) => boolean;
|
||||||
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;
|
||||||
@@ -25,7 +24,6 @@ export function registerMessageReceivedHook(deps: MessageReceivedDeps): void {
|
|||||||
const {
|
const {
|
||||||
api,
|
api,
|
||||||
baseConfig,
|
baseConfig,
|
||||||
getLivePluginConfig,
|
|
||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
debugCtxSummary,
|
debugCtxSummary,
|
||||||
ensureTurnOrder,
|
ensureTurnOrder,
|
||||||
@@ -40,7 +38,7 @@ export function registerMessageReceivedHook(deps: MessageReceivedDeps): void {
|
|||||||
const c = (ctx || {}) as Record<string, unknown>;
|
const c = (ctx || {}) as Record<string, unknown>;
|
||||||
const e = (event || {}) as Record<string, unknown>;
|
const e = (event || {}) as Record<string, unknown>;
|
||||||
const preChannelId = extractDiscordChannelId(c, e);
|
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)) {
|
if (shouldDebugLog(livePre, preChannelId)) {
|
||||||
api.logger.info(`dirigent: debug message_received preflight ctx=${JSON.stringify(debugCtxSummary(c, e))}`);
|
api.logger.info(`dirigent: debug message_received preflight ctx=${JSON.stringify(debugCtxSummary(c, e))}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,6 @@ type MessageSentDeps = {
|
|||||||
sessionAccountId: Map<string, string>;
|
sessionAccountId: Map<string, string>;
|
||||||
sessionTurnHandled: Set<string>;
|
sessionTurnHandled: Set<string>;
|
||||||
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
|
ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void;
|
||||||
getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig;
|
|
||||||
resolveDiscordUserId: (api: OpenClawPluginApi, accountId: string) => string | undefined;
|
resolveDiscordUserId: (api: OpenClawPluginApi, accountId: string) => string | undefined;
|
||||||
sendModeratorMessage: (
|
sendModeratorMessage: (
|
||||||
botToken: string,
|
botToken: string,
|
||||||
@@ -35,7 +34,6 @@ export function registerMessageSentHook(deps: MessageSentDeps): void {
|
|||||||
sessionAccountId,
|
sessionAccountId,
|
||||||
sessionTurnHandled,
|
sessionTurnHandled,
|
||||||
ensurePolicyStateLoaded,
|
ensurePolicyStateLoaded,
|
||||||
getLivePluginConfig,
|
|
||||||
resolveDiscordUserId,
|
resolveDiscordUserId,
|
||||||
sendModeratorMessage,
|
sendModeratorMessage,
|
||||||
} = deps;
|
} = deps;
|
||||||
@@ -69,7 +67,7 @@ export function registerMessageSentHook(deps: MessageSentDeps): void {
|
|||||||
|
|
||||||
if (!channelId || !accountId) return;
|
if (!channelId || !accountId) return;
|
||||||
|
|
||||||
const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & DebugConfig;
|
const live = baseConfig as DirigentConfig & DebugConfig;
|
||||||
ensurePolicyStateLoaded(api, live);
|
ensurePolicyStateLoaded(api, live);
|
||||||
const policy = resolvePolicy(live, channelId, policyState.channelPolicies as Record<string, any>);
|
const policy = resolvePolicy(live, channelId, policyState.channelPolicies as Record<string, any>);
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { registerBeforeMessageWriteHook } from "./hooks/before-message-write.js"
|
|||||||
import { registerMessageSentHook } from "./hooks/message-sent.js";
|
import { registerMessageSentHook } from "./hooks/message-sent.js";
|
||||||
import { registerDirigentCommand } from "./commands/dirigent-command.js";
|
import { registerDirigentCommand } from "./commands/dirigent-command.js";
|
||||||
import { registerDirigentTools } from "./tools/register-tools.js";
|
import { registerDirigentTools } from "./tools/register-tools.js";
|
||||||
import { getLivePluginConfig } from "./core/live-config.js";
|
|
||||||
import { ensurePolicyStateLoaded, persistPolicies, policyState } from "./policy/store.js";
|
import { ensurePolicyStateLoaded, persistPolicies, policyState } from "./policy/store.js";
|
||||||
import { buildAgentIdentity, buildUserIdToAccountIdMap, resolveAccountId } from "./core/identity.js";
|
import { buildAgentIdentity, buildUserIdToAccountIdMap, resolveAccountId } from "./core/identity.js";
|
||||||
import { extractMentionedUserIds, getModeratorUserId } from "./core/mentions.js";
|
import { extractMentionedUserIds, getModeratorUserId } from "./core/mentions.js";
|
||||||
@@ -34,6 +33,24 @@ type DebugConfig = {
|
|||||||
debugLogChannelIds?: string[];
|
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 {
|
function buildEndMarkerInstruction(endSymbols: string[], isGroupChat: boolean, schedulingIdentifier: string, waitIdentifier: string): string {
|
||||||
const symbols = endSymbols.length > 0 ? endSymbols.join("") : "🔚";
|
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}.`;
|
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",
|
id: "dirigent",
|
||||||
name: "Dirigent",
|
name: "Dirigent",
|
||||||
register(api: OpenClawPluginApi) {
|
register(api: OpenClawPluginApi) {
|
||||||
// Merge pluginConfig with defaults (in case config is missing from openclaw.json)
|
const baseConfig = normalizePluginConfig(api);
|
||||||
const baseConfig = {
|
ensurePolicyStateLoaded(api, 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);
|
|
||||||
|
|
||||||
// Resolve plugin directory for locating sibling modules (no-reply-api/)
|
// Resolve plugin directory for locating sibling modules (no-reply-api/)
|
||||||
// Note: api.resolvePath(".") returns cwd, not script directory. Use import.meta.url instead.
|
// Note: api.resolvePath(".") returns cwd, not script directory. Use import.meta.url instead.
|
||||||
@@ -77,7 +81,7 @@ export default {
|
|||||||
api.on("gateway_start", () => {
|
api.on("gateway_start", () => {
|
||||||
api.logger.info(`dirigent: gateway_start event received`);
|
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
|
// Check no-reply-api server file exists
|
||||||
const serverPath = path.resolve(pluginDir, "no-reply-api", "server.mjs");
|
const serverPath = path.resolve(pluginDir, "no-reply-api", "server.mjs");
|
||||||
@@ -112,9 +116,8 @@ export default {
|
|||||||
// Register tools
|
// Register tools
|
||||||
registerDirigentTools({
|
registerDirigentTools({
|
||||||
api,
|
api,
|
||||||
baseConfig: baseConfig as DirigentConfig,
|
baseConfig,
|
||||||
pickDefined,
|
pickDefined,
|
||||||
getLivePluginConfig,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Turn management is handled internally by the plugin (not exposed as tools).
|
// Turn management is handled internally by the plugin (not exposed as tools).
|
||||||
@@ -122,8 +125,7 @@ export default {
|
|||||||
|
|
||||||
registerMessageReceivedHook({
|
registerMessageReceivedHook({
|
||||||
api,
|
api,
|
||||||
baseConfig: baseConfig as DirigentConfig,
|
baseConfig,
|
||||||
getLivePluginConfig,
|
|
||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
debugCtxSummary,
|
debugCtxSummary,
|
||||||
ensureTurnOrder,
|
ensureTurnOrder,
|
||||||
@@ -135,7 +137,7 @@ export default {
|
|||||||
|
|
||||||
registerBeforeModelResolveHook({
|
registerBeforeModelResolveHook({
|
||||||
api,
|
api,
|
||||||
baseConfig: baseConfig as DirigentConfig,
|
baseConfig,
|
||||||
sessionDecision,
|
sessionDecision,
|
||||||
sessionAllowed,
|
sessionAllowed,
|
||||||
sessionChannelId,
|
sessionChannelId,
|
||||||
@@ -143,7 +145,6 @@ export default {
|
|||||||
policyState,
|
policyState,
|
||||||
DECISION_TTL_MS,
|
DECISION_TTL_MS,
|
||||||
ensurePolicyStateLoaded,
|
ensurePolicyStateLoaded,
|
||||||
getLivePluginConfig,
|
|
||||||
resolveAccountId,
|
resolveAccountId,
|
||||||
pruneDecisionMap,
|
pruneDecisionMap,
|
||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
@@ -152,13 +153,12 @@ export default {
|
|||||||
|
|
||||||
registerBeforePromptBuildHook({
|
registerBeforePromptBuildHook({
|
||||||
api,
|
api,
|
||||||
baseConfig: baseConfig as DirigentConfig,
|
baseConfig,
|
||||||
sessionDecision,
|
sessionDecision,
|
||||||
sessionInjected,
|
sessionInjected,
|
||||||
policyState,
|
policyState,
|
||||||
DECISION_TTL_MS,
|
DECISION_TTL_MS,
|
||||||
ensurePolicyStateLoaded,
|
ensurePolicyStateLoaded,
|
||||||
getLivePluginConfig,
|
|
||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
buildEndMarkerInstruction,
|
buildEndMarkerInstruction,
|
||||||
buildSchedulingIdentifierInstruction,
|
buildSchedulingIdentifierInstruction,
|
||||||
@@ -168,24 +168,22 @@ export default {
|
|||||||
// Register slash commands for Discord
|
// Register slash commands for Discord
|
||||||
registerDirigentCommand({
|
registerDirigentCommand({
|
||||||
api,
|
api,
|
||||||
baseConfig: baseConfig as DirigentConfig,
|
baseConfig,
|
||||||
policyState,
|
policyState,
|
||||||
persistPolicies,
|
persistPolicies,
|
||||||
ensurePolicyStateLoaded,
|
ensurePolicyStateLoaded,
|
||||||
getLivePluginConfig,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Handle NO_REPLY detection before message write
|
// Handle NO_REPLY detection before message write
|
||||||
registerBeforeMessageWriteHook({
|
registerBeforeMessageWriteHook({
|
||||||
api,
|
api,
|
||||||
baseConfig: baseConfig as DirigentConfig,
|
baseConfig,
|
||||||
policyState,
|
policyState,
|
||||||
sessionAllowed,
|
sessionAllowed,
|
||||||
sessionChannelId,
|
sessionChannelId,
|
||||||
sessionAccountId,
|
sessionAccountId,
|
||||||
sessionTurnHandled,
|
sessionTurnHandled,
|
||||||
ensurePolicyStateLoaded,
|
ensurePolicyStateLoaded,
|
||||||
getLivePluginConfig,
|
|
||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
ensureTurnOrder,
|
ensureTurnOrder,
|
||||||
resolveDiscordUserId,
|
resolveDiscordUserId,
|
||||||
@@ -195,13 +193,12 @@ export default {
|
|||||||
// Turn advance: when an agent sends a message, check if it signals end of turn
|
// Turn advance: when an agent sends a message, check if it signals end of turn
|
||||||
registerMessageSentHook({
|
registerMessageSentHook({
|
||||||
api,
|
api,
|
||||||
baseConfig: baseConfig as DirigentConfig,
|
baseConfig,
|
||||||
policyState,
|
policyState,
|
||||||
sessionChannelId,
|
sessionChannelId,
|
||||||
sessionAccountId,
|
sessionAccountId,
|
||||||
sessionTurnHandled,
|
sessionTurnHandled,
|
||||||
ensurePolicyStateLoaded,
|
ensurePolicyStateLoaded,
|
||||||
getLivePluginConfig,
|
|
||||||
resolveDiscordUserId,
|
resolveDiscordUserId,
|
||||||
sendModeratorMessage,
|
sendModeratorMessage,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ type ToolDeps = {
|
|||||||
api: OpenClawPluginApi;
|
api: OpenClawPluginApi;
|
||||||
baseConfig: DirigentConfig;
|
baseConfig: DirigentConfig;
|
||||||
pickDefined: (obj: Record<string, unknown>) => Record<string, unknown>;
|
pickDefined: (obj: Record<string, unknown>) => Record<string, unknown>;
|
||||||
getLivePluginConfig: (api: OpenClawPluginApi, fallback: DirigentConfig) => DirigentConfig;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseAccountToken(api: OpenClawPluginApi, accountId?: string): { accountId: string; token: string } | null {
|
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 {
|
export function registerDirigentTools(deps: ToolDeps): void {
|
||||||
const { api, baseConfig, pickDefined, getLivePluginConfig } = deps;
|
const { api, baseConfig, pickDefined } = deps;
|
||||||
|
|
||||||
async function executeDiscordAction(action: DiscordControlAction, params: Record<string, unknown>) {
|
async function executeDiscordAction(action: DiscordControlAction, params: Record<string, unknown>) {
|
||||||
const live = getLivePluginConfig(api, baseConfig as DirigentConfig) as DirigentConfig & {
|
const live = baseConfig as DirigentConfig & {
|
||||||
enableDiscordControlTool?: boolean;
|
enableDiscordControlTool?: boolean;
|
||||||
discordControlAccountId?: string;
|
discordControlAccountId?: string;
|
||||||
};
|
};
|
||||||
@@ -128,14 +127,16 @@ export function registerDirigentTools(deps: ToolDeps): void {
|
|||||||
}
|
}
|
||||||
|
|
||||||
api.registerTool({
|
api.registerTool({
|
||||||
name: "discord_channel_create",
|
name: "dirigent_discord_control",
|
||||||
description: "Create a private Discord channel with specific user/role permissions.",
|
description: "Create/update Discord private channels using the configured Discord bot token",
|
||||||
parameters: {
|
inputSchema: {
|
||||||
type: "object",
|
type: "object",
|
||||||
additionalProperties: false,
|
additionalProperties: false,
|
||||||
properties: {
|
properties: {
|
||||||
|
action: { type: "string", enum: ["channel-private-create", "channel-private-update"] },
|
||||||
accountId: { type: "string" },
|
accountId: { type: "string" },
|
||||||
guildId: { type: "string" },
|
guildId: { type: "string" },
|
||||||
|
channelId: { type: "string" },
|
||||||
name: { type: "string" },
|
name: { type: "string" },
|
||||||
type: { type: "number" },
|
type: { type: "number" },
|
||||||
parentId: { type: "string" },
|
parentId: { type: "string" },
|
||||||
@@ -146,34 +147,14 @@ export function registerDirigentTools(deps: ToolDeps): void {
|
|||||||
allowedRoleIds: { type: "array", items: { type: "string" } },
|
allowedRoleIds: { type: "array", items: { type: "string" } },
|
||||||
allowMask: { type: "string" },
|
allowMask: { type: "string" },
|
||||||
denyEveryoneMask: { type: "string" },
|
denyEveryoneMask: { type: "string" },
|
||||||
},
|
|
||||||
required: [],
|
|
||||||
},
|
|
||||||
async execute(_id: string, params: Record<string, unknown>) {
|
|
||||||
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"] },
|
mode: { type: "string", enum: ["merge", "replace"] },
|
||||||
addUserIds: { type: "array", items: { type: "string" } },
|
addUserIds: { type: "array", items: { type: "string" } },
|
||||||
addRoleIds: { type: "array", items: { type: "string" } },
|
addRoleIds: { type: "array", items: { type: "string" } },
|
||||||
removeTargetIds: { type: "array", items: { type: "string" } },
|
removeTargetIds: { type: "array", items: { type: "string" } },
|
||||||
allowMask: { type: "string" },
|
|
||||||
denyMask: { type: "string" },
|
denyMask: { type: "string" },
|
||||||
},
|
},
|
||||||
required: [],
|
required: ["action"],
|
||||||
},
|
},
|
||||||
async execute(_id: string, params: Record<string, unknown>) {
|
handler: async (params) => executeDiscordAction(params.action as DiscordControlAction, params as Record<string, unknown>),
|
||||||
return executeDiscordAction("channel-private-update", params);
|
});
|
||||||
},
|
|
||||||
}, { optional: false });
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,6 +113,39 @@ function setJson(pathKey, value) {
|
|||||||
runOpenclaw(["config", "set", pathKey, JSON.stringify(value), "--json"]);
|
runOpenclaw(["config", "set", pathKey, JSON.stringify(value), "--json"]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function isPlainObject(value) {
|
||||||
|
return !!value && typeof value === "object" && !Array.isArray(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
function mergePreservingExisting(base, updates) {
|
||||||
|
if (!isPlainObject(updates)) return updates;
|
||||||
|
const out = isPlainObject(base) ? { ...base } : {};
|
||||||
|
for (const [key, nextValue] of Object.entries(updates)) {
|
||||||
|
const currentValue = out[key];
|
||||||
|
if (nextValue === undefined) continue;
|
||||||
|
if (isPlainObject(nextValue)) {
|
||||||
|
out[key] = mergePreservingExisting(currentValue, nextValue);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (nextValue === null) {
|
||||||
|
if (currentValue === undefined) out[key] = null;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (typeof nextValue === "string") {
|
||||||
|
if (nextValue === "" && currentValue !== undefined) continue;
|
||||||
|
out[key] = nextValue;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (Array.isArray(nextValue)) {
|
||||||
|
if (nextValue.length === 0 && Array.isArray(currentValue) && currentValue.length > 0) continue;
|
||||||
|
out[key] = nextValue;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
out[key] = nextValue;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
function unsetPath(pathKey) {
|
function unsetPath(pathKey) {
|
||||||
runOpenclaw(["config", "unset", pathKey], true);
|
runOpenclaw(["config", "unset", pathKey], true);
|
||||||
}
|
}
|
||||||
@@ -185,7 +218,8 @@ if (mode === "install") {
|
|||||||
plugins.load.paths = loadPaths;
|
plugins.load.paths = loadPaths;
|
||||||
|
|
||||||
plugins.entries = plugins.entries || {};
|
plugins.entries = plugins.entries || {};
|
||||||
plugins.entries.dirigent = {
|
const existingDirigentEntry = isPlainObject(plugins.entries.dirigent) ? plugins.entries.dirigent : {};
|
||||||
|
const desiredDirigentEntry = {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
config: {
|
config: {
|
||||||
enabled: true,
|
enabled: true,
|
||||||
@@ -201,6 +235,7 @@ if (mode === "install") {
|
|||||||
noReplyPort: NO_REPLY_PORT,
|
noReplyPort: NO_REPLY_PORT,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
plugins.entries.dirigent = mergePreservingExisting(existingDirigentEntry, desiredDirigentEntry);
|
||||||
setJson("plugins", plugins);
|
setJson("plugins", plugins);
|
||||||
|
|
||||||
step(5, 6, "configure no-reply provider");
|
step(5, 6, "configure no-reply provider");
|
||||||
|
|||||||
Reference in New Issue
Block a user