fix(app): keep pending input and receipts in their conversation - #5528
Conversation
Signed-off-by: huangruiteng <huangrt01@163.com>
|
Local validation for c3b642b: production dashboard/Chat builds and packaged conversation admission, history recovery, image transport and typed-action journeys pass. The primary regression fails on unchanged main and passes on the candidate. Thirteen real HTTP/store and scripted Codex protocol steering tests pass. This covers the changed App presentation state; live provider adoption and broader managed/attached lifecycle remain unqualified by this PR. Risk-based premerge executed all 13 selected architecture, vocabulary, projection, content-operation and public-boundary checks plus 3 diff checks successfully. The first vocabulary attempt failed solely because the new worktree lacked root TypeScript dependencies; after provisioning them it passed. The second run's overall gate reported a stale quality receipt because a temporary desktop build dependency symlink was then classified as an untracked source. That locally-created symlink has been removed; the original four-file scope fingerprint is restored and the persisted change-quality receipt verifies valid. The successful check execution plus final exact-scope verification form the equivalent validation set; no product source changed and no test gate was relaxed. Future-facing refactor: consolidate six mutable App input fields under the existing conversation draft key in a 50-line typed presentation hook, replacing navigation resets and captured global flags. Backend Session/Turn ingress remains the sole effect owner. Navigation now retains unsent images with the originating conversation rather than discarding them. No new capability, provider, persisted receipt vocabulary, permission or scheduler rule is introduced. Initial layout/navigation is unchanged. No CI or paid model evaluation was queried or run. |
huangruiteng
left a comment
There was a problem hiding this comment.
Reviewer: actor_kind=model_agent; declaration_source=runtime_reported; model=gpt-5.6-luna; provider=OpenAI; effort=max
Request changes conclusion (author-owned PR; GitHub blocks formal self-review)
动机
本次审查的 affected_caller_or_operator 是:A user typing, steering or attaching an image in a Goal or steward conversation while another conversation has an in-flight ordinary send or delayed receipt. 旧行为 old_behavior 是:The component kept sending, steering, feedback, receipts and image attachments as one component-wide state, so an async completion or a pending request in one Goal/manager view could affect the visible peer. The head moves those fields to keyed state, but its normal-send failure path still restores the original image array unconditionally. 具体 before_after_scenario 是:Before: a pending request in Goal A could disable or overwrite the composer in manager B. After: the keyed hook keeps pending flags, feedback, receipts and navigation images per existing Goal/Agent key, but when manager B's ordinary request is pending, a newly pasted image is still accepted by the textarea and the late catch at sendMessage restores the older pendingImages value over it. 这项问题的 compounding_cost 是:The user can lose a newly pasted image at the exact moment a failed older request is reported, then must reconstruct the attachment and may no longer know which text/image pair is safe to resend; cross-conversation waiting also risks a disabled Send or feedback shown in the wrong conversation. exact-head 的 observable_outcome 是:The independent packaged browser replay passes the cross-conversation pending receipt/draft isolation already covered by the scenario, then fails a direct counterexample: paste pasted-during-send.png into the still-active textarea while an ordinary send is held, return confirmed not_delivered, and the image disappears. This is a material exact-head gap despite build, typecheck, HTTP steering and existing packaged positive checks. 本次 delivery 的 non_goals 是:No new capability/provider, Session/Turn effect owner, Todo, lease, quota, authority grant, protocol, automatic replay, receiver adoption, live model claim or complete GQ02/GQ04/GQ08/GQ09/team acceptance. remaining_gap 是:The exact-head normal-send catch can erase a newer pasted image in the same conversation; full live receiver adoption, native/installed acceptance and broader GQ/team lifecycle remain separate acceptance.
改动思路
Existing composerDraftKey, draft sessionStorage record, current per-key input state and the captured pendingImages array; Session/Turn and HTTP ingress remain existing callback owners. useConversationInputState owns presentation fields by key; PersonalWorkspacePage sendMessage/selectImages own local admission and restoration; existing callbacks own effect dispatch and receipts. A send clears only the captured conversation draft/images before the existing callback; keyed setters settle late sending/steering/feedback/receipt in the originating key, while selectImages can still append a textarea-pasted image during normal sending. Existing sendMessage catch reports delivery failure and restores draft/images; this exact-head path is the blocker because its image restore has no newer-image guard. Steering retry identity and confirmed non-delivery remain with steering-recovery and existing ingress owners. 这个方向正确地复用已有 Goal/Agent key,并把 presentation 与 Session/Turn effect authority 分开;但同一 key 内仍有时间顺序冲突,新的 paste 事件没有被旧 catch 保护。
具体改动
PR exact diff(base bb5cead 到 exact head c3b642b)为 4 个文件、113 行新增、15 行删除:两个共享 App production 文件、一个 RFC 和一个 packaged composer scenario。新增 hook 将 sending、steering、feedback、receipt、attachment 和 error 绑定到已有 composerDraftKey;PersonalWorkspacePage 的 send/steer/preview 分支用 captured key settlement,createPreview 也避免 late callback 选择当前 peer;RFC 记录了 conversation ownership 与 image retention;smoke 覆盖跨 conversation receipt、独立 rejection、draft/image navigation、uncertain retry、attached queue 与 unsupported adapter。
关键代码讲解
- apps/presentation/dashboard/src/features/personal-workspace/use-conversation-input-state.ts:25 的 useConversationInputState 是本地 typed presentation record;captured-key setters 保证 late callback 不写入 peer key,且不拥有网络或 Turn effect。
- apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx:878 的 composerDraftKey and hook integration 复用已有 Goal/Agent identity,替换旧的 component-wide mutable fields;这解释了 baseline peer-lockout 被修掉的部分。
- apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx:1688 的 sendMessage 仍由现有 callback/receipt owner 负责 admission;其 catch 在 1688 无条件恢复 captured pendingImages,是当前 P1 blocker。
- apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx:1802 的 handleComposerPaste / selectImages 允许 textarea paste 在 normal send 等待期间继续进入 current keyed record;file input button 的 disabled 状态不能代表整个 paste 入口已关闭。
审查依据是 accepted RFC docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md,immutable revision bb5cead,而不是 PR 新增段落倒推规范。Waiting is part of the conversation 的 same-conversation receipt/draft boundary 在该 revision mapped/implemented;Image admission and current-conversation position 要求 confirmed rejection restore draft and images,但 exact head 对 newer pasted image 为 not_met;GQ08 与 GQ09 的完整 receiver/runtime/native acceptance 在本 PR out_of_scope。
对主干的风险
存在一个可重复的 P1 blocking finding:An ordinary send is pending, the user pastes a new valid image into the still-active textarea, and the request then returns confirmed not_delivered. 代码路径是 PersonalWorkspacePage.sendMessage catch at line 1757 restores pendingImages after handleComposerPaste/selectImages at lines 1802-1793 appended the newer image.,错误结果是 The late failure overwrites the newer same-conversation image with the old captured array, so the promised manual resend state loses the user attachment.。我用 packaged public composer entrypoint 独立复现:held normal send 后通过 textarea paste pasted-during-send.png,再返回 confirmed not_delivered;exact head 报告 Failed delivery discarded a newer image pasted while the request was pending,可见 image preview 数量变为 0。这里不是 file chooser 的假设:当前 onPaste 明确可达,而 attach button/input 的 disabled 不能防止它。
现有 positive packaged scenario、dashboard build/typecheck、13 条 real HTTP steering、exact-scope CQA、risk premerge 和 diff/public checks 都不能覆盖这个时间反事实。最小修复是保留 hook 与现有 effect owners,只在 sendMessage/selectImages 边界加入 per-key attachment generation 或 expected-current-state/merge guard,并把该 hold/paste/not_delivered case 固化到 packaged smoke;修复后重新跑 exact-head result/body checker。没有 CI 查询或等待;旧 stale canary metadata 不被当作当前源码结果。
我的整体评价
problem_context verdict 是 justified_increment:跨 conversation 的 pending receipt/draft/feedback/image ownership 是真实且有价值的有界修复,long_horizon 判定为 preserved,因为没有新的 execution owner 或 authority grant;但 user_experience 目前不能判定为 improved,因 newer-image retention 在 exact head 上被反事实否定。observable_semantics 是 unintended_drift,change_proportionality 仍 proportionate,code_volume.compatibility_assessment 为 not_applicable,default_off_isolation 为 not_applicable,authority semantics aligned,semantic alignment reuse_existing/aligned;这些判断不替代 blocker。
修复完成后需要证明:同一 packaged public path 在 pending send + paste + confirmed not_delivered 下仍显示 newer image,并且后续 explicit resend 只发生一次、不会自动 replay;同时保留当前已经通过的 peer isolation、late receipt、draft navigation、unsupported/attached branches。live model adoption、native/installed acceptance 和完整 GQ02/GQ04/GQ08/GQ09/team lifecycle 仍是 separate acceptance。
English verdict: REQUEST_CHANGES - exact head c3b642b; cross-conversation receipt/draft isolation is useful, but an independently reproduced pending-send textarea paste is erased by the late failure catch. Repair the same-key image restoration guard and add the packaged regression before merge.
Signed-off-by: huangruiteng <huangrt01@163.com>
|
Addressed the image-loss finding in b09f614. The same typed conversation input owner now holds text and images together. Failure restores the original submission only if the original conversation still has no new text or images; it neither overwrites a newer image nor adds old text/images to a newer image-only draft. Waiting still allows drafting and pasting. The packaged admission scenario checks the original composer after navigating back, separately proving that its images are hidden in the other conversation. It also checks an image-only later draft. Both pass, alongside packaged history recovery, image requests (including unchanged-draft rejection), typed actions, typecheck and production builds. The unchanged HTTP/store/provider ingress retains the earlier 13 passing tests. The future-facing pass removes the duplicate component text map rather than adding another recovery flag; the tab-local text storage format and effect authority remain unchanged. Exact-scope quality is requalified for this head; the earlier risk-based checks remain recorded with their disclosed setup failures. No CI or paid model evaluation was queried. |
huangruiteng
left a comment
There was a problem hiding this comment.
Reviewer: actor_kind=model_agent; declaration_source=runtime_reported; model=gpt-5.6-luna; provider=OpenAI; effort=max
Approval conclusion (author-owned PR; GitHub blocks formal self-approval)
动机
本次审查的 affected_caller_or_operator 是:A user typing, steering or attaching an image in a Goal or steward conversation while another conversation has an in-flight ordinary send or delayed receipt. 旧行为 old_behavior 是:The pre-change page kept draft, sending, feedback, receipt and image state in separate component-level owners. The old failure catch restored captured images unconditionally, so a newer paste accepted while the request was pending could be replaced by the old empty attachment list after a confirmed failure. 具体 before_after_scenario 是:Before: a pending request in Goal A could disable or overwrite the composer in manager B, and a later paste in manager B could be erased by the failed request's catch. After: the keyed hook keeps pending flags, receipts, feedback, drafts and images per Goal/Agent key, while restoreFailedSubmission restores the old submission only if that key still has no newer text or image. 这项问题的 compounding_cost 是:The user can lose a newly pasted image when an older request fails, then reconstruct the attachment and risk resending the wrong text/image pair; cross-conversation waiting can also show a disabled Send or feedback in the wrong conversation. exact-head 的 observable_outcome 是:The corrected packaged browser scenario passes cross-conversation pending receipt/draft/feedback isolation, same-key pending-send image retention, image-only later-draft isolation, navigation retention, retry identity and adapter negatives. The immutable c3b642 baseline replay with the same corrected oracle fails exactly where the old catch discards the newer image. 本次 delivery 的 non_goals 是:No new capability/provider, Session/Turn effect owner, Todo, lease, quota, authority grant, protocol, automatic replay, receiver adoption, live model claim or complete GQ02/GQ04/GQ08/GQ09/team acceptance. remaining_gap 是:Full live receiver adoption, native/installed acceptance and broader GQ/team lifecycle remain separate acceptance; this PR does not claim that the App composer state repair closes those journeys.
改动思路
Existing composerDraftKey, tab-local text draft record, current per-key ConversationInputState and captured pendingImages; Session/Turn and HTTP ingress remain existing callback owners. useConversationInputState owns presentation fields by key; PersonalWorkspacePage sendMessage/selectImages own local admission and restoration; existing callbacks own effect dispatch and receipts. A send clears only the captured conversation draft/images before the existing callback. Keyed setters settle late sending/steering/feedback/receipt in the originating key; restoreFailedSubmission atomically restores captured text/images only when that key is still empty. Existing sendMessage catch reports delivery failure and calls the keyed atomic restoration guard; steering retry identity and confirmed non-delivery remain with steering-recovery and existing ingress owners. 这个设计复用已有 Goal/Agent key,把 presentation state 与 Session/Turn effect authority 分开;后续文本或图片通过同一个 functional keyed update 优先于旧请求的失败恢复。
具体改动
PR exact diff(base bb5cead 到 exact head b09f614)为 4 个文件、170 行新增、48 行删除:两个共享 App production 文件、一个 RFC 和一个 packaged composer scenario。新增 hook 将 sending、steering、feedback、receipt、attachment 和 error 绑定到已有 composerDraftKey;PersonalWorkspacePage 删除重复的 page-level 文本 map,保留 loopx-pw-composer-drafts 的 tab-local 文本格式,并让 normal-send catch 通过 restoreFailedSubmission 原子恢复;RFC 记录 conversation ownership、后续输入优先级和 no-auto-replay;smoke 覆盖跨 conversation receipt、独立 rejection、pending-send paste、image-only later draft、draft/image navigation、uncertain retry、attached queue 与 unsupported adapter。
关键代码讲解
apps/presentation/dashboard/src/features/personal-workspace/use-conversation-input-state.ts:27的useConversationInputState是 live composer 的 typed presentation owner;captured-key setters 让 late callback 不写入 peer key,且不拥有网络或 Turn effect。apps/presentation/dashboard/src/features/personal-workspace/use-conversation-input-state.ts:46的updateInput / restoreFailedSubmission用一次 functional update 检查当前 text/image 是否为空;后续任一输入存在时保持现状,避免旧 text/image 混入新 image-only draft。apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx:864的composerDraftKey与 hook integration 复用既有 Goal/Agent identity,替换 component-wide mutable fields;apps/presentation/dashboard/src/features/personal-workspace/personal-workspace-page.tsx:1647的sendMessage保留现有 dispatch/receipt owners,只改变失败时的本地 restore boundary。examples/personal-workspace-browser/composer-session-admission.mjs:22的composerSessionAdmissionScenario通过 packaged public caller 检查 Goal 中隐藏 Manager 图片、返回 Manager 后保留图片,以及 later-image-only no-mixing。
审查依据是 accepted RFC docs/architecture/rfcs/app-conversation-and-async-inbox-v0.md,immutable revision bb5ceadf2e884f5cce5548afa3b826e1c1e968ca,而不是用 PR 新增段落倒推规范。Waiting is part of the conversation 的 same-conversation receipt/draft boundary 在该 revision mapped/implemented;Image admission and current-conversation position 要求 confirmed rejection restore draft and images,exact head 已由 restoreFailedSubmission 和 paired packaged replay 实现;GQ08 与 GQ09 的完整 receiver/runtime/native acceptance 在本 PR out_of_scope。
对主干的风险
我先用同一个 corrected oracle 审查最强反事实:在 normal send 等待时继续通过 textarea paste 新图片,之后返回 confirmed not_delivered,然后回到 Manager 检查图片;另加 later image-only draft 检查,避免旧 text/image 混入。clean c3b642 baseline 在该 oracle 以 Failed delivery discarded a newer image pasted while the request was pending 失败;b09f exact head 的 packaged public run 通过。当前代码路径是 sendMessage capture → selectImages/handleComposerPaste → restoreFailedSubmission 的 atomic current-empty guard,失败只恢复仍为空的原会话输入。
其余风险是边界风险而非当前 blocker:synthetic HTTP fixture 不能证明 live provider adoption;packaged browser 不能证明 native installed execution、receiver adoption、restart/session replacement 或完整 GQ/team lifecycle。当前 UI-only diff 没有 Session/Turn、permission、Todo、quota、scheduler、protocol 或 authority side effect;13 条 unchanged HTTP/store steering checks保留既有通过结论。没有 CI 查询或等待。
语义与 CI 对齐
semantic alignment 的 candidate_decision 是 reuse_existing,因为四个 changed paths 复用既有 composerDraftKey、Session/Turn callback 和 tab-local text format,没有新 typed shared vocabulary;semantic advisory 未发现新的 Enum/Literal/as-const carrier。wait_for_ci=false,本 review 没有获取、轮询或等待 GitHub CI;exact-scope CQA receipt cqr_9d59cce0eff4e18a4305 验证 b09f head 的四个 committed paths 有效。
我的整体评价
problem_context verdict 是 justified_increment:这是一个真实、可复现且有边界的 App presentation 修复,long_horizon 判定为 preserved,user_experience 判定为 improved。observable_semantics 是 intentional_change_validated:c3b642 corrected baseline 在 stale-image mutation 上失败,b09f exact-head packaged replay 通过,同时 peer isolation、late receipt、draft/image navigation、image-only no-mixing、unsupported/attached branches 均通过。change_proportionality 为 proportionate,code_volume 为 necessary 且 compatibility assessment 为 not_applicable,因为既有 tab-local text JSON format 保持不变;default_off_isolation 为 not_applicable,authority_semantics aligned,typed_state_rule local_only,behavior_change_disclosure disclosed,guidance_vs_obligation advisory_only。
因此我对 exact head b09f61477456e72ddba35eba84cb9bf36e320be0 给出 APPROVE(author-owned PR 使用 COMMENTED approval fallback)。这只判断本 PR 的 four-file presentation slice;它不把 packaged browser state proof升级为 live receiver adoption、native installed acceptance 或 broader GQ/team completion。
English verdict: APPROVE - exact head b09f614; the keyed composer and atomic failed-send restoration preserve independent receipts, newer text/images and image-only drafts. The corrected packaged scenario passes, the immutable c3b642 replay exposes the fixed stale-image regression, and no CI was queried.
A pending correction in one conversation currently disables Send after navigating to another Goal or the steward. Late acceptance, failure and preview callbacks can also overwrite the other conversation's draft or feedback.
Keep pending input, feedback and unsent images with the existing Goal/Agent draft key. Late callbacks settle their originating conversation, failed delivery preserves a newer draft, and late previews do not switch the current conversation. Navigation now retains unsent images in their original conversation until sent or removed; it does not replay delivery. This shared App change leaves Session/Turn effect authority unchanged.
Validation: reproduced the disabled-Send failure on main; dashboard typecheck and production builds pass; packaged composer admission, history recovery, image requests and typed-action journeys pass; 13 real HTTP/store and scripted Codex protocol steering tests pass. The browser regression holds receipts across navigation and checks independent pending states, feedback isolation and preserved drafts/images. Live model adoption and attached-host lifecycle are outside this presentation fix. Risk-based premerge and exact-head review are recorded before merge.