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
+55
-7
@@ -1,6 +1,6 @@
|
||||
# Cyrene v2 客户端 API 契约
|
||||
|
||||
> **契约版本:`v0.70`**(上一版 `v0.69`,其条目见 §6;`v0.68` 及更早见本文件 §6,`v0.67` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`)。
|
||||
> **契约版本:`v0.71`**(上一版 `v0.70`,其条目见 §6;`v0.69` 及更早见本文件 §6,`v0.67` 及更早的基线已归档、只读:`Docs/archive/api-client.v0.65.md`)。
|
||||
> **读者**:Web 前端 / 移动端原生客户端(含 Android)/ IM 桥接等**跨平台客户端开发者**。
|
||||
> **唯一真源 = 当前代码**(`internal/services/gateway/*` 等)。本文档由代码逐条核对得出;与代码不符时**以代码为准**,并回改本文档。
|
||||
> **旧的超详细版已归档、只读**:`Docs/archive/api-client.v0.65.md`(412 KB,含 v0.28→v0.65 全部历史条目)与 `Docs/archive/api-reference.v0.65.md`(170 KB,后端全量字段级文档)。归档的原因是**结构性臃肿 + 历史条目并列 + 长期漂移**;本文档**不复制**那段历史,需要考古时去归档。
|
||||
@@ -10,6 +10,7 @@
|
||||
> **v0.68 是纯增量版(已实现,非契约先行)**:新增 1 条管理只读端点 `GET /api/v1/admin/config-facts`(配置事实面,见 §2.2 B12),并给 `GET /api/v1/admin/backthink` 的条目加 2 个可选字段(`urgency` / `quietHoursBypassed`,见 §2.2 B8)。**无破坏性变更**,老客户端零影响。
|
||||
> **v0.69 是缺口修复版(已实现)**:`PUT /api/v1/admin/channels/{platform}/config` 现在**接收并原样落盘**账号顶层 `directMsgPerMin`,`GET /api/v1/admin/channels` 的账号元素也**如实回显**它(§2.2 A5 / §2.3 C28 注)。修的是「面板保存一次渠道配置,就把主人手配的**关闭流控(-1)**静默重置回 `0` = 保护性默认 6 条/分」。**无破坏性变更**(老客户端不带该键,行为与 v0.68 相同)。
|
||||
> **v0.70 是精确化版(已实现,纯增量)**:`GET /api/v1/admin/backthink` 的 `urgency` **默认就是该轮实际生效的精确档位**(随记录落库),另加可选 `urgencyLowerBound:true` 专门标记**旧行的下限推导值**;实时 `backthink` 帧自本版起也带记录里的 `urgency`(§2.2 B8 / §4.10(b),并更正 §4.10(b) 原「实时帧不带这两键」的表述)。**无破坏性变更**(新键可选,老客户端不读即零影响;`urgency` 的既有取值域与 `quietHoursBypassed` 语义都未改)。
|
||||
> **v0.71 是链路图补版(已实现,纯增量)**:新增 1 条管理只读端点 **`GET /api/v1/admin/flow/graph`**,把 `ws.graph_update` 的**最近一轮快照**从「只能靠实时帧拿」变成「打开页面即可只读拉取」(§2.2 B13 / §4.11)。修的是「页面在轮次结束后才打开(或刷新过)⇒ `/#/admin/flow` 与后台思考行的『查看完整链路导图』永远空着」—— `graph_update` **只走实时帧**,前端图桶(`flowGraphs`)**不持久化**,此前**没有任何**补拉通路。**无破坏性变更**(新端点;`graph_update` 帧形状、角色门、发布会话范围**一字未改**)。
|
||||
|
||||
---
|
||||
|
||||
@@ -122,10 +123,12 @@
|
||||
| 档位 | 中间件 | 条数 | 说明 |
|
||||
|---|:--:|:--:|---|
|
||||
| **只读监控档** | `viewerAuth`(owner/admin/viewer) | **12** | 非敏感投影:无凭据、无他人账号数据、无消息正文 |
|
||||
| **owner/admin 读档** | `writeAuth`(owner/admin) | **12** | viewer 不得看到的一切:记忆、LLM 栈、账号/key、后台思考、审计链、渠道消息日志、完整会话导出、配置事实面(v0.68) |
|
||||
| **owner/admin 读档** | `writeAuth`(owner/admin) | **13** | viewer 不得看到的一切:记忆、LLM 栈、账号/key、后台思考、审计链、渠道消息日志、完整会话导出、配置事实面(v0.68)、最近一轮链路图快照(v0.71) |
|
||||
| **owner-only 档** | `ownerAuth`(owner) | **40** | 带审计的策略写、每个获取凭据的渠道端点、其余全部变更 |
|
||||
|
||||
**管理路由总数 = 12 + 12 + 40 = 64 条。**
|
||||
**管理路由总数 = 12 + 13 + 40 = 65 条。**
|
||||
|
||||
> **计数口径(v0.71 明确)**:总数是 §2.1 / §2.2 / §2.3 三张清单里**列出的行数**(与 v0.68 起的口径一致)。⚠️ `internal/services/gateway/routes.go` 里实际经 `writeAuth` 挂载的 **admin** 路由比 §2.2 多:v0.67 登记的 `GET /admin/channels/events` 与 `.../counters`(见 §2.4(a))**未计入本表**——这是历史口径,本版未改,也**未**把它算进上面的 13。逐条以代码为准。
|
||||
|
||||
> ⚠️ **与归档版不一致(已更正)**:`Docs/archive/api-client.v0.65.md` §2.0 写「12 + 11 + 38 = 61」(其 v0.62 条目又写「12 + 10 + 38 = 60」,自相矛盾)。**以代码为准 = 12 + 11 + 40 = 63**。
|
||||
|
||||
@@ -146,7 +149,7 @@
|
||||
| A11 | GET | `/api/v1/admin/observability/logs` | 后端日志尾部(最新优先,`?level=&limit=`;缓冲未注入时 501) |
|
||||
| A12 | GET | `/api/v1/admin/notifications` | 统一通知 feed(审计链 + 后台思考推送的只读投影) |
|
||||
|
||||
### 2.2 owner/admin 读(12 条,`writeAuth`)
|
||||
### 2.2 owner/admin 读(13 条,`writeAuth`)
|
||||
|
||||
| # | 方法 | 路径 | 要点 |
|
||||
|---|---|---|---|
|
||||
@@ -162,6 +165,7 @@
|
||||
| B10 | GET | `/api/v1/admin/sessions/{id}/export` | 完整会话导出 |
|
||||
| B11 | POST | `/api/v1/admin/rag/query` | 知识库检索(带审计的读型 POST) |
|
||||
| B12 | GET | `/api/v1/admin/config-facts` | **配置事实面(v0.68 新增,只读)**:记忆整理 7 键 + 安静时段 `QUIET_START_HOUR`/`QUIET_END_HOUR` 的**进程生效值**,逐条带 `default`/`isDefault`。**不含任何密钥/令牌值**(连「是否已配置」都不回凭据类的)。形状见 §4.10 |
|
||||
| B13 | GET | `/api/v1/admin/flow/graph` | **最近一轮链路图快照(v0.71 新增,只读、不审计)**。`?sessionId=`(省略 = 主会话 `web`);`{data:{sessionId,graph}}`,`graph` 是**一份 §3.4.5 `graph_update` 同形快照**,该会话还没有图时**如实为 `null`**(**绝不画假图**)。**只回最近一轮**(更早轮次的历史回放未实现,见 §7.4-18);未接线图源 → `500 flow_unavailable`。语义见 §4.11 |
|
||||
|
||||
> **读取 `GET /admin/observability/{logs,metrics,trace}` 兼任可观测推送的订阅握手**(该凭据的连接全部断开即退订)。REST 响应形状不变。
|
||||
|
||||
@@ -381,7 +385,7 @@
|
||||
| `device_update` | — | 桥接自 `iot.device_update` |
|
||||
| `automation_result` | — | 桥接自 `automation.result` |
|
||||
| `reminder` | — | 桥接自 `reminder.trigger` |
|
||||
| `graph_update` | — | 实时 flow 图快照(`data.sessionId` 标会话;**无 `turnId`** ⇒ 未按路分桶) |
|
||||
| `graph_update` | — | 实时 flow 图快照(`data.sessionId` 标会话;**无 `turnId`** ⇒ 未按路分桶)。⚠️ **纯实时、不重放**:断开/刷新期间的那一轮不会补发 ⇒ 打开页面时用 `GET /api/v1/admin/flow/graph`(§2.2 B13 / §4.11)拉**最近一轮快照** |
|
||||
| `capacity_awaken` | sessionId | `{fromState,toState,tool}` |
|
||||
| `mood` | (无 sessionId) | `{mood,personaId,affection}`;角色级情绪,仅变化时发 |
|
||||
| `proactive_message` | — | `{platform,channelId,channelType,content,contentType,sender,timestamp,messageId}`(已消毒,绝不含凭据/媒体字节) |
|
||||
@@ -719,6 +723,34 @@
|
||||
- **下限推导(只对没有落库档位的行)**:该列为空的行 —— 该列出现之前的**旧行**,以及从未走到档位判定点的行(系统事件降级、思考失败)—— 才回落到 v0.68 的安静时段窗口推导,并**必须**带 `urgencyLowerBound:true`。⚠️ 旧行**不得**被显示成精确档位,未知也不得被显示成某个档位。
|
||||
- 每轮 `Import` 落库(`urgency` 列)在 X2a 切片实现(`213aff2`);「该轮实际生效」的取数点见 `internal/services/backthink/backthink.go` 的判定点。⚠️ 本版**不改** `quietHoursBypassed` 的语义。
|
||||
|
||||
### 4.11 v0.71 新增:最近一轮链路图快照(`GET /api/v1/admin/flow/graph`)
|
||||
|
||||
**为什么有这条**:`graph_update`(§3.4.5)是**纯实时帧** —— 后端在每个 span 开始/结束时广播一次,**不重放**;前端把图存在**内存**的会话图桶里(`flowGraphs`,**不持久化**)。于是「**轮次结束后才打开页面**」或「**刷新过页面**」看到的永远是空态(`/#/admin/flow` 的「暂无流程」、后台思考行「查看完整链路导图」的「该会话还没有 graph_update 快照」)。本端点补上唯一缺的那一环:**打开页面时按会话只读拉一次最近一轮的快照**。
|
||||
|
||||
#### (a) 请求与响应
|
||||
|
||||
| 项 | 值 |
|
||||
|---|---|
|
||||
| 方法 / 路径 | `GET /api/v1/admin/flow/graph` |
|
||||
| 鉴权档 | `writeAuth`(**owner / admin**;`viewer` → `403`,未认证 → `401`)。与 `design/19 §3`「flow 可视化仅 owner/admin」一致 |
|
||||
| 查询参数 | `sessionId`(**可选**)。省略 / 空串 ⇒ 主会话 `web`(`internal/runtime/history.MainSessionID`)。其它取值就是该会话 id,如 `sess-backthink`(后台思考,design/20 §2)、`web:<uuid>`(次级会话) |
|
||||
| 响应体 | `{data:{sessionId:"<解析后的会话 id>", graph: <FlowGraph 对象>|null}}` |
|
||||
| `graph` 形状 | **与 `graph_update` 帧的 `data` 逐字同形**(§3.4.5:`{id,nodes[],edges[],sessionId,startedAt,endedAt}`,节点 `props`/`log` 同 §1.1 的脱敏口径) |
|
||||
| 归档 | **不审计**(只读查询,与同档的 `/admin/llm`、`/admin/config-facts` 一致) |
|
||||
| 失败 | 图源未接线 ⇒ `500 {"code":"flow_unavailable"}`(**不假装「这一轮没有图」**);该会话**确实**还没有图 ⇒ `200` + `graph:null` |
|
||||
|
||||
#### (b) 语义硬约束(客户端必须照这个读)
|
||||
|
||||
1. **它是「最近一轮」而不是「你要的那一轮」。** 后端只保留每个会话**最新**的一份图(`loop.FlowPublisher` 每会话一张,`StartRound` 重置)。因此后台思考行上点开的图**可能不是该行那一轮**——**面板文案必须说明这一点**(当前实现:`frontend/web/src/admin/modules/flow/FlowSnapshotHint.tsx`),不得让用户以为「每行都有自己那轮的图」。**多轮历史回放**仍是未实现项(`design/19 §4` 的 P3,见 §7.4-18)。
|
||||
2. **只读、不发布。** 拉快照**不产生** `graph_update` 帧、不改变发布白名单、不改变任何会话的实时图。
|
||||
3. **`graph:null` 是诚实的空。** 会话从未跑过有 span 的轮次、或图源里没有该会话(如发布白名单之外的隔离渠道会话 `sess-xxx` 从未建图)时返回 `null` ⇒ 客户端**必须**渲染空态,**绝不允许**补画任何节点/假图。
|
||||
4. **快照会晚于实时帧到达,反之亦然。** 客户端已有实时图时**不应**被快照覆盖(实时图更新;快照只是补冷启动的洞)——当前实现只在**本会话还没有任何图**时才拉。
|
||||
5. **不替代** `graph_update`。实时帧仍是「正在跑的那一轮」的唯一来源;本端点只补「已经跑完、帧已经错过」的那一轮。
|
||||
|
||||
#### (c) 与 design/19 的关系
|
||||
|
||||
`design/19 §4` 把「多轮历史回放」列为 **P3**;本端点落实的是 P3 里**最小的一步**(每会话最近一轮的只读快照),**不改** design/19 §1 的发布会话范围(`web` / `web:<uuid>` / `sess-backthink`,裁决 01.4.5)、**不改** `graph_update` 两路互顶的既有裁决(见 §7.4-19)、**不改**前端图桶的按会话分桶口径(裁决 16.4.2)。
|
||||
|
||||
---
|
||||
|
||||
## 5. env 与模型配置(与契约相关)
|
||||
@@ -782,7 +814,20 @@
|
||||
> 只保留最近若干版(**v0.60 起**);更早的全部历史见归档 `Docs/archive/api-client.v0.65.md` §5(v0.28 → v0.59)。
|
||||
> 每版一句话 + 三档标注。**三档定义见 [§0.4](#04-三档标注惯例)。**
|
||||
|
||||
### v0.70(当前契约版本)
|
||||
### v0.71(当前契约版本)
|
||||
|
||||
**链路图补版 —— 新增只读端点 `GET /api/v1/admin/flow/graph`,让「轮次结束后才打开的页面」也能看到最近一轮的链路图**(依据主人 2026-10-05 第二次报「`/#/admin/flow` 停在暂无流程图数据 + 后台思考行提示该会话还没有 graph_update 快照」;对齐 `design/19 §4` 的 **P3**「多轮历史回放」里的最小一步)。
|
||||
|
||||
- **纯增量**:
|
||||
① **新 admin 只读端点 `GET /api/v1/admin/flow/graph?sessionId=<id>`**(`owner`/`admin`,`?sessionId=` 省略 = 主会话 `web`):返回 `{data:{sessionId,graph}}`,`graph` 与 `graph_update` 帧的 `data` **逐字同形**;该会话还没有图 ⇒ **如实 `null`**(绝不画假图);图源未接线 ⇒ `500 flow_unavailable`(**不假装「这一轮没有图」**)—— **§2.2 B13 / §4.11**。权限矩阵随之更新:`writeAuth` 12 → **13**,总数 64 → **65**。
|
||||
② §3.4.5 `graph_update` 行补一句「**纯实时、不重放**」并指向本端点。
|
||||
③ §7.4 新增两条诚实边界:**「最近一轮」≠「你要的那一轮」**(后台思考行上的图可能不是该行那轮;面板必须在图上说明)、**图源是进程内内存**(后端重启后如实回 `null`)。
|
||||
- **修的是什么(根因)**:`graph_update` **只走实时帧**,前端图桶 `flowGraphs` **不持久化**(`store/persistOptions.ts` 的 `partialize` 不含它),且此前**没有任何 REST/WS 补拉通路** ⇒ 页面在轮次结束后打开、或刷新过页面,就永远空着。本端点补上唯一缺的那一环。
|
||||
- **行为变更**:无(既有端点/帧一字未动)。
|
||||
- **破坏性**:无(新端点;`graph_update` 帧形状、`frameReachesAll` 角色门、发布会话范围(裁决 01.4.5)**全部未改**)。
|
||||
- ⚠️ **诚实边界**:只回**每会话最近一轮**;**多轮历史回放**(按 turn/时间点取任意一轮)**仍未实现**(`design/19 §4` P3)—— 见 §7.4-18。**`graph_update` 两路互顶**的既有裁决**本版未动**(§7.4-19)。
|
||||
|
||||
### v0.70(上一版)
|
||||
|
||||
**后台思考行的紧急度精确化 —— `urgency` 默认即精确档位,另加 `urgencyLowerBound` 标记下限**(依据主人 2026-10-05 裁定「要**精确**」;P4 只能给下限,本版把精确值送到两面:管理 REST 与实时帧)。
|
||||
|
||||
@@ -986,6 +1031,9 @@
|
||||
- **v0.70 更正**:每轮 `Import` **已落库** ⇒ `urgency` **默认就是精确档位**(含窗口之外/白天的轮次);只有与 `urgencyLowerBound:true` 同时出现时才是**下限**(旧行 / 从未走到档位判定点的行,值由安静时段窗口规则推导)。实时 `backthink` 帧也带 `urgency`(精确值),但**不带** `urgencyLowerBound` / `quietHoursBypassed`(见 §4.10(b) 更正)。
|
||||
- **客户端两条硬约束(v0.70 新增)**:① **不得把带 `urgencyLowerBound:true` 的「下限」显示成精确档位**(`≥ 高` ≠ `高`;同形就等于把推导冒充事实);② **不得把键缺席显示成「例常 / routine」**(缺席 = 存档里没有这个事实),也不得据此推断安全域。
|
||||
|
||||
18. **(v0.71 登记)「最近一轮」≠「你要的那一轮」**:`GET /api/v1/admin/flow/graph`(§4.11)只回该会话**最新**的一张图(`loop.FlowPublisher` 每会话只留一份)。**后台思考行**上点开的图因此可能不是该行那一轮 ⇒ 面板**必须在图上说明**(当前 `frontend/web/src/admin/modules/flow/FlowSnapshotHint.tsx`),客户端**不得**把它渲染成「这一行的链路」。**多轮历史回放**(按 turn/时间点取任意一轮)**未实现**,属 `design/19 §4` 的 **P3**。
|
||||
19. **(v0.71 登记)图源是**进程内内存**,进程重启即清空**:`loop.FlowPublisher` 的每会话最近一图**不落库**(design/19 §1 的实时口径)⇒ 后端重启后、该会话还没跑新轮次时,端点如实回 `graph:null`。这是**有意**的诚实空态,不是丢数据(图从来不是持久资产)。同时**登记既有裁决不改**:`graph_update` 的两路互顶(同会话并行两路时后发的那张图会顶掉先发的)仍**未修**,本版**不碰**它。
|
||||
|
||||
---
|
||||
|
||||
*本文件由「以代码为真源」维护(当前 `v0.70`;`v0.69` / `v0.68` 见本文件 §6,`v0.67` 及更早见本文件 §6 与 `Docs/archive/`)。范围:`v0.70` 同步了 `internal/services/gateway/**`、`frontend/web/src/**` 与本文件;`v0.69` 同步了 `internal/services/gateway/**` / `internal/config/**` 与本文件。*
|
||||
*本文件由「以代码为真源」维护(当前 `v0.71`;`v0.70` / `v0.69` / `v0.68` 见本文件 §6,`v0.67` 及更早见本文件 §6 与 `Docs/archive/`)。范围:`v0.71` 同步了 `internal/services/gateway/**`、`internal/runtime/loop/flow.go`、`frontend/web/src/**` 与本文件;`v0.70` 同步了 `internal/services/gateway/**`、`frontend/web/src/**` 与本文件;`v0.69` 同步了 `internal/services/gateway/**` / `internal/config/**` 与本文件。*
|
||||
Reference in new issue
Block a user