主人 2026-10-03 拍板「注释一律中文」;gateway 至此整包注释中文化完成(本会话共 3 批: 已推库 16 个文件 + 本批剩余文件)。 校验:机械校验(非注释改动行)= 空;剥注释后代码文本逐行比对全等(不一致文件数 0); 全树 go test ./... 58 包 ok / 0 FAIL;交叉编译绿。
249 lines
12 KiB
Go
249 lines
12 KiB
Go
package gateway
|
||
|
||
import (
|
||
"context"
|
||
"net/http"
|
||
"time"
|
||
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/auth"
|
||
capreg "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/registry"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/channel/session"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/config"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/iot"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/persona/emotion"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/persona/loader"
|
||
personareg "git.yeij.top/AskaEth/Cyrene_v2/internal/persona/registry"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/history"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/knowledge"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/loop"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/memory"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/orchestration"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/task"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/services/automations"
|
||
"git.yeij.top/AskaEth/Cyrene_v2/internal/services/backthink"
|
||
)
|
||
|
||
// 本文件声明网关所消费的依赖契约。每一个都是窄接口,
|
||
// 由组合根注入的具体后端满足(见 deps.go 中的
|
||
// Deps),因此网关绝不直接取用具体实现,
|
||
// 测试也能替换成桩。
|
||
|
||
// Authenticator 抽象凭据 + RBAC 服务。*auth.JWTService 是
|
||
// 生产实现;它是接口,因此测试可替换为 mock。
|
||
type Authenticator interface {
|
||
// Sign 为 sub 签发一个带给定角色与生命周期的 token。
|
||
Sign(sub string, role auth.Role, ttl time.Duration) (string, error)
|
||
// Verify 校验一个 token 并返回其 claims。
|
||
Verify(token string) (*auth.Claims, error)
|
||
// Authorize 返回一个 HTTP 中间件,强制至少一个所需角色。
|
||
Authorize(required ...auth.Role) func(http.Handler) http.Handler
|
||
}
|
||
|
||
// CapabilityStore 抽象管理 API 中用于枚举插件与渠道的全局
|
||
// 能力注册表。*capability.Registry 是生产
|
||
// 实现。
|
||
type CapabilityStore interface {
|
||
// List 返回 Kind 匹配的每一个能力,kind 为空时
|
||
// 返回全部。
|
||
List(kind capreg.Kind) []capreg.Capability
|
||
// SetPolicy 替换指定能力的可达性标记。
|
||
SetPolicy(name string, p capreg.ToolPolicy) error
|
||
}
|
||
|
||
// Chatter 运行单次 agent 回合。*loop.Loop 是生产实现。
|
||
type Chatter interface {
|
||
// Run 为 sessionID 驱动一个回合并返回最终答案。
|
||
Run(ctx context.Context, sessionID string, messages []model.Message) (*loop.Result, error)
|
||
}
|
||
|
||
// SessionStore 列出已知的渠道会话。*session.SessionManager 是
|
||
// 生产实现。
|
||
type SessionStore interface {
|
||
List(ctx context.Context) ([]session.Session, error)
|
||
}
|
||
|
||
// PersonaStore 列出已注册角色(每个与一个人格一一绑定)。
|
||
// *persona/registry.Registry 是生产实现。网关把
|
||
// 每个 Character 映射为其线上摘要;该接口刻意返回
|
||
// 领域类型,使线上结构不进入存储契约。
|
||
type PersonaStore interface {
|
||
List() []personareg.Character
|
||
// Persona 返回给定 id 的人格(用于读取其视觉资产)。
|
||
// 生产注册表实现了它;线上摘要从它读取 Avatar /
|
||
// ChatBackground,使每个角色的视觉保持角色作用域。
|
||
Persona(id string) (*loader.Persona, bool)
|
||
}
|
||
|
||
// PersonaMoodStore 是人格存储的*可选*能力,该存储同时持有
|
||
// 每人格的情绪状态(design/01 §2:情绪跟随人格)。
|
||
// 生产实现 *personareg.Registry 实现了它;网关通过对这个窄
|
||
// 接口做断言来读取某角色的 mood,因此只列出人格的存储
|
||
// (测试替身、未来的只读目录)无需持有情绪机
|
||
// 就能继续满足 PersonaStore。当存储未实现它时,
|
||
// 某角色的 mood 只读作 "",而不是被凭空造出。
|
||
type PersonaMoodStore interface {
|
||
Emotions() *emotion.Manager
|
||
}
|
||
|
||
// MemoryStore 列出记忆条目。*runtime/memory.Store 是生产
|
||
// 实现。网关把每个 MemoryItem 映射为其线上摘要。
|
||
type MemoryStore interface {
|
||
List(ctx context.Context) ([]memory.MemoryItem, error)
|
||
}
|
||
|
||
// HistoryStore 是持久化聊天历史存储(design/16)。*runtime/history.Store
|
||
// 是生产实现。为 nil 时会话/消息端点退化:列出
|
||
// 会话回退到渠道会话视图,而创建/取消息/删除
|
||
// 端点返回明确的 501。
|
||
type HistoryStore interface {
|
||
Append(ctx context.Context, msg history.Message) error
|
||
// AppendFull 像 Append 一样持久化 msg,但返回完整的已存消息
|
||
// (含分配的 id/时间戳),使编排器能广播它以进行
|
||
// 跨端实时同步(design/16 §4.2)。
|
||
AppendFull(ctx context.Context, msg history.Message) (history.Message, error)
|
||
List(ctx context.Context, sessionID string, before int64, limit int, platform string) ([]history.Message, error)
|
||
// ListAfter 按最旧优先返回至多 limit 条严格新于 after 的消息,
|
||
// 用于重连后的前向增量追赶("before" 游标只能
|
||
// 向后走)。platform 的过滤同 List。
|
||
ListAfter(ctx context.Context, sessionID string, after int64, limit int, platform string) ([]history.Message, error)
|
||
Recent(ctx context.Context, sessionID string, n int) ([]history.Message, error)
|
||
ListPlatformLog(ctx context.Context, platform string, before int64, limit int) ([]history.Message, error)
|
||
ListAdapterLog(ctx context.Context, adapterID string, before int64, limit int) ([]history.Message, error)
|
||
ListAllLog(ctx context.Context, before int64, limit int) ([]history.Message, error)
|
||
DeleteSession(ctx context.Context, sessionID string) error
|
||
// Search 为 ?q= 消息搜索路径返回会话作用域的内容匹配
|
||
// (最新优先)。空查询得到空切片。
|
||
Search(ctx context.Context, sessionID, query string, before int64, limit int) ([]history.Message, error)
|
||
// UpdateMessage 编辑单条消息的内容并返回更新后的
|
||
// 消息。未知 id 返回 history.ErrMessageNotFound。
|
||
UpdateMessage(ctx context.Context, id, content string) (history.Message, error)
|
||
// DeleteMessage 移除单条消息。未知 id 返回
|
||
// history.ErrMessageNotFound。
|
||
DeleteMessage(ctx context.Context, id string) error
|
||
// RenameSession 设置会话的显示标题(main 或 sub)。未知 id
|
||
// 返回 history.ErrSessionNotFound。
|
||
RenameSession(ctx context.Context, sessionID, title string) error
|
||
// SetPinned 置顶/取消置顶一个会话(main 或 sub)。未知 id 返回
|
||
// history.ErrSessionNotFound。
|
||
SetPinned(ctx context.Context, sessionID string, pinned bool) error
|
||
NewSubSession(ctx context.Context, ownerID string) (string, error)
|
||
// MarkSessionRead 把会话的读游标(D4)推进到 before 并返回
|
||
// 结果游标作为 Unix 毫秒时间戳;before 为零意味着
|
||
// 「现在」。游标只向前移动。未知 id 返回
|
||
// history.ErrSessionNotFound。
|
||
MarkSessionRead(ctx context.Context, sessionID string, before time.Time) (int64, error)
|
||
ListSessions(ctx context.Context) ([]history.SessionMeta, error)
|
||
}
|
||
|
||
// IoTStore 列出 IoT 设备。*iot.Manager 是生产实现。
|
||
type IoTStore interface {
|
||
List(ctx context.Context) ([]iot.Device, error)
|
||
}
|
||
|
||
// SubagentAuditor 为 subagent_ask WS 路由审计子代理的对话
|
||
// 上下文。*orchestration.Scheduler 是生产实现。
|
||
type SubagentAuditor interface {
|
||
AuditContext(ctx context.Context, id string, mode orchestration.AuditMode, n int, query string) (*orchestration.AuditResult, error)
|
||
}
|
||
|
||
// TaskOutputStore 为 task_output_query WS 路由查询任务输出。
|
||
// *task.Manager 是生产实现。
|
||
type TaskOutputStore interface {
|
||
TaskOutput(ctx context.Context, id string) (task.OutputEvent, error)
|
||
}
|
||
|
||
// AutomationStore 为管理列表枚举自动化与提醒。
|
||
// *automations.Service 是生产实现。网关把每条
|
||
// 记录映射为其线上结构。
|
||
type AutomationStore interface {
|
||
List() []automations.AutomationRecord
|
||
}
|
||
|
||
// UserStore 枚举管理面板可见的账号与 API key。
|
||
// 生产实现读取已配置的管理账号加上
|
||
// auth.APIKey 表。网关把每个摘要映射为其线上结构。
|
||
type UserStore interface {
|
||
List() []auth.UserSummary
|
||
}
|
||
|
||
// RefreshGrantStore 管理非透明 refresh token(auth.RefreshStore 是
|
||
// 生产实现)。它是登录与 POST /auth/refresh、/auth/logout 端点
|
||
// 所返回 refreshToken 背后的凭据。只持久化哈希;
|
||
// 明文 token 每次签发/轮换恰好穿过该接口一次。
|
||
// 可选:为 nil 时登录省略 refreshToken,
|
||
// refresh 与 logout 端点返回明确的 not-configured 错误。
|
||
type RefreshGrantStore interface {
|
||
// Issue 为 userID/role 铸造一个 refresh token,返回其明文
|
||
// (仅可见一次)及其过期时间。
|
||
Issue(ctx context.Context, userID string, role auth.Role, ttl time.Duration) (string, time.Time, error)
|
||
// Rotate 校验 plaintext,并在 opts.Rotate 设置时吊销它并
|
||
// 返回替代品。对未知、已吊销或已过期的 token 返回
|
||
// auth.ErrInvalidGrant 错误。
|
||
Rotate(ctx context.Context, plaintext string, opts auth.RotateOptions) (auth.RefreshToken, string, error)
|
||
// Revoke 把匹配 plaintext 的 token 标记为已吊销,并返回是否
|
||
// 找到了存活的 token。它是幂等的。
|
||
Revoke(ctx context.Context, plaintext string) (bool, error)
|
||
// RevokeAll 删除每一个 refresh token(秘密轮换时的全局
|
||
// 失效)并返回移除了多少个。
|
||
RevokeAll(ctx context.Context) (int64, error)
|
||
// RevokeByUser 删除 userID 的每一个 refresh token(按访问
|
||
// 令牌登出,此时不知道具体是哪一份授权)并返回
|
||
// 移除了多少个。
|
||
RevokeByUser(ctx context.Context, userID string) (int64, error)
|
||
}
|
||
|
||
// BackthinkStore 为 M9/M10 监控面板枚举持久化的后台思考
|
||
// 记录。*backthink.Backthink 是生产实现。
|
||
type BackthinkStore interface {
|
||
List(ctx context.Context, limit int) ([]backthink.Record, error)
|
||
}
|
||
|
||
// ChannelConfigStore 是运行时可编辑的渠道适配器配置
|
||
// (channels.json,design/04 §3.1)。它由组合根的渠道存储
|
||
// 实现:Load 返回按平台生效的账号(文件 → 环境变量
|
||
// 回退),Path 报告 channels.json 路径,Reload 在面板保存后
|
||
// 热替换某平台运行中的适配器(Bridge.Reload)。Load 返回
|
||
// 带真实 token 的账号配置,使 owner 写路径能持久化它们;
|
||
// 管理读路径在返回客户端前对它们脱敏。
|
||
type ChannelConfigStore interface {
|
||
Path() string
|
||
// Load 返回按平台生效的账号(文件 → 环境变量回退),
|
||
// 含持久化它们所需的秘密 token。
|
||
Load() map[string][]config.ChannelAccount
|
||
// Reload 重建并热替换该平台运行中的适配器。
|
||
Reload(platform string) error
|
||
}
|
||
|
||
// OwnerRegistrar 把一个渠道账号 id 登记进主人白名单
|
||
// (configs/users/owner.yaml)。它是结构化的身份红线:扫码登录
|
||
// 确认会把扫码者绑定的 ilink_user_id 盖进白名单,使
|
||
// 渠道 bridge 经 actor 元数据认出它是主人,绝不是提示词。
|
||
// 组合根实现了它(cmd/cyrene 的 ownerRegistrar)以持久化
|
||
// 白名单并热更新活的 owner profile。登记是尽力而为:
|
||
// 失败由调用方记日志,但不会让已经施加的
|
||
// channels.json 持久化失败。
|
||
type OwnerRegistrar interface {
|
||
RegisterChannelOwner(ctx context.Context, platform, accountID string) error
|
||
}
|
||
|
||
// RagStore 支撑管理侧知识库 RAG 模块(design/10 §3.12 module 8)。
|
||
// *runtime/knowledge.Store 是生产实现:它列出/搜索
|
||
// 项目索引、导入文档(切分 + 索引)、删除文档
|
||
// (项目条目 + 级联章节)。不能写入的实现
|
||
// 会报告明确的错误(knowledge.ErrWriteUnsupported),而不是编造
|
||
// 成功;handler 把文档缺失映射为 404。
|
||
type RagStore interface {
|
||
// ListDocuments 枚举知识库文档(项目索引
|
||
// 条目)。
|
||
ListDocuments(ctx context.Context) ([]knowledge.Doc, error)
|
||
// Search 应答一次知识库查询(行级层级 + 可选
|
||
// 语义/向量排序)。
|
||
Search(ctx context.Context, query string) ([]knowledge.Hit, error)
|
||
// IndexDocument 把一份文档上传/切分/嵌入进项目索引。
|
||
IndexDocument(ctx context.Context, doc knowledge.Doc) error
|
||
// DeleteDocument 按 id 移除一份文档(及其章节),在
|
||
// 不存在该文档时返回 knowledge.ErrDocumentNotFound。
|
||
DeleteDocument(ctx context.Context, id string) error
|
||
}
|