Repository navigation
Keep verified conversation history after a source check is interrupted - #8534
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: model_agent, GPT-6 family, OpenAI, self_reported
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机 / Motivation
从桌面 App 打开同一原会话的用户会遇到这个问题。绑定会话已经读完历史,最后一次来源核验却因传输中断未完成。旧版重试会从头读取历史。新版暂存已读页面,在下一次核对同一来源和历史版本后再显示。HTTP 回归在三页读完后中断,恢复时只核对一次来源,没有重新下载历史。本改动处理断线后的历史恢复和发送按钮状态。首个来源请求的偶发延迟,以及现网采用后的真实附件点击,仍需分别验收。
改动思路 / Approach
现有 fetchChatHistory 拥有完整读回和恢复,原 HTTP/Core 仍拥有来源授权,现有 hook 负责同一会话的重试。范围是原绑定 App 会话在完整读回后的传输中断,连同可见历史及发送状态。
在现有历史读取函数里保留一份已经读完但尚未通过最后来源核验的结果。下次仍请求原绑定,且只有会话身份、历史版本和运行状态都相同时,才把暂存记录显示出来。来源撤销会在重新读取历史前拒绝,历史变化则重新读取当前版本。
具体改动 / Changes
- 历史读取复用原有传输错误分类和会话检查。暂存记录只留在内存,始终与可以显示的记录分开。 历史读取
- 工作区在原会话历史不可用时继续锁定发送,即使前一次选择的执行器与当前来源不同。 发送保护
- HTTP 回归在完整读取后注入传输失败,并覆盖恢复、撤销授权以及等待期间新增消息。 恢复回归
读到同一会话的新版本时,客户端按现有分页契约读取当前记录。来源复核失败时可见快照为空,暂存值只参与下一次校验。打包测试使用 452 条记录,恢复前后总计仍只有三次历史分页请求。
对主干的风险 / Risks
主要风险是把旧记录显示在变化后的来源或版本上。恢复前会读回原绑定,检查会话、历史版本及运行状态。原版在相同输入下失败,候选版恢复时只请求一次绑定。撤销授权和新增记录仍按现有规则处理。正在恢复的页面也不能发起一次新的对话回合。打包版在锁定期间和恢复后分别核对按钮与请求日志,未提交任何回合。类型检查和标准合入检查通过。
我的整体评价 / Verdict
这次交付去掉了重试时的重复下载,同时保留原绑定的读取条件。打包页面中断时显示恢复提示并锁定发送,恢复后显示原文并恢复按钮,测试没有发送消息。打包测试使用真实 HTTP/Core 与合成来源。原桌面附件点击仍待独立验收。
English verdict: APPROVE
songoow
left a comment
There was a problem hiding this comment.
Reviewer: model_agent · GPT-6 Astra Pro · OpenAI · self_reported
评审提交:f0ff63320a90cb94d6a04aa9c198df8bb8a71928。完整复核 3 个文件(+71/-10)、作者自评、现有来源契约和调用路径;本轮没有发现需要阻断该提交的新问题。
动机 / Motivation
从 App 返回已绑定原会话的用户,需要在临时断线后继续读原历史,而不是每次重新下载完整会话。 同一来源的 452 条历史已经分三页读完,最后一次来源核验却断线:基准读取器抛出连接错误,候选暂存不可见的完整结果,恢复后只重新核验原绑定。 本轮真实 HTTP/SQLite/Core 验证中,恢复只增加一次来源查询,历史分页仍为三次;撤销来源仍拒绝,历史版本变化仍重新读取。 本 PR 只修读回恢复与既有 Send 保护,不改变授权、会话创建、执行器或模型配置,也不完成安装版 App 与外部消息的全面验收。
改动思路 / Approach
在现有历史读取器中暂存派生结果,并由原来源 owner 再次核验,比新增持久化缓存或第二套重试队列更合适。 当前边界是同一原绑定完成读回后的短暂传输失败;暂存结果不属于可见消息,也不赋予发送或会话替换权限。 fetchChatHistory 继续通过原绑定发现读取来源;useConversationHistory 继续持有会话范围和既有重试节奏。只有绑定 ID、Session 来源、非空历史版本、更新时间、活跃 Turn 和状态都相同时,下一次新鲜发现才可将此前暂存的快照加入已知结果。版本变化沿正常分页读取,拒绝和损坏沿现有错误路径返回,没有绕过 Core 的来源授权。
这里新增的是内存中“读完但仍待来源复核”的数据,与可以显示的 messages、snapshots 分开。没有新增持久化状态、业务准入条件或独立调度器。页面复用同一个 historyBlocksSend 判断,连接现有发送函数、提示和按钮;这使执行器显示状态暂时不匹配时,原会话不可用也仍然禁止发出新消息。
具体改动 / Changes
关键代码讲解
sameHistoryRevision/historyTransportUnavailable抽出原有精确版本比较和传输失败分类。只有chat_api_unavailable且未提供 HTTP 状态或状态至少为 500 才保留重试进度;授权拒绝、错误来源及不可用的旧协议没有被扩大成可复用成功。fetchChatHistory在全部页面完成但最终来源检查暂时失败时返回空的可见历史,并独立携带绑定快照。下一次仍先读原绑定;同版本才复用,新版本重新分页。这里没有把临时缓存写入 Core、跨绑定使用,或创建替代 Session。historyBlocksSend同时供sendMessage、恢复提示及 Send 按钮使用。新增分支是原会话且历史unavailable,其余控制、草稿、图片和队列 owner 保留。- HTTP 回归 实际启动 SQLite/Core 服务,在 452 条记录三页读完后让最终绑定检查断线,断言记录暂不可见、恢复不重复分页,并覆盖撤销授权及等待期间新增消息。另一个 steward fallback 子流程也通过,未模拟真实模型执行。
规范依据是评审基准上已有的 Continue an original private conversation 契约。逐项对应:exact_original_source implemented,恢复继续要求原绑定和 Session;fail_closed_pending implemented,未复核结果不进入可见历史;same_revision_reuse implemented,新鲜核验后才能复用,变化重新分页;installed_app_roundtrip out_of_scope,该开放验收项不由这次局部恢复修复关闭。
对主干的风险 / Risks
最强反例是读完后来源被撤销或历史发生变化,客户端仍展示旧页面。候选在复用前重读原绑定,并比较完整版本身份;本轮真实 HTTP 测试覆盖了这两个分支。对照固定基准 beff0f3e55d1 的同一读取器代码、使用相同候选 HTTP fixture 和新增断言,在最终检查处复现 ChatApiError;候选通过。这一反例只用于证明客户端恢复差异,未声称运行了整个旧版 App。评审基准与已取回主干 c49d59b24d3f 在这两个生产文件没有差异。
验证:PASS npm run build:desktop(含 TypeScript 检查)和 npm run build:chat;PASS npm run smoke:bound-conversation 的真实 HTTP/SQLite/Core 主流程及 steward 子流程;PASS quick premerge 的 4 项直接检查和 3 项定向检查。最初 HTTP 启动缺失生成的 Chat bundle,生成 bundle 后同命令通过,原失败日志保留。远端 dashboard-acceptance、三组 dashboard-browser 通过,但 chat-bundle-browser (1)、typescript-core (2/3) 和汇总仍失败,部分 Python shard 未完成;本轮没有把这些状态归因为已修复或与 PR 无关,合入检查需要继续按仓库流程处理。
语义与 CI 对齐
该改动复用已有来源、版本及传输错误语义,新增字段只是私有派生缓存。来源拒绝仍是强制边界,暂时传输失败才交回既有重试 owner。没有新增 shared enum、权限或模型义务,无需为局部缓存另建跨域词汇。没有 feature flag,因而不存在声称 default-off 却改全局准入的问题;普通、managed 和无变更来源分支的入口及 owner 均保留。
我的整体评价 / Verdict
对长时任务的决策质量和最终产出影响为 not_applicable:这次证明的是历史读回恢复,不能据此推导模型收益。用户体验为 improved,可复用已完成读取,且失联时 Send 继续受原来源保护。新增约 46 行生产逻辑承担了可复现的恢复缺口,复用版本比较及现有重试,未引入另一缓存生命周期;我认为机制与收益相称。
本次未改 CSS、文案、消息组件或布局结构;我复核了基准树中既有同组件的桌面和窄屏截图,并逐处确认本次只改变恢复提示及 Send 的触发条件。 桌面截图中长中英混合回复、底部草稿、附件缩略图和回到最新消息按钮保持清楚层次;窄屏保持纵向正文和可达的底部输入。复用资产见 desktop / narrow。本轮 exact-head 浏览器启动和云端 loopback 连接受环境限制,未取得新截图,也未执行 DOM 点击;复用截图的原始打包提交未记录,引用的 revision 是资产所在基准提交。新恢复状态以真实 HTTP/Core 和源码调用链验证,不据此声称安装版 App、附件点击或真实用户理解已经验收。
English verdict: APPROVE at f0ff63320a90cb94d6a04aa9c198df8bb8a71928. The bounded recovery change preserves the original source fence and reuses completed reads only after fresh verification. Remote merge checks and the stated installed-App/browser validation limits remain separate.
When a bound conversation finishes reading its transcript but the final source check loses its connection, the next refresh starts downloading the transcript again. Keep the completed read in memory and only show it after a fresh check of the same binding, session and revision. A changed transcript reads its current pages, and a revoked source still refuses access. The original conversation’s composer stays locked while the check is pending.
The HTTP/Core regression makes the final source check fail after three pages. The old client fails there. The new client keeps history hidden during the interruption, then recovers with one binding read and zero repeated pages. The same test covers a revoked source and a changed transcript. A packaged browser test also verifies automatic recovery with no extra history request and no submitted turn. Dashboard type checking, the ordinary and bound conversation smokes, and the standard premerge checks pass.