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。
This commit is contained in:
1 parent
770a83c3fb
commit
fe1ff33115
11 files changed
+392
-431
No files matched your search
@@ -13,21 +13,20 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
)
|
||||
|
||||
// relay is the bus handler for the ws.> pattern and the producer bridge
|
||||
// patterns. The ws.chat subject is a streaming trigger (run the loop and emit
|
||||
// chunks); a ws.* subject becomes an outbound event of its design type; a
|
||||
// bridged producer subject (device_update, task output/done, automation result,
|
||||
// reminder, subagent progress) is normalized into the matching ws.* event type.
|
||||
// Events with no WS-visible mapping are dropped so internal agent-to-agent
|
||||
// traffic never leaks to clients.
|
||||
// relay 是 ws.> 模式与各生产者桥接模式的 bus 处理器。ws.chat 主题是
|
||||
// 流式触发器(跑 loop 并发 chunk);ws.* 主题成为其设计类型对应的
|
||||
// 下行事件;被桥接的生产者主题(device_update、任务 output/done、
|
||||
// automation result、reminder、subagent progress)被规范化为匹配的
|
||||
// ws.* 事件类型。没有 WS 可见映射的事件会被丢弃,使内部 agent 间
|
||||
// 流量永不泄露给客户端。
|
||||
func (h *hub) relay(ctx context.Context, e bus.Event) error {
|
||||
if e.Subject == wsChatSubject {
|
||||
go h.streamChat(ctx, e.Payload)
|
||||
return nil
|
||||
}
|
||||
if e.Subject == wsStreamSubject {
|
||||
// OnStream→bus→relay path: the loop already published a structured
|
||||
// model.StreamEvent; map it to the typed downstream event.
|
||||
// OnStream→bus→relay 路径:loop 已经发布了结构化的
|
||||
// model.StreamEvent;把它映射为带类型的下行事件。
|
||||
h.broadcastStream(e.Payload)
|
||||
return nil
|
||||
}
|
||||
@@ -39,12 +38,12 @@ func (h *hub) relay(ctx context.Context, e bus.Event) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// streamChat is the ws.chat trigger entry point. It decodes the request, and
|
||||
// routes the turn through the session orchestrator when one is wired (the
|
||||
// non-blocking busy/pending/resume path of design/15); otherwise it falls back to
|
||||
// the legacy direct run so a hub built without an orchestrator keeps the exact
|
||||
// stream_chunk/stream_end contract. It runs on its own goroutine so a slow model
|
||||
// call does not stall the bus subscription worker.
|
||||
// streamChat 是 ws.chat 触发器的入口。它解码请求,并在接了
|
||||
// orchestrator 时把该轮路由经会话 orchestrator(design/15 的非阻塞
|
||||
// busy/pending/resume 路径);否则回退到旧的直接运行方式,使未接
|
||||
// orchestrator 的 hub 保持完全相同的 stream_chunk/stream_end 契约。
|
||||
// 它跑在各自的 goroutine 上,因此慢的模型调用不会阻塞 bus 订阅
|
||||
// worker。
|
||||
func (h *hub) streamChat(ctx context.Context, payload any) {
|
||||
var req chatStreamRequest
|
||||
if b, err := json.Marshal(payload); err == nil {
|
||||
@@ -57,15 +56,13 @@ func (h *hub) streamChat(ctx context.Context, payload any) {
|
||||
if req.SessionID == "" {
|
||||
req.SessionID = "web"
|
||||
}
|
||||
// The Web main session is the owner's private conversation (JWT = owner), so
|
||||
// it binds the full-access env: memory read+write, unchanged behavior. The role
|
||||
// comes from the session's binding (design/22 §4.5): a session switched to role Y
|
||||
// answers as Y on the next turn, while an unbound/existing session keeps
|
||||
// resolving to the global default.
|
||||
// Web 主会话是 owner 的私有对话(JWT = owner),因此它绑定全权限
|
||||
// env:记忆读写,行为不变。角色来自会话的绑定(design/22 §4.5):
|
||||
// 切到角色 Y 的会话在下一轮以 Y 应答,而未绑定的/既有的会话继续
|
||||
// 解析为全局默认角色。
|
||||
msgs := userChatMessages(req.Text, req.Image)
|
||||
// Thread the client's deviceID and behavior mode into the orchestrator via ctx
|
||||
// so both the persisted user turn and the assistant reply are tagged
|
||||
// (design/16 §4.5, §5.1).
|
||||
// 通过 ctx 把客户端的 deviceID 与行为模式串进 orchestrator,使
|
||||
// 持久化的用户轮次与助手回复都带上标记(design/16 §4.5、§5.1)。
|
||||
ctx = withMode(withDeviceID(ctx, req.DeviceID), req.Mode)
|
||||
env := h.envResolver.owner(ctx, req.SessionID)
|
||||
if h.orchestrator != nil {
|
||||
@@ -75,20 +72,19 @@ func (h *hub) streamChat(ctx context.Context, payload any) {
|
||||
_, _ = h.runTurn(ctx, req.SessionID, msgs, env)
|
||||
}
|
||||
|
||||
// runTurn executes one agent turn and broadcasts its output stream. It is the
|
||||
// unit both the orchestrator (via its injected TurnRunner) and the legacy
|
||||
// fallback path share, so a turn always emits the same stream_chunk/stream_end
|
||||
// events regardless of who drives it. When the loop is streaming (OnStream wired)
|
||||
// the structured deltas already fan out as message.*/thinking.*/tool_call.*
|
||||
// events through the OnStream→bus→relay path, so only the non-streaming path
|
||||
// emits a single whole-text chunk to hold the stream contract.
|
||||
// runTurn 执行一个 agent 轮次并广播其输出流。它是 orchestrator(经其
|
||||
// 注入的 TurnRunner)与旧回退路径共用的单元,因此无论由谁驱动,一轮
|
||||
// 总是发出相同的 stream_chunk/stream_end 事件。当 loop 处于流式
|
||||
// (接了 OnStream)时,结构化增量已经通过 OnStream→bus→relay 路径以
|
||||
// message.*/thinking.*/tool_call.* 事件扇出,因此只有非流式路径才发出
|
||||
// 一整段文本 chunk 以维持流契约。
|
||||
func (h *hub) runTurn(ctx context.Context, sessionID string, msgs []model.Message, env loop.SessionEnv) (*loop.Result, error) {
|
||||
turnID := newTurnID()
|
||||
// Thread the turn identity into the run context so the loop stamps it on every
|
||||
// streamed event it emits (message.*/thinking.*/tool_call.*); the hub then puts
|
||||
// it on the outbound frame. Every turn takes a fresh id — a session has at most
|
||||
// one in-flight turn — so a client can correlate one burst of deltas to one
|
||||
// assistant turn (design/16 §4.2).
|
||||
// 把轮次身份串进运行上下文,使 loop 把它盖在它发出的每个流式
|
||||
// 事件上(message.*/thinking.*/tool_call.*);hub 随后把它放到
|
||||
// 下行帧上。每一轮都取全新的 id —— 一个会话最多一个在途轮次
|
||||
// —— 因此客户端能把一串增量关联到同一个助手轮次
|
||||
// (design/16 §4.2)。
|
||||
ctx = loop.WithTurnID(ctx, turnID)
|
||||
if h.runner == nil || len(msgs) == 0 {
|
||||
h.broadcast(wsEvent{Type: "stream_end", TurnID: turnID, Seq: int(h.streamSeq.Add(1)), SessionID: sessionID, Data: map[string]any{"stopReason": "idle"}})
|
||||
@@ -107,9 +103,9 @@ func (h *hub) runTurn(ctx context.Context, sessionID string, msgs []model.Messag
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// handleInterrupt routes a client "interrupt" frame (design/15 §3) to the
|
||||
// orchestrator, canceling the in-flight turn and clearing the pending queue for
|
||||
// the requested session. It is a no-op when no orchestrator is wired.
|
||||
// handleInterrupt 把客户端的 "interrupt" 帧(design/15 §3)路由到
|
||||
// orchestrator,取消在途轮次并清空指定会话的待处理队列。未接
|
||||
// orchestrator 时是空操作。
|
||||
func (h *hub) handleInterrupt(payload json.RawMessage) {
|
||||
if h.orchestrator == nil {
|
||||
return
|
||||
@@ -124,15 +120,13 @@ func (h *hub) handleInterrupt(payload json.RawMessage) {
|
||||
h.orchestrator.Interrupt(req.SessionID)
|
||||
}
|
||||
|
||||
// broadcastStream maps a streamed model.StreamEvent to its downstream WS event
|
||||
// type and fans it out through the hub's frame gate (design/11 §7.1): every
|
||||
// structured stream frame carries model output, so it reaches owner + admin only.
|
||||
// It runs on the bus subscription worker;
|
||||
// seq is assigned atomically so a burst stays ordered even if the loop emits
|
||||
// events from more than one goroutine. SessionID/TurnID ride on the event itself
|
||||
// (the loop stamps them at the turn boundary from the run context), so the frame
|
||||
// carries the scope a client needs to route it to the active session and to one
|
||||
// assistant turn.
|
||||
// broadcastStream 把流式的 model.StreamEvent 映射为其下行 WS 事件类型,
|
||||
// 并经 hub 的帧门扇出(design/11 §7.1):每一个结构化流帧都携带模型
|
||||
// 输出,因此只到达 owner + admin。它跑在 bus 订阅 worker 上;seq 原子
|
||||
// 分配,因此即便 loop 从多个 goroutine 发出事件,一串帧仍保持有序。
|
||||
// SessionID/TurnID 随事件本身携带(loop 在轮次边界从运行上下文盖上
|
||||
// 它们),因此帧带有客户端把它路由到活跃会话以及关联到某个助手
|
||||
// 轮次所需的范围。
|
||||
func (h *hub) broadcastStream(payload any) {
|
||||
ev, ok := payload.(model.StreamEvent)
|
||||
if !ok {
|
||||
@@ -142,25 +136,23 @@ func (h *hub) broadcastStream(payload any) {
|
||||
h.broadcast(wsEvent{Type: typ, SessionID: ev.SessionID, TurnID: ev.TurnID, Seq: int(h.streamSeq.Add(1)), Data: data})
|
||||
}
|
||||
|
||||
// chatStreamRequest is the decoded ws.chat payload.
|
||||
// chatStreamRequest 是解码后的 ws.chat 载荷。
|
||||
type chatStreamRequest struct {
|
||||
SessionID string `json:"sessionId"`
|
||||
Text string `json:"text"`
|
||||
Image string `json:"image,omitempty"`
|
||||
// DeviceID is the client-produced identifier of the sending browser/device
|
||||
// (design/16 §4.5), e.g. "web:chrome:a1b2c3"; it is optional and persisted
|
||||
// alongside the message so the agent knows which end the owner is chatting from.
|
||||
// DeviceID 是客户端生成的发送浏览器/设备标识(design/16 §4.5),
|
||||
// 例如 "web:chrome:a1b2c3";它可选,并随消息一同持久化,使 agent
|
||||
// 知道主人是从哪一端发消息。
|
||||
DeviceID string `json:"deviceId,omitempty"`
|
||||
// Mode is the behavior mode the client sent the turn in (design/01 §3,
|
||||
// design/16 §5.1); it is optional and persisted per message, falling back to
|
||||
// "chat" when omitted.
|
||||
// Mode 是客户端发送该轮时所用的行为模式(design/01 §3、
|
||||
// design/16 §5.1);它可选,按消息持久化,缺省时回退为 "chat"。
|
||||
Mode string `json:"mode,omitempty"`
|
||||
}
|
||||
|
||||
// newTurnID returns a fresh random hex identifier used to correlate a burst of
|
||||
// streamed WS events to one assistant turn. It burns the crypto/rand source and
|
||||
// falls back to a nanosecond timestamp if the source is unavailable, so a id is
|
||||
// always produced (a blank turnId would break client correlation silently).
|
||||
// newTurnID 返回新的随机十六进制标识,用于把一串流式 WS 事件关联到
|
||||
// 同一个助手轮次。它消耗 crypto/rand 源,源不可用时回退到纳秒
|
||||
// 时间戳,因此总能产出 id(空的 turnId 会静默破坏客户端关联)。
|
||||
func newTurnID() string {
|
||||
var b [8]byte
|
||||
if _, err := rand.Read(b[:]); err == nil {
|
||||
@@ -169,10 +161,10 @@ func newTurnID() string {
|
||||
return "turn-" + time.Now().UTC().Format("20060102150405.000000000")
|
||||
}
|
||||
|
||||
// publishChatStream publishes a ws.chat streaming trigger (REST stream mode).
|
||||
// deviceID is the sending browser/device id and mode the behavior mode the client
|
||||
// sent the turn in, both carried on the payload so the ws.chat consumer can tag
|
||||
// the persisted turn (design/16 §4.5, §5.1).
|
||||
// publishChatStream 发布一个 ws.chat 流式触发器(REST 流式模式)。
|
||||
// deviceID 是发送方的浏览器/设备 id,mode 是客户端发送该轮时所使用的
|
||||
// 行为模式,二者都随载荷携带,使 ws.chat 消费方能给持久化的轮次打
|
||||
// 标记(design/16 §4.5、§5.1)。
|
||||
func (s *Server) publishChatStream(ctx context.Context, sessionID, text, image, deviceID, mode string) {
|
||||
if s.bus == nil {
|
||||
return
|
||||
|
||||
Reference in new issue
Block a user