docs(gateway): gateway 剩余文件注释改中文(逐行 1:1,代码零字节改动)
主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批: 已推库 16 个文件 + 本批剩余文件)。 校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0); 全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
This commit is contained in:
1 parent
91e8d7a029
commit
074ffc4ebf
73 files changed
+2529
-2529
No files matched your search
@@ -12,39 +12,39 @@ import (
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||||
)
|
||||
|
||||
// This file implements the session-message orchestrator (DevDocs/design/15).
|
||||
// 本文件实现会话消息编排器(DevDocs/design/15)。
|
||||
//
|
||||
// It is deliberately NOT a task engine. design/04 §2 (task/subagent) decides
|
||||
// whether a heavy job gets pushed to the background; this orchestrator decides
|
||||
// how NEW user messages queue and resume while a session is busy. The two layers
|
||||
// are orthogonal and compose: a session runs a task (04), a new message arrives
|
||||
// mid-run (15), and the orchestrator holds it in a bounded pending queue and
|
||||
// resumes it with the previous turn's context once the current turn finishes.
|
||||
// 它刻意不是任务引擎。design/04 §2(task/subagent)决定
|
||||
// 一个重活是否被推到后台;本编排器决定
|
||||
// 会话忙碌时新的用户消息如何排队与续跑。两层
|
||||
// 正交且可组合:一个会话跑一个任务(04),新消息在
|
||||
// 运行中途到达(15),编排器把它握在有界待处理队列中,
|
||||
// 并在当前回合结束后带着上一个回合的上下文续跑它。
|
||||
//
|
||||
// Non-blocking is a hard constraint: Submit/Interrupt never wait on a running
|
||||
// turn. The queue is bounded per session and isolated across sessions, and no
|
||||
// identity decision is made here — the orchestrator only orders intent/messages,
|
||||
// identity stays in the channel actor metadata (project red line).
|
||||
// 非阻塞是硬约束:Submit/Interrupt 绝不等待运行中的
|
||||
// 回合。队列按会话有界,且跨会话隔离,这里不做
|
||||
// 任何身份判定 —— 编排器只排序意图/消息,
|
||||
// 身份始终留在 channel 的 actor 元数据中(项目红线)。
|
||||
|
||||
// Mode is the per-session busy-state lane of the orchestrator. It is exposed so
|
||||
// a loop/subagent hook can raise it to executing/subagent (an injection point
|
||||
// the composition root may wire later), but the orchestrator itself tracks an
|
||||
// abstract "busy" via mode != ModeIdle.
|
||||
// Mode 是编排器的每会话忙碌状态通道。它被暴露出来,
|
||||
// 使 loop/subagent 钩子能把它升到 executing/subagent(一个
|
||||
// 组合根之后可能接线的注入点),但编排器自身只用
|
||||
// mode != ModeIdle 跟踪抽象的「忙碌」。
|
||||
type Mode string
|
||||
|
||||
const (
|
||||
// ModeIdle means the session has no turn in flight and no resume pending.
|
||||
// ModeIdle 意味着该会话没有在飞回合、也没有待处理续跑。
|
||||
ModeIdle Mode = "idle"
|
||||
// ModeThinking means a turn is in flight (the initial start state).
|
||||
// ModeThinking 意味着有回合在飞(起始的初始状态)。
|
||||
ModeThinking Mode = "thinking"
|
||||
// ModeExecuting means a turn is executing tools/substeps.
|
||||
// ModeExecuting 意味着回合正在执行工具/子步骤。
|
||||
ModeExecuting Mode = "executing"
|
||||
// ModeSubagent means a turn is delegated to a subagent (design/04 §2).
|
||||
// ModeSubagent 意味着回合被委派给子代理(design/04 §2)。
|
||||
ModeSubagent Mode = "subagent"
|
||||
)
|
||||
|
||||
// State event types pushed over the WS channel to the frontend. The data map of
|
||||
// each carries sessionId so a client can correlate per-session state.
|
||||
// 经 WS 通道推给前端的状态事件类型。每个的 data map 都
|
||||
// 携带 sessionId,使客户端能按会话关联状态。
|
||||
const (
|
||||
EventSessionBusy = "session_busy"
|
||||
EventPendingAck = "pending_ack"
|
||||
@@ -55,85 +55,85 @@ const (
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultMaxPending bounds the per-session pending queue (design/15 §4).
|
||||
// defaultMaxPending 限定每会话的待处理队列(design/15 §4)。
|
||||
defaultMaxPending = 5
|
||||
// defaultMaxTail bounds the conversation tail kept for continuation, so the
|
||||
// resumed context is incremental, not a full replay (design/15 §2.2, §4).
|
||||
// defaultMaxTail 限定为续跑而保留的对话尾部,使
|
||||
// 续跑的上下文是增量的,而不是全量重放(design/15 §2.2、§4)。
|
||||
defaultMaxTail = 20
|
||||
// defaultSessionID is used when a caller passes an empty session id.
|
||||
// 调用方传入空会话 id 时使用 defaultSessionID。
|
||||
defaultSessionID = "web"
|
||||
)
|
||||
|
||||
// SubmitResult is the outcome of handing one user turn to the orchestrator. It
|
||||
// is always returned without blocking: Accepted is false only when the turn was
|
||||
// rejected (pending queue full, or an empty turn), never because a turn is busy.
|
||||
// SubmitResult 是把一个用户回合交给编排器的结果。它
|
||||
// 总是不阻塞地返回:Accepted 为 false 仅在回合被
|
||||
// 拒绝时(待处理队列满,或空回合),绝不因为回合忙碌。
|
||||
type SubmitResult struct {
|
||||
// Accepted reports whether the turn was taken. A false value with
|
||||
// PendingFull or Empty carries the reject reason; it never means "try later".
|
||||
// Accepted 报告回合是否被接收。为 false 且带
|
||||
// PendingFull 或 Empty 时携带拒绝原因;它绝不意味着「稍后重试」。
|
||||
Accepted bool
|
||||
// Immediate reports that a turn started now because the session was idle.
|
||||
// Immediate 报告因会话空闲而立即开始了一个回合。
|
||||
Immediate bool
|
||||
// Pending reports that the turn was queued behind a busy turn.
|
||||
// Pending 报告该回合排在忙碌回合之后入队了。
|
||||
Pending bool
|
||||
// QueueDepth is the pending depth after this insertion (0 when Immediate).
|
||||
// QueueDepth 是本次插入后的待处理深度(Immediate 时为 0)。
|
||||
QueueDepth int
|
||||
// PendingFull reports the turn was rejected because the queue is at capacity
|
||||
// (design/15 §4: bounded pending, over-capacity is refused with a hint).
|
||||
// PendingFull 报告回合被拒绝,因为队列已满
|
||||
// (design/15 §4:有界待处理,超容量时拒绝并给出提示)。
|
||||
PendingFull bool
|
||||
// Empty reports the turn was rejected because it carried no text or image.
|
||||
// Empty 报告回合因既无文本也无图片而被拒绝。
|
||||
Empty bool
|
||||
// Err is set on the synchronous path when the run errored; the REST caller
|
||||
// uses it to return a 5xx rather than a fabricated reply.
|
||||
// Err 在同步路径上于运行出错时设置;REST 调用方
|
||||
// 用它返回 5xx,而不是编造回复。
|
||||
Err error
|
||||
}
|
||||
|
||||
// TurnRunner executes one agent turn and returns its result. The gateway wires
|
||||
// it to the loop plus the streaming broadcast, so the orchestrator stays agnostic
|
||||
// of how a turn is physically driven (it never re-implements a loop engine).
|
||||
// TurnRunner 执行一个 agent 回合并返回其结果。网关把它
|
||||
// 接到 loop 加上流式广播上,因此编排器对回合
|
||||
// 实际如何驱动保持不可知(它绝不重新实现 loop 引擎)。
|
||||
type TurnRunner func(ctx context.Context, sessionID string, messages []model.Message, env loop.SessionEnv) (*loop.Result, error)
|
||||
|
||||
// StateSink reports a per-session state transition (busy/pending/idle/interrupt)
|
||||
// to the transport. The gateway wires it to the WS hub broadcast.
|
||||
// StateSink 把每会话状态转移(busy/pending/idle/interrupt)报告给
|
||||
// 传输层。网关把它接到 WS hub 广播上。
|
||||
type StateSink func(sessionID, typ string, data map[string]any)
|
||||
|
||||
// OrchestratorOptions configures an Orchestrator. Run and Log are required
|
||||
// (Log may be a discard logger); Emit is optional (state events are dropped when
|
||||
// nil). MaxPending/MaxTail fall back to the defaults when non-positive.
|
||||
// OrchestratorOptions 配置 Orchestrator。Run 与 Log 必需
|
||||
// (Log 可以是丢弃型 logger);Emit 可选(为 nil 时状态事件被丢弃)。
|
||||
// MaxPending/MaxTail 非正时回退到默认值。
|
||||
type OrchestratorOptions struct {
|
||||
Run TurnRunner
|
||||
Emit StateSink
|
||||
Log *slog.Logger
|
||||
MaxPending int
|
||||
MaxTail int
|
||||
// History is the optional persistent chat-history store (design/16 §3.3).
|
||||
// When nil every Append is skipped, so the orchestrator runs without storage
|
||||
// (tests / a gateway built without a history store).
|
||||
// History 是可选的持久化聊天历史存储(design/16 §3.3)。
|
||||
// 为 nil 时每次 Append 都被跳过,因此编排器无需存储也能运行
|
||||
// (测试 / 未接 history 存储构建的网关)。
|
||||
History HistoryStore
|
||||
// OnPersisted, when non-nil, is called with the complete stored message right
|
||||
// after a user or assistant message is written, so the transport can broadcast
|
||||
// a `message_persisted` event for cross-end realtime sync (design/16 §4.2).
|
||||
// Dropped when nil (tests / a gateway built without a transport hook).
|
||||
// OnPersisted 非 nil 时,会在用户或助手消息写入后立即
|
||||
// 带着完整已存消息被调用,使传输层能广播
|
||||
// 一个 `message_persisted` 事件用于跨端实时同步(design/16 §4.2)。
|
||||
// 为 nil 时丢弃(测试 / 未接传输钩子构建的网关)。
|
||||
OnPersisted func(history.Message)
|
||||
}
|
||||
|
||||
// sessionState is the per-session message state (design/15 §2). Every mutation
|
||||
// happens under the Orchestrator mutex; the actual loop run is done outside the
|
||||
// lock so a slow model call never blocks Submit.
|
||||
// sessionState 是每会话的消息状态(design/15 §2)。每次变更
|
||||
// 都在 Orchestrator 互斥锁下发生;实际的 loop 运行在锁外
|
||||
// 完成,因此慢的模型调用绝不阻塞 Submit。
|
||||
type sessionState struct {
|
||||
mode Mode
|
||||
gen uint64 // generation: bumped on interrupt and on a fresh top-level turn
|
||||
gen uint64 // generation:在打断时以及新的顶层回合时递增
|
||||
cancel context.CancelFunc
|
||||
pending []pendingMsg
|
||||
tail []model.Message // bounded conversation tail for context continuation
|
||||
tail []model.Message // 用于上下文续跑的有界对话尾部
|
||||
}
|
||||
|
||||
// pendingMsg is one queued user turn. It carries its own env so a resume runs
|
||||
// under the same memory-isolation boundary — and, since design/22, the same role
|
||||
// (env.PersonaID) — the message arrived with, plus the client deviceID of the
|
||||
// originating turn (design/16 §4.5) so the resumed assistant reply is tagged with
|
||||
// the same client that sent the message, and the turn's behavior mode (design/16
|
||||
// §5.1) so the resumed reply is persisted under the same mode as the user message
|
||||
// that started it.
|
||||
// pendingMsg 是一条已排队用户回合。它携带自己的 env,使续跑
|
||||
// 在该消息到达时所用的同一记忆隔离边界 —— 且自 design/22 起
|
||||
// 同一角色(env.PersonaID)—— 之下运行,并携带产出该回合的
|
||||
// 客户端 deviceID(design/16 §4.5),使续跑的助手回复被标上
|
||||
// 发送该消息的同一个客户端;以及该回合的行为模式(design/16
|
||||
// §5.1),使续跑回复与发起它的用户消息以同一模式
|
||||
// 被持久化。
|
||||
type pendingMsg struct {
|
||||
messages []model.Message
|
||||
env loop.SessionEnv
|
||||
@@ -142,10 +142,10 @@ type pendingMsg struct {
|
||||
at time.Time
|
||||
}
|
||||
|
||||
// Orchestrator owns the per-session message state machine. It is concurrency-safe
|
||||
// and non-blocking: Submit/Interrupt lock only to mutate state, then hand the
|
||||
// actual run off to a goroutine. It is the single owner of "is this session busy"
|
||||
// for the chat path; the loop/task/subagent engines are reused, not re-created.
|
||||
// Orchestrator 拥有每会话的消息状态机。它对并发安全
|
||||
// 且非阻塞:Submit/Interrupt 只在变更状态时加锁,然后把
|
||||
// 实际运行交给一个 goroutine。它是 chat 路径上「该会话是否
|
||||
// 忙碌」的唯一归属者;loop/task/subagent 引擎被复用,而不是重建。
|
||||
type Orchestrator struct {
|
||||
mu sync.Mutex
|
||||
states map[string]*sessionState
|
||||
@@ -158,10 +158,10 @@ type Orchestrator struct {
|
||||
maxTail int
|
||||
}
|
||||
|
||||
// NewOrchestrator builds an orchestrator from opts. Run is required: it is the
|
||||
// turn-driver the orchestrator reuses (the loop, never a re-implemented engine).
|
||||
// A nil Run makes every Submit reject rather than run, so a misconfiguration
|
||||
// cannot silently leave a session busy. Tests inject a fake runner here.
|
||||
// NewOrchestrator 从 opts 构建编排器。Run 必需:它是编排器
|
||||
// 复用的回合驱动器(loop,绝不是重新实现的引擎)。
|
||||
// Run 为 nil 会让每次 Submit 拒绝而非运行,因此错误配置
|
||||
// 不可能静默地让会话处于忙碌。测试在此注入假 runner。
|
||||
func NewOrchestrator(opts OrchestratorOptions) *Orchestrator {
|
||||
maxPending := opts.MaxPending
|
||||
if maxPending <= 0 {
|
||||
@@ -186,27 +186,27 @@ func NewOrchestrator(opts OrchestratorOptions) *Orchestrator {
|
||||
}
|
||||
}
|
||||
|
||||
// Submit hands one user turn to the session. It never blocks: an idle session
|
||||
// starts a turn on a background goroutine and returns Immediate; a busy session
|
||||
// enqueues the turn into the bounded pending queue and returns Pending; a full
|
||||
// queue refuses with PendingFull. Interrupt words are NOT auto-detected here —
|
||||
// the caller (frontend/intent classifier) calls Interrupt when the user asks to
|
||||
// stop, per design/15 §3 (interrupt is an intent, not an identity decision).
|
||||
// Submit 把一个用户回合交给会话。它绝不阻塞:空闲会话在
|
||||
// 后台 goroutine 上启动回合并返回 Immediate;忙碌会话把
|
||||
// 回合入队到有界待处理队列并返回 Pending;队列
|
||||
// 满时以 PendingFull 拒绝。此处不自动检测打断词 ——
|
||||
// 调用方(前端/意图分类器)在用户要求停止时调用 Interrupt,
|
||||
// 依 design/15 §3(打断是一种意图,不是身份判定)。
|
||||
func (o *Orchestrator) Submit(ctx context.Context, sessionID string, messages []model.Message, env loop.SessionEnv) SubmitResult {
|
||||
sr, _ := o.submitInternal(ctx, o.normalize(sessionID), messages, env, false)
|
||||
return sr
|
||||
}
|
||||
|
||||
// SubmitSync is the synchronous variant used by the REST non-streaming path. An
|
||||
// idle session runs the turn to completion and returns the Result; a busy session
|
||||
// returns a Pending result without blocking so the caller can reply with a
|
||||
// non-blocking ack (design/15 §2.1). It never waits on an in-flight turn.
|
||||
// SubmitSync 是 REST 非流式路径所用的同步变体。空闲会话
|
||||
// 把回合跑完并返回 Result;忙碌会话不阻塞地返回
|
||||
// Pending 结果,使调用方能以非阻塞 ack 回复
|
||||
// (design/15 §2.1)。它绝不等待在飞回合。
|
||||
func (o *Orchestrator) SubmitSync(ctx context.Context, sessionID string, messages []model.Message, env loop.SessionEnv) (SubmitResult, *loop.Result) {
|
||||
return o.submitInternal(ctx, o.normalize(sessionID), messages, env, true)
|
||||
}
|
||||
|
||||
// submitInternal is the shared Submit/SubmitSync body. sync selects the
|
||||
// synchronous turn path (REST) vs the background goroutine path (WS).
|
||||
// submitInternal 是 Submit/SubmitSync 共用的主体。sync 选择
|
||||
// 同步回合路径(REST)还是后台 goroutine 路径(WS)。
|
||||
func (o *Orchestrator) submitInternal(ctx context.Context, sessionID string, messages []model.Message, env loop.SessionEnv, sync bool) (SubmitResult, *loop.Result) {
|
||||
if o.run == nil {
|
||||
o.log.Error("orchestrator submit", "err", "no runner configured", "session", sessionID)
|
||||
@@ -243,14 +243,14 @@ func (o *Orchestrator) submitInternal(ctx context.Context, sessionID string, mes
|
||||
st.gen++
|
||||
gen := st.gen
|
||||
st.mode = ModeThinking
|
||||
// A fresh turn continues the session's conversation: the persisted transcript
|
||||
// is seeded in front of the just-sent messages (design/15 §2.2/§4, design/22
|
||||
// §7-5), and its assistant utterances keep the role that spoke them. The read
|
||||
// happens here, before persistUserMessages below writes the new message, so the
|
||||
// message just sent is never duplicated in the context. A store without history
|
||||
// (or an empty session) leaves the turn on the caller's messages alone. It is a
|
||||
// bounded indexed read (transcriptContextCount rows) under the state lock, which
|
||||
// no running turn ever holds, so it cannot block a Submit behind a turn.
|
||||
// 新回合延续该会话的对话:持久化转录被播种在
|
||||
// 刚发送的消息之前(design/15 §2.2/§4,design/22
|
||||
// §7-5),且其助手话语保持说话时的角色。该读取
|
||||
// 在此发生,在下面 persistUserMessages 写入新消息之前,因此刚
|
||||
// 发送的消息绝不在上下文中重复。没有历史(或空会话)的
|
||||
// 存储让该回合只使用调用方给出的消息。它是在状态锁下
|
||||
// 的有界索引读取(transcriptContextCount 行),而状态锁
|
||||
// 从不会被运行中的回合持有,因此它无法把 Submit 堵在回合后面。
|
||||
turn := o.startTurnMessages(ctx, sessionID, messages)
|
||||
st.tail = o.boundedTail(turn)
|
||||
runCtx, cancel := context.WithCancel(ctx)
|
||||
@@ -258,8 +258,8 @@ func (o *Orchestrator) submitInternal(ctx context.Context, sessionID string, mes
|
||||
o.mu.Unlock()
|
||||
|
||||
o.emitState(sessionID, EventSessionBusy, map[string]any{"sessionId": sessionID, "mode": ModeThinking})
|
||||
// Only the messages that arrived now are persisted: the seeded transcript is
|
||||
// already durable, and re-appending it would duplicate the whole conversation.
|
||||
// 只有此刻到达的消息会被持久化:已播种的转录
|
||||
// 已经持久,重新追加它会重复整个对话。
|
||||
o.persistUserMessages(ctx, sessionID, messages, deviceID, msgMode, env.PersonaID)
|
||||
|
||||
if sync {
|
||||
@@ -275,12 +275,12 @@ func (o *Orchestrator) submitInternal(ctx context.Context, sessionID string, mes
|
||||
return SubmitResult{Accepted: true, Immediate: true}, nil
|
||||
}
|
||||
|
||||
// goTurn runs a single turn on a background goroutine and, when it finishes,
|
||||
// hands the outcome to completeTurn so the orchestrator can resume the queue.
|
||||
// deviceID tags the persistent assistant reply with the client that sent the turn
|
||||
// (design/16 §4.5), mode with the turn's behavior mode (design/16 §5.1) and env is
|
||||
// the turn's resolved role/isolation environment (design/22 §4.5), so the reply is
|
||||
// filed under the role that produced it.
|
||||
// goTurn 在后台 goroutine 上运行单个回合,并在它结束时
|
||||
// 把结果交给 completeTurn,使编排器能续跑队列。
|
||||
// deviceID 用发送该回合的客户端标注持久化助手回复
|
||||
// (design/16 §4.5),mode 用该回合的行为模式(design/16 §5.1),而 env 是
|
||||
// 该回合解析出的角色/隔离环境(design/22 §4.5),因此回复被
|
||||
// 归到产出它的角色名下。
|
||||
func (o *Orchestrator) goTurn(sessionID string, gen uint64, messages []model.Message, env loop.SessionEnv, ctx context.Context, cancel context.CancelFunc, deviceID, mode string) {
|
||||
defer cancel()
|
||||
res, err := o.run(ctx, sessionID, messages, env)
|
||||
@@ -290,15 +290,15 @@ func (o *Orchestrator) goTurn(sessionID string, gen uint64, messages []model.Mes
|
||||
_ = o.completeTurn(sessionID, gen, res, err, deviceID, mode, env.PersonaID)
|
||||
}
|
||||
|
||||
// completeTurn runs after a turn settles (either it finished, errored, or was
|
||||
// canceled). It folds the assistant reply into the session's continuation tail,
|
||||
// then pops the next pending turn and resumes it carrying that tail — so the next
|
||||
// message "remembers" the prior context instead of starting from a blank slate
|
||||
// (design/15 §2.2: incremental, not full replay). A stale generation (an
|
||||
// interrupt or a newer top-level turn superseded this one) is a no-op.
|
||||
// deviceID is the originating turn's client id, mode its behavior mode and
|
||||
// personaID the role it spoke as, all used to tag the persisted reply
|
||||
// (design/16 §4.5, §5.1; design/22 §7-5).
|
||||
// completeTurn 在一个回合结算后运行(它完成、出错或被
|
||||
// 取消)。它把助手回复折进会话的续跑尾部,
|
||||
// 然后弹出下一条待处理回合并带着该尾部续跑它 —— 因此下一条
|
||||
// 消息「记得」先前上下文,而不是从空白开始
|
||||
// (design/15 §2.2:增量,而不是全量重放)。陈旧的代次(一次
|
||||
// 打断或一个更新的顶层回合取代了本次)是空操作。
|
||||
// deviceID 是产出回合的客户端 id,mode 是其行为模式,
|
||||
// personaID 是它说话时的角色,都用于标注持久化的回复
|
||||
// (design/16 §4.5、§5.1;design/22 §7-5)。
|
||||
func (o *Orchestrator) completeTurn(sessionID string, gen uint64, res *loop.Result, runErr error, deviceID, mode, personaID string) error {
|
||||
o.persistAssistantReply(sessionID, res, deviceID, mode, personaID)
|
||||
o.mu.Lock()
|
||||
@@ -337,18 +337,18 @@ func (o *Orchestrator) completeTurn(sessionID string, gen uint64, res *loop.Resu
|
||||
return nil
|
||||
}
|
||||
|
||||
// Interrupt cancels the in-flight turn, clears the pending queue and returns the
|
||||
// session to idle (design/15 §3, the "停下/算了/换个话题" path). It is the only
|
||||
// way a busy session resets without draining the queue; the running goroutine's
|
||||
// completion becomes a stale generation and does not resume anything. It returns
|
||||
// the number of pending turns that were dropped.
|
||||
// Interrupt 取消在飞回合、清空待处理队列并把会话
|
||||
// 回到空闲(design/15 §3,「停下/算了/换个话题」路径)。它是忙碌
|
||||
// 会话不必排空队列就能复位的唯一方式;运行中 goroutine 的
|
||||
// 完成变成陈旧代次,不续跑任何东西。它返回
|
||||
// 被丢弃的待处理回合数。
|
||||
func (o *Orchestrator) Interrupt(sessionID string) int {
|
||||
return o.interrupt(o.normalize(sessionID))
|
||||
}
|
||||
|
||||
// InterruptAll cancels every in-flight session and clears all pending queues. It
|
||||
// is used on gateway shutdown so no background turn outlives the hub. It returns
|
||||
// the total number of dropped pending turns.
|
||||
// InterruptAll 取消每一个在飞会话并清空所有待处理队列。它
|
||||
// 在网关关闭时使用,使没有后台回合比 hub 活得更久。它返回
|
||||
// 被丢弃的待处理回合总数。
|
||||
func (o *Orchestrator) InterruptAll() int {
|
||||
o.mu.Lock()
|
||||
total := 0
|
||||
@@ -381,10 +381,10 @@ func (o *Orchestrator) interruptLocked(sessionID string, st *sessionState) int {
|
||||
return dropped
|
||||
}
|
||||
|
||||
// SetMode raises the reported busy-state lane of a session (thinking →
|
||||
// executing → subagent). It is an injection point a loop/subagent hook can call;
|
||||
// any non-idle mode keeps the session busy, and idle un-busies it. A nil session
|
||||
// is created lazily so a hook may set a mode before the first message.
|
||||
// SetMode 提升某会话所报告的忙碌状态通道(thinking →
|
||||
// executing → subagent)。它是 loop/subagent 钩子可调用的注入点;
|
||||
// 任何非空闲模式都让会话保持忙碌,而 idle 解除忙碌。为 nil 的会话
|
||||
// 被惰性创建,因此钩子可以在第一条消息之前设模式。
|
||||
func (o *Orchestrator) SetMode(sessionID string, mode Mode) {
|
||||
sessionID = o.normalize(sessionID)
|
||||
o.mu.Lock()
|
||||
@@ -403,7 +403,7 @@ func (o *Orchestrator) SetMode(sessionID string, mode Mode) {
|
||||
o.emitState(sessionID, EventSessionBusy, map[string]any{"sessionId": sessionID, "mode": mode})
|
||||
}
|
||||
|
||||
// Busy reports whether the session currently has a turn in flight.
|
||||
// Busy 报告该会话当前是否有在飞回合。
|
||||
func (o *Orchestrator) Busy(sessionID string) bool {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
@@ -411,7 +411,7 @@ func (o *Orchestrator) Busy(sessionID string) bool {
|
||||
return st != nil && st.mode != ModeIdle
|
||||
}
|
||||
|
||||
// QueueDepth reports the number of pending turns for a session.
|
||||
// QueueDepth 报告某会话的待处理回合数。
|
||||
func (o *Orchestrator) QueueDepth(sessionID string) int {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
@@ -422,7 +422,7 @@ func (o *Orchestrator) QueueDepth(sessionID string) int {
|
||||
return len(st.pending)
|
||||
}
|
||||
|
||||
// ModeOf reports the current busy-state lane of a session (idle when unknown).
|
||||
// ModeOf 报告某会话当前的忙碌状态通道(未知时为 idle)。
|
||||
func (o *Orchestrator) ModeOf(sessionID string) Mode {
|
||||
o.mu.Lock()
|
||||
defer o.mu.Unlock()
|
||||
@@ -433,8 +433,8 @@ func (o *Orchestrator) ModeOf(sessionID string) Mode {
|
||||
return st.mode
|
||||
}
|
||||
|
||||
// normalize maps an empty session id to the default so the state map is stable
|
||||
// for callers that omit it (the Web main session).
|
||||
// normalize 把空会话 id 映射为默认值,使状态 map 对省略它的
|
||||
// 调用方保持稳定(Web 主会话)。
|
||||
func (o *Orchestrator) normalize(sessionID string) string {
|
||||
if sessionID == "" {
|
||||
return defaultSessionID
|
||||
@@ -442,33 +442,33 @@ func (o *Orchestrator) normalize(sessionID string) string {
|
||||
return sessionID
|
||||
}
|
||||
|
||||
// emitState forwards a state transition to the transport when one is wired.
|
||||
// emitState 在接线了传输层时把状态转移转发给它。
|
||||
func (o *Orchestrator) emitState(sessionID, typ string, data map[string]any) {
|
||||
if o.emit != nil {
|
||||
o.emit(sessionID, typ, data)
|
||||
}
|
||||
}
|
||||
|
||||
// notifyPersisted hands a just-persisted message to the transport hook when one is
|
||||
// wired, so the gateway can broadcast `message_persisted` for cross-end sync.
|
||||
// notifyPersisted 在接线了传输钩子时把刚持久化的消息交给它,
|
||||
// 使网关能广播 `message_persisted` 以进行跨端同步。
|
||||
func (o *Orchestrator) notifyPersisted(msg history.Message) {
|
||||
if o.onPersisted != nil {
|
||||
o.onPersisted(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// deviceIDContextKey is the context key carrying a message's client deviceID
|
||||
// (design/16 §4.5) from the HTTP/WS boundary into the orchestrator's persistence.
|
||||
// deviceIDContextKey 是携带消息客户端 deviceID 的 context 键
|
||||
// (design/16 §4.5)从 HTTP/WS 边界引入编排器的持久化。
|
||||
type deviceIDContextKey struct{}
|
||||
|
||||
// withDeviceID returns a context carrying deviceID so the orchestrator can tag the
|
||||
// persisted user message. An empty deviceID is a no-op (no tag is written).
|
||||
// withDeviceID 返回携带 deviceID 的 context,使编排器能标注
|
||||
// 持久化的用户消息。空 deviceID 是空操作(不写入标签)。
|
||||
func withDeviceID(ctx context.Context, deviceID string) context.Context {
|
||||
return context.WithValue(ctx, deviceIDContextKey{}, deviceID)
|
||||
}
|
||||
|
||||
// deviceIDFrom returns the deviceID carried by ctx, or "" when none was set. It
|
||||
// returns only well-formed non-empty values; anything else is treated as absent.
|
||||
// deviceIDFrom 返回 ctx 携带的 deviceID,未设置时返回 ""。它
|
||||
// 只返回格式良好的非空值;其他一律视为缺失。
|
||||
func deviceIDFrom(ctx context.Context) string {
|
||||
v, _ := ctx.Value(deviceIDContextKey{}).(string)
|
||||
return v
|
||||
|
||||
Reference in new issue
Block a user