refactor+docs(W27): 删掉只被测试养着的 AdminAccess.availablePages,让 ChatImageBudget 的公式只留一处,并就地更正 devdocs/03 四处过时表述
清理(零行为变化)
- 删 domain/admin/AdminAccess.kt 的 availablePages():全仓(含 app/src/test)调用点 0 处 ——
main 里只有它自己的声明、测试里 2 处(AdminAccessTest 那 1 条用例一并删掉)。它早已被
ui/admin/AdminHomeScreen.kt 的做法取代:遍历 AdminPage.entries + 每行 canAccess 决定是否禁用。
- ChatImageBudget.SERVER_MAX_CHARS 原来把 base64Chars 的 ((n+2)/3)*4 又内联重写了一遍,
改为 base64Chars(SERVER_MAX_DECODED_BYTES) + PREFIX_ALLOWANCE_CHARS(一处公式、一处真相)。
代价:该常量由 const val 变 val(const 不允许调用函数)。取值逐位不变 = 6990572,由
ChatSendPolicyTest 的独立重算断言与 ChatDtoRealPayloadTest 对服务端真实文案的断言继续钉住。
文档(devdocs/03,一律删除线保留原文 + 更正括注,不动别人的回执段落)
- §4 重连补全:02/A5 的 after 正向增量已接入(loadAfter + ChatViewModel.catchUp,28c3142)。
- §4 忙碌/队列 UI 映射:客户端只发 scope:"current",该档 dropped 恒为 0、all 从不发,
所以「已打断,丢弃 N 条」在今天的客户端里不可达。
- §5 mode 回落:mode 已落库(后端 b686d5f),MessageBubble 按 mode 分支。
- §4 打断模式:补上 W25 接线的依据 cfdc094 / feat/W25-interrupt-scope。
devdocs/05 追加 W27 小节;另记下 data/ws/WsEvent.kt:272 的同类过时 KDoc(不在本轮允许改动的
文件清单里,未动)。
验证:assembleDebug 通过;testDebugUnitTest 891 条 / 0 失败 / 0 错误(改动前 892,删掉 1 条)。
This commit is contained in:
1 parent
e4c6c7c72a
commit
805d8b530a
5 files changed
+49
-21
No files matched your search
+12
-3
@@ -110,13 +110,19 @@
|
||||
## 4. WS 实时通道
|
||||
|
||||
- **连接状态机**:`Idle → Connecting → Connected → Reconnecting(退避 1/2/4…30s + 抖动) → Offline(需登录)`。握手 401 不再重试,直接登出。
|
||||
- **重连补全**:主会话用新连接的 `history` + REST 补差(`before = 本地最后一条 createdAt`),次级会话走 REST,全程按 `id` 去重。⚠️ `02/A5` 已提供 `after` 正向增量端点,**客户端尚未接入**。
|
||||
- **重连补全**:主会话用新连接的 `history` + REST 补差(`before = 本地最后一条 createdAt`),次级会话走 REST,全程按 `id` 去重。~~⚠️ `02/A5` 已提供 `after` 正向增量端点,**客户端尚未接入**。~~
|
||||
⚠️ **2026-10-04 更正:已接入**——`data/repository/MessageRepository.kt:92` 的 `loadAfter` +
|
||||
`ui/chat/ChatViewModel.kt:1303` 的 `catchUp`(掉线记游标 / 重连正向追),落地 `28c3142`(2026-10-04 深夜)。
|
||||
- **事件路由**:结构化流帧(`message.*` / `thinking.*` / `tool_call.*` / `tool_execution.*`)**后端 v0.36 起已带 `sessionId` + `turnId`** → **必须先按 `sessionId` 过滤再分发**。⚠️ 历史上本端 `WsEvent.parse` 把这两个字段丢掉了,才产生「不带 `sessionId` 的帧归属当前 active turn」这一假设与 **D14 硬约束**;**D14 已退役**(2026-10-04:`WsEvent` 已透传帧壳上的 `sessionId`,`ChatViewModel.onEvent` 用 `isForSession` 过滤;**这条约束从未被任何代码强制过**,`ChatSocket.sendInterrupt` 在 W25 之前也一条帧都没发过——现已接上唯一调用方,见 `05` 的 A2 补记)。另注意:广播是发给**每条连接**、**不按会话订阅**,且每个 `ChatScreen` 的 VM **各建一条 `ChatSocket`**——所以带着旧 socket 的旧 VM 也会收到新会话的帧,**过滤是必需的,不是优化**(已落地)。遗留问题:旧 VM 的 socket 仍**不会**随切会话关闭(每个 VM 一条,且 VM 存在 Activity 级 `ViewModelStore` 里,删会话也不回收)。
|
||||
- **流式段落状态机**:`index` 编号规则文档未定义 → 按首次出现顺序建段,`start` 建 / `delta` 追加 / `end` 收口;`stream_end` 才算整轮结束。要容忍丢帧(服务端发送缓冲 32,满则丢)。
|
||||
- **忙碌/队列 UI 映射**:`session_busy{mode}` → 轻状态条;`pending_ack{queueDepth}` → 「已排队 N 条」;`pending_full{max}` → 队列满(默认 5);`session_interrupted{dropped}` → 「已打断,丢弃 N 条」。注意编排 `mode`(`idle/thinking/executing/subagent`)与消息渲染 `mode` 是两个命名空间。
|
||||
⚠️ **2026-10-04 更正**:客户端**只发** `scope:"current"`(`core/prefs/AppSettings.kt:39`、`data/ws/WsOutbound.kt:63`),
|
||||
该档下 `dropped` **恒为 `0`**(契约 v0.56 明文);`dropped > 0` 只会在 `scope:"all"`(清空 pending)时出现,
|
||||
而**客户端从不发 `all`** ⇒ 上面那句「已打断,丢弃 N 条」在**今天的客户端里不可达**,留作契约面备忘即可。
|
||||
- **打断模式**:~~后端 `interrupt` 会清空 pending(`02/A2`)→ 该模式下 UI 限制一次只发一条并明确提示。~~
|
||||
⚠️ **补记(W25,2026-10-04)**:`02/A2` 已由后端 v0.56 修好——`scope:"current"` = 只取消在跑回合、
|
||||
**保留待处理队列**,客户端也已在发送路径按偏好发这个 `scope`(见 `05` 的 A2 补记)⇒ 「清空 pending」
|
||||
**保留待处理队列**,客户端也已在发送路径按偏好发这个 `scope`(`feat/W25-interrupt-scope`,`cfdc094`;
|
||||
见 `05` 的 A2 补记)⇒ 「清空 pending」
|
||||
这个前提**不再成立**,上面那条「限制一次只发一条」**不必做**:队列里的消息一条都不会丢。
|
||||
|
||||
---
|
||||
@@ -128,7 +134,10 @@
|
||||
- `<action>` / `<narration>` → 灰斜体旁白,依附气泡:首部连续块在上、尾部连续块在下、中间块上提到 head。
|
||||
- 兼容漂移:全角 `*…*`(短于 40 字符且无换行 → action);半角 `*…*` **仅当以 动作/旁白/内心/神态/轻声 开头**才算旁白,否则保持普通文本。
|
||||
- **流式增量解析**:delta 会把标签切半(`<act` + `ion>`)→ 带缓冲的增量解析器,保留尾部未完成片段,只在能确定边界时产出;未闭合 `<p>` 不显示直到闭合。行为对齐 Web 端 `parseContent.ts` + `chat.tsx`。
|
||||
- **mode 回落**:后端不落库(`02/A3`)→ P0 全部按 `chat` 渲染。
|
||||
- **mode 回落**:~~后端不落库(`02/A3`)→ P0 全部按 `chat` 渲染。~~
|
||||
⚠️ **2026-10-04 更正:已落库**(后端 `b686d5f`,见 `02` 的 `A3` 行与「回执五」补记第 5 条)——客户端按 `mode` 分支:
|
||||
`ui/chat/MessageBubble.kt:110` 的 `val isChat = message.mode.isNullOrBlank() || message.mode == "chat"`
|
||||
(只有 `chat` 才按 markup 解析 `<p>` 切气泡;其余 mode 走单块纯文本兜底,`null` / 空白同样回落 `chat`)。
|
||||
- **thinking / tool_call**:仅 WS 实时有;历史思维链按需请求 REST `/messages`(D11)。工具卡把 `tool_call.delta`(参数 JSON)与 `tool_execution.update` 的 `delta`(局部输出)分两块渲染;执行状态由 `tool_execution.start/update/end` 的 **`phase`**(`running`/`done`/`error`)驱动,三帧按 **`id`** 关联到对应的 `tool_call.*`(契约 v0.50;这三帧不带 `index`)。
|
||||
- **图片**:Coil 加载;base64 先读尺寸再决定是否解码,避免 OOM。
|
||||
- **Markdown**:P0 不渲染,代码只给等宽字体 + 横向滚动。
|
||||
|
||||
Reference in new issue
Block a user