Repository navigation
fix(chat): keep same-second replies in source order - #7382
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
loopx-agent
left a comment
There was a problem hiding this comment.
Reviewer: actor_kind=model_agent; model=gpt-6.1-sol; provider=OpenAI; runtime_reported; reasoning_effort=max
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
详细中文评审
动机
管家和项目对话的用户在重新打开历史时会遇到此问题。 同一秒内先提问再得到回答,旧版却可能把回答排到问题前;修复后保留原会话记录顺序。 传输回归与桌面、窄屏的完整恢复场景均证实问题在前、回答在后,正文和消息身份保留。 本 PR 不声称完成完整历史分页、回复线程或真实 App/飞书双向送达。 完整同步仍需原安装入口的分页、媒体与原生输入独立验收。
改动思路
已有共享历史合并器拥有消息顺序;只更换同时间戳的比较依据,保留单一读取和呈现链路,不另建排序服务或持久状态。 本 PR 交付同秒消息原序显示和可用的既有恢复回归;不改变发送、授权、存储格式或执行者。 消息的原始追加顺序已存在于权威快照中,不需要再保存一份序号。稳定排序在同一会话的时间戳相同时保留输入顺序;独立会话同时发生的消息按会话身份稳定显示,不冒称它们具有全局因果顺序。下游提案排序负责卡片位置,不能恢复已被历史合并破坏的顺序,因此修复留在共享读取 owner。
| 原始触发 | 旧行为 | 当前行为 |
|---|---|---|
| 同一会话,同秒先问再答 | 随机 ID 可能让回答在前 | 保留存储追加顺序 |
| 不同会话,时间相同且读取完成先后变化 | 消息身份不等于全局时间 | 按会话稳定显示,不合并身份 |
| 旧读取超时,新读取已恢复 | 检查可能等不到已取消请求的响应 | 等旧 handler 结束,再查界面与零重放 |
该表说明准确提交 a068c7b2738ef05841b14e0d8ad8f84a6f8db44e 的源码路径;它不是运行证据。实际证据是原传输与完整浏览器场景的基线失败、修复后成功。
具体改动
评审依据是修改前的 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md,spec_revision f794ee4642353b70cde185e2774fa0059c467d6a。history-readable 对应“旧 Session 失败不能隐藏可读消息”,由真实 HTTP/store 历史恢复和完整界面场景落实;history-preserve 对应保留实时正文与草稿,场景仍检查草稿、状态投影的精确身份与替代 Turn 控制;history-no-replay 对应不得从读取失败推断空会话或新建驱动,原零执行/零重放断言继续通过。新增双语段落只说明同秒顺序边界和未验收范围,没有反向重写这些既有要求。
关键代码讲解
mergeChatSessionMessages继续按原 Session/消息身份去重。时间不同仍按时间显示;相同时只比较 Session,稳定排序自然保留同一 Session 的原顺序。长正文、相同文本的两个真实消息以及跨 Session 同 ID 都由原传输检查覆盖,没有正文去重或新写入。overlappingHistoryRefresh仍挂起旧读、完成新读、再放开旧失败。旧请求可能已经超时,浏览器不会产生响应;现在等待被挂起 handler 的结束,再检查提示没有重开、草稿未丢、发送可用且没有回放。状态投影的导航恢复先等精确行出现,再要求计数为一,修正检查时序而未删身份断言。checkSessionHistoryRecoveryContract增加同秒倒序 ID、完整长文、重复文本、跨会话冲突与输入快照顺序变化。默认脚本沿已有 Vite SSR 构建,修复独立 tsc/Node 的混合扩展名解析失败;完整 Dashboard 类型检查仍单独执行。
对主干的风险
最强反例是普通不同时刻的历史检查全绿,但同秒随机 ID 仍把回答放前。独立传输与真实共享时间线检查已在不改生产代码的基线上失败,在准确提交的源码和完整打包版本通过。跨 Session 相同 ID 和重复正文仍被保留,读历史没有新增 Turn、授权或存储副作用。缺失历史仍显示失败并退避恢复,读取取消也不能夺走新 Turn 的控制权。
准确提交已完成源码环境预检:4 项直接检查、19 项选中检查均执行通过,三张二进制截图由画面及 PNG chunk 独立检查。首次使用系统 Python 缺少 pytest 的预检失败留存,随后使用仓库 uv test 环境通过;旧 runner 的 TS5097、模块找不到与诊断中断也保留,没有删断言或放宽阈值。按当前策略没有获取、等待或轮询 CI。本修复没有新增生命周期分类、协议名、默认关闭功能或预算;完整同步的 provider/原生验收范围继续开放,不把界面顺序当作业务送达。
我的整体评价
没有发现阻塞问题,结论 APPROVE。long_horizon 为 preserved:原恢复与零重放约束保留;user_experience 为 improved:同秒回复可按原问题顺序阅读。已逐张检查桌面修改前后与窄屏截图:回答不再先于原问题,完整消息卡片和底部输入区可辨认;合成历史读取失败提示仍可见且可重试。 真实原生输入、全量分页及 provider/媒体双向送达没有由这些合成截图证明。 已有共享历史合并器拥有消息顺序;只更换同时间戳的比较依据,保留单一读取和呈现链路,不另建排序服务或持久状态。 本 PR 交付同秒消息原序显示和可用的既有恢复回归;不改变发送、授权、存储格式或执行者。 相较另造排序状态,本实现复用原 typed identity 和共享 owner,成本与已复现问题相称。此准确头的评审通过不授予合并权限;仍须即时读取同一提交的 merge-readiness,并保留原维护者授权及全部未闭同步工作。
英文简短结论
English verdict: APPROVE — exact head a068c7b2738ef05841b14e0d8ad8f84a6f8db44e preserves source order for timestamp ties without changing stored identities or execution. Baseline transport/UI oracles fail; fixed source and complete packaged recovery, HTTP/store, typecheck and 4 direct + 19 local premerge checks pass. Visuals reviewed. Full native/provider synchronization remains unqualified; merge readiness is separate.
When two stored messages have the same timestamp, sorting by opaque message ID can put an answer before its question. Preserve the Session append order and retain deterministic ties across Sessions. Complete bodies, repeated text and exact Session/message deduplication stay intact.
Also repair the existing recovery checks: wait for projection hydration and for a held reader to settle even if its request timed out. The chat-route smoke now uses the existing Vite SSR path, which resolves the mixed TypeScript imports that broke its standalone tsc/Node runner; dashboard typechecking still runs separately.
同秒消息沿原 Session 顺序显示,避免回答跑到问题前面。只修共享读取/显示及原恢复验收;完整历史分页、回复线程、真实 App/飞书双向媒体和原生输入仍未验收。
Validation:
Synthetic fixtures only; screenshots and PNG metadata reviewed.
Before (desktop):

After (desktop):

Narrow:
