feat(gateway): 忙时并行帧层(turn_begin + steer_ack.turnId + interrupt.turnId)
新下行帧 turn_begin:编排器每启动一路恰好一帧(单路 kind:"turn" / 并行路 "parallel"),
data{kind, messageIds, leg, activeTurns};角色门 fail-closed 仅 owner+admin,未进 frameReachesAll。
steer_ack 增帧壳 turnId(与该路 turn_begin 同源)—— 并行下没有它配不上正确的 turn.end。
上行 interrupt.turnId(仅 scope:"current" 生效):精确停某一路;目标不存在 ⇒ 定向
{type:"error",data:{code:"bad_request"}},不广播、不动任何会话;省略 turnId = v0.65 原口径(全部在途路)。
帧序按实现:session_busy / pending_drain → message_persisted(user) → turn_begin
(规范草案 §2.9 的时序图需按实现更正:messageIds 必须是已落库的权威 id,故只能在落库之后)。
测试:编排器侧 + 真 WS/JWT 端到端;变异 5 条(每路一帧 / 帧壳 turnId / 目标化打断 / 取消筛选)。
Docs 待升 v0.66(由监工登记)。
This commit is contained in:
1 parent
e5cc9c6054
commit
c0dc435770
13 files changed
+800
-45
No files matched your search
@@ -48,17 +48,24 @@ type steerTurn struct {
|
||||
orchestrator *Orchestrator
|
||||
sessionID string
|
||||
gen uint64
|
||||
consumed map[string]bool
|
||||
// turnID 是本路(=本回合)的权威回合标识:`steer_ack` 的**帧壳**必须带上它
|
||||
// (忙时并行规范草案 §2.5)。真并行下同一会话有两条交错的路,只按 sessionId
|
||||
// 分桶的话客户端不知道这条消息被**哪一路**吃进,灰态也就配不上正确的
|
||||
// `turn.end`。它在出生后不可变,因此这里存下来即可,无需在发帧时回查状态锁。
|
||||
turnID string
|
||||
consumed map[string]bool
|
||||
}
|
||||
|
||||
// beginSteer 为一个刚启动的回合建一份转向游标。编排器把它挂到**该回合自己的 ctx**
|
||||
// 上(loop.WithSteerSource),因此每个回合、每次续跑各有一份独立游标,上一个回合
|
||||
// 已吃进的消息绝不会被下一个回合重复注入或漏掉。
|
||||
func (o *Orchestrator) beginSteer(sessionID string, gen uint64) *steerTurn {
|
||||
// 已吃进的消息绝不会被下一个回合重复注入或漏掉。turnID 是该路的权威回合标识,
|
||||
// 与 `turn_begin` 帧壳同值(客户端靠它把 steer_ack 配到正确的 `turn.end`)。
|
||||
func (o *Orchestrator) beginSteer(sessionID string, gen uint64, turnID string) *steerTurn {
|
||||
return &steerTurn{
|
||||
orchestrator: o,
|
||||
sessionID: sessionID,
|
||||
gen: gen,
|
||||
turnID: turnID,
|
||||
consumed: make(map[string]bool),
|
||||
}
|
||||
}
|
||||
@@ -112,7 +119,10 @@ func (s *steerTurn) Steer(ctx context.Context, req loop.SteerRequest) []model.Me
|
||||
// steer_ack:该消息已被本回合吃进、尚未被正面回答(前端据此把它渲染成
|
||||
// 灰态,design/15 §5)。它携带具体消息身份,因此不进 frameReachesAll 的
|
||||
// 状态机信号白名单,落 fail-closed 默认档:只发 owner + admin。
|
||||
o.emitState(sessionID, wsSteerAck, map[string]any{"messageId": c.row.ID, "atStep": req.Step})
|
||||
//
|
||||
// 帧壳必须带本路的 turnId(忙时并行规范草案 §2.5):真并行下客户端要
|
||||
// 靠它把这条消息配到**正确那一路**的 `turn.end`,否则灰态收不了口。
|
||||
o.emitTurnState(sessionID, s.turnID, wsSteerAck, map[string]any{"messageId": c.row.ID, "atStep": req.Step})
|
||||
out = append(out, transcriptMessage(c.row))
|
||||
}
|
||||
steerLog(o).Info("pending steered", "session", sessionID, "step", req.Step, "messages", len(out))
|
||||
|
||||
Reference in new issue
Block a user