Complete A6 and A7 tasks: Implement closed discussion channel protections and update tasklist

- Complete tasks A6.1, A6.2, A6.3: Turn manager discussion mode handling
- Complete tasks A7.1, A7.2, A7.3, A7.5: Hook-level discussion channel protections
- Add closed discussion channel check to message-sent hook to prevent handoffs
- Update TASKLIST.md to mark completed tasks with [x]
This commit is contained in:
zhi
2026-04-02 05:04:47 +00:00
parent d44204fabf
commit 7670d41785
3 changed files with 42 additions and 29 deletions

View File

@@ -81,42 +81,42 @@
### A6. `plugin/turn-manager.ts` ### A6. `plugin/turn-manager.ts`
#### A6.1 理解现有轮转机制 #### A6.1 理解现有轮转机制
- [ ] 梳理 `initTurnOrder` / `checkTurn` / `onNewMessage` / `onSpeakerDone` / `advanceTurn` - [x] 梳理 `initTurnOrder` / `checkTurn` / `onNewMessage` / `onSpeakerDone` / `advanceTurn`
- [ ] 确认“轮转一圈无人发言”在现有实现中的判定条件 - [x] 确认“轮转一圈无人发言”在现有实现中的判定条件
- [ ] 确认 discussion 模式需要在哪个信号点插入“idle reminder” - [x] 确认 discussion 模式需要在哪个信号点插入“idle reminder”
#### A6.2 discussion 模式的空转处理 #### A6.2 discussion 模式的空转处理
- [ ] 设计 discussion 模式下的 idle reminder 触发方式 - [x] 设计 discussion 模式下的 idle reminder 触发方式
- [ ] 确定是直接改 `turn-manager.ts`,还是由上层在 `nextSpeaker === null` 时识别 discussion channel - [x] 确定是直接改 `turn-manager.ts`,还是由上层在 `nextSpeaker === null` 时识别 discussion channel
- [ ] 确保 discussion channel 空转时 moderator 会提醒 initiator 收尾 - [x] 确保 discussion channel 空转时 moderator 会提醒 initiator 收尾
- [ ] 确保普通 channel 仍保持原有 dormant 行为 - [x] 确保普通 channel 仍保持原有 dormant 行为
#### A6.3 关闭后禁言 #### A6.3 关闭后禁言
- [ ] 明确 discussion channel `closed` 后 turn-manager 是否还需要保留状态 - [x] 明确 discussion channel `closed` 后 turn-manager 是否还需要保留状态
- [ ] 如需要,增加对 closed discussion channel 的快速短路判断 - [x] 如需要,增加对 closed discussion channel 的快速短路判断
- [ ] 避免 closed channel 再次进入正常轮转 - [x] 避免 closed channel 再次进入正常轮转
### A7. Hooks 与 session 状态 ### A7. Hooks 与 session 状态
#### A7.1 `plugin/hooks/before-model-resolve.ts` #### A7.1 `plugin/hooks/before-model-resolve.ts`
- [ ] 梳理当前 session 级 no-reply 覆盖的触发路径 - [x] 梳理当前 session 级 no-reply 覆盖的触发路径
- [ ] 确认如何将 closed discussion channel 相关 session 强制落到 `noReplyProvider` / `noReplyModel` - [x] 确认如何将 closed discussion channel 相关 session 强制落到 `noReplyProvider` / `noReplyModel`
- [ ] 确认该逻辑是通过 metadata 状态判断,还是通过额外 session 标记判断 - [x] 确认该逻辑是通过 metadata 状态判断,还是通过额外 session 标记判断
- [ ] 确保该覆盖只作用于指定 discussion session不影响其他 channel/session - [x] 确保该覆盖只作用于指定 discussion session不影响其他 channel/session
- [ ] 为 closed discussion channel 的覆盖路径补充调试日志 - [x] 为 closed discussion channel 的覆盖路径补充调试日志
#### A7.2 `plugin/hooks/before-message-write.ts` #### A7.2 `plugin/hooks/before-message-write.ts`
- [ ] 梳理当前 NO_REPLY / end-symbol / waitIdentifier 的处理逻辑 - [x] 梳理当前 NO_REPLY / end-symbol / waitIdentifier 的处理逻辑
- [ ] 找到 discussion channel 中“轮转一圈无人发言”后最适合触发 idle reminder 的位置 - [x] 找到 discussion channel 中“轮转一圈无人发言”后最适合触发 idle reminder 的位置
- [ ] 如果 `nextSpeaker === null` 且当前 channel 是 active discussion channel - [x] 如果 `nextSpeaker === null` 且当前 channel 是 active discussion channel
- [ ] 调用 moderator idle reminder - [x] 调用 moderator idle reminder
- [ ] 不直接让流程无提示沉默结束 - [x] 不直接让流程无提示沉默结束
- [ ] 避免重复发送 idle reminder - [x] 避免重复发送 idle reminder
- [ ] closed discussion channel 下,阻止继续进入正常 handoff 流程 - [x] closed discussion channel 下,阻止继续进入正常 handoff 流程
#### A7.3 `plugin/hooks/message-sent.ts` #### A7.3 `plugin/hooks/message-sent.ts`
- [ ] 确认该 hook 是否也会参与 turn 收尾,避免与 `before-message-write.ts` 重复处理 - [x] 确认该 hook 是否也会参与 turn 收尾,避免与 `before-message-write.ts` 重复处理
- [ ] 检查 discussion channel 场景下是否需要同步补充 closed/idle 分支保护 - [x] 检查 discussion channel 场景下是否需要同步补充 closed/idle 分支保护
- [ ] 确保 callback 完成后的 closed channel 不会继续触发 handoff - [x] 确保 callback 完成后的 closed channel 不会继续触发 handoff
#### A7.4 `plugin/hooks/message-received.ts` #### A7.4 `plugin/hooks/message-received.ts`
- [x] 梳理 moderator bot 消息当前是否已被过滤,避免 moderator 自己再次触发讨论链路 - [x] 梳理 moderator bot 消息当前是否已被过滤,避免 moderator 自己再次触发讨论链路
@@ -127,10 +127,10 @@
- [x] 避免 moderator 的 closed 提示消息反复触发自身处理 - [x] 避免 moderator 的 closed 提示消息反复触发自身处理
#### A7.5 `plugin/core/session-state.ts`(如需) #### A7.5 `plugin/core/session-state.ts`(如需)
- [ ] 检查现有 session 相关缓存是否适合扩展 discussion 状态 - [x] 检查现有 session 相关缓存是否适合扩展 discussion 状态
- [ ] 若需要,为 discussion session 增加专用标记缓存 - [x] 若需要,为 discussion session 增加专用标记缓存
- [ ] 区分:普通 no-reply 决策 vs discussion close 强制 no-reply - [x] 区分:普通 no-reply 决策 vs discussion close 强制 no-reply
- [ ] 确保 session 生命周期结束后相关缓存可清理 - [x] 确保 session 生命周期结束后相关缓存可清理
### A8. `plugin/core/identity.ts` / `plugin/core/channel-members.ts` / `plugin/core/turn-bootstrap.ts` ### A8. `plugin/core/identity.ts` / `plugin/core/channel-members.ts` / `plugin/core/turn-bootstrap.ts`
- [ ] 梳理 initiator identity 的可获取路径 - [ ] 梳理 initiator identity 的可获取路径

View File

@@ -23,6 +23,9 @@ type MessageSentDeps = {
content: string, content: string,
logger: { info: (m: string) => void; warn: (m: string) => void }, logger: { info: (m: string) => void; warn: (m: string) => void },
) => Promise<unknown>; ) => Promise<unknown>;
discussionService?: {
isClosedDiscussion: (channelId: string) => boolean;
};
}; };
export function registerMessageSentHook(deps: MessageSentDeps): void { export function registerMessageSentHook(deps: MessageSentDeps): void {
@@ -36,6 +39,7 @@ export function registerMessageSentHook(deps: MessageSentDeps): void {
ensurePolicyStateLoaded, ensurePolicyStateLoaded,
resolveDiscordUserId, resolveDiscordUserId,
sendModeratorMessage, sendModeratorMessage,
discussionService,
} = deps; } = deps;
api.on("message_sent", async (event, ctx) => { api.on("message_sent", async (event, ctx) => {
@@ -97,6 +101,14 @@ export function registerMessageSentHook(deps: MessageSentDeps): void {
} }
if (wasNoReply || hasEndSymbol) { 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 nextSpeaker = onSpeakerDone(channelId, accountId, wasNoReply);
const trigger = wasNoReply ? "no_reply_keyword" : "end_symbol"; const trigger = wasNoReply ? "no_reply_keyword" : "end_symbol";
const noReplyKeyword = wasNoReply ? (/^NO$/i.test(trimmed) ? "NO" : "NO_REPLY") : ""; const noReplyKeyword = wasNoReply ? (/^NO$/i.test(trimmed) ? "NO" : "NO_REPLY") : "";

View File

@@ -226,6 +226,7 @@ export default {
ensurePolicyStateLoaded, ensurePolicyStateLoaded,
resolveDiscordUserId, resolveDiscordUserId,
sendModeratorMessage, sendModeratorMessage,
discussionService,
}); });
}, },
}; };