package gateway import ( "context" "time" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop" "git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model" ) // 本文件归属会话编排器的**待处理队列**(design/15 §2.1/§2.2):忙时到达、且 // 未声明并行(或并行额度已满)的消息怎么入队、怎么落库、怎么回 ack。它从 // session_orchestrator.go 拆出,使那个文件继续只留状态机骨架(并守住 // 「函数 ≤80 行」的硬约束)。 // enqueueLocked 把一条忙时到达的用户消息放进有界待处理队列,并返回本次提交的 // 判定。它**必须在 o.mu 下调用**,并在返回前解锁:落库是慢操作,绝不能在状态 // 锁下做,因此它自己解锁后再做落库与状态帧广播(与 startParallelLocked 同一 // 约定,调用方直接 return 它的返回值)。 // // 入队时携带该消息到达时的设备 / 模式 / 语音回调,使它的续跑回合与「它就是当前 // 回合」时逐项一致(design/16 §4.5、§5.1;协同 C1)。落库两段式的第一段在这里: // 用户消息立刻进历史(抽取结论等回合跑起来才有,由续跑结算回写,见 // session_document_status.go)。 func (o *Orchestrator) enqueueLocked(ctx context.Context, st *sessionState, sessionID string, messages []model.Message, env loop.SessionEnv, deviceID, msgMode string, hint clientMsgHint) SubmitResult { st.pendingSeq++ seq := st.pendingSeq st.pending = append(st.pending, pendingMsg{ messages: messages, env: env, deviceID: deviceID, mode: msgMode, at: time.Now(), seq: seq, voice: voiceReplySinkFrom(ctx), }) depth := len(st.pending) mode := st.mode o.mu.Unlock() o.attachPendingDocuments(sessionID, seq, o.persistUserMessages(ctx, sessionID, messages, deviceID, msgMode, env.PersonaID)) // 声明了并行却落回排队时如实给出 reason,让客户端把气泡渲染成「排队中」而不是 // 「被拒」(design/15 §2.6);未声明时该键缺席,帧与今天逐字相同。 o.emitState(sessionID, EventPendingAck, pendingAckPayload(sessionID, mode, depth, parallelRequested(ctx))) return SubmitResult{Accepted: true, Pending: true, QueueDepth: depth, MessageID: hint.MessageID} }