Repository navigation
fix(chat): retain original conversation drafts through source recovery - #7345
Conversation
Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>
…ispatch 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 · OpenAI · self_reported(宿主执行观测不可用)
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
使用原会话的用户在来源恢复后应能继续编辑原先未发送的消息。旧页面在连接不可用时允许输入,恢复后却切到空草稿;候选保留同一正文和图片。在真实打包页面与 Chat/Core 后端上,恢复后正文和图片仍可编辑,来源不可用时发送保持禁止。本次只修复输入恢复,不认证完整安装态跨端同步,也不新增图片跨刷新持久化。完整历史、媒体、游标恢复和实际双端往返仍需沿原同步交付继续验收。
改动思路
现有输入状态与重试身份拥有者足以承接修复,无需新增服务、任务库或 Core 协议;仅在固定原会话内统一草稿键。当前 PR 限于原会话输入、旧草稿与原重试身份恢复,以及同一输入框随宽度重新测量。草稿是用户尚未提交的意图;执行器的读取状态只是显示信息。把两者混作键的一部分,会在恢复时换一份输入。此次将固定原会话的键收回现有输入 owner,普通多执行器仍各自保存。旧记录由既有恢复路径承接,多个不同草稿不拼接、不猜先后;未知发送则沿原请求复制、持久读回,再交给原入口。单改一行键不足以保留升级前的草稿和重试义务,新增服务或第二存储又没有必要。
| 场景 | 主干 | 当前头 |
|---|---|---|
| 同一来源由不可用恢复 | 正文、图片从输入框消失 | 保留原正文和图片 |
| 多份旧草稿 | 容易随执行器分键隐藏 | 分别保留,显式恢复,不覆盖当前内容 |
| 未确认请求复制失败 | 原记录仍保留 | 实际发送先读回重试身份,失败即停止 |
具体改动
判断依据是改前的 docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md,修订 201a14da57a5b974ee94cb8cbd9309dbdd64e23a。以下是对原文未编号条目的评审标识:original-session-identity 已实现,固定原 binding/Session,拒绝切换到另一会话;uncertain-send-identity 已实现,恢复文字不等于已经具备可重试身份;bounded-multiline-input 已实现,正文随宽度重新测量并保留原高度上限。RFC 新增文字是在解释这些行为,不作为证明自身正确的唯一依据。
关键代码讲解
conversationInputKey只让固定会话使用稳定键;普通执行器分键保持原样。Hook 在原有 sessionStorage 与内存输入中恢复唯一旧草稿,多个冲突项留在原来的按需恢复区域。recoverBoundConversationInput不修改已有当前草稿,包括已清空的记录;唯一旧项迁移,其他会话和多个不同项保留。这使恢复不会自动拼接用户意图。restoreLegacySteeringRequest识别同 Session 的旧分键,保留原 helper 的可恢复文字返回语义和原请求、Turn、图片字段。只有持久复制成功才退休旧位置。sendMessage在发送前检查已恢复的合格重试身份。复制失败时即使文字仍可显示,也不能落入一条全新普通消息;现有反馈说明存储问题,原记录留待恢复。
复用既有 steering 测试承接新增分键场景,没有保留第二套重复断言。最初头误改 helper 返回约定,旧测试出现一项失败;当前头恢复了原约定,九项既有测试与一项新增测试通过。输入 smoke、Chat 构建/typecheck、真实 HTTP/SQLite/Core 的 bound 与 App-steward smoke 也通过。
对主干的风险
最大风险是把旧的未知投递恢复成新请求。此次以实际打包页面点击发送制造存储复制失败,独立后端读回证明 Session 仍三条、消息仍十四条、原生协议请求仍十二条;原请求身份未改,未启动新工作。来源拒绝仍禁止发送,恢复仍回到准确原目标。普通草稿及旧 inline 路径继续使用原 owner,输入迁移不增加权限或执行器。
预合并四项直接检查、十三项选中检查无执行失败。出口仍列三项二进制图片人工检查:已逐一看过像素、来源和 PNG 元数据,内容为合成 UI,没有私有来源、账号和路径;这不是把文本扫描当图片审查。该 Goal 的变更质量门关闭,CI 按当前合同未读取、轮询或等待。测试中的 provider/model 为合成实现,不能据此宣告安装态或完整消息同步完成。回滚仍是前端版本回退,旧历史与重试记录未被批量删除。
我的整体评价
APPROVE,准确头 0f7fb18fd228ecf001b5f014379f379563c0806a。这是有用且可独立验收的恢复增量:long_horizon 为 improved,依据是来源丢失后可以保留意图继续工作并保持原重试身份,并非吞吐收益结论;user_experience 为 improved,正常路径无需再输入或另做一次确认。已读桌面和390px窄屏的完整打包视图:长报告仍在会话中,保留的输入与图片位于底部,来源失败及恢复操作保持可见;多份旧草稿只在按需展开时出现。未以这些合成页面认证真实原生输入法、图片跨刷新保存或完整安装态跨端往返。当前本地分键规则复用既有类型,没有新增共享词汇或模型决策合同。完整跨端验收继续归既有交付,合并资格仍单独读回,不从测试数或本次意见推断。
English verdict: APPROVE — 0f7fb18. The packaged base reproduces lost text/images; the final source preserves fixed-Session input and refuses dispatch when retained retry identity cannot be qualified. Existing recovery semantics and ordinary executor drafts remain intact. Synthetic protocol tests do not certify installed full cross-channel synchronization.
An unavailable original conversation used an executor placeholder as part of its composer key. When the same source recovered, its unsent text and images disappeared from view. Keep that input with the fixed binding/Session, recover older drafts without overwriting distinct messages, and preserve uncertain-send identities before retry. Ordinary executor-specific drafts remain separate. The existing composer also resizes retained text when its width changes.
Validation: Chat production build/typecheck; input regression plus the ten existing/extended legacy identity tests; real HTTP/SQLite/Core bound-conversation and App-steward smokes. Ego Lite reproduced text and image loss on main
201a14da5; the candidate retained both, denied Send while the source was unavailable, and recovered the same target. Unique/conflicting older drafts and failed retry-storage copy were checked; the refused copy started zero new model requests. A populated 14-message/six-report fixture was inspected at 1280×900 and 390×844.This is composer recovery, with synthetic provider/model fixtures. It does not certify full installed cross-channel synchronization or add image persistence across reloads.
Visual evidence
Exact source
0f7fb18fd228ecf001b5f014379f379563c0806a; packaged UI with the real disposable Chat/Core backend. Both before and after use the same populated synthetic workload. The retained image is also public synthetic UI.Before: recovery changes the visible input to an empty draft.
After: recovered text and the in-memory image stay beside the existing conversation.
Narrow: retained text expands within the existing composer; toolbar and Send remain visible.