fix(chat): steer a draft with image attachments instead of refusing it - #640
Merged
xintaofei merged 5 commits intoSep 4, 2026
Merged
Conversation
The composer's "insert into current turn" entry went dark the moment an image was staged, and a draft with file badges was silently rerouted to the queue. The whole steering chain narrowed the draft to a bare string (ConnectionCommand::Steer carried text, build_steer_params hardcoded a single text block), so those gates were the honest option. The wire never had that limit: the claude adapter's _session/steering handler feeds the prompt array through the same conversion as session/prompt, images included. Steer now carries the draft's PromptInputBlocks end to end. The composer sends the full block list whenever the draft holds more than plain text, with the display text as the recorded note; the backend maps the blocks with map_prompt_blocks, the exact session/prompt encoding, and re-hydrates uploaded file:// markers like /acp_prompt does, so web and remote mode work unchanged. The prompt ledger fingerprints the steered blocks the same way a prompt's are, and a text-only steer stays byte-identical to before. The pull channel still delivers plain text, so a blocks-bearing note on a session that downgraded mid-race is rejected with NoActiveTurn and the composer's existing fallback queues the whole draft, attachments included; an attachment is never silently dropped. Steering while an upload is still settling gets the same toast a plain send does, and the now-unused steerAttachmentsUnsupported string is gone from all ten locales.
`onSteer` widened to `(text, blocks?)` in `MessageInput` and in the panel's handler, but `ConversationShell` and `ChatInput` — the two layers the prop actually travels through — still declared the one-argument form. The optional second parameter keeps that assignable, so tsc reports nothing and the leak would only appear the day either layer wraps the callback instead of forwarding it. Also lock down the two claims the change rests on: the text-only steer's wire shape is now asserted by exact equality rather than field probes, and the enqueue fallback is asserted to carry the image block, not just the prose.
Attachment hydration is the one await `submit_feedback_native` puts between the `turn_in_flight` admission check and the enqueue, and it runs for as long as reading the uploads takes. The loop's idle arm covers "the turn ended" — it replies `NoActiveTurn` and the composer queues the whole draft — but it cannot cover "the next turn started in the meantime": the flag reads true either way, so the loop is in its active arm and injects the note into a turn the user never aimed at, recorded `Delivered` while the composer clears. `turn_in_flight` says only that some turn is running, never which one, so give the state a turn identity: `SessionState.turns_completed`, bumped next to the `turn_in_flight` clear in the `TurnComplete` handler — the single production site that ends a turn. A steer captures it during admission and re-checks it after hydration; a change means the admitted turn is over and the note takes the caller's queue fallback, which re-routes the whole draft, attachment included. A counter rather than the existing `pending_user_message_started_at`: that stamp only exists once a turn has published a user message, and `user_message` is `None` for delegation children and unbound conversations, so those turns would have carried no identity at all. The counter is monotonic rather than an exact turn count — `TurnComplete` has three emitters and a repeat can land on a settled turn — and only inequality is ever read.
main's 830ea83 dropped fork-and-send, taking the `onForkSend` prop and the composer's send split button with it — the same declarations this branch edits to widen `onSteer` with the steered draft's blocks. Three files conflicted: message-input.tsx, chat-input.tsx and conversation-shell.tsx, all on that adjacent prop block. Each resolves the same way: keep main's removal of fork-and-send, keep the widened `onSteer(text, blocks?)`. Nothing else needed hand-resolving; the steering chain, the i18n removals and the backend guard all merged clean, and no fork-send reference survives.
`830ea832` removed fork-and-send and `8eb05f8e` swept its comments, but the steering handler's note still contrasted itself with "the synchronous send/enqueue/fork paths". There is no fork path in the composer any more.
Owner
|
codeg work task |
xintaofei
added a commit
that referenced
this pull request
Sep 5, 2026
main's #640 rewrote the same mid-turn send this branch widens, so both sides of every steer hunk are kept: * `onSteer` takes main's `(text, blocks?)` across all three composer layers, alongside this branch's `steerChannel`. `onForkSend` stays deleted. * `handleSteerClick` keeps main's body — the uploading gate and the block payload — and this branch's channel-keyed label, icon and failure toast. A pull session with an attachment now reaches the backend's blocks rejection as ordinary operation, and lands on the same queue fallback. * `steerAttachmentsUnsupported` goes with main's deletion (attachments are supported now); `steerNoteFailed` stays in all ten locales. * Both test suites keep every case from both sides. `submit_feedback`'s comment on the pull-path blocks rejection said it only arises after a native downgrade; with the affordance on every session it is now the normal path for a pull session, so it says both.
xintaofei
added a commit
that referenced
this pull request
Sep 5, 2026
Talking to an agent while it is still replying now works on every session that has a delivery channel — with attachments, shown as a real message in the transcript, and recoverable if the agent finished without reading it. Mobile gets a terminal key bar and a ⋯ menu on every file-tree row. ## New - **Send a message into a running turn on any session with a delivery channel** — Codex, Grok, Gemini and OpenCode sessions get the mid-turn send too, worded honestly per channel: Claude inserts into the current turn, the others leave a note the agent picks up at its next check, shown as waiting until it actually does. (#637, @Adam-Dalloul) - **A message sent mid-turn now reads as a message** — it lands in the transcript as its own turn, and the reply to it starts a new turn instead of running into the previous answer mid-paragraph. (#636, @Adam-Dalloul) - **Mid-turn sends carry images and file references**, instead of going dark the moment you stage an attachment. An image shows on the message right away in the running turn rather than only after reopening the conversation, and if the turn ends first the whole draft — attachment included — is queued for the next one rather than dropped. (#640, @Adam-Dalloul) - **A note the agent finished without reading is no longer lost** — the list stays up after the turn, says the agent never got to it, and offers to send it as an ordinary message or dismiss it. - **"Adapter version" is now a per-agent setting** — follow the newest adapter release instead of waiting for the reviewed pin, which is what makes a brand-new model reachable the day it ships. Pinned stays the default and recommended; the newest release is unreviewed and can break the agent, and an install that can't fetch it falls back to the pinned version rather than failing. (#635, @Adam-Dalloul) - **A virtual key bar for the terminal on mobile** — ESC, TAB, CTRL, ALT, arrows, Home/End and PgUp/PgDn, with CTRL/ALT latches that wrap the next key you type, lifted clear of the soft keyboard and collapsible from the tab bar. (#647, @mrytsr) - **Every file-tree row gets a ⋯ button** that opens the same menu right-click does, so touch devices have a way in now that long-press is reserved for drag. (#648, @mrytsr) - **Transcript file links open beside the conversation when the workspace column is off screen** — on the task board and the infinite canvas, a file badge, a markdown link or "view diff" now opens a read-only viewer next to the transcript, with "Open in workspace" one click away, instead of doing nothing visible. - **Codex reports its background terminals** (codex-acp 1.10.0) — a long-running command Codex pushes to the background is badged "Background" and joins the live task strip with its Stop button and output link, the way Claude's already did. - **"Disable hardware acceleration" now works on Linux**, not just Windows. ## Improved - **Updated bundled agents:** Kimi Code 0.41.0, CodeBuddy 2.144.0, Qoder 1.1.44, Cursor 2026.09.02, Antigravity 1.1.1, OpenCode 1.18.29, plus the Claude and Codex adapters (claude-agent-acp 0.74.0, codex-acp 1.10.0). - **Antigravity now takes a custom version** in Agent Settings, like the other downloaded agents — and a version that was never published now fails the download instead of quietly filing the pinned build under the number you asked for. - **Signing out of Claude mid-session now ends only that turn** — the session stays open and asks to be signed in again, instead of the connection dropping and the conversation flipping to cancelled. A delegated sub-agent in the same state reports "needs sign-in" rather than an unrecognized failure. ## Fixed - **A Codex search that finds nothing now says "No matches"** instead of a red failed tool call. (#651, reported by @rubybrowncoat; fixed by @dawNotPoi in #652) - **Cleaning up a task with no changes now removes its worktree completely**, instead of half-removing it and then reporting it still holds uncommitted files so the retry never finished. (#642, reported by @rubybrowncoat; fixed by @dawNotPoi in #650) - **Worktree removal now acts on exactly the checkout you pointed at.** Git matches a worktree argument by path suffix before resolving it as a path, so a same-named checkout belonging to another project could be deleted, uncommitted files included; a path that resolves two ways is now refused rather than guessed at. - **A `/goal` turn no longer appears twice** — a slash command is now matched against what codeg actually sent, so its turn stops coming back as a background overlay beside itself. (#643, @Adam-Dalloul) - **"Fork from here" no longer silently forks the whole reply** when you point at the half before a mid-turn message — the button greys out with an explanation until the fork point is named, the way it already does during a running turn. - **Turning "Disable hardware acceleration" back off now takes effect** after the restart, instead of the setting being inherited by the new process and staying on. Thanks to @Adam-Dalloul, @mrytsr and @dawNotPoi for contributing to this release, and to @rubybrowncoat for the reports. ----------------------------- # 发布版本 0.30.3 智能体正在回复时插话,现在在每一个有投递通道的会话上都能用了——能带图片和文件、在会话记录里就是一条正经消息,智能体没来得及读完就收尾的,也还能捞回来。 移动端还多了终端虚拟按键条,文件树每一行也多了 ⋯ 菜单。 ## 新增 - **只要会话有投递通道,就能往正在跑的这一轮里发消息**——Codex、Grok、Gemini、OpenCode 也都有了,而且措辞按通道如实区分:Claude 是直接插进当前轮次,其余的是留一张便条、等智能体下次查看时取走,在它真的读到之前一直显示为等待中。(#637,@Adam-Dalloul) - **中途发出的消息现在就是一条消息**——它会作为独立的一轮出现在会话记录里,针对它的回答也会另起一轮,不再和上一段答案粘在同一个气泡里连成一片。(#636,@Adam-Dalloul) - **中途发送支持图片和文件引用**,不再一贴附件按钮就变灰。图片当场就显示在正在进行的这一轮里,不用关掉会话再打开才看得到;万一那一轮刚好结束了,整条草稿(连同附件)会排进下一轮,而不是被丢掉。(#640,@Adam-Dalloul) - **智能体没读就收尾的便条不会再消失**——列表在这一轮结束后仍然保留,写明智能体没来得及读,并提供「作为消息发送」或直接忽略。 - **「适配器版本」成为每个智能体各自的设置项**——可以选择跟随最新的适配器发布,不必等官方钉住的版本更新,新模型上线当天就能用上。默认仍是钉住的版本并标为推荐;最新版未经审核、可能让智能体不可用,安装时若取不到最新版会自动回退到钉住的版本,而不是直接装不上。(#635,@Adam-Dalloul) - **移动端终端虚拟按键条**——ESC、TAB、CTRL、ALT、方向键、Home/End、PgUp/PgDn,CTRL/ALT 按下后会锁定并作用于你输入的下一个字符,按键条会浮在软键盘上方,也可以从标签栏收起。(#647,@mrytsr) - **文件树每一行都多了 ⋯ 按钮**,点开就是右键那份菜单——长按手势已留给拖拽,触屏这下有了入口。(#648,@mrytsr) - **工作区被挡住时,会话里的文件链接改在旁边打开**——在任务看板和无限画布这类整页视图下,点文件徽章、Markdown 链接或「查看差异」会在会话旁弹出只读预览(「在工作区中打开」也只有一步之遥),不再点了没反应。 - **Codex 的后台终端能报上来了**(随 codex-acp 1.10.0 到位)——被 Codex 挂到后台的长命令会标上「Background」,并进入顶部的实时任务条,带停止按钮和输出链接,和 Claude 的一样。 - **「禁用硬件加速」在 Linux 上也生效了**,不再只支持 Windows。 ## 改进 - **内置智能体版本更新:** Kimi Code 0.41.0、CodeBuddy 2.144.0、Qoder 1.1.44、Cursor 2026.09.02、Antigravity 1.1.1、OpenCode 1.18.29,以及 Claude 和 Codex 的适配器(claude-agent-acp 0.74.0、codex-acp 1.10.0)。 - **Antigravity 现在也能指定自定义版本了**,和其他下载安装的智能体一样;填了一个从未发布过的版本号会直接下载失败,而不是把钉住的那份安装包悄悄记成你填的号。 - **会话进行中登出 Claude 现在只结束当前这一轮**——会话本身保留,只是提示需要重新登录,不再整条连接断开、会话被标成已取消。委托出去的子智能体遇到同样情况会报「需要登录」,而不是一句看不懂的异常结束。 ## 修复 - **Codex 搜索没有命中时显示「无匹配结果」**,不再是一张红色的失败工具卡。(#651,由 @rubybrowncoat 反馈;@dawNotPoi 在 #652 中修复) - **清理没有改动的任务时,工作树现在能被完整删除**,不再删一半、然后反过来说它还有未提交的改动,导致重试永远走不完。(#642,由 @rubybrowncoat 反馈;@dawNotPoi 在 #650 中修复) - **删除工作树只会作用于你指定的那一个。** git 会先按路径后缀去匹配工作树参数、再当作路径解析,因此另一个项目下同名的 checkout 有可能被连同未提交文件一起删掉;现在遇到会有两种解释的路径会直接拒绝,而不是替你猜。 - **`/goal` 之类的斜杠命令不再出现两遍**——斜杠命令现在会和 codeg 实际发出的内容比对,那一轮不会再以「后台活动」的形式在自己旁边重复一次。(#643,@Adam-Dalloul) - **「从这里分叉」不再悄悄把整条回复都分出去**——当你点的是中途插话之前的那半段时,按钮会像回复生成中那样变灰并说明原因,直到分叉点被确定。 - **「禁用硬件加速」关掉后重启真的会生效**,不再被新进程继承下来、看起来像是没关掉。 感谢 @Adam-Dalloul、@mrytsr、@dawNotPoi 为本次发布做出的贡献,也感谢 @rubybrowncoat 的问题反馈。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
With the Claude Code agent, "Insert into current turn" refused any draft that had an image staged: the menu entry disabled itself with a text-only tooltip, and a draft holding file badges was quietly rerouted to the queue. The limit was on our side, not the wire's. The steering chain narrowed everything to a bare string (
ConnectionCommand::Steer { text },build_steer_paramshardcoding a single text block), while claude-agent-acp's_session/steeringhandler feeds the prompt array through the exact same conversion assession/prompt, image blocks included.Steer now carries the draft's
PromptInputBlocks end to end. The composer sends the full block list whenever the draft holds more than plain text, with the display text as the recorded note; the backend maps it withmap_prompt_blocks(thesession/promptencoding) and re-hydrates uploadedfile://markers exactly like/acp_prompt, so web and remote mode work unchanged. The prompt ledger fingerprints the steered blocks the way a prompt's are, and a text-only steer stays byte-identical to before.Attachments are still never silently dropped: the pull tool carries plain text only, so a blocks-bearing note that races a mid-session channel downgrade is rejected with
NoActiveTurnand the composer's existing fallback queues the whole draft, image included, with the usual toast. Steering while an upload is still settling gets the same "still uploading" toast a plain send does, and the now-unusedsteerAttachmentsUnsupportedstring is removed from all ten locales.