feat(gateway): 链路图冷启动快照(v0.71)+ 面板来源说明条 —— 修「轮次结束后打开页面一片空白」
- 断点已证 = **时序层**:graph_update 只走实时帧从不重放、flowGraphs 不持久化、补拉通路此前根本不存在(EndRound 注释承诺过「刷新能重放」,代码里从来没有)
铁证:15:16:38 页面重载抹掉 15:09 那轮图,而下一轮心跳要等 ~15:24 ⇒ 打开后台思考页时桶必然为空
- 新增只读端点 GET /api/v1/admin/flow/graph?sessionId=(owner|admin;省略=主会话;graph 与 graph_update 帧的 data 逐字同形)
无图 ⇒ 200 + graph:null(不画假图);图源未接线 ⇒ 500 flow_unavailable(不伪装成「没有图」);不审计、不发布
- loop.FlowPublisher.LastGraph:只读快照(不发布/不改图/不动轮次计数),FlowGraph.Clone 深拷贝避免锁外编码撕裂
- 前端 useFlowSnapshot:只在「本会话还没有图」时补拉(避免每行一个请求);Flow/Agents/Backthink 三处接入 + 来源说明条(明确「最近一轮」与「多轮历史回放尚未实现」)
- 变异实测:LastGraph 恒 nil ⇒ 4 条测试红;白名单去掉 sess-backthink ⇒ 对应测试红(首次变异因未使用 import 编译失败 ⇒ 已作废重做)
- ⚠️ 诚实边界:每会话只回**最近一轮**(多轮回放属 F2);白名单外会话(IM 群聊/陌生人)永远没有图;**后端重启后未跑新轮次前端点如实返 null(图是内存资产,不落库)**—— 主人要的「本地数据」正是 F2 要补的
This commit is contained in:
1 parent
a3482ed06e
commit
1bf85a955b
23 files changed
+1427
-36
No files matched your search
@@ -218,12 +218,13 @@ func (a *app) wireRuntime() error {
|
||||
// 发布会话范围(裁决 01.4.5)的既有裁决,本批未改。nil flow 保持 loop 原行为。
|
||||
flowPub := loop.NewFlowPublisher(
|
||||
func(subject string, payload any) { _ = a.bus.Publish(context.Background(), subject, payload) },
|
||||
func(sessionID string) bool {
|
||||
return sessionID == history.MainSessionID ||
|
||||
strings.HasPrefix(sessionID, history.MainSessionID+":") ||
|
||||
sessionID == backthink.DefaultSessionID
|
||||
},
|
||||
flowSessionAllowed,
|
||||
)
|
||||
// 同一个发布器也交给网关:`GET /api/v1/admin/flow/graph` 的只读出口
|
||||
// (契约 v0.71)就近读它每会话保留的最近一轮图 —— 写入面(这里)与查询面
|
||||
// (wireFrontend)必须**是同一个对象**,否则面板补拉到的图与实时收到的图
|
||||
// 会是两份状态。
|
||||
a.flow = flowPub
|
||||
checkpoint, perr := persist.New(context.Background(), a.db, a.log)
|
||||
if perr != nil {
|
||||
return fmt.Errorf("checkpoint store: %w", perr)
|
||||
@@ -417,3 +418,34 @@ func (a *app) defaultInstructions() string {
|
||||
}
|
||||
return fallbackInstruction
|
||||
}
|
||||
|
||||
// flowSessionAllowed 是实时链路图的**发布会话白名单**(design/19 §1 裁决 01.4.5
|
||||
// + 裁决 16.4.2),也是后端唯一一条「哪些会话能产出链路图」的判据 ——
|
||||
// `FlowPublisher.LastGraph`(契约 v0.71 的只读快照出口)读的就是按它建过图的那些
|
||||
// 会话,因此白名单外的会话**既发不出实时帧、也查不到快照**。
|
||||
//
|
||||
// 三档(本项目当前实现口径,逐档都有理由):
|
||||
// - `web`:Web 主会话(`history.MainSessionID`)—— 主人对话的正身;
|
||||
// - `web:<uuid>`:Web 次级会话(IM 主人私聊镜像也落这个族);
|
||||
// - `sess-backthink`:自主后台思考的固定内部会话(design/20 §2)。
|
||||
//
|
||||
// **隔离的渠道会话(群聊 / 陌生人,`sess-xxx`)不在此列** —— 那是防敏感信息
|
||||
// 泄向管理面板的既有裁决,不是省事。子代理的 span 由 `loop.WithFlowBranch`
|
||||
// 并进**派生它的那个会话**(design/19 §2),因此不需要单独一档。
|
||||
//
|
||||
// 抽成具名函数只为可测:白名单是裁决本身,删掉任何一档都必须有测试打红
|
||||
// (见 wire_runtime_test.go 的 TestFlowSessionAllowed)。
|
||||
func flowSessionAllowed(sessionID string) bool {
|
||||
switch {
|
||||
case sessionID == "":
|
||||
return false
|
||||
case sessionID == history.MainSessionID:
|
||||
return true
|
||||
case strings.HasPrefix(sessionID, history.MainSessionID+":"):
|
||||
return true
|
||||
case sessionID == backthink.DefaultSessionID:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user