refactor #22
@@ -243,9 +243,9 @@
|
|||||||
- [ ] 评估是否需要增加 shuffle 默认配置项
|
- [ ] 评估是否需要增加 shuffle 默认配置项
|
||||||
|
|
||||||
#### B2.2 `plugin/rules.ts` / config 类型
|
#### B2.2 `plugin/rules.ts` / config 类型
|
||||||
- [ ] 为 multi-message mode 相关配置补类型定义
|
- [x] 为 multi-message mode 相关配置补类型定义
|
||||||
- [ ] 为 shuffle mode 相关 channel state / config 补类型定义
|
- [x] 为 shuffle mode 相关 channel state / config 补类型定义
|
||||||
- [ ] 确保运行时读取配置逻辑可访问新增字段
|
- [x] 确保运行时读取配置逻辑可访问新增字段
|
||||||
|
|
||||||
### B3. `plugin/core/` 新增 channel mode / shuffle state 模块
|
### B3. `plugin/core/` 新增 channel mode / shuffle state 模块
|
||||||
- [x] 新增 channel mode state 模块(如 `plugin/core/channel-modes.ts`)
|
- [x] 新增 channel mode state 模块(如 `plugin/core/channel-modes.ts`)
|
||||||
@@ -305,14 +305,14 @@
|
|||||||
- [x] 命令帮助文本补充说明
|
- [x] 命令帮助文本补充说明
|
||||||
|
|
||||||
### B8. `plugin/index.ts`
|
### B8. `plugin/index.ts`
|
||||||
- [ ] 注入 channel mode / shuffle state 模块依赖
|
- [x] 注入 channel mode / shuffle state 模块依赖
|
||||||
- [ ] 将新状态能力传给相关 hooks / turn-manager
|
- [x] 将新状态能力传给相关 hooks / turn-manager
|
||||||
- [ ] 保持初始化关系清晰,避免 mode 逻辑散落
|
- [x] 保持初始化关系清晰,避免 mode 逻辑散落
|
||||||
|
|
||||||
### B9. moderator 消息模板
|
### B9. moderator 消息模板
|
||||||
- [ ] 定义 multi-message mode 下的 prompt marker 发送规则
|
- [x] 定义 multi-message mode 下的 prompt marker 发送规则
|
||||||
- [ ] 明确是否需要 start / end 的 moderator 确认消息
|
- [x] 明确是否需要 start / end 的 moderator 确认消息
|
||||||
- [ ] 定义退出 multi-message mode 后的 scheduling handoff 触发格式
|
- [x] 定义退出 multi-message mode 后的 scheduling handoff 触发格式
|
||||||
|
|
||||||
### B10. 测试
|
### B10. 测试
|
||||||
#### B10.1 Multi-Message Mode
|
#### B10.1 Multi-Message Mode
|
||||||
|
|||||||
@@ -30,6 +30,9 @@ Optional:
|
|||||||
- `channelPoliciesFile` (per-channel overrides in a standalone JSON file)
|
- `channelPoliciesFile` (per-channel overrides in a standalone JSON file)
|
||||||
- `schedulingIdentifier` (default `➡️`) — moderator handoff identifier
|
- `schedulingIdentifier` (default `➡️`) — moderator handoff identifier
|
||||||
- `enableDirigentPolicyTool` (default true)
|
- `enableDirigentPolicyTool` (default true)
|
||||||
|
- `multiMessageStartMarker` (default `↗️`)
|
||||||
|
- `multiMessageEndMarker` (default `↙️`)
|
||||||
|
- `multiMessagePromptMarker` (default `⤵️`)
|
||||||
|
|
||||||
Unified optional tool:
|
Unified optional tool:
|
||||||
- `dirigent_tools`
|
- `dirigent_tools`
|
||||||
@@ -59,6 +62,15 @@ When the current speaker NO_REPLYs, the moderator bot sends: `<@NEXT_USER_ID>➡
|
|||||||
|
|
||||||
This is a non-semantic scheduling message. The scheduling identifier (`➡️` by default) carries no meaning — it simply signals the next agent to check chat history and decide whether to speak.
|
This is a non-semantic scheduling message. The scheduling identifier (`➡️` by default) carries no meaning — it simply signals the next agent to check chat history and decide whether to speak.
|
||||||
|
|
||||||
|
## Multi-message mode / shuffle mode
|
||||||
|
|
||||||
|
- Human sends the configured start marker (default `↗️`) → channel enters multi-message mode.
|
||||||
|
- While active, agents are forced to no-reply and the moderator sends only the configured prompt marker (default `⤵️`) after each additional human message.
|
||||||
|
- Human sends the configured end marker (default `↙️`) → channel exits multi-message mode and normal scheduling resumes.
|
||||||
|
- No separate moderator "entered/exited mode" confirmation message is sent; the markers themselves are the protocol.
|
||||||
|
- The first moderator message after exit uses the normal scheduling handoff format: `<@NEXT_USER_ID>➡️`.
|
||||||
|
- `/dirigent turn-shuffling`, `/dirigent turn-shuffling on`, and `/dirigent turn-shuffling off` control per-channel reshuffling between completed rounds.
|
||||||
|
|
||||||
## Slash command (Discord)
|
## Slash command (Discord)
|
||||||
|
|
||||||
```
|
```
|
||||||
@@ -66,6 +78,9 @@ This is a non-semantic scheduling message. The scheduling identifier (`➡️` b
|
|||||||
/dirigent turn-status
|
/dirigent turn-status
|
||||||
/dirigent turn-advance
|
/dirigent turn-advance
|
||||||
/dirigent turn-reset
|
/dirigent turn-reset
|
||||||
|
/dirigent turn-shuffling
|
||||||
|
/dirigent turn-shuffling on
|
||||||
|
/dirigent turn-shuffling off
|
||||||
```
|
```
|
||||||
|
|
||||||
Debug logging:
|
Debug logging:
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
export type ChannelMode = "normal" | "multi-message";
|
import type { ChannelRuntimeMode, ChannelRuntimeState } from "../rules.js";
|
||||||
|
|
||||||
export type ChannelModesState = {
|
export type ChannelMode = ChannelRuntimeMode;
|
||||||
mode: ChannelMode;
|
export type ChannelModesState = ChannelRuntimeState;
|
||||||
shuffling: boolean;
|
|
||||||
lastShuffledAt?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
const channelStates = new Map<string, ChannelModesState>();
|
const channelStates = new Map<string, ChannelModesState>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
||||||
import { resolvePolicy, type DirigentConfig } from "../rules.js";
|
import { resolvePolicy, type DirigentConfig } from "../rules.js";
|
||||||
import { getTurnDebugInfo, onSpeakerDone, setWaitingForHuman } from "../turn-manager.js";
|
import { getTurnDebugInfo, onSpeakerDone, setWaitingForHuman } from "../turn-manager.js";
|
||||||
import { isMultiMessageMode } from "../core/channel-modes.js";
|
|
||||||
|
|
||||||
type DebugConfig = {
|
type DebugConfig = {
|
||||||
enableDebugLogs?: boolean;
|
enableDebugLogs?: boolean;
|
||||||
@@ -20,6 +19,7 @@ type BeforeMessageWriteDeps = {
|
|||||||
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;
|
||||||
|
isMultiMessageMode: (channelId: string) => boolean;
|
||||||
sendModeratorMessage: (
|
sendModeratorMessage: (
|
||||||
botToken: string,
|
botToken: string,
|
||||||
channelId: string,
|
channelId: string,
|
||||||
@@ -45,6 +45,7 @@ export function registerBeforeMessageWriteHook(deps: BeforeMessageWriteDeps): vo
|
|||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
ensureTurnOrder,
|
ensureTurnOrder,
|
||||||
resolveDiscordUserId,
|
resolveDiscordUserId,
|
||||||
|
isMultiMessageMode,
|
||||||
sendModeratorMessage,
|
sendModeratorMessage,
|
||||||
discussionService,
|
discussionService,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|||||||
import { evaluateDecision, type Decision, type DirigentConfig } from "../rules.js";
|
import { evaluateDecision, type Decision, type DirigentConfig } from "../rules.js";
|
||||||
import { checkTurn } from "../turn-manager.js";
|
import { checkTurn } from "../turn-manager.js";
|
||||||
import { deriveDecisionInputFromPrompt } from "../decision-input.js";
|
import { deriveDecisionInputFromPrompt } from "../decision-input.js";
|
||||||
import { isMultiMessageMode } from "../core/channel-modes.js";
|
|
||||||
|
|
||||||
type DebugConfig = {
|
type DebugConfig = {
|
||||||
enableDebugLogs?: boolean;
|
enableDebugLogs?: boolean;
|
||||||
@@ -30,6 +29,7 @@ type BeforeModelResolveDeps = {
|
|||||||
pruneDecisionMap: () => void;
|
pruneDecisionMap: () => void;
|
||||||
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;
|
||||||
|
isMultiMessageMode: (channelId: string) => boolean;
|
||||||
discussionService?: {
|
discussionService?: {
|
||||||
isClosedDiscussion: (channelId: string) => boolean;
|
isClosedDiscussion: (channelId: string) => boolean;
|
||||||
};
|
};
|
||||||
@@ -51,6 +51,7 @@ export function registerBeforeModelResolveHook(deps: BeforeModelResolveDeps): vo
|
|||||||
pruneDecisionMap,
|
pruneDecisionMap,
|
||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
ensureTurnOrder,
|
ensureTurnOrder,
|
||||||
|
isMultiMessageMode,
|
||||||
discussionService,
|
discussionService,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|||||||
import { onNewMessage, setMentionOverride, getTurnDebugInfo } from "../turn-manager.js";
|
import { onNewMessage, setMentionOverride, getTurnDebugInfo } from "../turn-manager.js";
|
||||||
import { extractDiscordChannelId } from "../channel-resolver.js";
|
import { extractDiscordChannelId } from "../channel-resolver.js";
|
||||||
import type { DirigentConfig } from "../rules.js";
|
import type { DirigentConfig } from "../rules.js";
|
||||||
import { enterMultiMessageMode, exitMultiMessageMode, isMultiMessageMode } from "../core/channel-modes.js";
|
|
||||||
|
|
||||||
type DebugConfig = {
|
type DebugConfig = {
|
||||||
enableDebugLogs?: boolean;
|
enableDebugLogs?: boolean;
|
||||||
@@ -19,6 +18,8 @@ type MessageReceivedDeps = {
|
|||||||
recordChannelAccount: (api: OpenClawPluginApi, channelId: string, accountId: string) => boolean;
|
recordChannelAccount: (api: OpenClawPluginApi, channelId: string, accountId: string) => boolean;
|
||||||
extractMentionedUserIds: (content: string) => string[];
|
extractMentionedUserIds: (content: string) => string[];
|
||||||
buildUserIdToAccountIdMap: (api: OpenClawPluginApi) => Map<string, string>;
|
buildUserIdToAccountIdMap: (api: OpenClawPluginApi) => Map<string, string>;
|
||||||
|
enterMultiMessageMode: (channelId: string) => void;
|
||||||
|
exitMultiMessageMode: (channelId: string) => void;
|
||||||
discussionService?: {
|
discussionService?: {
|
||||||
maybeReplyClosedChannel: (channelId: string, senderId?: string) => Promise<boolean>;
|
maybeReplyClosedChannel: (channelId: string, senderId?: string) => Promise<boolean>;
|
||||||
};
|
};
|
||||||
@@ -35,6 +36,8 @@ export function registerMessageReceivedHook(deps: MessageReceivedDeps): void {
|
|||||||
recordChannelAccount,
|
recordChannelAccount,
|
||||||
extractMentionedUserIds,
|
extractMentionedUserIds,
|
||||||
buildUserIdToAccountIdMap,
|
buildUserIdToAccountIdMap,
|
||||||
|
enterMultiMessageMode,
|
||||||
|
exitMultiMessageMode,
|
||||||
discussionService,
|
discussionService,
|
||||||
} = deps;
|
} = deps;
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { debugCtxSummary, pickDefined, shouldDebugLog } from "./core/utils.js";
|
|||||||
import { resolveDiscordUserId, sendModeratorMessage } from "./core/moderator-discord.js";
|
import { resolveDiscordUserId, sendModeratorMessage } from "./core/moderator-discord.js";
|
||||||
import { startNoReplyApi, stopNoReplyApi } from "./core/no-reply-process.js";
|
import { startNoReplyApi, stopNoReplyApi } from "./core/no-reply-process.js";
|
||||||
import { createDiscussionService } from "./core/discussion-service.js";
|
import { createDiscussionService } from "./core/discussion-service.js";
|
||||||
|
import { enterMultiMessageMode, exitMultiMessageMode, isMultiMessageMode } from "./core/channel-modes.js";
|
||||||
import {
|
import {
|
||||||
DECISION_TTL_MS,
|
DECISION_TTL_MS,
|
||||||
forceNoReplySessions,
|
forceNoReplySessions,
|
||||||
@@ -49,6 +50,9 @@ function normalizePluginConfig(api: OpenClawPluginApi): NormalizedDirigentConfig
|
|||||||
noReplyPort: 8787,
|
noReplyPort: 8787,
|
||||||
schedulingIdentifier: "➡️",
|
schedulingIdentifier: "➡️",
|
||||||
waitIdentifier: "👤",
|
waitIdentifier: "👤",
|
||||||
|
multiMessageStartMarker: "↗️",
|
||||||
|
multiMessageEndMarker: "↙️",
|
||||||
|
multiMessagePromptMarker: "⤵️",
|
||||||
...(api.pluginConfig || {}),
|
...(api.pluginConfig || {}),
|
||||||
} as NormalizedDirigentConfig;
|
} as NormalizedDirigentConfig;
|
||||||
}
|
}
|
||||||
@@ -146,6 +150,8 @@ export default {
|
|||||||
recordChannelAccount,
|
recordChannelAccount,
|
||||||
extractMentionedUserIds,
|
extractMentionedUserIds,
|
||||||
buildUserIdToAccountIdMap,
|
buildUserIdToAccountIdMap,
|
||||||
|
enterMultiMessageMode,
|
||||||
|
exitMultiMessageMode,
|
||||||
discussionService,
|
discussionService,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -164,6 +170,7 @@ export default {
|
|||||||
pruneDecisionMap,
|
pruneDecisionMap,
|
||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
ensureTurnOrder,
|
ensureTurnOrder,
|
||||||
|
isMultiMessageMode,
|
||||||
discussionService,
|
discussionService,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -203,6 +210,7 @@ export default {
|
|||||||
shouldDebugLog,
|
shouldDebugLog,
|
||||||
ensureTurnOrder,
|
ensureTurnOrder,
|
||||||
resolveDiscordUserId,
|
resolveDiscordUserId,
|
||||||
|
isMultiMessageMode,
|
||||||
sendModeratorMessage,
|
sendModeratorMessage,
|
||||||
discussionService,
|
discussionService,
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ export type DirigentConfig = {
|
|||||||
schedulingIdentifier?: string;
|
schedulingIdentifier?: string;
|
||||||
/** Wait identifier: agent ends with this when waiting for a human reply (default: 👤) */
|
/** Wait identifier: agent ends with this when waiting for a human reply (default: 👤) */
|
||||||
waitIdentifier?: string;
|
waitIdentifier?: string;
|
||||||
|
/** Human-visible marker that enters multi-message mode for a channel (default: ↗️) */
|
||||||
|
multiMessageStartMarker?: string;
|
||||||
|
/** Human-visible marker that exits multi-message mode for a channel (default: ↙️) */
|
||||||
|
multiMessageEndMarker?: string;
|
||||||
|
/** Moderator marker sent after each human message while multi-message mode is active (default: ⤵️) */
|
||||||
|
multiMessagePromptMarker?: string;
|
||||||
noReplyProvider: string;
|
noReplyProvider: string;
|
||||||
noReplyModel: string;
|
noReplyModel: string;
|
||||||
noReplyPort?: number;
|
noReplyPort?: number;
|
||||||
@@ -19,6 +25,14 @@ export type DirigentConfig = {
|
|||||||
moderatorBotToken?: string;
|
moderatorBotToken?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ChannelRuntimeMode = "normal" | "multi-message";
|
||||||
|
|
||||||
|
export type ChannelRuntimeState = {
|
||||||
|
mode: ChannelRuntimeMode;
|
||||||
|
shuffling: boolean;
|
||||||
|
lastShuffledAt?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ChannelPolicy = {
|
export type ChannelPolicy = {
|
||||||
listMode?: "human-list" | "agent-list";
|
listMode?: "human-list" | "agent-list";
|
||||||
humanList?: string[];
|
humanList?: string[];
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
* - If sender IS in turn order → current = next after sender
|
* - If sender IS in turn order → current = next after sender
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { isMultiMessageMode, exitMultiMessageMode } from "./core/channel-modes.js";
|
import { getChannelShuffling, isMultiMessageMode, markLastShuffled } from "./core/channel-modes.js";
|
||||||
|
|
||||||
export type ChannelTurnState = {
|
export type ChannelTurnState = {
|
||||||
/** Ordered accountIds for this channel (auto-populated, shuffled) */
|
/** Ordered accountIds for this channel (auto-populated, shuffled) */
|
||||||
@@ -379,6 +379,7 @@ export function onSpeakerDone(channelId: string, accountId: string, wasNoReply:
|
|||||||
const newOrder = reshuffleTurnOrder(channelId, state.turnOrder, prevSpeaker);
|
const newOrder = reshuffleTurnOrder(channelId, state.turnOrder, prevSpeaker);
|
||||||
if (newOrder !== state.turnOrder) {
|
if (newOrder !== state.turnOrder) {
|
||||||
state.turnOrder = newOrder;
|
state.turnOrder = newOrder;
|
||||||
|
markLastShuffled(channelId);
|
||||||
console.log(`[dirigent][turn-debug] reshuffled turn order for channel=${channelId} newOrder=${JSON.stringify(newOrder)}`);
|
console.log(`[dirigent][turn-debug] reshuffled turn order for channel=${channelId} newOrder=${JSON.stringify(newOrder)}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user