docs: 收下后端回执十二 + 登记本轮三条接入结论 + 05 锚点补记二(含两条新教训)
devdocs/02(与后端会话共用的协作文档): - 收下他们的**回执十二**:knowledge 的根因(参数角色缺席 + 收尾静默分支)与修法、并行 tool_use/tool_result 配对的新 P0(客户端不需要适配)、契约 v0.64 的只读 GET /admin/llm/usage、IM 入站文件「能拿到字节」(真机验证只能由主人做)、 llm_usage.session_id 恒为 NULL。 - 追加**我方登记**:v0.63 那两条(reason / 404)都已接入并推送;after 正向增量的两条口径; 及一条要他们确认的**我方主动口径** —— unreadable 且 mime 是 text/ 时让 reason 压过正文渲染。 - 他们点出的三处旧口径(1049/1110/1309 行)我核过,**都在他们自己的回执段落里**,按同一条规矩 请他们自己更正,我们没有动手。 devdocs/05(我方进度与交接): - 锚点更新:HEAD、884 测试、本轮 5 条落地、待办重排、家务办完。 - 新增「本轮未验证的」一节:把**验过的**与**没验过的**分开写(404 兜底验通;after 只验到「真请求 打出去了」、补洞端到端仍未验;W24/W23 的设备侧只验到能开的程度)。 - 两条新教训:① 那个「未响应弹窗」是模拟器/宿主机被拖垮(日志里系统自己的 PointerEventDispatcher 卡了 37 秒),uiautomator dump 要省着用;② **一次性告知不能挂在会被销毁的 状态上** —— 404 兜底第一版就是这么悄悄失效的,只有真机看一眼才能发现。 本次提交只动文档,无代码改动。代码侧本轮四个提交(28c3142 /1794e53/b5c0b13/ 2d382b1)都已推到 origin/android。
This commit is contained in:
1 parent
b5c0b13210
commit
b8f0833137
2 files changed
+162
-10
No files matched your search
+79
-10
@@ -683,7 +683,7 @@ PDF · 563 B · 24 字 · 已读入
|
||||
|
||||
### 状态
|
||||
|
||||
- **`android` = `origin/android` = `d2169c6`,工作区干净**,**831 测试 / 0 失败 / 0 错误**。
|
||||
- **`android` = `origin/android` = `1794e53`,工作区干净**,**884 测试 / 0 失败 / 0 错误**(`--rerun-tasks` 全量重跑后自己读 JUnit XML 求和)。
|
||||
- 可安装包:`app/build/outputs/apk/debug/app-debug.apk`。
|
||||
- 那轮「装机验收」的**四项全部有设备证据**(含 ① 的更正:本来就是好的,是我测量假象):见上一节「装机验收轮」。
|
||||
- **联调凭据(压缩后最容易丢,务必先看这条)**:后端调试用的 `X-API-Key` **已由主人授权落地到本机 `dev-secrets.local.md`**(仓库根目录,**已 gitignore、不在仓库里**——已用 `git check-ignore` / `git ls-files` 双重核对)。用法:`curl -H "X-API-Key: <该文件里的值>" $BASE/api/v1/…`,**owner 级**(`/sessions`、`/admin/observability/{metrics,logs}`、`/admin/system/storage`、`/admin/personas` 全 `200`;不带 key 回 `401`)。**上一个 key(`rG14nMNX…`)已失效(实测 `401`),不要再试。** 轮换只改那个文件,代码/文档里没有任何硬编码。
|
||||
@@ -709,25 +709,64 @@ PDF · 563 B · 24 字 · 已读入
|
||||
|
||||
(W20/W22 是**修「非 bug」的空转**,零改动,见上一节的教训。)
|
||||
|
||||
### ★ 本轮(压缩恢复后 · 2026-10-04 深夜续)已落地并推送
|
||||
|
||||
| # | 内容 | 提交 |
|
||||
|---|---|---|
|
||||
| — | 收下后端**回执十一**(四点确认 + `knowledge` 已立案 + IM-1/U1 在实施) | `e4915a6` |
|
||||
| A5 | **`after` 正向增量**(断线补洞):掉线记游标 / 重连正向追 / 补拉循环抽成纯函数 | `28c3142` |
|
||||
| — | **`404 session_not_found` 兜底**:非主会话读不到 ⇒ 如实说一句 + 自动退回主会话 | `28c3142` |
|
||||
| W23 | 接上契约 v0.63 的 **`documents[].reason`**(只在 `unreadable` 时下发,原样显示) | `2d382b1`(`e5fa1b8`) |
|
||||
| W24 | **编辑弹层只显示纯文本**(真有标记时才剥;弹层明说会替换掉旁白) | `1794e53`(`66c2291`) |
|
||||
|
||||
**这一轮的两条关键事实(复用价值高)**
|
||||
|
||||
1. **可编辑的只有 `user` 消息,而 user 消息存的是用户原文、从不带标记** —— 我扫过线上主会话 64 条 user 消息,**零条含标记**;所有 `<p>` / `<action>` 都在 assistant 消息里(assistant 不可编辑)。所以 W24 的「剥标记」在今天是一条**防御性口径**,对真实数据是恒等变换;`hasMarkup` 的判据因此取的是「`text != content`」(= 保存真的会替换掉什么),而不是「出现过 `<`」。
|
||||
2. **`after` 是升序、`before` 是倒序**,且 `after` 方向的下一条游标是**本页最新**那条(不是最旧的)。服务端按 `limit` 从最旧一端给,所以取最旧当游标等于原地不动。同毫秒并列超过一页时纯 `createdAt` 游标推不动(契约 §A5 明写的边界)⇒ 循环里必须有「游标严格前进」+「轮数上限」两道闸,否则无限请求。
|
||||
|
||||
### 待办(按优先级)
|
||||
|
||||
1. **`after` 正向增量**(`GET /sessions/{id}/messages?after=`;与 `q` 互斥)—— 本轮唯一还没动的原计划项。
|
||||
2. **W19 留下的两处**:① 编辑框里显示的是**原始 `content`(含 `<p>`/`<action>` 标记)**(剥标记再回写会静默丢掉旁白与动作)—— 要不要给「只显示纯文本」的编辑口径,**待主人拍板**;② **删除后抽屉里的「最后消息预览」不刷新**(本端只改了消息列表,没触发 `GET /sessions`)。
|
||||
3. **遗留 ①:连接所有权** —— 每个会话一个 `ChatScreen` 的 VM 各建一条 `ChatSocket`,而 VM 活在 Activity 级 `ViewModelStore` 里 ⇒ 访问过的每个会话都**永久留一条连接**(含各自的 `OkHttpClient` 与连接池),**删会话也不回收**。自然解法:**共享一条 socket + 按会话路由**。
|
||||
4. **`ChatSocket.sendInterrupt` 零调用方**(「切会话时打断」这个出口从未接线,是死 API)。
|
||||
5. `observability_audit` 帧(**后端未做**,审计仍是 REST);`POST /admin/users` 仍是 `501` ⇒ 铸不出 `viewer` 账号,viewer 降级只能单测背书。
|
||||
6. 子会话流程(主人已确认搁置)。
|
||||
1. **W19 剩下的那一处**:**删除后抽屉里的「最后消息预览」不刷新**(本端只改了消息列表,没触发 `GET /sessions`)。① 编辑口径已由主人拍板并落地(W24)。
|
||||
2. **遗留 ①:连接所有权** —— 每个会话一个 `ChatScreen` 的 VM 各建一条 `ChatSocket`,而 VM 活在 Activity 级 `ViewModelStore` 里 ⇒ 访问过的每个会话都**永久留一条连接**(含各自的 `OkHttpClient` 与连接池),**删会话也不回收**。自然解法:**共享一条 socket + 按会话路由**。
|
||||
3. **`ChatSocket.sendInterrupt` 零调用方**(「切会话时打断」这个出口从未接线,是死 API)。
|
||||
4. `observability_audit` 帧(**后端未做**,审计仍是 REST);`POST /admin/users` 仍是 `501` ⇒ 铸不出 `viewer` 账号,viewer 降级只能单测背书。
|
||||
5. 子会话流程(主人已确认搁置)。
|
||||
|
||||
### 家务(等主人一句话)
|
||||
### 本轮**未验证**的(别当成已验证)
|
||||
|
||||
- **三条验收会话留着没动**:`E2E-DOC` / `E2E-PDF` / `E2E-RETRY`(后者含一条被编辑过的消息)。要清就说。
|
||||
- **15+ 个 worktree**(W4–W22)都合并推送过、目录里是各自构建产物。要清的话**用 Git Bash 的 `rm -rf`**(Windows 直接用 git 自带的删会撞 `MAX_PATH`)。
|
||||
- **`after` 的设备侧验收**:真机上**确实打出过**真请求 —— `GET /sessions/web/messages?limit=50&after=1791121966668` → `200`(说明「重连补洞」那条路真的在跑)。但**「断线超过 50 条」那个洞没造出来**(要造得让后端不可达再灌消息,而后端当时正被另一个会话实时开发,我实测到一次 `HTTP 000`)⇒ **补洞的端到端仍是未验证**。逻辑侧由 `ChatRecoveryTest`(循环两道闸)与 `MessageAfterRepositoryTest`(真 OkHttp 断言线上 URI)钉住。
|
||||
- **`404 session_not_found` 兜底:已装机端到端验通**(`b5c0b13`)。证据:logcat `GET /sessions/web:{已删}/messages?limit=50` → **404** →(0.9 s 后)`GET /sessions` → 界面回主会话;红字横幅「这个会话在服务端已经不存在了(可能已在别处删除),已切回主会话」可见(`app/build/e2e-shots/w7-gone-2.png`)。**第一次验收它是失败的** —— 切换对了、横幅一个字没显示(原因见下面那条新教训),修完才通过。
|
||||
- **W24 的编辑弹层:弹层本身在设备上开过**(user 消息 → ⋯ → 编辑;弹层正常,计数 `0 / 4000`、保存键按预期禁用)。**但没验到「非空初值」**:主会话里那一片 user 消息**全是纯附件回合、`content` 长度为 0**(已对着 `GET /messages` 逐条核过),初值本来就是空串。**也没验到「原文带标记」那句告知**(线上 64 条 user 消息零条含标记,见上)。⇒ 「剥标记」那条路径仍**只有单测背书**。
|
||||
- **W23 的 `reason` 弹层显示**:纯逻辑链(DTO → chips → previewPlan → 弹层文案)由 17 条单测打通,**没有在设备上点开过一个真正 `unreadable` 的附件**。
|
||||
- **已用设备证据确证的那一条**:待发附件的**小预览框 + 叉**(主人点名要的那个)**本来就存在且可用** —— 截图 `app/build/e2e-shots/v4-draft.png`(`attach-demo.txt / TXT · 45 B` + 叉,挂在输入框上方)与 `v5-removed.png`(点叉后预览框消失、发送键复灰)。**零代码改动**。
|
||||
|
||||
|
||||
### 家务(**2026-10-04 深夜续已办完**)
|
||||
|
||||
- **三条验收会话已删**(主人放行后执行,带证据):`E2E-DOC`(1 条消息)/ `E2E-PDF`(2 条)/
|
||||
`E2E-RETRY`(2 条)三条 `DELETE /api/v1/sessions/{id}` 全 `200`;现在服务端只剩主会话 `web`。
|
||||
- **18 个 worktree 已全部清除**(W4–W19 + W23/W24),用的是 **Git Bash 的 `rm -rf`**(Windows 直接用
|
||||
git 自带的删会撞 `MAX_PATH`),随后 `git worktree prune`;`git worktree list` 现在只剩主树。
|
||||
**分支引用全部保留**(每个都 `git merge-base --is-ancestor <branch> android` 验过已并入,
|
||||
留着方便回退)。
|
||||
- 清理手法留在下面的「构建与环境」一节:这两个动作都是**不可逆**的,删之前先跑一次
|
||||
「每个分支是否已并入」的检查。
|
||||
|
||||
### 与本轮配套的两条新纪律(已进上面的教训段,这里再点一次)
|
||||
|
||||
1. **动代码之前先用像素确认症状**;`uiautomator dump` 只用来取坐标。
|
||||
2. **症状与代码结构矛盾时,先怀疑自己的测量**。
|
||||
|
||||
### 还有一条:那个「未响应弹窗」的真相(主人问过两次)
|
||||
- 现象:模拟器隔一阵弹 `Cyrene isn't responding` / `Process system isn't responding`。
|
||||
- **证据**:我在 40 秒内连跑 3 次 `uiautomator dump`,紧接着一次 `adb shell input tap` **48 秒才返回**;
|
||||
WindowManager 报 `ANR in top.yeij.cyrene … Waited 5121ms for MotionEvent`,**而同一段日志里还有
|
||||
`PointerEventDispatcher0 is not responding. Waited 37729ms`** —— 系统自己的输入分发器都卡了
|
||||
37 秒,那不是 App 的 bug。紧接着就弹了 `Process system isn't responding`。
|
||||
- ⇒ **`uiautomator dump` 要省着用**(它是整棵视图树的重量级遍历),一次够用就够;一次交互超过
|
||||
几秒没回来就当**环境卡了**,别记成 App 的 bug。真卡住了就 `adb emu kill` 重启(本次重启后
|
||||
同一操作的延迟从 48 s 回到 0.27 s)。
|
||||
|
||||
> **★ 锚点补记(2026-10-04 深夜 · 压缩前落)**:后端回了 **回执十**,契约升到 **v0.63**,其中两条**直接冲我们**:
|
||||
>
|
||||
> 1. **`documents[].reason`(可选)** —— 只在 `status:"unreadable"` 时下发;文案是**他们注入模型清单里的同一份字符串**(10 条,例如「无法抽取正文:这是图片型/无文本层 PDF,需要 OCR 才能读(当前未做)」)。⚠️ **别做整句相等判断**(真实字符串比登记里给我看的更完整),要分支就用稳定子串(如「需要 OCR」),**默认原样显示**。
|
||||
@@ -736,3 +775,33 @@ PDF · 563 B · 24 字 · 已读入
|
||||
> ⇒ **新待办**:用这个 404 做「**当前会话已被删除 ⇒ 自动退回主会话 + 如实说一句**」的兜底(就是主人那次「图片凭空消失」的体验问题)。
|
||||
>
|
||||
> 另有 **v0.62** 的 `POST /admin/llm/reset-from-env`(owner-only 写、要 `confirm:true`)——客户端暂无 LLM 管理页,**纯增量、不用管**。
|
||||
|
||||
### 又一条硬教训:**一次性告知不能挂在「会被销毁的状态」上**
|
||||
|
||||
404 兜底的第一版把「会话已删」那句横幅放在 `ChatUiState.sessionGone` 上。逻辑看着完全对,真机上
|
||||
**用户一个字都看不到**:
|
||||
|
||||
`onSessionGone` 一被调用 ⇒ `activeSessionId` 收敛回主会话 ⇒ `ChatScreen` 的 `key` 变化 ⇒
|
||||
**承载那面横幅的 VM 连同状态立刻被销毁**。
|
||||
|
||||
⇒ 规矩:**告知要挂到活得比它久的那一层**(这里是 `ChatHomeScreen` 的 `remember`),并且
|
||||
**先立告知、再触发刷新** —— 顺序反了同样看不到。
|
||||
|
||||
这类 bug 单测抓不到、编译也不报,只有**真机看一眼**才会发现。→ 又一个「UI 改动必须真的看一遍」的证据。
|
||||
|
||||
> **★ 锚点补记二(2026-10-04 深夜续 · 压缩前落)**:本轮把**三条待办做完了两条半**,另收了后端
|
||||
> **回执十二**(`knowledge` 根因 + 并行 `tool_use` 配对的新 P0 + **契约 v0.64 的 `GET /admin/llm/usage`**)。
|
||||
>
|
||||
> 1. **`after` 正向增量已接**(`28c3142`):掉线记游标 / 重连正向追 / 循环抽成纯函数 `loadForwardPages`
|
||||
> (两道闸:游标严格前进 + 20 轮上限)。**真机打出过真请求**,但「断线超 50 条」的洞没造出来。
|
||||
> 2. **`documents[].reason` 已接**(W23 / `2d382b1`):弹层里**原样显示**他们那句,芯片仍是「读不到」。
|
||||
> ⚠️ 一处**我们主动选的口径**:`unreadable` 且 `mime` 是 `text/` 时让 `reason` **压过**正文渲染
|
||||
> (理由与代价都写进了 `02` 的登记,等他们确认)。
|
||||
> 3. **`404 session_not_found` 兜底已装机验通**(`28c3142` + 修复 `b5c0b13`)。
|
||||
> 4. **W24 编辑弹层只显示纯文本**(`1796e53`→`66c2291` 合并为 `1794e53`):真有标记才剥;
|
||||
> `hasMarkup` 取「`text != content`」(= 保存真的会替换掉什么),弹层明说会替换掉旁白/动作。
|
||||
> 5. **家务已办完**(3 条验收会话 + 18 个 worktree,见上一节)。
|
||||
> 6. **后端那边与我们有关的两条**:`knowledge` 的修法已经落地(**历史里会落一条诚实说明**,我们在
|
||||
> `89776fe3…` 那条会话里亲眼看到);并行 `tool_use` 配对的 P0 在修,**客户端不需要适配**。
|
||||
> 7. **`.env` 新增 `LLM_MULTIMODAL`**(主人加的)⇒ 主模型能识图,`H1` 那条结构上消失。
|
||||
> **我们还没做「如果主模型本身能识图」的客户端侧跟进**,等主人指路。
|
||||
Reference in new issue
Block a user