Files
Cyrene_v2/internal/services/gateway/broadcast.go
T
AskaEth fe1ff33115
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
docs(gateway): WS 帧门相关 11 个文件的注释改中文(只改注释,代码零字节改动)
主人 2026-10-03 拍板「注释一律中文」。本次先把本会话动过的文件就地翻;
不做全仓一次性翻译,存量按包分批排期(见 工作小结.md 待办)。

范围:ws_frame_scope.go / ws_frame_scope_test.go / broadcast.go / ws_hub_turn.go /
ws_hub_message_event.go / session_read.go / server.go / ws_hub_stream_scope_test.go /
history_write_broadcast_test.go / mood_test.go / extra_test.go
含中文的整行注释 18 → 382。

校验(判据 = "只改注释、零代码字节改动"):
- `git diff -U0 | 排除 +++/--- | 排除注释行/空行/行尾注释` → 为空
- 独立实现:两侧剥掉注释后逐行比对代码文本 → 11/11 文件全等,不一致数 0
- 行尾注释 7 行逐条列出,代码列与原文逐字节相同(gofmt 未要求重排)
- gofmt -l 干净;全树 go build/vet 绿;go test ./... = 58 包 ok / 0 FAIL;
  linux/amd64 + darwin/arm64 交叉编译绿
语义未删减:部分折行英文长句合并为更少行,故整行注释数 438 → 399。
2026-10-03 22:07:56 +08:00

80 lines
3.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package gateway
import (
"strings"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
)
// 本文件归属 gateway 的下行实时推送:与 channel bridge 共用的
// 持久化消息帧,以及前端渲染的角色级状态帧(心情徽标、能力唤醒)。
// BroadcastPersisted 把一条刚刚持久化的聊天消息作为 WS 事件广播出去,
// 使同一账号的每个浏览器无需手动刷新即可追加该消息
// (design/16 §4.2)。它由 orchestrator(Web 轮次)与 channel bridge
// (IM 消息)共用,因此两条路径走同一种帧结构。事件类型按会话
// 区分:Web 主会话及其 Web 子会话广播 `message_persisted` 用于
// Web 聊天实时同步,而其他任何会话 id 都是隔离的渠道适配器会话
// (sess-xxx),广播 `channel_persisted` 供 IM 适配器日志页使用
// —— Web 聊天没有后者的处理器,因此在那里自然被忽略、永不污染
// 会话。Thinking 会被抹除,以与 WS 历史回放保持一致(design/16 §6)。
// 当 hub 接好广播时它是安全空操作。两种帧类型都携带消息内容,
// 因此 design/11 §7.1 帧门只把它们投递给 owner/admin 连接。
func (s *Server) BroadcastPersisted(msg history.Message) {
wire := toHistoryMessage(msg)
wire.Thinking = ""
typ := "channel_persisted"
if msg.SessionID == history.MainSessionID || strings.HasPrefix(msg.SessionID, history.MainSessionID+":") {
typ = "message_persisted"
}
s.hub.broadcast(wsEvent{Type: typ, SessionID: msg.SessionID, Data: map[string]any{"message": wire}})
}
// wsCapacityAwaken 是能力状态迁移的 WS 事件类型(design/14 §5)。
// 其 data 字段是前端的 AwakenPayload {fromState,toState,tool}。
const wsCapacityAwaken = "capacity_awaken"
// BroadcastCapacityAwaken 把一次能力状态迁移作为 `capacity_awaken` 帧
// 扇出到 Web UI(design/14 §5)。sessionID 是发生迁移的会话,因此
// 客户端能定位到正确的会话,而不会把唤醒事件当成全局事件。data 与
// 前端 AwakenPayload {fromState, toState, tool} 对应;tool 可以为空
// (没有点名单个工具,客户端此时保留原有的工具徽标)。它不属于
// design/11 §7.1 的「状态机信号」(其 `tool` 指明的是被什么唤醒,而
// 渲染它的面板属于 owner 界面),因此帧门只把它投递给 owner/admin
// 连接。
func (s *Server) BroadcastCapacityAwaken(sessionID, fromState, toState, tool string) {
s.hub.broadcast(wsEvent{
Type: wsCapacityAwaken,
SessionID: sessionID,
Data: map[string]any{
"fromState": fromState,
"toState": toState,
"tool": tool,
},
})
}
// wsMoodEvent 是携带角色轮次结束后心情的 WS 事件类型(design/07 §3
// 下行 `mood`;design/01 §2 情绪状态机)。前端既有的 `mood` 处理器
// (ws.ts)读取 data.mood 并显示心情徽标。
const wsMoodEvent = "mood"
// BroadcastMood 把一次情绪变化作为 `mood` 帧扇出到 owner/admin 的 Web
// 客户端(design/07 §3)。角色心情/好感度属于私有状态,因此 design/11
// §7.1 帧门让它远离 viewer 连接。它仅在心情确实发生变化时被调用
// —— 组合根中的机制就是那道门 —— 因此徽标不会因为每轮都出现未变化
// 的值而闪烁。data 与前端 `mood` 处理器读取的内容对应({mood});
// affection/personaId 作为诚实的额外细节一同携带(affection 是该角色
// 当前好感度,personaId 是心情发生变化的角色),不渲染它们的客户端
// 会忽略之。心情是角色级而非会话级的值,因此不附加 sessionId。
func (s *Server) BroadcastMood(personaID, mood string, affection int) {
s.hub.broadcast(wsEvent{
Type: wsMoodEvent,
Data: map[string]any{
"mood": mood,
"personaId": personaID,
"affection": affection,
},
})
}