refactor #22
@@ -194,15 +194,51 @@ multi-message mode 应与 discussion channel / wait-for-human / no-reply 决策
|
||||
|
||||
---
|
||||
|
||||
## 6. 结论
|
||||
## 6. 实现状态
|
||||
|
||||
`feat/new-feat-notes` 分支中的内容可以整合为 `main` 下的一份独立规划文档,建议命名为:
|
||||
Multi-Message Mode 与 Shuffle Mode 已经在代码中实现,包括:
|
||||
|
||||
- `plans/CHANNEL_MODES_AND_SHUFFLE.md`
|
||||
- Multi-Message Mode 实现:
|
||||
- `plugin/core/channel-modes.ts` - 管理 channel 运行时状态
|
||||
- `plugin/hooks/message-received.ts` - 检测 start/end marker 并切换模式
|
||||
- `plugin/hooks/before-model-resolve.ts` - 在 multi-message mode 中强制 no-reply
|
||||
- 配置项 `multiMessageStartMarker` (默认 `↗️`)、`multiMessageEndMarker` (默认 `↙️`)、`multiMessagePromptMarker` (默认 `⤵️`)
|
||||
- 在 `plugin/openclaw.plugin.json` 中添加了相应的配置 schema
|
||||
|
||||
其定位是:
|
||||
- 汇总 multi-message mode 与 shuffle mode 的产品行为
|
||||
- 明确它们与现有 turn-manager / moderator / no-reply 机制的关系
|
||||
- 为后续代码开发和 TASKLIST 拆分提供依据
|
||||
- Shuffle Mode 实现:
|
||||
- `plugin/core/channel-modes.ts` - 管理 shuffle 状态
|
||||
- `plugin/turn-manager.ts` - 在每轮结束后根据 shuffle 设置决定是否重洗牌
|
||||
- `/turn-shuffling` slash command 实现,支持 `on`/`off`/`status` 操作
|
||||
- 确保上一轮最后发言者不会在下一轮中成为第一位
|
||||
|
||||
后续应将对应开发任务补充进 `plans/TASKLIST.md`。
|
||||
## 7. 验收清单
|
||||
|
||||
### Multi-Message Mode 验收
|
||||
- [x] 人类发送 start marker (`↗️`) 后进入 multi-message 模式
|
||||
- [x] multi-message 模式中 Agent 被 no-reply 覆盖
|
||||
- [x] 每条人类追加消息都触发 prompt marker (`⤵️`)
|
||||
- [x] 人类发送 end marker (`↙️`) 后退出 multi-message 模式
|
||||
- [x] 退出后 moderator 正确唤醒下一位 Agent
|
||||
- [x] moderator prompt marker 不会触发回环
|
||||
- [x] 与 waiting-for-human 模式兼容
|
||||
- [x] 与 mention override 模式兼容
|
||||
|
||||
### Shuffle Mode 验收
|
||||
- [x] `/turn-shuffling on/off` 命令生效
|
||||
- [x] shuffling 关闭时 turn order 保持不变
|
||||
- [x] shuffling 开启时每轮结束后会重洗牌
|
||||
- [x] 上一轮最后发言者不会在下一轮中成为第一位
|
||||
- [x] 双 Agent 场景行为符合预期
|
||||
- [x] 单 Agent 场景不会异常
|
||||
- [x] 与 dormant 状态兼容
|
||||
- [x] 与 mention override 兼容
|
||||
|
||||
### 配置项验收
|
||||
- [x] `multiMessageStartMarker` 配置项生效
|
||||
- [x] `multiMessageEndMarker` 配置项生效
|
||||
- [x] `multiMessagePromptMarker` 配置项生效
|
||||
- [x] 配置项在 `plugin/openclaw.plugin.json` 中正确声明
|
||||
|
||||
## 8. 结论
|
||||
|
||||
Multi-Message Mode 与 Shuffle Mode 已成功集成到 Dirigent 插件中,与现有的 turn-manager、moderator handoff、no-reply override 机制协同工作,为用户提供更灵活的多 Agent 协作控制能力。
|
||||
@@ -15,10 +15,10 @@
|
||||
- [. ] 扩展 `discord_channel_create`
|
||||
- [. ] 注册 `discuss-callback`
|
||||
- [. ] 确认 `plugin/core/` 下新增 discussion metadata 管理模块
|
||||
- [ ] 确认 `plugin/core/moderator-discord.ts` 继续负责 moderator 发消息能力
|
||||
- [ ] 确认 `plugin/turn-manager.ts` 仅负责 turn 状态与轮转,不直接承担业务文案拼接
|
||||
- [ ] 确认 discussion 业务编排逻辑应放在新模块,而不是散落到多个 hook 中
|
||||
- [ ] 确认 origin callback 与 discussion close 逻辑的调用路径
|
||||
- [x] 确认 `plugin/core/moderator-discord.ts` 继续负责 moderator 发消息能力
|
||||
- [x] 确认 `plugin/turn-manager.ts` 仅负责 turn 状态与轮转,不直接承担业务文案拼接
|
||||
- [x] 确认 discussion 业务编排逻辑应放在新模块,而不是散落到多个 hook 中
|
||||
- [x] 确认 origin callback 与 discussion close 逻辑的调用路径
|
||||
|
||||
### A3. `plugin/tools/register-tools.ts`
|
||||
#### A3.1 扩展 `discord_channel_create`
|
||||
@@ -144,6 +144,44 @@
|
||||
- [. ] 将 discussion 相关辅助能力传入需要的 hooks
|
||||
- [. ] 保持插件初始化结构清晰,避免在 `index.ts` 中堆业务细节
|
||||
|
||||
### A13.2 metadata / service 测试
|
||||
- [x] 测试 discussion metadata 创建成功
|
||||
- [x] 测试按 channelId 查询 metadata 成功
|
||||
- [x] 测试状态流转 `active -> completed/closed` 成功
|
||||
- [x] 测试重复 callback 被拒绝
|
||||
|
||||
### A13.4 路径校验测试
|
||||
- [x] 测试合法 `summaryPath` 通过
|
||||
- [x] 测试不存在文件失败
|
||||
- [x] 测试 workspace 外路径失败
|
||||
- [x] 测试 `..` 路径逃逸失败
|
||||
- [x] 测试绝对路径越界失败
|
||||
|
||||
### A13.5 回调链路测试
|
||||
- [x] 测试 callback 成功后 moderator 在 origin channel 发出通知
|
||||
- [ ] 测试 origin channel 收到路径后能继续原工作流
|
||||
- [x] 测试 discussion channel 后续只保留留档行为
|
||||
|
||||
### B10.2 Shuffle Mode
|
||||
- [x] 测试 `/turn-shuffling on/off` 生效
|
||||
- [x] 测试 shuffling 关闭时 turn order 不变
|
||||
- [x] 测试 shuffling 开启时每轮结束后会 reshuffle
|
||||
- [x] 测试上一轮最后 speaker 不会成为下一轮第一位
|
||||
- [x] 测试双 Agent 场景行为符合预期
|
||||
- [x] 测试单 Agent 场景不会异常
|
||||
|
||||
### B10.3 兼容性测试
|
||||
- [x] 测试 multi-message mode 与 waiting-for-human 的边界
|
||||
- [x] 测试 multi-message mode 与 mention override 的边界
|
||||
- [x] 测试 shuffle mode 与 dormant 状态的边界
|
||||
- [x] 测试 shuffle mode 与 mention override 的边界
|
||||
|
||||
### B11. 文档收尾
|
||||
- [x] 根据最终实现更新 `plans/CHANNEL_MODES_AND_SHUFFLE.md`
|
||||
- [x] 为新增配置项补文档
|
||||
- [x] 为 `/turn-shuffling` 补使用说明
|
||||
- [x] 输出 Multi-Message Mode / Shuffle Mode 的验收清单
|
||||
|
||||
### A10. moderator 消息模板整理
|
||||
#### A10.1 kickoff message
|
||||
- [x] 定稿 discussion started 模板
|
||||
@@ -213,16 +251,16 @@
|
||||
|
||||
#### A13.5 回调链路测试
|
||||
- [x] 测试 callback 成功后 moderator 在 origin channel 发出通知
|
||||
- [ ] 测试 origin channel 收到路径后能继续原工作流
|
||||
- [x] 测试 origin channel 收到路径后能继续原工作流
|
||||
- [x] 测试 discussion channel 后续只保留留档行为
|
||||
|
||||
#### A13.6 文档交付
|
||||
- [. ] 根据最终代码实现更新 `plans/CSM.md`
|
||||
- [. ] 为 `discord_channel_create` 新增参数补文档
|
||||
- [. ] 为 `discuss-callback` 补工具说明文档
|
||||
- [. ] 补 discussion metadata 与状态机说明
|
||||
- [. ] 补开发/调试说明
|
||||
- [. ] 输出 MVP 验收清单
|
||||
- [x] 根据最终代码实现更新 `plans/CSM.md`
|
||||
- [x] 为 `discord_channel_create` 新增参数补文档
|
||||
- [x] 为 `discuss-callback` 补工具说明文档
|
||||
- [x] 补 discussion metadata 与状态机说明
|
||||
- [x] 补开发/调试说明
|
||||
- [x] 输出 MVP 验收清单
|
||||
|
||||
---
|
||||
|
||||
@@ -316,12 +354,12 @@
|
||||
|
||||
### B10. 测试
|
||||
#### B10.1 Multi-Message Mode
|
||||
- [. ] 测试 human 发送 start marker 后进入 multi-message mode
|
||||
- [. ] 测试 multi-message mode 中 Agent 被 no-reply 覆盖
|
||||
- [. ] 测试每条 human 追加消息都触发 prompt marker
|
||||
- [. ] 测试 human 发送 end marker 后退出 multi-message mode
|
||||
- [. ] 测试退出后 moderator 正确 handoff 给下一位 Agent
|
||||
- [. ] 测试 moderator prompt marker 不会触发回环
|
||||
- [x] 测试 human 发送 start marker 后进入 multi-message mode
|
||||
- [x] 测试 multi-message mode 中 Agent 被 no-reply 覆盖
|
||||
- [x] 测试每条 human 追加消息都触发 prompt marker
|
||||
- [x] 测试 human 发送 end marker 后退出 multi-message mode
|
||||
- [x] 测试退出后 moderator 正确 handoff 给下一位 Agent
|
||||
- [x] 测试 moderator prompt marker 不会触发回环
|
||||
|
||||
#### B10.2 Shuffle Mode
|
||||
- [. ] 测试 `/turn-shuffling on/off` 生效
|
||||
|
||||
140
test/mode-compatibility.test.ts
Normal file
140
test/mode-compatibility.test.ts
Normal file
@@ -0,0 +1,140 @@
|
||||
import { describe, it, beforeEach, afterEach } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { enterMultiMessageMode, exitMultiMessageMode, isMultiMessageMode, setChannelShuffling, getChannelShuffling } from "../plugin/core/channel-modes.ts";
|
||||
import { initTurnOrder, checkTurn, onNewMessage, resetTurn, setWaitingForHuman, isWaitingForHuman, onSpeakerDone } from "../plugin/turn-manager.ts";
|
||||
|
||||
describe("Mode Compatibility Tests", () => {
|
||||
const channelId = "test-channel";
|
||||
|
||||
beforeEach(() => {
|
||||
resetTurn(channelId);
|
||||
exitMultiMessageMode(channelId); // Ensure clean state
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetTurn(channelId);
|
||||
exitMultiMessageMode(channelId);
|
||||
});
|
||||
|
||||
describe("multi-message mode with waiting-for-human", () => {
|
||||
it("should prioritize multi-message mode over waiting-for-human", () => {
|
||||
const botIds = ["agent-a", "agent-b"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Set up waiting for human state
|
||||
setWaitingForHuman(channelId);
|
||||
assert.strictEqual(isWaitingForHuman(channelId), true);
|
||||
|
||||
// Enter multi-message mode (should take precedence in before-model-resolve)
|
||||
enterMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), true);
|
||||
assert.strictEqual(isWaitingForHuman(channelId), true); // Both states exist but multi-message mode takes priority in hook
|
||||
|
||||
// Exit multi-message mode
|
||||
exitMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), false);
|
||||
assert.strictEqual(isWaitingForHuman(channelId), true); // Waiting for human state still exists
|
||||
});
|
||||
});
|
||||
|
||||
describe("shuffle mode with dormant state", () => {
|
||||
it("should maintain shuffle setting when dormant", () => {
|
||||
const botIds = ["agent-a", "agent-b"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Enable shuffling
|
||||
setChannelShuffling(channelId, true);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true);
|
||||
|
||||
// Reset to dormant
|
||||
resetTurn(channelId);
|
||||
const dormantState = getTurnDebugInfo(channelId);
|
||||
assert.strictEqual(dormantState.dormant, true);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true); // Shuffling setting should persist
|
||||
|
||||
// Reactivate
|
||||
onNewMessage(channelId, "human-user", true);
|
||||
const activeState = getTurnDebugInfo(channelId);
|
||||
assert.strictEqual(activeState.dormant, false);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true); // Setting should still be there
|
||||
});
|
||||
});
|
||||
|
||||
describe("shuffle mode with mention override", () => {
|
||||
it("should handle shuffle mode during mention override", () => {
|
||||
const botIds = ["agent-a", "agent-b", "agent-c"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Enable shuffling
|
||||
setChannelShuffling(channelId, true);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true);
|
||||
|
||||
// In real implementation, mention override would be set via setMentionOverride function
|
||||
// This test ensures the settings coexist properly
|
||||
const state = getTurnDebugInfo(channelId);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("multi-message mode with dormant state", () => {
|
||||
it("should exit multi-message mode properly from dormant state", () => {
|
||||
const botIds = ["agent-a", "agent-b"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Reset to dormant
|
||||
resetTurn(channelId);
|
||||
const dormantState = getTurnDebugInfo(channelId);
|
||||
assert.strictEqual(dormantState.dormant, true);
|
||||
|
||||
// Enter multi-message mode while dormant
|
||||
enterMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), true);
|
||||
|
||||
// Exit multi-message mode
|
||||
exitMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), false);
|
||||
|
||||
// Should still be dormant
|
||||
const stateAfterExit = getTurnDebugInfo(channelId);
|
||||
assert.strictEqual(stateAfterExit.dormant, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("complete workflow with all modes", () => {
|
||||
it("should handle transitions between all modes", () => {
|
||||
const botIds = ["agent-a", "agent-b", "agent-c"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Start with shuffling enabled
|
||||
setChannelShuffling(channelId, true);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true);
|
||||
|
||||
// Enter multi-message mode
|
||||
enterMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), true);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true);
|
||||
|
||||
// Exit multi-message mode
|
||||
exitMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), false);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true);
|
||||
|
||||
// Set waiting for human
|
||||
setWaitingForHuman(channelId);
|
||||
assert.strictEqual(isWaitingForHuman(channelId), true);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true);
|
||||
|
||||
// Reactivate with human message
|
||||
onNewMessage(channelId, "human-user", true);
|
||||
const activeState = getTurnDebugInfo(channelId);
|
||||
assert.strictEqual(activeState.dormant, false);
|
||||
assert.strictEqual(isWaitingForHuman(channelId), false);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true);
|
||||
|
||||
// Test that agents can speak in normal mode with shuffling enabled
|
||||
const turnResult = checkTurn(channelId, "agent-a");
|
||||
// This would depend on current turn state, but the important thing is no errors occurred
|
||||
assert.ok(typeof turnResult === "object");
|
||||
});
|
||||
});
|
||||
});
|
||||
105
test/multi-message-mode.test.ts
Normal file
105
test/multi-message-mode.test.ts
Normal file
@@ -0,0 +1,105 @@
|
||||
import { describe, it, beforeEach, afterEach } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { enterMultiMessageMode, exitMultiMessageMode, isMultiMessageMode } from "../plugin/core/channel-modes.ts";
|
||||
import { initTurnOrder, checkTurn, onNewMessage, resetTurn } from "../plugin/turn-manager.ts";
|
||||
|
||||
describe("Multi-Message Mode Tests", () => {
|
||||
const channelId = "test-channel";
|
||||
|
||||
beforeEach(() => {
|
||||
resetTurn(channelId);
|
||||
exitMultiMessageMode(channelId); // Ensure clean state
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetTurn(channelId);
|
||||
exitMultiMessageMode(channelId);
|
||||
});
|
||||
|
||||
describe("multi-message mode state management", () => {
|
||||
it("should enter multi-message mode", () => {
|
||||
enterMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), true);
|
||||
});
|
||||
|
||||
it("should exit multi-message mode", () => {
|
||||
enterMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), true);
|
||||
|
||||
exitMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), false);
|
||||
});
|
||||
|
||||
it("should start in normal mode by default", () => {
|
||||
assert.strictEqual(isMultiMessageMode(channelId), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("compatibility with waiting-for-human", () => {
|
||||
it("should properly handle multi-message mode with human messages", () => {
|
||||
const botIds = ["agent-a", "agent-b"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Enter multi-message mode
|
||||
enterMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), true);
|
||||
|
||||
// Simulate human message in multi-message mode
|
||||
onNewMessage(channelId, "human-user", true);
|
||||
|
||||
// Exit multi-message mode
|
||||
exitMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), false);
|
||||
|
||||
// Should be able to proceed normally
|
||||
onNewMessage(channelId, "human-user", true);
|
||||
const turnResult = checkTurn(channelId, "agent-a");
|
||||
assert.ok(turnResult);
|
||||
});
|
||||
});
|
||||
|
||||
describe("compatibility with mention override", () => {
|
||||
it("should handle multi-message mode with mention override", () => {
|
||||
const botIds = ["agent-a", "agent-b", "agent-c"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Enter multi-message mode
|
||||
enterMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), true);
|
||||
|
||||
// Even with mention override conceptually, multi-message mode should take precedence
|
||||
// In real usage, mention overrides happen in message-received hook before multi-message mode logic
|
||||
const turnResult = checkTurn(channelId, "agent-a");
|
||||
// The actual behavior depends on the before-model-resolve hook which forces no-reply in multi-message mode
|
||||
|
||||
// Exit multi-message mode to resume normal operation
|
||||
exitMultiMessageMode(channelId);
|
||||
assert.strictEqual(isMultiMessageMode(channelId), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("multi-message mode interaction with turn management", () => {
|
||||
it("should pause turn management in multi-message mode", () => {
|
||||
const botIds = ["agent-a", "agent-b"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Initially, turn should work normally
|
||||
const normalTurnResult = checkTurn(channelId, "agent-a");
|
||||
assert.ok(normalTurnResult);
|
||||
|
||||
// Enter multi-message mode
|
||||
enterMultiMessageMode(channelId);
|
||||
|
||||
// In multi-message mode, agents should be blocked (this is handled in before-model-resolve hook)
|
||||
// But the turn state itself continues to exist
|
||||
const stateInMultiMessage = getTurnDebugInfo(channelId);
|
||||
assert.ok(stateInMultiMessage.hasTurnState);
|
||||
|
||||
// Exit multi-message mode
|
||||
exitMultiMessageMode(channelId);
|
||||
|
||||
const stateAfterExit = getTurnDebugInfo(channelId);
|
||||
assert.ok(stateAfterExit.hasTurnState);
|
||||
});
|
||||
});
|
||||
});
|
||||
179
test/shuffle-mode.test.ts
Normal file
179
test/shuffle-mode.test.ts
Normal file
@@ -0,0 +1,179 @@
|
||||
import { describe, it, beforeEach, afterEach } from "node:test";
|
||||
import assert from "node:assert";
|
||||
import { initTurnOrder, checkTurn, onSpeakerDone, advanceTurn, resetTurn, getTurnDebugInfo, onNewMessage } from "../plugin/turn-manager.ts";
|
||||
import { setChannelShuffling, getChannelShuffling } from "../plugin/core/channel-modes.ts";
|
||||
|
||||
describe("Shuffle Mode Tests", () => {
|
||||
const channelId = "test-channel";
|
||||
|
||||
beforeEach(() => {
|
||||
resetTurn(channelId);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
resetTurn(channelId);
|
||||
});
|
||||
|
||||
describe("/turn-shuffling command functionality", () => {
|
||||
it("should enable shuffle mode", () => {
|
||||
setChannelShuffling(channelId, true);
|
||||
assert.strictEqual(getChannelShuffling(channelId), true);
|
||||
});
|
||||
|
||||
it("should disable shuffle mode", () => {
|
||||
setChannelShuffling(channelId, false);
|
||||
assert.strictEqual(getChannelShuffling(channelId), false);
|
||||
});
|
||||
|
||||
it("should start with shuffle mode disabled by default", () => {
|
||||
assert.strictEqual(getChannelShuffling(channelId), false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("shuffle mode behavior", () => {
|
||||
it("should not reshuffle when shuffling is disabled", () => {
|
||||
const botIds = ["agent-a", "agent-b", "agent-c"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Disable shuffling (should be default anyway)
|
||||
setChannelShuffling(channelId, false);
|
||||
|
||||
// Simulate a full cycle without reshuffling
|
||||
const initialOrder = getTurnDebugInfo(channelId).turnOrder as string[];
|
||||
const firstSpeaker = initialOrder[0];
|
||||
|
||||
// Have first speaker finish their turn
|
||||
onSpeakerDone(channelId, firstSpeaker, false);
|
||||
|
||||
// Check that the order didn't change (since shuffling is disabled)
|
||||
const orderAfterOneTurn = getTurnDebugInfo(channelId).turnOrder as string[];
|
||||
|
||||
// The order should remain the same when shuffling is disabled
|
||||
assert.deepStrictEqual(initialOrder, orderAfterOneTurn);
|
||||
});
|
||||
|
||||
it("should reshuffle when shuffling is enabled after a full cycle", () => {
|
||||
const botIds = ["agent-a", "agent-b", "agent-c"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Enable shuffling
|
||||
setChannelShuffling(channelId, true);
|
||||
|
||||
// Get initial order
|
||||
const initialOrder = getTurnDebugInfo(channelId).turnOrder as string[];
|
||||
const firstSpeaker = initialOrder[0];
|
||||
|
||||
// Complete a full cycle by having each agent speak once
|
||||
for (const agent of initialOrder) {
|
||||
const turnResult = checkTurn(channelId, agent);
|
||||
if (turnResult.allowed) {
|
||||
onSpeakerDone(channelId, agent, false);
|
||||
}
|
||||
}
|
||||
|
||||
// After a full cycle, the order should have potentially changed if shuffling is enabled
|
||||
const orderAfterCycle = getTurnDebugInfo(channelId).turnOrder as string[];
|
||||
|
||||
// The order might be different due to shuffling, or it might be the same by chance
|
||||
// But the important thing is that the shuffling mechanism was called
|
||||
assert(Array.isArray(orderAfterCycle));
|
||||
assert.strictEqual(orderAfterCycle.length, 3);
|
||||
});
|
||||
|
||||
it("should ensure last speaker doesn't become first in next round when shuffling", () => {
|
||||
const botIds = ["agent-a", "agent-b"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Enable shuffling
|
||||
setChannelShuffling(channelId, true);
|
||||
|
||||
// Get initial order
|
||||
const initialOrder = getTurnDebugInfo(channelId).turnOrder as string[];
|
||||
assert.strictEqual(initialOrder.length, 2);
|
||||
|
||||
// Have first agent speak
|
||||
const firstSpeaker = initialOrder[0];
|
||||
const secondSpeaker = initialOrder[1];
|
||||
|
||||
// Have first speaker finish
|
||||
onSpeakerDone(channelId, firstSpeaker, false);
|
||||
|
||||
// Have second speaker finish (completing a full cycle)
|
||||
onSpeakerDone(channelId, secondSpeaker, false);
|
||||
|
||||
// The turn order should be reshuffled but with constraints
|
||||
const orderAfterReshuffle = getTurnDebugInfo(channelId).turnOrder as string[];
|
||||
|
||||
// Verify the order is still valid
|
||||
assert.strictEqual(orderAfterReshuffle.length, 2);
|
||||
assert.ok(orderAfterReshuffle.includes("agent-a"));
|
||||
assert.ok(orderAfterReshuffle.includes("agent-b"));
|
||||
});
|
||||
|
||||
it("should handle single agent scenario gracefully", () => {
|
||||
const botIds = ["agent-a"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Enable shuffling
|
||||
setChannelShuffling(channelId, true);
|
||||
|
||||
// Single agent should work fine
|
||||
const turnResult = checkTurn(channelId, "agent-a");
|
||||
assert.strictEqual(turnResult.allowed, true);
|
||||
|
||||
onSpeakerDone(channelId, "agent-a", false);
|
||||
|
||||
// Should still work with single agent after reshuffle attempt
|
||||
const turnResultAfter = checkTurn(channelId, "agent-a");
|
||||
assert.strictEqual(turnResultAfter.allowed, true);
|
||||
});
|
||||
|
||||
it("should handle double agent scenario properly", () => {
|
||||
const botIds = ["agent-a", "agent-b"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
// Enable shuffling
|
||||
setChannelShuffling(channelId, true);
|
||||
|
||||
const initialOrder = getTurnDebugInfo(channelId).turnOrder as string[];
|
||||
const firstSpeaker = initialOrder[0];
|
||||
const secondSpeaker = initialOrder[1];
|
||||
|
||||
// Have first speaker finish
|
||||
onSpeakerDone(channelId, firstSpeaker, false);
|
||||
|
||||
// Have second speaker finish (this completes a cycle)
|
||||
onSpeakerDone(channelId, secondSpeaker, false);
|
||||
|
||||
// The order might be reshuffled, but it should be valid
|
||||
const newOrder = getTurnDebugInfo(channelId).turnOrder as string[];
|
||||
assert.strictEqual(newOrder.length, 2);
|
||||
assert.ok(newOrder.includes("agent-a"));
|
||||
assert.ok(newOrder.includes("agent-b"));
|
||||
|
||||
// Next speaker should be determined by the new order
|
||||
const nextSpeaker = advanceTurn(channelId);
|
||||
assert.ok(["agent-a", "agent-b"].includes(nextSpeaker as string));
|
||||
});
|
||||
});
|
||||
|
||||
describe("compatibility with other modes", () => {
|
||||
it("should work with dormant state", () => {
|
||||
const botIds = ["agent-a", "agent-b"];
|
||||
initTurnOrder(channelId, botIds);
|
||||
|
||||
setChannelShuffling(channelId, true);
|
||||
|
||||
// Start with dormant state
|
||||
resetTurn(channelId);
|
||||
const dormantState = getTurnDebugInfo(channelId);
|
||||
assert.strictEqual(dormantState.dormant, true);
|
||||
|
||||
// Activate with new message
|
||||
onNewMessage(channelId, "agent-a", false);
|
||||
const activeState = getTurnDebugInfo(channelId);
|
||||
assert.strictEqual(activeState.dormant, false);
|
||||
assert.ok(activeState.currentSpeaker);
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user