package channelbridge // 本文件承载「**主人私聊**」这条车道的判据与车道键派生(M22 切片 LANEQUEUE; // M23 的红线「主人私聊不要应用静默窗口」建立在这里的判据上)。 // // 判据只用 actor 元数据 + 白名单:**绝不读正文**、绝不看提示词(项目红线)。 // // 注意判据是 M22 既有的 `!msg.IsGroup() && actor.IsOwner()`,**不是**任务书里写的 // `actor.IsGroupChat()`:两者在「适配器把群事件标成 Sender.IsGroup 但 ChannelType 仍是 // Direct」这类帧上可能分叉(那条帧会走 direct,更保守,红线方向不受影响)。 // 本片不改这个判据(改它会动 M22 的车道键与丢弃策略,属另一片)。 // 它是 `neverDrop`(永不拒收)与 `direct`(无窗口、拿不到定时器工厂)两件事的 // **同一个**来源,因此不存在「键说是主人、丢弃策略说不是」的两套口径。 import ( "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/adapter" "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/session" ) // ownerPrivateLane 报告这条消息是否属于「主人私聊」车道 —— 也就是**永不丢弃**的那条。 // 判据与 laneKey 完全同源(私聊 + actor 元数据说他是主人),因此不会出现「键说是主人、 // 丢弃策略说不是」的两套口径。这里绝不读正文:身份只来自 actor 元数据 + 白名单。 func ownerPrivateLane(msg adapter.UnifiedMessage, actor adapter.Actor) bool { return !msg.IsGroup() && actor.IsOwner() } // laneStrangerPrivate 报告一条入站是否属于**陌生人私聊**(非群 + 非主人)。 // // 它与 `isStrangerPrivate(sess)` 同源(同一判据的两面):会话层从 actor 派生 // `sess.Owner`,这里直接用 actor 元数据,因此不需要先查会话就能判定「这条车道 // 要不要受 applyToStrangers 管」。主人私聊走不到这里(那是 direct 车道)。 func laneStrangerPrivate(item laneItem) bool { return !item.msg.IsGroup() && !item.actor.IsOwner() } // laneKey 决定「哪些消息共用一条车道」,也就是「什么算同一个会话」。它必须与 // **会话键同源**(M22 复审 P1-1):两套各算各的判据会让同一个 session 出现两条车道、 // 两个回合并发,而两条 run 共享 sessionID ⇒ checkpoint 行 id `{sessionID}:{step}` 互相 // 覆盖、`loadCheckpoint` 可能续到另一条在途 run 的快照而重放工具调用,flow 桶 // `p.graphs[sessionID]` 被先收尾者删掉(loop 没有会话级互斥,IM 侧也没有网关那层 // `session_turn_fence`)。 // // - **主人私聊**:无论来自哪个平台,都被 inboundTargetFor 路由进同一个 Web 主会话 // (history.MainSessionID),因此共用一条 `main` 车道。这里的键与会话键**故意不同** // ——`main` 是那条路由的目的地,比渠道键更强(把各平台的主人私聊并成一条)。 // - 其余:**逐字就是** `session.Key(msg)`(`platform \x00 channelID`,与会话层缓存键 // 同一个出处),所以「会话层认为是一个会话」与「车道认为是一个会话」不可能分叉。 // // 🔴 **取舍(比旧实现少了一个维度,是有意的)**:键里**没有账号名**。同一平台的两个账号 // 收到同一对端/同一个群的消息时,它们是**同一个 session**(会话键只含 platform 与 // channel),因此必须共用一条车道 —— 分开就会让同一会话两个回合并发。副作用是同一对端 // 的多个账号的入站会串行排队;回复出口不受影响:出站路由取的是**每条消息自己的** // `msg.Platform`/`ChannelID`/`GroupID` 与它自己的适配器(`laneItem.a`),车道本身从不 // 持有任何账号级状态。 // // 同理也**不要**在这里重新引入「频道」维度:kook 同一 guild 的两个 text channel 在会话层 // 是同一个群会话(键按 `group:`),按 `ChannelID` 分车道同样会造成双车道。 func laneKey(msg adapter.UnifiedMessage, actor adapter.Actor) string { if ownerPrivateLane(msg, actor) { return ownerPrivateLaneKey } if session.ChannelKey(msg) == "" { // 没有任何渠道可用(私聊既无 ChannelID 也无 SenderID):会话层会以 // ErrInvalidChannel 拒掉这条消息,这里只需要一个不会与真实会话撞键的稳定键。 return "unbound" + laneKeySep + adapter.ConversationKey(msg) } return session.Key(msg) }