diff --git a/devdocs/05-开发进度与交接.md b/devdocs/05-开发进度与交接.md index 62cc7f0..188933d 100644 --- a/devdocs/05-开发进度与交接.md +++ b/devdocs/05-开发进度与交接.md @@ -1374,3 +1374,28 @@ curl -s -X PATCH "$BASE/api/v1/sessions/$SID" -H "Authorization: Bearer $TOK" \ > **① P1(我方接):主动消息实时看不到。** 核实结论:`proactive_message` 帧我们**只解了错的 id**(`messageId`,而落库行是另一个 `id`;`ignoreUnknownKeys` 把新 `id` 静默丢弃),且**整帧零消费**(`ChatViewModel.onEvent` 无该分支)⇒ **「显示两次」不会发生,但「实时看不到她主动说的话」是真缺口**(只能等重连/重开/切会话拉历史)。修法位置已定(`WsEvent.ProactiveData` 补 `id`/`createdAt` + `ChatViewModel` 加分支,**按已落库 assistant 行并入 `mergeMessages`**,**必须自己钉死主会话**——该帧壳**不带 `sessionId`**,否则会窜进每个次级会话)。 > **② `tool_execution` 口径:选 (a)**(只认 `.start`/`.end` 按 `id` 关联;**不要他们补 `.update`**)。取舍已明确:**没有 `.update` ⇒ 工具卡的结果区是空的**(`ToolCallState.output` 只由 `.update` 的 delta 追加)⇒ **我们真正要的是他们已认领的「持久化 `toolCalls[]` 回看面」**,并请求把输出/摘要一并带上。 > **③ 移动端推送:暂不接**(等主人自建)。并已告知他们:他们建议的「应用内提示」`message_persisted{proactive:true}` 在我们这边**零实现**(`PersistedData` 只读 `message`;客户端无任何 Notification 代码)⇒ **那条建议目前不产生任何效果**,别当兜底。 + +> ## ★★ 锚点补记二十五(2026-10-06 深夜 · W48 + W49 落地:S1 与 S6 的客户端半边) +> +> ### 状态 +> `android` = `origin/android` = **`5dc6408`**(两个合并提交);主树 **1177 测试 / 0 失败 / 0 错误**;**worktree 全清**(只剩主树),主仓只剩 `02` 那笔后端会话的在途追加。 +> +> ### W48(分支 `4f860aa`,合并 `e589ab3`):修 **S1** + 注释纠错 + 工具归位 +> - **账号指纹进 VM key**:新增顶层纯函数 **`accountFingerprint(baseUrl, username)`**(`core/prefs/SessionPreferences.kt`,**口径就是既有的 `baseUrl|username`,不另造第二套** —— `SessionsViewModel` 的私有实现也改成调它),VM key = `指纹 + "|" + sessionId`(`ui/chat/ChatScreen.kt` 的 `chatViewModelKey`)。 +> - **顺带修了同类缺陷**:**会话内搜索小 VM** 的 key 也加了指纹(它持有的 `hits` 就是消息正文,换环境后在主会话(id 恒为 `web`)重开搜索会把**别的服务器**的消息摆进面板)。 +> - **5 处错注释纠正**(`ChatHomeScreen` ×3、`ChatViewModel` ×2,加上点名的 `ChatScreen:161-164`):原写「key 一变 VM 立刻被销毁」是错的(key 变只销毁**组合**,VM 仍在 Activity store 里收帧)。**行为一字未动**(`onCleared` 仍空)。 +> - **`ws_block_proxy.py` 挪进 `devtools/ws-block-proxy/`** + `README.md`(用途 / 用法 / **「Web 前端不经过 `adb reverse` ⇒ 代理日志每一行都只可能来自 App」**这条关键性质 / `adb reverse tcp:10942 tcp:18098` 的典型用法 / 提醒抹掉日志里的 token 查询串),并**冒烟测过**(503 BLOCKED + 200 PASS)。**一次 `clean` 再也不会把它扫掉了。** +> - ⚠️ **未做设备验收**(禁碰 adb):换账号后屏幕上是否真的不再有旧消息,只由纯函数单测钉住。 +> +> ### W49(分支 `93cf3e3`,合并 `5dc6408`):修 **S6** 的客户端半边 +> - 新增判据层 **`ui/sessions/InactiveSessionPull.kt`**:`silentRefreshStep` 等纯函数 + **3 秒节流窗口**。 +> - `SessionsViewModel`:非激活会话落 `message_persisted` ⇒ **节流地走既有 `refresh(silent = true)`**(**与打开抽屉那次逐字相同**,含 `withReadCursors` 遮罩),让红点在**不开抽屉**时也能亮;登录态变化时**清账本 + 取消补拉任务**。 +> - **3 秒的理由**(写进 KDoc):够快(3 秒还在「就是刚发生」的感知里);够粗(一次静默重拉是**两个**请求,而播出方**按每条消息**推帧,3 秒能把人手连打 5 条收进同一窗口);与 `SHARE_WAIT_NOTICE_MILLIS` 同量级。 +> - **trailing 是结构性保证**:同一时刻**最多一个**补拉任务;窗口内的后续成因只更新账本并返回 `Ignore`;到点**直接拉**并把窗口重开(不回头问判据,避免自锁);边界取 `>=`。 +> - **不拉的三档**:当前打开的会话(那条路「看到即已读」,多拉是白花两个请求)、同一窗口内的后续消息、窗口内且补拉已排上。 +> - **它不是「造数」**:一个数都不编 —— 帧本身只说「某会话有新消息落库」,客户端据此**去问一次服务端**。同理这是「增量到位之前红点唯一能亮的办法」;**后端补上 `unreadCount` 后这条路可退成不必走**。 +> - ⚠️ **一个刻意的偏差**:**`select`(切会话)不接触发** —— 它紧接着发 `POST /read`,而游标还没落地时重拉会**按旧游标算出未读、把刚清掉的角标闪回来**。判据对「切会话」够用,KDoc 写明了理由。**若将来发现「切回来后红点不对」,这里就是第一嫌疑处。** +> - ⚠️ **未做设备验收**:「红点确实在没开抽屉时自己出现」没有实机证据。 +> +> ### 顺带要改的旧引用(我) +> `devdocs/05` 里那条「建议把 `ws_block_proxy.py` 挪进 `devtools/`」的建议**已执行**(`devtools/ws-block-proxy/`);文中提到旧路径(`app/build/e2e-shots/ws_block_proxy.py`)的地方**现在过时**,下次一并更正。