Files
AskaEth d6ed9cfa2b
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled
feat(memory): 记忆整理「试算(dry-run)」入口 + 面板卡(契约 v0.75)
主人要「现在就想看到在 736 条上它会建议合并哪些」,而不是等 23:00 的低峰首跑。
- 后端:Tidier.DryRun + TidyService.DryRun,**复用同一条扫描→召回→判定→构造候选路径**(抽出 tidyRoundPolicy,生产轮与试算轮只差「候选出口」与「召回降级策略」两处;禁止第二套实现)
- 🔴 零副作用:不写 memory_tidy_candidates / memory_tidy_runs,绝不改 memory_items 任何列;机械证明 = 跑前跑后 cyrene.db 与 -wal 的 SHA256 一致(测试断言「表行数不变」)
- 失败码诚实:500 tidy_unavailable / 409 tidy_disabled / 500 tidy_recall_unavailable(**embedding 不可用不降级成 0 条**)/ 503 tidy_timeout / 500 tidy_dry_run_failed;**空数组唯一含义 = 算过了没建议**
- 端点 POST /api/v1/admin/memories/tidy/dry-run(writeAuth);面板「记忆」页加试算卡(点了才跑、无轮询、三态分开、明写「仅试算,不会改动任何记忆」)
- 真机 e2e(741 条活库副本):76 ms / 15 分区 / 53 候选 / 41 skipped / 1 vetoed(low_confidence);样例 merge reason=fact_subset score=1.0000、link reason=similar_but_not_contained score=0.9248
- 变异:dry-run 顺手写库 ⇒ 两条测试红(行数 0→2);自造阈值 ⇒ 红;失败回 200 空数组 ⇒ 红
- 如实登记:Compared=688 被 400/分区护栏截断、候选上限 20/分区被打满 ⇒ 护栏值需在 741 条量级重定(非 bug)
2026-10-05 22:06:54 +08:00

175 lines
9.4 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package gateway
import (
"database/sql"
"io/fs"
"log/slog"
"net/http"
"git.yeij.top/AskaEth/Cyrene_v2/internal/bus"
pluginmgr "git.yeij.top/AskaEth/Cyrene_v2/internal/capability/plugin"
"git.yeij.top/AskaEth/Cyrene_v2/internal/runtime/model"
)
// VectorState 是组合根报告的向量增强(pgvector)运行状态,供管理侧
// 「存储状态」端点回答「PG 到底有没有生效」(design/03 §3)。
//
// 它是**复用**而非探测:网关不读环境变量、不新开 PG 连接、不
// 自行 ping —— 真相只有组合根知道(DB_PG_URL 是否配置 + 活的向量
// 索引是哪个后端),因此由它注入,避免面板看到与运行时不一致的
// 第二个判断。
type VectorState struct {
// Configured 报告 DB_PG_URL 是否已配置(非空即算),即
// 增强开关在不在。
Configured bool
// Live 报告当前活的向量索引确实是 PostgreSQL+pgvector 后端。
// 配置了但连接/schema 准备失败时为 false —— 此时语义检索已降级,
// 面板必须显示「未就绪」而不是假装增强生效。
Live bool
}
// Deps 承载网关被注入的依赖。除 Auth 外每个字段都容忍
// nil,Auth 是必需的(认证支撑登录与每一个 RBAC
// 决策)。Registry/Loop 由具体的 *capability.Registry 与
// *loop.Loop 满足;Sessions/Personas/Memories 由具体的会话
// 管理器、人格注册表与记忆存储满足,只有当依赖留为
// nil 时 handler 才返回 []。
type Deps struct {
// Log 是基础 logger。为 nil 时使用丢弃型 logger。
Log *slog.Logger
// Auth 是 JWT/RBAC 服务。必需。
Auth Authenticator
// Bus 是 WS hub 所订阅的进程内事件总线。可选。
Bus bus.Bus
// Registry 枚举能力/插件/渠道。可选。
Registry CapabilityStore
// Loop 运行聊天回合。可选(未设置时 chat 返回错误)。
Loop Chatter
// Session 列出渠道会话。可选。
Session SessionStore
// Personas 列出人格。可选。
Personas PersonaStore
// Memories 列出记忆条目。可选。
Memories MemoryStore
// History 是持久化聊天历史存储(design/16)。可选;为 nil 时
// 会话/消息端点退化(列出回退到渠道会话,其余 501)。
History HistoryStore
// IoT 列出 IoT 设备。可选。
IoT IoTStore
// Scheduler 为 subagent_ask WS 路由审计子代理上下文。
// 可选(未设置时 subagent_ask 返回明确的错误)。
Scheduler SubagentAuditor
// Tasks 为 task_output_query WS 路由查询任务输出。
// 可选(未设置时 task_output_query 返回明确的错误)。
Tasks TaskOutputStore
// Automations 列出自动化与提醒。可选。
Automations AutomationStore
// Users 列出账号与 API key。可选(回退到已配置的
// 单个管理账号)。
Users UserStore
// Refresh 是非透明 refresh token 存储。可选:为 nil 时登录省略
// refreshToken,/auth/refresh + /auth/logout 返回 not-configured。
Refresh RefreshGrantStore
// Backthink 列出后台思考记录。可选。
Backthink BackthinkStore
// RAG 支撑管理侧知识库 RAG 模块。可选;未设置时列出/搜索
// 返回空,写端点报告明确的 501。
RAG RagStore
// LLM 是可热切换的 provider 栈(models.json,design/04 §3.1)。
// 可选:为 nil 时管理 /llm 端点回退到环境配置,使
// 既有的网关测试继续可用。
LLM *model.RuntimeLLM
// EnvPath 是进程启动时解析的 `.env` 文件路径(组合根传给 config.Load 的同一个)。
// 「重置为 env 默认」动作(切片 R1)按**既有 config 解析入口**重读 env 默认层,
// 网关自己绝不 fopen `.env`。空表示只看进程环境变量(与 config.Load("") 的
// 约定一致,生产走 systemd/env_file 时就是这种形态)。可选。
EnvPath string
// Channels 是运行时可编辑的渠道适配器配置(channels.json)。
// 可选:为 nil 时管理 /channels/config 端点返回明确的
// not-configured 错误,与 LLM 栈未设置时的行为一致。
Channels ChannelConfigStore
// Plugins 是运行时可安装的外部工具 provider 管理器
// (plugins.json + 活的 MCP server)。可选:为 nil 时安装返回
// 明确的 not-implemented 错误,卸载回退为普通的能力
// 移除,使既有的网关测试继续可用。
Plugins *pluginmgr.Manager
// OwnerRegistrar 把扫码登录绑定的主人 id 盖进主人
// 白名单(configs/users/owner.yaml)并热更新活的 profile。
// 可选:为 nil 时 weixin 确认流程仍会持久化 channels.json,但
// 跳过白名单登记(按设计即为尽力而为)。
OwnerRegistrar OwnerRegistrar
// AuditStore 持久化管理审计链(observability → audit)。为 nil 时
// 网关退化为内存存储,使端点仍能返回
// 数据;组合根注入基于 a.db 的 NewSQLiteAuditStore 以获得真实
// 持久化。可选。
AuditStore AuditStore
// DatabasePath 是组合根实际打开的 SQLite 数据库文件解析后
// 路径(由 resolveSQLitePath 推导的值,当数据目录默认值
// 生效时可能与 cfg.DB.SQLitePath 不同)。它支撑
// 管理备份/恢复端点。可选;为空时那些端点诚实作答,
// 返回明确的 not-configured 错误。
DatabasePath string
// DataDir 是解析后的数据目录(resolveDataDir 返回的值,当操作系统
// 默认值生效时可能与 cfg.DataDir 不同)。它支撑
// 图片上传存储(<DataDir>/uploads)及其静态路由。可选;
// 为空时上传 handler 回退到 cfg.DataDir。
DataDir string
// Database 是组合根已打开的活 SQLite 连接池。管理侧的
// 「存储状态」只读端点用它读页级统计与 documents 表 ——
// 复用既有池,端点自身不另开连接或连接池。可选;为 nil 时
// 该端点诚实作答(500 storage_unavailable),绝不假装
// 拿到了数据。
Database *sql.DB
// Vector 是组合根报告的向量增强(pgvector)运行时状态,
// 见 VectorState。网关只读它,绝不自行探测。可选;零值
// 表示「未配置」(kind:"none"),与未启用增强的部署一致。
Vector VectorState
// Streaming 标记注入的 Loop 具备流式能力(其 OnStream
// 已接线为发布 ws.stream_chunk)。设置后 hub 会跳过它自己的
// 单次最终 stream_chunk 发送,因为增量已通过 OnStream 流出。
Streaming bool
// Frontend 是为所有非 API、非 WS 路由提供的 SPA 文件系统。
// 为 nil 时网关不提供任何静态资产(只有 API 与 WS)。
// 使用内嵌的 dist(internal/webui)或外部 dist 目录。
Frontend fs.FS
// Webhooks 是外部挂载的入站 HTTP handler(端点路径 →
// handler),通常来自接收 webhook 回调的渠道适配器
// (例如 webhook)。每个都挂到 ServeMux 上且不经网关认证;
// handler 自身强制其签名/授权(webhook HMAC)。
// 可选。
Webhooks map[string]http.Handler
// Usage 是 LLM 用量汇总的只读出口(切片 U1)。可选;为 nil 时
// GET /api/v1/admin/llm/usage 诚实作答 500 usage_unavailable,
// 绝不回一份全 0 的假汇总。
Usage LlmUsageStore
// EventStore 是渠道事件留存的只读查询出口(design/26 §4.5;切片 E6)。
// 组合根注入的正是交给桥接层写入的**同一个** `*events.SQLStore` 句柄
// (同一个库、同一份口径)。可选;为 nil 时
// GET /api/v1/admin/channels/events 与 `.../counters` 诚实作答
// 500 channel_events_unavailable,绝不回一份全 0 的假汇总。
EventStore ChannelEventStore
// MaxParallelTurns 是单会话在途并行路数的上限(design/15 §2.5.7)。
// 零值/负数 ⇒ 编排器用默认 2;1 ⇒ 关闭真并行(恒 1 路,完全回落到
// queue + 回合内 steer 的现状);超过硬上限 3 ⇒ 钳到 3。
// 它刻意是编排器的构造期旋钮而不是一个全局:真并行只由显式声明
// (上行 `chat.parallel`)触发,因此调大它不会改变任何未声明并行的客户端行为。
MaxParallelTurns int
// Flow 是「最近一轮链路图」的只读出口(契约 v0.71 的
// GET /api/v1/admin/flow/graph)。组合根注入的正是交给 loop 的那个
// *loop.FlowPublisher —— 同一个对象,因此查询面读到的就是实时发布的那张图。
// 可选;为 nil 时端点诚实作答 500 flow_unavailable,绝不回
// graph:null(那会把「图源没接线」伪装成「这一轮确实没有图」)。
Flow FlowGraphSource
// FlowGraphs 是**已落盘轮次图**的只读出口(契约 v0.73;design/19 §4 的 P3
// 「多轮历史回放」)。组合根注入的是发布器**每轮收尾落一条**用的同一个
// `*flowgraph.SQLStore`。可选;为 nil 时端点回退到只有内存的行为
// (turns 为空、turnId 只能命中内存里那一轮),内存与落盘都没接线才 500。
FlowGraphs FlowGraphStore
// MemoryTidy 是记忆整理「试算」的注入出口(契约 v0.75;切片 MT1)。组合根
// 注入的就是低峰心跳跑的那个 `*memory.TidyService` —— 同一套规则、同一份配置,
// 因此试算给出的建议与当晚那一轮同口径。试算自身**零副作用**(不写任何表)。
// 可选;为 nil(真 nil,不是带类型的 nil 指针)时端点回 500 `tidy_unavailable`,
// 绝不回空候选假装「没有建议」。
MemoryTidy MemoryTidyDryRunner
}