import type { OpenClawPluginApi } from "openclaw/plugin-sdk"; import { advanceTurn, getTurnDebugInfo, resetTurn } from "../turn-manager.js"; import type { DirigentConfig } from "../rules.js"; import { setChannelShuffling, getChannelShuffling } from "../core/channel-modes.js"; type CommandDeps = { api: OpenClawPluginApi; baseConfig: DirigentConfig; policyState: { filePath: string; channelPolicies: Record }; persistPolicies: (api: OpenClawPluginApi) => void; ensurePolicyStateLoaded: (api: OpenClawPluginApi, config: DirigentConfig) => void; }; export function registerDirigentCommand(deps: CommandDeps): void { const { api, baseConfig, policyState, persistPolicies, ensurePolicyStateLoaded } = deps; api.registerCommand({ name: "dirigent", description: "Dirigent runtime commands", acceptsArgs: true, handler: async (cmdCtx) => { const args = cmdCtx.args || ""; const parts = args.trim().split(/\s+/); const subCmd = parts[0] || "help"; if (subCmd === "help") { return { text: `Dirigent commands:\n` + `/dirigent status - Show current channel status\n` + `/dirigent turn-status - Show turn-based speaking status\n` + `/dirigent turn-advance - Manually advance turn\n` + `/dirigent turn-reset - Reset turn order\n` + `/dirigent turn-shuffling [on|off] - Enable/disable turn order shuffling\n` + `/dirigent_policy get \n` + `/dirigent_policy set \n` + `/dirigent_policy delete `, }; } if (subCmd === "status") { return { text: JSON.stringify({ policies: policyState.channelPolicies }, null, 2) }; } if (subCmd === "turn-status") { const channelId = cmdCtx.channelId; if (!channelId) return { text: "Cannot get channel ID", isError: true }; return { text: JSON.stringify(getTurnDebugInfo(channelId), null, 2) }; } if (subCmd === "turn-advance") { const channelId = cmdCtx.channelId; if (!channelId) return { text: "Cannot get channel ID", isError: true }; const next = advanceTurn(channelId); return { text: JSON.stringify({ ok: true, nextSpeaker: next }) }; } if (subCmd === "turn-reset") { const channelId = cmdCtx.channelId; if (!channelId) return { text: "Cannot get channel ID", isError: true }; resetTurn(channelId); return { text: JSON.stringify({ ok: true }) }; } if (subCmd === "turn-shuffling") { const channelId = cmdCtx.channelId; if (!channelId) return { text: "Cannot get channel ID", isError: true }; const arg = parts[1]?.toLowerCase(); if (arg === "on") { setChannelShuffling(channelId, true); return { text: JSON.stringify({ ok: true, channelId, shuffling: true }) }; } else if (arg === "off") { setChannelShuffling(channelId, false); return { text: JSON.stringify({ ok: true, channelId, shuffling: false }) }; } else if (!arg) { const isShuffling = getChannelShuffling(channelId); return { text: JSON.stringify({ ok: true, channelId, shuffling: isShuffling }) }; } else { return { text: "Invalid argument. Use: /dirigent turn-shuffling [on|off]", isError: true }; } } return { text: `Unknown subcommand: ${subCmd}`, isError: true }; }, }); api.registerCommand({ name: "dirigent_policy", description: "Dirigent channel policy CRUD", acceptsArgs: true, handler: async (cmdCtx) => { const live = baseConfig; ensurePolicyStateLoaded(api, live); const args = (cmdCtx.args || "").trim(); if (!args) { return { text: "Usage:\n" + "/dirigent_policy get \n" + "/dirigent_policy set \n" + "/dirigent_policy delete ", isError: true, }; } const [opRaw, channelIdRaw, ...rest] = args.split(/\s+/); const op = (opRaw || "").toLowerCase(); const channelId = (channelIdRaw || "").trim(); if (!channelId || !/^\d+$/.test(channelId)) { return { text: "channelId is required and must be numeric Discord channel id", isError: true }; } if (op === "get") { const policy = (policyState.channelPolicies as Record)[channelId]; return { text: JSON.stringify({ ok: true, channelId, policy: policy || null }, null, 2) }; } if (op === "delete") { delete (policyState.channelPolicies as Record)[channelId]; persistPolicies(api); return { text: JSON.stringify({ ok: true, channelId, deleted: true }) }; } if (op === "set") { const jsonText = rest.join(" ").trim(); if (!jsonText) { return { text: "set requires ", isError: true }; } let parsed: Record; try { parsed = JSON.parse(jsonText); } catch (e) { return { text: `invalid policy-json: ${String(e)}`, isError: true }; } const next: Record = {}; if (typeof parsed.listMode === "string") next.listMode = parsed.listMode; if (Array.isArray(parsed.humanList)) next.humanList = parsed.humanList.map(String); if (Array.isArray(parsed.agentList)) next.agentList = parsed.agentList.map(String); if (Array.isArray(parsed.endSymbols)) next.endSymbols = parsed.endSymbols.map(String); (policyState.channelPolicies as Record)[channelId] = next; persistPolicies(api); return { text: JSON.stringify({ ok: true, channelId, policy: next }, null, 2) }; } return { text: `unsupported op: ${op}. use get|set|delete`, isError: true }; }, }); }