docs(gateway): gateway 剩余文件注释改中文(逐行 1:1,代码零字节改动)
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批:
已推库 16 个文件 + 本批剩余文件)。
校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0);
全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
This commit is contained in:
AskaEth committed 2026-10-03 22:28:25 +08:00
1 parent 91e8d7a029
commit 074ffc4ebf
73 files changed
+2529 -2529

No files matched your search

+59 -59
View File
@@ -11,82 +11,82 @@ import (
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
)
// errPersonaBindingUnsupported is returned by newSubSession when a caller asks for
// a role-bound session but the injected history store cannot bind roles. The
// handler maps it to 501 rather than pretending the binding landed.
// errPersonaBindingUnsupported 由 newSubSession 在调用方要求一个
// 绑定角色的会话、但注入的 history 存储无法绑定角色时返回。
// handler 把它映射为 501,而不是假装绑定已生效。
var errPersonaBindingUnsupported = errors.New("gateway: history store does not support session persona binding")
// This file owns the gateway's turn-environment resolution (DevDocs/design/22
// §2/§4.5): which role answers, expressed as the loop.SessionEnv the turn runs
// under.
// 本文件拥有网关的回合环境解析(DevDocs/design/22
// §2/§4.5):由哪个角色应答,表达为该回合运行的
// loop.SessionEnv 环境。
//
// Precedence (design/22 §2, top down):
// 1. request-level override — NOT implemented in this batch (batch 1 exposes the
// session-level binding; a per-request override is additive and unpersisted);
// 2. session-level binding — chat_sessions.persona_id, resolved here;
// 3. channel-level mapping — batch 2 (channels.json / channel_sessions);
// 4. global default — expressed as PersonaID == "", which the loop resolves to its
// configured default role (Options.PersonaID). That is what makes every
// pre-existing session behave exactly as before.
// 优先级(design/22 §2,自上而下):
// 1. 请求级覆盖 —— 本批次不实现(批次 1 暴露的是
// 会话级绑定;按请求覆盖是加性且不持久化的);
// 2. 会话级绑定 —— chat_sessions.persona_id,在此解析;
// 3. 渠道级映射 —— 批次 2(channels.json / channel_sessions);
// 4. 全局默认 —— 表达为 PersonaID == "",由 loop 解析到其
// 已配置的默认角色(Options.PersonaID)。这正是让每个
// 既有会话的行为与以往完全一致的原因。
//
// The env's OTHER fields are untouched: ActorEnv and MemWritable still come from
// loop.OwnerEnv() for these owner-facing Web/voice paths. That is the identity red
// line — who may do what is decided by the channel actor metadata (here: a JWT
// owner), never by the role being played. Persona selection only changes the
// prompt, the memory namespace, the emotion machine and the thinking budget.
// env 的其他字段不受影响:对这些面向 owner 的 Web/语音路径,
// ActorEnv 与 MemWritable 仍来自 loop.OwnerEnv()。这就是身份红线
// —— 谁可以做什么由 channel 的 actor 元数据决定(此处是一个 JWT
// owner),绝不是由所扮演的角色决定。人格选择只改变
// 提示词、记忆命名空间、情绪机与思考预算。
// sessionPersonaReader is the optional narrower view of HistoryStore used to read a
// session's bound role. *history.Store implements it; a store that does not (a test
// fake, or a store predating design/22) simply yields "" — the global default — so
// the gateway keeps working.
// sessionPersonaReader 是 HistoryStore 可选的更窄视图,用于读取
// 会话的绑定角色。*history.Store 实现了它;未实现的存储(测试
// 替身,或早于 design/22 的存储)只是得到 "" —— 全局默认 —— 因此
// 网关继续工作。
type sessionPersonaReader interface {
SessionPersona(ctx context.Context, sessionID string) (string, error)
}
// sessionPersonaWriter is the optional narrower view of HistoryStore for writing a
// session's role binding. A session's role is decided at creation (design/22 §8-1),
// so no request path rebinds one any more — creation goes through subSessionCreator —
// and the gateway no longer type-asserts this interface. It stays next to the reader
// and the creator so the store's role-binding surface is documented in one place:
// *history.Store still implements SetSessionPersona (design/22 §4.1) and the
// admin/owner write path that would use it is a separate endpoint, not a PATCH.
// sessionPersonaWriter 是 HistoryStore 可选的更窄视图,用于写入
// 会话的角色绑定。会话角色在创建时决定(design/22 §8-1),
// 因此不再有任何请求路径重绑 —— 创建走 subSessionCreator ——
// 而网关也不再对该接口做类型断言。它留在读取器
// 与创建器旁边,使存储的角色绑定表面集中记录在一处:
// *history.Store 仍实现 SetSessionPersona(design/22 §4.1),而会用它的
// 管理/owner 写路径是独立端点,不是 PATCH。
type sessionPersonaWriter interface {
SetSessionPersona(ctx context.Context, sessionID, personaID string) error
}
// subSessionCreator is the optional narrower view of HistoryStore used to create a
// session already bound to a role (POST /sessions {personaId}, design/22 §4.5).
// *history.Store implements it; a store without it can only create an unbound
// session, so a request that explicitly asks for a role is refused with 501.
// subSessionCreator 是 HistoryStore 可选的更窄视图,用于创建一个
// 已绑定角色的会话(POST /sessions {personaId},design/22 §4.5)。
// *history.Store 实现了它;没有它的存储只能创建未绑定的
// 会话,因此显式要求角色的请求以 501 拒绝。
type subSessionCreator interface {
NewSubSessionFor(ctx context.Context, ownerID, personaID string) (string, error)
}
// turnEnvResolver builds the loop session environment for one turn. It holds the
// two read-only dependencies the resolution needs, so both the HTTP/WS handlers
// and the hub resolve roles through the same code path (design/22 §4.5: one
// resolver, not a hardcoded loop.OwnerEnv() per entry point).
// turnEnvResolver 为一个回合构建 loop 会话环境。它持有
// 解析所需的两个只读依赖,因此 HTTP/WS handler
// 与 hub 都经同一条代码路径解析角色(design/22 §4.5:一个
// 解析器,而不是每个入口各硬编码一份 loop.OwnerEnv())。
type turnEnvResolver struct {
history HistoryStore
personas PersonaStore
log *slog.Logger
}
// owner returns the per-turn environment for one owner-facing turn of sessionID:
// the full-access owner env (identity unchanged) plus the session's bound role.
// A blank role means "inherit the global default"; the loop performs that final
// fallback, so the gateway never has to know the default role's id.
// owner 返回 sessionID 一个面向 owner 回合的每回合环境:
// 全访问 owner env(身份不变)加上该会话绑定的角色。
// 空白角色意味着「继承全局默认」;loop 执行那最后一次
// 回退,因此网关永远不必知道默认角色的 id。
func (t turnEnvResolver) owner(ctx context.Context, sessionID string) loop.SessionEnv {
env := loop.OwnerEnv()
env.PersonaID = t.sessionPersona(ctx, sessionID)
return env
}
// sessionPersona returns the role bound to sessionID, or "" when the session
// inherits the global default. An unknown session, a store without the reader, or
// a read error all degrade to "" (the default) rather than failing the turn: a
// chat must never be blocked by a metadata lookup, and the fallback is the
// pre-multi-character behavior.
// sessionPersona 返回绑定到 sessionID 的角色,会话继承全局
// 默认时返回 ""。未知会话、没有读取器的存储、或
// 读错误都退化为 ""(默认),而不是让该回合失败:
// 聊天绝不能被一次元数据查找阻塞,而该回退正是
// 多角色之前的行为。
func (t turnEnvResolver) sessionPersona(ctx context.Context, sessionID string) string {
if t.history == nil || sessionID == "" {
return ""
@@ -105,10 +105,10 @@ func (t turnEnvResolver) sessionPersona(ctx context.Context, sessionID string) s
return personaID
}
// defaultPersonaID returns the persona id of the library's default character, or
// "" when no persona store is wired or no character is marked default. It is the
// gateway-side view of owner.yaml:defaultPersona used where a role id is needed
// without a session (the admin memory search fallback, design/22 §4.5).
// defaultPersonaID 返回库中默认角色的人格 id,未接线人格
// 存储或没有角色被标记为默认时返回 ""。它是
// owner.yaml:defaultPersona 的网关侧视图,用于在没有会话时
// 需要角色 id 的场合(管理记忆搜索回退,design/22 §4.5)。
func (s *Server) defaultPersonaID() string {
if s.personas == nil {
return ""
@@ -121,10 +121,10 @@ func (s *Server) defaultPersonaID() string {
return ""
}
// requireKnownPersona rejects a personaId the registry does not know, answering
// 400 and returning false. An empty id is always allowed (it means "inherit the
// global default"), and a gateway without a persona store cannot validate, so the
// binding is accepted there and simply resolves as an unknown role later.
// requireKnownPersona 拒绝注册表不认识的 personaId,返回
// 400 并返回 false。空 id 一律允许(它意味着「继承
// 全局默认」),而没有 persona 存储的网关无法校验,因此
// 那里接受该绑定,之后只是解析为未知角色。
func (s *Server) requireKnownPersona(w http.ResponseWriter, personaID string) bool {
if personaID == "" || s.personas == nil {
return true
@@ -136,10 +136,10 @@ func (s *Server) requireKnownPersona(w http.ResponseWriter, personaID string) bo
return true
}
// newSubSession creates a sub session bound to personaID, using the role-aware
// creator when the store provides one. A store without it can only create an
// unbound session, so an explicit role request is refused (501) instead of
// silently dropping the binding.
// newSubSession 创建一个绑定到 personaID 的子会话,存储提供
// 角色感知创建器时使用它。没有它的存储只能创建
// 未绑定会话,因此显式的角色请求被拒绝(501),而不是
// 静默丢弃该绑定。
func (s *Server) newSubSession(ctx context.Context, ownerID, personaID string) (string, error) {
creator, ok := s.history.(subSessionCreator)
if !ok {