- server.go 204 + 新 routes.go(193,路由注册/Handler/withMiddleware)/deps.go(108)/contracts.go(231,17依赖接口)/broadcast.go(78,BroadcastPersisted/CapacityAwaken/Mood); 导出面不变; 74条路由pattern+RBAC门逐条比对一致 - model/openai_stream.go: scanSSE 138→25(parse/apply/finalize 对齐 anthropic_stream); openaiStreamState结构体 - channelbridge/inbound.go: processFrom 112→46(blacklistDrop/inboundTargetFor/runInboundTurn/deliverReply) - 纯行为等价; build/vet/test绿(56包)+win/darwin交叉编译
79 lines
3.5 KiB
Go
79 lines
3.5 KiB
Go
package gateway
|
|
|
|
import (
|
|
"strings"
|
|
|
|
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
|
|
)
|
|
|
|
// This file owns the gateway's outbound realtime pushes: the persisted-message
|
|
// frames shared with the channel bridge plus the per-persona state frames the
|
|
// frontend renders (mood badge, capacity awaken).
|
|
|
|
// BroadcastPersisted broadcasts a just-persisted chat message as a WS event so
|
|
// every browser of the same account appends it without a manual refresh
|
|
// (design/16 §4.2). It is shared by the orchestrator (Web turns) and the channel
|
|
// bridge (IM messages), so both paths flow through one frame shape. The event type
|
|
// is split by session: the main web session and its web sub-sessions broadcast
|
|
// `message_persisted` for web-chat realtime sync, while any other session id is an
|
|
// isolated channel adapter session (sess-xxx) and broadcasts `channel_persisted`
|
|
// for the IM adapter log page — the web chat has no handler for the latter, so it
|
|
// is naturally ignored there and never pollutes the conversation. Thinking is
|
|
// stripped to stay consistent with the WS history replay (design/16 §6). It is a
|
|
// no-op-safe call when the hub has broadcast wired.
|
|
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 is the WS event type for a capacity state transition
|
|
// (design/14 §5). Its data field is the frontend AwakenPayload {fromState,toState,tool}.
|
|
const wsCapacityAwaken = "capacity_awaken"
|
|
|
|
// BroadcastCapacityAwaken fans a capacity state transition to the Web UI as a
|
|
// `capacity_awaken` frame (design/14 §5). sessionID is the conversation that
|
|
// transitioned, so the client targets the correct session rather than treating the
|
|
// wake event as global. data matches the frontend AwakenPayload {fromState, toState,
|
|
// tool}; tool may be empty (no single tool was lit, the client then keeps the prior
|
|
// tool badge).
|
|
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 is the WS event type carrying the persona's mood after a turn
|
|
// (design/07 §3 下行 `mood`; design/01 §2 情绪状态机). The frontend's existing
|
|
// `mood` handler (ws.ts) reads data.mood and shows the 心情 badge.
|
|
const wsMoodEvent = "mood"
|
|
|
|
// BroadcastMood fans one emotion change to every Web client as a `mood` frame
|
|
// (design/07 §3). It is called only when the mood actually changed — the
|
|
// machinery in the composition root is the gate — so the badge does not flicker
|
|
// with an unchanged value on every turn. data matches what the frontend `mood`
|
|
// handler reads ({mood}); affection/personaId ride along as the honest extra
|
|
// detail (affection is the persona's current 好感度, personaId the persona whose
|
|
// mood moved) and are ignored by clients that do not render them. The mood is a
|
|
// persona-level value, not a per-session one, so no sessionId is attached.
|
|
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,
|
|
},
|
|
})
|
|
}
|