package channelbridge import ( "context" "git.yeij.top/AskaEth/Cyrene_v2/internal/channel/session" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/transcript" ) // 本文件让 IM 的**主人私聊**回合与 Web 主会话回合的环境逐项一致(主人 2026-10-05 要求: // 处理其他平台的主人消息时和主会话环境完全一致)。 // // 三条环境在 loop.SessionEnv 上早已一致(ActorEnv=owner、MemWritable=true、PersonaID 取 // 主会话绑定,design/22 §7-3),缺的是**历史转录**:Web 主会话回合由编排器播种该会话最近 // N 条转录(gateway/session_transcript.go),而 IM 回合此前只有「来源系统行 + 当次消息」。 // 于是消息明明落在同一条会话流里,模型看到的历史却不同 —— 她像是第一次见到主人。 // // 本文件补上这一半:主人私聊回合在装配完本回合自己的消息之后、交给循环之前,前置**主会话** // 的最近 N 条转录。重建逻辑与条数上限都取自 internal/runtime/transcript —— 与网关同一份 // 实现、同一个常量,绝不另写第二套(两份映射必然漂移)。受限车道(群聊/陌生人)**绝不注入**: // 那是隔离会话,只看得到自己会话里的当次消息,主人的主会话历史不得外泄(design/16 §4.6)。 // turnEnv 返回一条入站消息本轮运行所用的 loop 会话环境。 // // - 主人私聊(inboundTarget.private)与 Web 主会话是**同一条车道**,因此经主人车道的 // 唯一构造入口 loop.OwnerEnvFor 派生:两条车道逐字段同源,SessionEnv 将来新增字段时 // 会一起拿到,而不是各自漂移出「同一个她、两套环境」。 // - 受限会话(群聊/陌生人)由结构化解析出的隔离边界决定:记忆只读(MemWritable=false)、 // 并强制默认角色(design/22 §7-4 —— resolvePersona 对受限会话恒返回 "")。身份与权限 // 始终来自 actor 元数据,绝不来自这里的任何字符串(项目红线)。 func turnEnv(sess session.Session, target inboundTarget, personaID string) loop.SessionEnv { if target.private { return loop.OwnerEnvFor(personaID) } return loop.SessionEnv{ActorEnv: sess.Env(), MemWritable: sess.MemWritable, PersonaID: personaID} } // seedTurnTranscript 在主人私聊车道上把**主会话**最近的转录置于本回合模型可见消息之前 // (与网关编排器的 startTurnMessages 同序:转录在前、本次消息在后),使 IM 回合与 Web 主 // 会话看到同一份对话历史。受限(群聊/陌生人)车道原样返回 —— 隔离上下文绝不注入主人的 // 主会话历史。 // // 时刻与网关一致:调用点在本回合用户消息落库**之前**(runInboundTurn 里装配先于 // persistToMain),因此当次消息绝不会在上下文里出现两次。 // // 转录读不出来(未接历史存储、读失败、空会话)时同样原样返回,这正是既有行为:主人私聊 // 回合退化为「来源行 + 当次消息」,而不是让回合失败。 func (b *Bridge) seedTurnTranscript(ctx context.Context, target inboundTarget, msgs []model.Message) []model.Message { if !target.private { return msgs } seed := transcript.Read(ctx, b.history, target.sessionID) if len(seed) == 0 { return msgs } out := make([]model.Message, 0, len(seed)+len(msgs)) out = append(out, seed...) return append(out, msgs...) }