feat(gateway): 消息mode落库(A3) — 持久化真实写入渲染模式
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

- POST /api/v1/chat 与 ws.chat 上行新增可选 mode(行为模式 design/01 §3/design/16 §5.1)
- Web路径经ctx透传本轮mode到编排器持久化(persistUserMessages/persistAssistantReply),缺省回落chat
- pendingMsg记录mode,恢复/续接路径沿用发起轮次mode
- IM路径(persistToMain/persistIsolated)恒为chat
- 新增/扩充测试:REST/WS/orchestrator/channelbridge 覆盖mode写入
- Docs/api-client.md v0.20 同步mode字段
This commit is contained in:
AskaEth committed 2026-09-08 21:05:50 +08:00
1 parent fe5dbcddba
commit b686d5fec8
9 files changed
+199 -41

No files matched your search

+13 -7
View File
@@ -235,9 +235,10 @@ func (h *hub) streamChat(ctx context.Context, payload any) {
// The Web main session is the owner's private conversation (JWT = owner), so
// it binds the full-access env: memory read+write, unchanged behavior.
msgs := userChatMessages(req.Text, req.Image)
// Thread the client's deviceID into the orchestrator via ctx so both the
// persisted user turn and the assistant reply are tagged (design/16 §4.5).
ctx = withDeviceID(ctx, req.DeviceID)
// 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 = withMode(withDeviceID(ctx, req.DeviceID), req.Mode)
if h.orchestrator != nil {
_ = h.orchestrator.Submit(ctx, req.SessionID, msgs, loop.OwnerEnv())
return
@@ -312,6 +313,10 @@ type chatStreamRequest struct {
// (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 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 string `json:"mode,omitempty"`
}
// handleWS upgrades /ws/chat after authenticating the JWT from the query token or
@@ -439,13 +444,14 @@ func (c *wsConn) enqueueJSON(ev wsEvent) {
}
// publishChatStream publishes a ws.chat streaming trigger (REST stream mode).
// deviceID is the sending browser/device id carried on the payload so the ws.chat
// consumer can tag the persisted turn (design/16 §4.5).
func (s *Server) publishChatStream(ctx context.Context, sessionID, text, image, deviceID string) {
// 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).
func (s *Server) publishChatStream(ctx context.Context, sessionID, text, image, deviceID, mode string) {
if s.bus == nil {
return
}
_ = s.bus.Publish(ctx, wsChatSubject, map[string]any{"sessionId": sessionID, "text": text, "image": image, "deviceId": deviceID})
_ = s.bus.Publish(ctx, wsChatSubject, map[string]any{"sessionId": sessionID, "text": text, "image": image, "deviceId": deviceID, "mode": mode})
}
// wsToken extracts the JWT from the ?token= query or the Authorization header.