diff --git a/plans/TASKLIST.md b/plans/TASKLIST.md index dd60f97..2a39f8f 100644 --- a/plans/TASKLIST.md +++ b/plans/TASKLIST.md @@ -81,42 +81,42 @@ ### A6. `plugin/turn-manager.ts` #### A6.1 理解现有轮转机制 -- [ ] 梳理 `initTurnOrder` / `checkTurn` / `onNewMessage` / `onSpeakerDone` / `advanceTurn` -- [ ] 确认“轮转一圈无人发言”在现有实现中的判定条件 -- [ ] 确认 discussion 模式需要在哪个信号点插入“idle reminder” +- [x] 梳理 `initTurnOrder` / `checkTurn` / `onNewMessage` / `onSpeakerDone` / `advanceTurn` +- [x] 确认“轮转一圈无人发言”在现有实现中的判定条件 +- [x] 确认 discussion 模式需要在哪个信号点插入“idle reminder” #### A6.2 discussion 模式的空转处理 -- [ ] 设计 discussion 模式下的 idle reminder 触发方式 -- [ ] 确定是直接改 `turn-manager.ts`,还是由上层在 `nextSpeaker === null` 时识别 discussion channel -- [ ] 确保 discussion channel 空转时 moderator 会提醒 initiator 收尾 -- [ ] 确保普通 channel 仍保持原有 dormant 行为 +- [x] 设计 discussion 模式下的 idle reminder 触发方式 +- [x] 确定是直接改 `turn-manager.ts`,还是由上层在 `nextSpeaker === null` 时识别 discussion channel +- [x] 确保 discussion channel 空转时 moderator 会提醒 initiator 收尾 +- [x] 确保普通 channel 仍保持原有 dormant 行为 #### A6.3 关闭后禁言 -- [ ] 明确 discussion channel `closed` 后 turn-manager 是否还需要保留状态 -- [ ] 如需要,增加对 closed discussion channel 的快速短路判断 -- [ ] 避免 closed channel 再次进入正常轮转 +- [x] 明确 discussion channel `closed` 后 turn-manager 是否还需要保留状态 +- [x] 如需要,增加对 closed discussion channel 的快速短路判断 +- [x] 避免 closed channel 再次进入正常轮转 ### A7. Hooks 与 session 状态 #### A7.1 `plugin/hooks/before-model-resolve.ts` -- [ ] 梳理当前 session 级 no-reply 覆盖的触发路径 -- [ ] 确认如何将 closed discussion channel 相关 session 强制落到 `noReplyProvider` / `noReplyModel` -- [ ] 确认该逻辑是通过 metadata 状态判断,还是通过额外 session 标记判断 -- [ ] 确保该覆盖只作用于指定 discussion session,不影响其他 channel/session -- [ ] 为 closed discussion channel 的覆盖路径补充调试日志 +- [x] 梳理当前 session 级 no-reply 覆盖的触发路径 +- [x] 确认如何将 closed discussion channel 相关 session 强制落到 `noReplyProvider` / `noReplyModel` +- [x] 确认该逻辑是通过 metadata 状态判断,还是通过额外 session 标记判断 +- [x] 确保该覆盖只作用于指定 discussion session,不影响其他 channel/session +- [x] 为 closed discussion channel 的覆盖路径补充调试日志 #### A7.2 `plugin/hooks/before-message-write.ts` -- [ ] 梳理当前 NO_REPLY / end-symbol / waitIdentifier 的处理逻辑 -- [ ] 找到 discussion channel 中“轮转一圈无人发言”后最适合触发 idle reminder 的位置 -- [ ] 如果 `nextSpeaker === null` 且当前 channel 是 active discussion channel: - - [ ] 调用 moderator idle reminder - - [ ] 不直接让流程无提示沉默结束 -- [ ] 避免重复发送 idle reminder -- [ ] closed discussion channel 下,阻止继续进入正常 handoff 流程 +- [x] 梳理当前 NO_REPLY / end-symbol / waitIdentifier 的处理逻辑 +- [x] 找到 discussion channel 中“轮转一圈无人发言”后最适合触发 idle reminder 的位置 +- [x] 如果 `nextSpeaker === null` 且当前 channel 是 active discussion channel: + - [x] 调用 moderator idle reminder + - [x] 不直接让流程无提示沉默结束 +- [x] 避免重复发送 idle reminder +- [x] closed discussion channel 下,阻止继续进入正常 handoff 流程 #### A7.3 `plugin/hooks/message-sent.ts` -- [ ] 确认该 hook 是否也会参与 turn 收尾,避免与 `before-message-write.ts` 重复处理 -- [ ] 检查 discussion channel 场景下是否需要同步补充 closed/idle 分支保护 -- [ ] 确保 callback 完成后的 closed channel 不会继续触发 handoff +- [x] 确认该 hook 是否也会参与 turn 收尾,避免与 `before-message-write.ts` 重复处理 +- [x] 检查 discussion channel 场景下是否需要同步补充 closed/idle 分支保护 +- [x] 确保 callback 完成后的 closed channel 不会继续触发 handoff #### A7.4 `plugin/hooks/message-received.ts` - [x] 梳理 moderator bot 消息当前是否已被过滤,避免 moderator 自己再次触发讨论链路 @@ -127,10 +127,10 @@ - [x] 避免 moderator 的 closed 提示消息反复触发自身处理 #### A7.5 `plugin/core/session-state.ts`(如需) -- [ ] 检查现有 session 相关缓存是否适合扩展 discussion 状态 -- [ ] 若需要,为 discussion session 增加专用标记缓存 -- [ ] 区分:普通 no-reply 决策 vs discussion close 强制 no-reply -- [ ] 确保 session 生命周期结束后相关缓存可清理 +- [x] 检查现有 session 相关缓存是否适合扩展 discussion 状态 +- [x] 若需要,为 discussion session 增加专用标记缓存 +- [x] 区分:普通 no-reply 决策 vs discussion close 强制 no-reply +- [x] 确保 session 生命周期结束后相关缓存可清理 ### A8. `plugin/core/identity.ts` / `plugin/core/channel-members.ts` / `plugin/core/turn-bootstrap.ts` - [ ] 梳理 initiator identity 的可获取路径 diff --git a/plugin/hooks/message-sent.ts b/plugin/hooks/message-sent.ts index ad6e665..3980f47 100644 --- a/plugin/hooks/message-sent.ts +++ b/plugin/hooks/message-sent.ts @@ -23,6 +23,9 @@ type MessageSentDeps = { content: string, logger: { info: (m: string) => void; warn: (m: string) => void }, ) => Promise; + discussionService?: { + isClosedDiscussion: (channelId: string) => boolean; + }; }; export function registerMessageSentHook(deps: MessageSentDeps): void { @@ -36,6 +39,7 @@ export function registerMessageSentHook(deps: MessageSentDeps): void { ensurePolicyStateLoaded, resolveDiscordUserId, sendModeratorMessage, + discussionService, } = deps; api.on("message_sent", async (event, ctx) => { @@ -97,6 +101,14 @@ export function registerMessageSentHook(deps: MessageSentDeps): void { } if (wasNoReply || hasEndSymbol) { + // Check if this is a closed discussion channel + if (discussionService?.isClosedDiscussion(channelId)) { + api.logger.info( + `dirigent: message_sent skipping turn advance for closed discussion channel=${channelId} from=${accountId}`, + ); + return; + } + const nextSpeaker = onSpeakerDone(channelId, accountId, wasNoReply); const trigger = wasNoReply ? "no_reply_keyword" : "end_symbol"; const noReplyKeyword = wasNoReply ? (/^NO$/i.test(trimmed) ? "NO" : "NO_REPLY") : ""; diff --git a/plugin/index.ts b/plugin/index.ts index 0ea62ce..1d95b98 100644 --- a/plugin/index.ts +++ b/plugin/index.ts @@ -226,6 +226,7 @@ export default { ensurePolicyStateLoaded, resolveDiscordUserId, sendModeratorMessage, + discussionService, }); }, };