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:
AskaEth committed 2026-10-05 09:38:11 +08:00
1 parent e5cc9c6054
commit c0dc435770
13 files changed
+800 -45

No files matched your search

+7 -3
View File
@@ -22,9 +22,13 @@ const wsSubagentResult = "subagent_result"
// (「已读、未答」,对齐 Claude Code 的 gray-until-responding);「正面回答」是随后
// 抵达的 message.* / turn.end 帧,不需要第二个确认帧。
//
// 帧壳:{type:"steer_ack", sessionId}。载荷 data:{messageId, atStep} —— messageId
// 是那条用户消息的权威 id(客户端据此把本地乐观气泡对齐到它,而不是靠文本相等这类
// 脆弱匹配),atStep 是吃进它的模型步序号(从 1 开始)。
// 帧壳:{type:"steer_ack", sessionId, turnId}。载荷 data:{messageId, atStep} ——
// messageId 是那条用户消息的权威 id(客户端据此把本地乐观气泡对齐到它,而不是靠
// 文本相等这类脆弱匹配),atStep 是吃进它的模型步序号(从 1 开始)。
//
// 帧壳的 turnId 是**吃进它的那一路**的权威回合标识(design/15 §2.5.9 /
// 忙时并行规范草案 §2.5):灰态要靠它与该路的 `turn.end` 配对才能收口,而真并行
// 下同一会话有两条交错的路 —— 少了它,客户端只能猜这条消息属于哪一路。
//
// 它携带具体消息身份(客户端能定位到某一条气泡,而不只是会话忙闲),因此**不登记**
// 进 frameReachesAll 的「状态机信号」白名单,落 design/11 §7.1 的 fail-closed 默认