feat(chat): busy queue, mid-turn steer, zap send - #308
Conversation
89c2c49 to
1713a32
Compare
asto18089
left a comment
There was a problem hiding this comment.
感谢这个 PR!先说结论:方向正确、根因诚实、意义真实,但当前实现存在 1 个对中文输入致命的 BLOCKER 和多处 MAJOR 缺陷,需要大修后再合。以下结论基于将 PR 净差异合入最新 main(cb4549b4,零冲突)后的完整审计:cargo check / eslint / tests/bridge_domain_* / tests/scheduled_tasks_unit.test.js / cargo test --lib features::assistant(210 全绿)/ fork-guard.sh --fast 均通过,但 cargo test --lib app::commands 实测红(见 M-1)。
先肯定的部分
- 对照 merge-base 核实,PR 描述的 6 个根因全部真实存在(包括坦白分支早期
interruptAndSend从未导出的 bug),修复方向均正确。 - 相对 main(busy 只能轮末排队、无打断 UI、无确定性取消握手),steer 中途注入 / ⚡ 一键打断 / CancelOutcome+generation 终态握手是真实能力增量,不是重复造轮子。
- ⚡ 打断主链路(按钮 → cancel → waitForChatDone → doSendFor)闭环质量较高:
interruptInFlight挡 flush 抢跑、interruptSending防双击、P0-B emit 后置的设计意图清晰。 - 远控白名单接线正确(
access-policy.json是真 allowlist、RUST_FORWARDED_EVENTS仅去重,无双重/漏转发);敏感信息未落日志。 - 18+7 个文件全部与主题相关,无夹带。
🔴 BLOCKER
B-1 内容指纹跨语言不一致,中文 steer 的事件路径全断
- Rust
steer_content_hash按 UTF-8 字节做 FNV-1a(CodeWhaleturn_loop.rs:4271,content.trim().bytes());前端steerContentHash按 UTF-16 码元(bridge/chat-events.js:338,charCodeAt)。实测「中文」两侧哈希不同;任何非 ASCII 输入必不匹配。 - 后果:
chat:steer_committed永远匹配不到 chip(committed 场景靠 transcript_committed 兜底侥幸存活);chat:steer_dropped没有任何兜底——按 ⏹ 停止后中文 chip 永久悬挂;且flushQueued遇队首steeredchip 直接 return(chat.js:346,无超时清理)→ 该会话后续所有排队消息被永久堵死。本产品主力用户是中文输入者,这是主路径失效。 - 修复方向:前端改用
TextEncoder按 UTF-8 字节哈希;或(更推荐)随 steer 携带引擎生成的 opaque id,从根上消除编码/trim(JS 会 trim U+FEFF、Rust 不会)双重歧义。
🟠 MAJOR
引擎侧(CodeWhale#16,gitlink c3809140e)
- M-2
kill_running误杀:cancel 分支(turn_loop.rs:3150/3516)调用ShellManager::kill_running(shell.rs:2259)会杀掉本 session manager 内全部 Running 进程——包括之前轮次用户刻意转后台的任务(task_shell_start的跨 turn 语义)、以及设计上与父轮取消解耦的后台子智能体 shell(subagent/mod.rs的background_runtime注释明示解耦意图)。且触发源不限「停止」:compaction、Op::CancelRequest等任何取消都会连带全杀。旧代码是按task_id精确 kill;仓库已有owner_agent_id过滤原语和turn_shell_tasks映射可复用。 - M-3 停止语义不彻底:keep_inbox=false 只
std::mem::take(&mut self.pending_steers)(turn_loop.rs:1515),不 drainrx_steerchannel 残留;同时handle_deepseek_turn入口原有的 drain 被删除(engine.rs:4028)。已在 channel 里、尚未收集的 steer 在「停止」后存活并在下一轮静默注入 transcript——前端已按 SteerDropped 移除 chip 并提示「已取消」,消息却又发给了模型。这正是 PR 要消灭的「UI 与引擎状态分裂」,只是方向反转。其余 Interrupted 出口(loop-top/流建立取消/paused/末尾)连 pending_steers 都不处置,SteerDropped 覆盖率有缺口。 - M-4 违反自身「committed or dropped, never silent」契约:evict/reclaim(
engine_pool.rsreclaim_engine_entry)与Op::Shutdown销毁pending_steers与 channel 残留时不发任何 SteerDropped;Op::SyncSession(engine.rs:2541)换会话既不清队列也不发事件——A 会话 park 的 steer 会注入 B 会话的 transcript,跨会话消息污染。 - M-5 引擎侧新行为零测试:keepInbox park/drop、三处注入点的 SteerCommitted 时序、跨轮保留、新 turn 不 drain、cancel 杀 shell 均无一行新测试(commit 自述只跑了既有套件)。
宿主 Rust 侧
- M-6
terminal_emitted=true ≠ 闸门已开:EnginePool::cancel用is_terminal_emitted()(engine_pool.rs:1421)判 terminal=true 的依据是「claim 已完成 ⇒ 闸门已重开」,但 forwarder 在 claim(terminal_emitted 置位)与finish_terminal_emission(闸门重开)之间有多个spawn_blocking持久化 await(forwarder.rs:745→847)。在该窗口内 cancel 返回 terminal=true → 前端跳过等待直接 doSendFor → reserve 撞session_turn_in_progress,⚡ 间歇性投递失败。建议改读「闸门已开」状态(如!is_active()或新增 gate 探针)而非 terminal_emitted。 - M-1
cargo test --lib app::commands红:protocol_tests.rs:77的 chat_protocol 清单需补"steer_chat"(一行修复)。PR 描述称「两仓 cargo check 通过」,但该测试是 CI 门禁,说明测试未跑全。 - M-7
EnginePool::steer引擎不在场时静默Ok(())(engine_pool.rs:1542):前端 chip 只能等 committed/dropped 事件收敛,引擎不在场时两者都不会来 → 落进 B-1 同款队列堵死。建议返回错误让前端走降级路径(降级路径本身也需修,见 M-9)。
前端
- M-9 steer 失败降级静默丢消息(
chat.js:518-528):steer 失败 → 移除 chip(输入框、附件此前已清空)→ 降级invoke("chat")在 busy 下必然再撞session_turn_in_progress且.catch(function(){})吞掉——文本+附件无痕蒸发,与 PR 自己「消息绝不静默丢失」的口号直接矛盾。建议:失败时恢复文本到输入框/回退旧排队语义(chip 转普通排队),并呈现错误。 - M-10 附件处理双向缺陷:busy 发送时
state.attachments = []直接丢弃 ready 附件(纯附件+点发送场景里 steerText 为空串,后端空串 steer 被丢弃——附件和这次操作整体蒸发,chat.js:500);反之 ⚡ 成功后不清state.attachments(chat.js:807)→ 同一附件随下一条消息重复发送。 - M-11 Stop 按钮回归:main 上 busy 恒显 Stop;新代码
busy && !hasDraftText(ChatView.jsx:2042)——生成中打了字就无法「停止但保留草稿」,必须先清空草稿。 - M-12 Web 端 ⚡ 可见但静默 no-op:web bridge 无
interruptAndSend(domain-adapter.js:82),按钮渲染只看busy,点击无声无息。建议按平台能力隐藏或禁用+提示。 - M-13 transcript_committed 兜底在 compaction 后永久死亡:
lastSeenMessageCount单调不减(chat-events.js:419),compaction 使消息数收缩后newMessages.length <= preCount恒成立 → 兜底 drain 永久失效,叠加 B-1 即队列永久堵死。
🟡 流程与规范
- P-1
docs/fork-modifications.md未登记(AGENTS.md 硬性要求):steer queue/steer events/keepInbox/P1-D shell kill 四项新 fork-distinct 行为零登记(grep 零命中)。fork-guard.sh 指纹层已更新,但文档层缺口需补。 - P-2 测试整体缺口:PR 两大卖点 P0-A(投递确认协议)/P0-B(确定性取消)在前端、宿主 Rust、引擎 Rust 三层均无一行新测试;
scheduled_tasks_unit.test.js改写锁死的是将被哈希路径取代的兜底路径(只 emit transcript_committed,从不 emit steer_committed/dropped),权威路径零 JS 覆盖。 - P-3 CodeWhale gitlink c3809140e 仅通过 CodeWhale#16 的 PR ref 可达,不在任何分支上——#16 合并前,本 PR 的 CI submodule 拉取存在失败风险(本地已验证该 SHA 可 fetch)。合并顺序需要协调。
- P-4 PR 正文引用的引擎 commit
d2972b91d不存在(实际为 2f376e493 + badc91dfe + c3809140e);CodeWhale 源码新增大段中文注释含 pinvou 私有语境(chip、deepseek-harness 引用),不利上游回馈(AGENTS.md §2「通用修复优先回馈上游」)。
⚪ MINOR(择要)
chat.js:802空else {}死代码;steer()未使用的queuedItem参数;chat.js:490与:699注释互相矛盾且描述已废弃设计。interruptAndSend用state.busy而非isBusyFor(sid)(chat.js:785),await 期间切换会话后张冠李戴。- handleSend 失败路径
personalWorkbenchTemplateId清理时机与旧代码不一致(ChatView.jsx:1331)。 - transcript_committed drain 会清掉与本轮无关的 turnErrorNotice;
indexOf双向包含模糊匹配存在误配对窗口。 - 测试
steerCallCount死条件(scheduled_tasks_unit.test.js:3011,state 中不存在该字段);mock 的load_session缺transcript_revision字段,与现实契约脱节。 ChatView.jsx:2057中文硬编码 fallback(键已存在,实际是死代码,但违反 i18n 公约且与同行t.queueMsg写法自相矛盾)。- FNV-1a 已是仓内第 4 份实现(CodeWhale
user_constitution.rs:1289、appmemory/util.rs:62已有)。 - 5s waitForChatDone 超时对长 tool chain 的取消 unwind 偏短;超时先于 listen Promise resolve 时监听器泄漏一个(有 resolved 守卫,无功能危害)。
- CancelOutcome doc 注释「目标轮已自然结束(mismatch)⇒ terminal=true」与实现不符(mismatch+新轮已 reserve 时实际为 false,方向无害:多等 5s)。
修复优先级建议
- B-1(哈希/改 opaque id)+ M-3/M-4(drop 与 evict/SyncSession 覆盖)——steer 协议的根。
- M-6(terminal 判定改读闸门状态)——⚡ 可靠性的根。
- M-1(一行)+ M-9/M-10(静默丢失三连)。
- M-2(kill 收敛到 owner/turn 过滤)、M-11/M-12(UI 回归)、P-1/P-2(登记+测试)。
这个功能值得做,底座侧的 steer 事件契约设计也是对的——把上述语义漏洞补齐后很期待合入。两仓改动需要联动(B-1/M-3/M-4/M-5 主要在 CodeWhale#16),建议先在那里收敛引擎语义,父仓再对齐。
按 Pinvou#308 评审意见修复(引擎侧对应 CodeWhale#16,gitlink 前进到 a5ef9eb91): - B-1:steer 事件关联从内容哈希改为引擎生成的 opaque steer_id (steer_chat 返回 id、事件带 steer_id),修掉非 ASCII 内容跨语言 哈希不一致导致的中文输入事件路径全断。 - 排队区交互重设计:busy 发送进排队浮层(盖住输入框上方),每条消息 自带 ×(撤回,注入前真正生效)与 ⚡(先撤回再打断瞬发,不重复投递); 移除输入框清空按钮与发送区 ⚡;Stop 恢复 busy 恒显(保留草稿可停止); Web 端按 interruptSend 能力隐藏 ⚡。 - × 取消 bug 结构性修复:新增 withdraw_steer 命令,引擎保证被撤回 steer 永不注入;附件排队 chip 的 × 全程零引擎调用。 - M-6:CancelOutcome.terminal 改读 reserve 闸门状态 (is_reserve_gate_open_for),修掉 claim 与 finish_terminal_emission 之间窗口内 ⚡ 间歇性撞 session_turn_in_progress。 - M-7:steer 引擎不在场返回 Err(不再静默 Ok 悬挂 chip)。 - M-1:chat_protocol 清单补 steer_chat/withdraw_steer(CI 门禁修复)。 - M-9/M-10/M-13 等:steer 失败恢复草稿+提示、附件双向修复、 transcript 兜底在 compaction 收缩后恢复。 - P-1:docs/fork-modifications.md(中英)登记 steer/keepInbox/撤回/ kill 收敛四项 fork 行为;fork-guard 指纹同步(含 gitlink 前进)。 验证:引擎 cargo test -p codewhale-tui --lib 无新增失败(13 条新 steer/ 撤回测试全绿);宿主 cargo check + app::commands 158 全绿、 features::assistant 通过(platform/multiagent 个别测试在基线同样抖动, 与本次无关);前端 scheduled_tasks_unit / bridge_domain 契约 / lint:ui / architecture-guard / fork-guard --fast 全部通过。 Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
按 #308 评审意见修复(引擎侧对应 CodeWhale#16,gitlink 前进到 a5ef9eb91): - B-1:steer 事件关联从内容哈希改为引擎生成的 opaque steer_id (steer_chat 返回 id、事件带 steer_id),修掉非 ASCII 内容跨语言 哈希不一致导致的中文输入事件路径全断。 - 排队区交互重设计:busy 发送进排队浮层(盖住输入框上方),每条消息 自带 ×(撤回,注入前真正生效)与 ⚡(先撤回再打断瞬发,不重复投递); 移除输入框清空按钮与发送区 ⚡;Stop 恢复 busy 恒显(保留草稿可停止); Web 端按 interruptSend 能力隐藏 ⚡。 - × 取消 bug 结构性修复:新增 withdraw_steer 命令,引擎保证被撤回 steer 永不注入;附件排队 chip 的 × 全程零引擎调用。 - M-6:CancelOutcome.terminal 改读 reserve 闸门状态 (is_reserve_gate_open_for),修掉 claim 与 finish_terminal_emission 之间窗口内 ⚡ 间歇性撞 session_turn_in_progress。 - M-7:steer 引擎不在场返回 Err(不再静默 Ok 悬挂 chip)。 - M-1:chat_protocol 清单补 steer_chat/withdraw_steer(CI 门禁修复)。 - M-9/M-10/M-13 等:steer 失败恢复草稿+提示、附件双向修复、 transcript 兜底在 compaction 收缩后恢复。 - P-1:docs/fork-modifications.md(中英)登记 steer/keepInbox/撤回/ kill 收敛四项 fork 行为;fork-guard 指纹同步(含 gitlink 前进)。 验证:引擎 cargo test -p codewhale-tui --lib 无新增失败(13 条新 steer/ 撤回测试全绿);宿主 cargo check + app::commands 158 全绿、 features::assistant 通过(platform/multiagent 个别测试在基线同样抖动, 与本次无关);前端 scheduled_tasks_unit / bridge_domain 契约 / lint:ui / architecture-guard / fork-guard --fast 全部通过。 Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
审查结论:高质量、根因全部属实且完全解决;已帮修 2 处前端失败路径 MAJOR,另有 2 处 MAJOR 与 1 个产品变更待作者确认感谢这个 PR——mid-turn 注入/打断链路的设计整体优雅(steer 语义全在底座 已代修并推送(8cf6be0f)MAJOR-1:steer invoke 挂起 → 文字永久丢失 + 排队区被悬挂 chip 阻塞( MAJOR-2:⚡ 失败恢复的 steered chip 回插队头 → 排队区可卡死( 两个修复均带回归测试(假时钟触发超时恢复 / 降级 chip 被 chat:done flush 消费), 待作者处理的缺陷MAJOR-3:引擎重启/崩溃时未注入的 steer 静默丢失——底座 shutdown 的 MAJOR-4: 需要确认的产品变更(超出「打断链路修复」范围)桌面输入区的**「清空草稿」按钮(Trash2)被整体删除**,清空入口仅剩平板语音悬浮模式。这是 busy-恒显-Stop 布局重构的直接结果,但属于独立的用户可见产品变化——请确认是有意为之(若有意,建议在 PR 描述补充说明;若无意,需要恢复)。 测试缺口(非阻断,建议补齐)
小问题(NIT)
其余验证:CodeWhale 4 个新提交全部主题内(steer 原语/测试构造/fmt/评审修复),11 条新指纹逐条存在, |
7c21bfe to
8cf6be0
Compare
按 #308 评审意见修复(引擎侧对应 CodeWhale#16,gitlink 前进到 a5ef9eb91): - B-1:steer 事件关联从内容哈希改为引擎生成的 opaque steer_id (steer_chat 返回 id、事件带 steer_id),修掉非 ASCII 内容跨语言 哈希不一致导致的中文输入事件路径全断。 - 排队区交互重设计:busy 发送进排队浮层(盖住输入框上方),每条消息 自带 ×(撤回,注入前真正生效)与 ⚡(先撤回再打断瞬发,不重复投递); 移除输入框清空按钮与发送区 ⚡;Stop 恢复 busy 恒显(保留草稿可停止); Web 端按 interruptSend 能力隐藏 ⚡。 - × 取消 bug 结构性修复:新增 withdraw_steer 命令,引擎保证被撤回 steer 永不注入;附件排队 chip 的 × 全程零引擎调用。 - M-6:CancelOutcome.terminal 改读 reserve 闸门状态 (is_reserve_gate_open_for),修掉 claim 与 finish_terminal_emission 之间窗口内 ⚡ 间歇性撞 session_turn_in_progress。 - M-7:steer 引擎不在场返回 Err(不再静默 Ok 悬挂 chip)。 - M-1:chat_protocol 清单补 steer_chat/withdraw_steer(CI 门禁修复)。 - M-9/M-10/M-13 等:steer 失败恢复草稿+提示、附件双向修复、 transcript 兜底在 compaction 收缩后恢复。 - P-1:docs/fork-modifications.md(中英)登记 steer/keepInbox/撤回/ kill 收敛四项 fork 行为;fork-guard 指纹同步(含 gitlink 前进)。 验证:引擎 cargo test -p codewhale-tui --lib 无新增失败(13 条新 steer/ 撤回测试全绿);宿主 cargo check + app::commands 158 全绿、 features::assistant 通过(platform/multiagent 个别测试在基线同样抖动, 与本次无关);前端 scheduled_tasks_unit / bridge_domain 契约 / lint:ui / architecture-guard / fork-guard --fast 全部通过。 Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
8cf6be0 to
72cdf44
Compare
72cdf44 to
e0464fb
Compare
|
CI 状态补充说明:最终 head 为 e0464fb(在 72cdf44 基础上补了 protocol_tests.rs 的 cargo fmt,修复我上一轮推送引入的 rust-lint 失败)。当前 PR Check 中 fast-gate「公开底座 gitlink 可达性」失败是存量问题(作者此前的推送同样失败):gitlink a5ef9eb9 目前只存在于 CodeWhale 的 |
按 Pinvou#308 评审意见修复(引擎侧对应 CodeWhale#16,gitlink 前进到 a5ef9eb91): - B-1:steer 事件关联从内容哈希改为引擎生成的 opaque steer_id (steer_chat 返回 id、事件带 steer_id),修掉非 ASCII 内容跨语言 哈希不一致导致的中文输入事件路径全断。 - 排队区交互重设计:busy 发送进排队浮层(盖住输入框上方),每条消息 自带 ×(撤回,注入前真正生效)与 ⚡(先撤回再打断瞬发,不重复投递); 移除输入框清空按钮与发送区 ⚡;Stop 恢复 busy 恒显(保留草稿可停止); Web 端按 interruptSend 能力隐藏 ⚡。 - × 取消 bug 结构性修复:新增 withdraw_steer 命令,引擎保证被撤回 steer 永不注入;附件排队 chip 的 × 全程零引擎调用。 - M-6:CancelOutcome.terminal 改读 reserve 闸门状态 (is_reserve_gate_open_for),修掉 claim 与 finish_terminal_emission 之间窗口内 ⚡ 间歇性撞 session_turn_in_progress。 - M-7:steer 引擎不在场返回 Err(不再静默 Ok 悬挂 chip)。 - M-1:chat_protocol 清单补 steer_chat/withdraw_steer(CI 门禁修复)。 - M-9/M-10/M-13 等:steer 失败恢复草稿+提示、附件双向修复、 transcript 兜底在 compaction 收缩后恢复。 - P-1:docs/fork-modifications.md(中英)登记 steer/keepInbox/撤回/ kill 收敛四项 fork 行为;fork-guard 指纹同步(含 gitlink 前进)。 验证:引擎 cargo test -p codewhale-tui --lib 无新增失败(13 条新 steer/ 撤回测试全绿);宿主 cargo check + app::commands 158 全绿、 features::assistant 通过(platform/multiagent 个别测试在基线同样抖动, 与本次无关);前端 scheduled_tasks_unit / bridge_domain 契约 / lint:ui / architecture-guard / fork-guard --fast 全部通过。 Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
e0464fb to
b27bd56
Compare
zhuowp
left a comment
There was a problem hiding this comment.
复审结论:当前仍需修改。
- P1(集成/发布门禁):父仓 gitlink 为
a5ef9eb91a7c9180e1327e2b80ec6f0500b82a98,但公开pinvou-v0.9.5-r7仍解引用为a36e6cd533024cfe5724bae21875aea42b2ed87a;scripts/verify-public-submodule.sh因此失败,线上fast-gate与required-gate也已失败。请先合并/发布对应 CodeWhale 变更并把公开 tag、验证脚本和父仓 gitlink 对齐。 - P1(当前 main 冲突):对
origin/main@c75f2fb2执行 merge-tree,pinvou3-app/tests/bridge_domain_protocol.test.mjs存在内容冲突。请基于最新 main 解决,并重算最终合并树的协议指纹。 - P2(公开 API 的超时语义):
interruptAndSend()在waitForChatDone(..., 25000)超时后不复查目标会话是否仍 busy/闸门是否已打开,直接调用doSendFor()。若取消 unwind 超过 25 秒,会稳定撞session_turn_in_progress;UI chip 路径虽有恢复,但该函数作为bridge.chat.interruptAndSend对其他调用方公开,调用方不接异常时仍可丢消息。请让 wait 返回是否真的观察到终态,超时时显式失败且保留/返回消息,或在发送前读取权威闸门状态,并补超时仍 busy 的公开 API 回归测试。
已确认此前中文哈希、停止清场、SyncSession/Shutdown、kill 范围、steer invoke 超时、失败 chip 降级等问题已在当前 head 修复。验证通过:scheduled tasks、bridge protocol、architecture guard、diff-check;CI 的 frontend-test/windows-rust-test/rust-lint 也成功。当前阻塞为上述三项。
按 Pinvou#308 评审意见修复(引擎侧对应 CodeWhale#16,gitlink 前进到 a5ef9eb91): - B-1:steer 事件关联从内容哈希改为引擎生成的 opaque steer_id (steer_chat 返回 id、事件带 steer_id),修掉非 ASCII 内容跨语言 哈希不一致导致的中文输入事件路径全断。 - 排队区交互重设计:busy 发送进排队浮层(盖住输入框上方),每条消息 自带 ×(撤回,注入前真正生效)与 ⚡(先撤回再打断瞬发,不重复投递); 移除输入框清空按钮与发送区 ⚡;Stop 恢复 busy 恒显(保留草稿可停止); Web 端按 interruptSend 能力隐藏 ⚡。 - × 取消 bug 结构性修复:新增 withdraw_steer 命令,引擎保证被撤回 steer 永不注入;附件排队 chip 的 × 全程零引擎调用。 - M-6:CancelOutcome.terminal 改读 reserve 闸门状态 (is_reserve_gate_open_for),修掉 claim 与 finish_terminal_emission 之间窗口内 ⚡ 间歇性撞 session_turn_in_progress。 - M-7:steer 引擎不在场返回 Err(不再静默 Ok 悬挂 chip)。 - M-1:chat_protocol 清单补 steer_chat/withdraw_steer(CI 门禁修复)。 - M-9/M-10/M-13 等:steer 失败恢复草稿+提示、附件双向修复、 transcript 兜底在 compaction 收缩后恢复。 - P-1:docs/fork-modifications.md(中英)登记 steer/keepInbox/撤回/ kill 收敛四项 fork 行为;fork-guard 指纹同步(含 gitlink 前进)。 验证:引擎 cargo test -p codewhale-tui --lib 无新增失败(13 条新 steer/ 撤回测试全绿);宿主 cargo check + app::commands 158 全绿、 features::assistant 通过(platform/multiagent 个别测试在基线同样抖动, 与本次无关);前端 scheduled_tasks_unit / bridge_domain 契约 / lint:ui / architecture-guard / fork-guard --fast 全部通过。 Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
b27bd56 to
632af49
Compare
|
三项阻塞已全部处理(分支已 rebase 到最新 main 并推送):
验证:fork-guard --fast 全过、architecture-guard 通过、bridge_domain_protocol / web_bridge_domain_contract / scheduled_tasks_unit(含新回归)/ ui_language_coverage 全绿、eslint clean、cargo fmt --check 与 cargo check --all-targets 通过。 |
|
跟进 8-19 审查中待作者处理的各项(分支已 rebase 到最新 main):
|
按 #308 评审意见修复(引擎侧对应 CodeWhale#16,gitlink 前进到 a5ef9eb91): - B-1:steer 事件关联从内容哈希改为引擎生成的 opaque steer_id (steer_chat 返回 id、事件带 steer_id),修掉非 ASCII 内容跨语言 哈希不一致导致的中文输入事件路径全断。 - 排队区交互重设计:busy 发送进排队浮层(盖住输入框上方),每条消息 自带 ×(撤回,注入前真正生效)与 ⚡(先撤回再打断瞬发,不重复投递); 移除输入框清空按钮与发送区 ⚡;Stop 恢复 busy 恒显(保留草稿可停止); Web 端按 interruptSend 能力隐藏 ⚡。 - × 取消 bug 结构性修复:新增 withdraw_steer 命令,引擎保证被撤回 steer 永不注入;附件排队 chip 的 × 全程零引擎调用。 - M-6:CancelOutcome.terminal 改读 reserve 闸门状态 (is_reserve_gate_open_for),修掉 claim 与 finish_terminal_emission 之间窗口内 ⚡ 间歇性撞 session_turn_in_progress。 - M-7:steer 引擎不在场返回 Err(不再静默 Ok 悬挂 chip)。 - M-1:chat_protocol 清单补 steer_chat/withdraw_steer(CI 门禁修复)。 - M-9/M-10/M-13 等:steer 失败恢复草稿+提示、附件双向修复、 transcript 兜底在 compaction 收缩后恢复。 - P-1:docs/fork-modifications.md(中英)登记 steer/keepInbox/撤回/ kill 收敛四项 fork 行为;fork-guard 指纹同步(含 gitlink 前进)。 验证:引擎 cargo test -p codewhale-tui --lib 无新增失败(13 条新 steer/ 撤回测试全绿);宿主 cargo check + app::commands 158 全绿、 features::assistant 通过(platform/multiagent 个别测试在基线同样抖动, 与本次无关);前端 scheduled_tasks_unit / bridge_domain 契约 / lint:ui / architecture-guard / fork-guard --fast 全部通过。 Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
632af49 to
7beef3b
Compare
第三轮审查结论:根因全部落地、架构与测试质量高;已代修 2 处 CI 必红 + 5 处前端/文档缺陷并推送(head
|
asto18089
left a comment
There was a problem hiding this comment.
第三轮审查完成(详见 https://github.com/Pinvou/pinvou-agent/pull/308#issuecomment-5412750887):方向、架构与测试质量均为三轮最佳,上轮 4 个 MAJOR 全部实质落地;已代修 2 处 CI 必红(test_ci_gate_policy r9 断言、3 个超 50 字符 subject)与 5 处前端/文档缺陷并推送(head 7beef3b18,全量门禁绿)。
Request changes 仅剩三项待作者处理:
- 「清空草稿」按钮移除的决策依据(× 删的是排队 chip,不能替代清空输入框草稿)——恢复或说明;
- 空闲/已切轮窗口下 stop 不发布 StopDropInbox(parked steer 逃过清空契约,修法一行);
- 补 P0-B 事件驱动等待的成功路径 + pendingSteerEvents 先到事件竞态的测试覆盖。
|
三件事跟进:
验证:scheduled_tasks_unit 全绿(含打断链路全部回归)、cargo check --all-targets / cargo fmt --check / eslint 通过。 |
b5f143f to
8de684d
Compare
按 Pinvou#308 评审意见修复(引擎侧对应 CodeWhale#16,gitlink 前进到 a5ef9eb91): - B-1:steer 事件关联从内容哈希改为引擎生成的 opaque steer_id (steer_chat 返回 id、事件带 steer_id),修掉非 ASCII 内容跨语言 哈希不一致导致的中文输入事件路径全断。 - 排队区交互重设计:busy 发送进排队浮层(盖住输入框上方),每条消息 自带 ×(撤回,注入前真正生效)与 ⚡(先撤回再打断瞬发,不重复投递); 移除输入框清空按钮与发送区 ⚡;Stop 恢复 busy 恒显(保留草稿可停止); Web 端按 interruptSend 能力隐藏 ⚡。 - × 取消 bug 结构性修复:新增 withdraw_steer 命令,引擎保证被撤回 steer 永不注入;附件排队 chip 的 × 全程零引擎调用。 - M-6:CancelOutcome.terminal 改读 reserve 闸门状态 (is_reserve_gate_open_for),修掉 claim 与 finish_terminal_emission 之间窗口内 ⚡ 间歇性撞 session_turn_in_progress。 - M-7:steer 引擎不在场返回 Err(不再静默 Ok 悬挂 chip)。 - M-1:chat_protocol 清单补 steer_chat/withdraw_steer(CI 门禁修复)。 - M-9/M-10/M-13 等:steer 失败恢复草稿+提示、附件双向修复、 transcript 兜底在 compaction 收缩后恢复。 - P-1:docs/fork-modifications.md(中英)登记 steer/keepInbox/撤回/ kill 收敛四项 fork 行为;fork-guard 指纹同步(含 gitlink 前进)。 验证:引擎 cargo test -p codewhale-tui --lib 无新增失败(13 条新 steer/ 撤回测试全绿);宿主 cargo check + app::commands 158 全绿、 features::assistant 通过(platform/multiagent 个别测试在基线同样抖动, 与本次无关);前端 scheduled_tasks_unit / bridge_domain 契约 / lint:ui / architecture-guard / fork-guard --fast 全部通过。 Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
zhuowp
left a comment
There was a problem hiding this comment.
本轮审核结论:Request changes。
发现 3 个仍存在于当前 head 8de684d 的问题:
-
[P1] ⚡ 瞬发可能重复投递同一条 steer。
withdrawSteerChip()fire-and-forget 调用withdraw_steer后立即执行 cancel + 新chat;后端返回Result<(), String>,无法区分“撤回成功”和“已经 committed”。若 committed 事件尚未到 UI,原 steer 已进入 transcript,新 chat 又会发送一次;后续气泡去重不能撤销模型已收到的重复指令。建议后端返回明确的 withdrawal outcome,前端 await 后再决定是否重发。 -
[P1]
steer_chat超时后的晚成功会丢失steerId并可能重复投递。Promise.race超时后恢复输入框,但原 invoke 后续 resolve 没有处理;引擎仍可能提交原消息,用户重试后形成第二次投递。建议为晚 resolve 增加补偿撤回,或提供可取消/可查询最终状态的协议。 -
[P2] cancel 返回后才注册
chat:done监听,可能固定假等待 25 秒。terminal:false时,事件若在 invoke 返回与监听注册之间到达,Tauri 分支没有 busy 预检,会一直等到兜底超时。建议 cancel 前预注册监听,或在注册前后复查 busy/generation。
本地定向验证:scheduled_tasks_unit.test.js、bridge_domain_protocol.test.mjs、bridge_domain_contract.mjs 均通过;现有测试没有覆盖上述 committed/timeout 晚到竞态。
按 Pinvou#308 评审意见修复(引擎侧对应 CodeWhale#16,gitlink 前进到 a5ef9eb91): - B-1:steer 事件关联从内容哈希改为引擎生成的 opaque steer_id (steer_chat 返回 id、事件带 steer_id),修掉非 ASCII 内容跨语言 哈希不一致导致的中文输入事件路径全断。 - 排队区交互重设计:busy 发送进排队浮层(盖住输入框上方),每条消息 自带 ×(撤回,注入前真正生效)与 ⚡(先撤回再打断瞬发,不重复投递); 移除输入框清空按钮与发送区 ⚡;Stop 恢复 busy 恒显(保留草稿可停止); Web 端按 interruptSend 能力隐藏 ⚡。 - × 取消 bug 结构性修复:新增 withdraw_steer 命令,引擎保证被撤回 steer 永不注入;附件排队 chip 的 × 全程零引擎调用。 - M-6:CancelOutcome.terminal 改读 reserve 闸门状态 (is_reserve_gate_open_for),修掉 claim 与 finish_terminal_emission 之间窗口内 ⚡ 间歇性撞 session_turn_in_progress。 - M-7:steer 引擎不在场返回 Err(不再静默 Ok 悬挂 chip)。 - M-1:chat_protocol 清单补 steer_chat/withdraw_steer(CI 门禁修复)。 - M-9/M-10/M-13 等:steer 失败恢复草稿+提示、附件双向修复、 transcript 兜底在 compaction 收缩后恢复。 - P-1:docs/fork-modifications.md(中英)登记 steer/keepInbox/撤回/ kill 收敛四项 fork 行为;fork-guard 指纹同步(含 gitlink 前进)。 验证:引擎 cargo test -p codewhale-tui --lib 无新增失败(13 条新 steer/ 撤回测试全绿);宿主 cargo check + app::commands 158 全绿、 features::assistant 通过(platform/multiagent 个别测试在基线同样抖动, 与本次无关);前端 scheduled_tasks_unit / bridge_domain 契约 / lint:ui / architecture-guard / fork-guard --fast 全部通过。 Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
8de684d to
89b2267
Compare
The T2 entry claimed user-backgrounded tasks and sub-agent shells "survive any cancel source" — that holds for the foundation's ShellManager only; the app layer's own SessionTurnShellTasks cleanup still reaps the interrupted turn's registered background tasks and owned sub-agent shells. Scope the sentence to the foundation and note the app-layer behavior instead of overstating it. Also drop the stale "(fire-and-forget)" description of withdraw_steer (superseded by Pinvou#30's outcome return in the same baseline), cite the withdrawal guard test under its real forkguard_ name, and align the English heading level with its siblings. Signed-off-by: asto <asto18089@126.com>
On web (no interruptSend capability) a busy+draft click queues the message and the aria-label already says so, but the tooltip still advertised sending. Gate the title on the same capability: desktop keeps the steer-capable tip, web now shows the queue label. Signed-off-by: asto <asto18089@126.com>
The v2 embedded-policy test locked the reasoning events into the policy allowlist and RUST_FORWARDED_EVENTS but not the two steer events, leaving the relay-side forwarded-subset invariant unguarded for them. Mirror the existing assertion pairs. Signed-off-by: asto <asto18089@126.com>
Biome counts the prototype-safe hasOwnProperty.call as a new warning. The repo pins ES2021, where this is the correct idiom (Object.hasOwn is ES2022), so add a scoped suppression with the reason instead of churning working code. No invoke/listen signature changes: the bridge protocol digest is unaffected. Signed-off-by: asto <asto18089@126.com>
Two remaining no-loss/no-duplicate gaps from zhuowp's re-review of 7bf2374: - A rejected withdraw_steer is not a confirmed retirement. A chip with a backfilled steer id only reaches the zap path after steer_chat succeeded, so a withdrawal rejection means the engine that accepted the steer is gone — and it may have committed the steer into the persisted transcript before dying. Treating the rejection as "retired" and resending could deliver the same user message to the model twice. withdrawSteerOutcome now maps non-timeout rejections to a distinct "withdraw_unreachable" outcome that forbids the resend and defers to the reconcile watchdog (late committed → bubble / dropped → silent / event lost → text restored after 60s), same as not_pending and transport timeouts. The steer_chat invoke's own deterministic rejection stays safe to resend (no engine ever accepted anything). Regression: interruptQueuedWithdrawRejectionWaitsForReconcile — no cancel, no chat resend, late committed renders the bubble and consumes the watchdog without a composer restore. - The engine-generation stamp must be collision-free. spawned_at_ms is wall-clock milliseconds: two rebuilds inside one tick (or a clock rollback) mint the same generation, and the raw foundation ordinals restart at steer-1, so the equality check would delegate a stale chip's withdrawal into the live engine and retire the wrong steer. The stamp source is now a process-monotonic AtomicU64 incarnation sequence allocated per spawn (kept separate from spawned_at_ms, which still feeds the zombie-worker epoch ledger); the withdrawal delegation decision is extracted into a pure helper and covered by deterministic same-tick rebuild/collision tests (steer_incarnations_stay_unique_across_same_tick_rebuilds, stale_incarnation_withdrawal_never_delegates_on_same_tick_rebuild). The multiagent source-regex test now pins the split and the sequence. Verification: scheduled_tasks_unit (with the new regression), bridge domain protocol/contract, web contract, multiagent (49/0), composer smoke, eslint 0, fork-guard --fast (submodule aligned to the r12 gitlink), architecture guard, cargo fmt and cargo check --all-targets all pass. Local cargo test cannot link on this machine (rustc 1.97.1 STATUS_STACK_OVERFLOW in the foundation crate's thin-LTO codegen for any job count / incremental setting — environment pathology; the suite runs in CI where this tree family is green). Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
The P1-2 fix allocates the generation from the pool's incarnation sequence, but every test supplies its own generation: reverting the steer()/withdraw_steer() entry reads (engine_pool.rs) back to e.spawned_at_ms keeps the whole suite green. Pin both consumption sites (count == 2) in the multiagent source-regex contract; red verified by flipping the reads back (0 !== 2). Signed-off-by: asto <asto18089@126.com>
The steer-settle watchdog comment still said the zap path resends on a withdraw rejection; since withdraw_unreachable (d21c3ec) that path is fail-closed too. State the actual contract so nobody restores the resend from the stale comment. Signed-off-by: asto <asto18089@126.com>
A live dev run surfaced one rendering freeze and a cluster of steer presentation defects; all were reproduced with targeted probes, fixed, and pinned with regressions. - Steer settlement promises poisoned the subscription snapshot. The zap gate stored the steer() settlement handle on the queued chip, but queued chips are part of the subscription-visible chat slice whose snapshot validator only accepts arrays/plain objects/JSON scalars — every notify() after a busy queue threw "Subscription state only supports arrays and plain objects", killing each chat:delta and chat:reasoning_delta handler: the streaming UI (text and thinking) froze until the steer settled and the chip left the queue, then the authoritative reload painted everything at once. The settlement now lives in a module-side table keyed by sid + chip id (self-deleting on settle, taken by the zap, purged with the session); chips stay JSON-like. Regression registers a real subscriber to force the validated snapshot path the bare harness never exercised. - Auto-follow keyed only on the LAST chat item's html; once the injected user bubble became the last item, the streaming item above it kept growing without retriggering the scroll effect. Follow now also keys on the last streaming item's html and the last running item's output length. - Steered bubbles rendered with a phantom lifecycle badge. A steered message is not a turn admission (no user_start timing record): letting it consume one mismatched the pairing — a natural queue badged "interrupted" while its own response segment had already finished. Steered bubbles now carry steeredMidTurn (chip settlement, late withdrawn commit, legacy fallback, and hydration — where the engine-baked <turn_meta> block is the discriminator, ordinary admissions persist without one), the projection excludes them from record consumption, in-flight records are skipped-and-consumed while busy, surplus orphan records prefer terminal-bearing ones, and the engine turn's terminal is displayed only on the run's tail turn. - Hydrated steered bubbles leaked the raw <turn_meta> envelope after a restart; rerenderFromMessages now always strips exact internal envelope blocks (previously only scheduled sessions did). - multiagent source-regex diagnostics added with the incarnation work are English again (zhuowp round-11 policy finding). Verification: scheduled_tasks_unit (with the new subscription-safety, steer-marker/hydration regressions), deepseek_conversation_timeline (in-flight / terminal-transfer / orphan / sticky-terminal scenarios), multiagent 49/0, bridge protocol (hash recomputed) + domain contracts, web contract, composer smoke 15, eslint 0 — all green; Rust sources untouched this round. Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
Two presentation fixes from the live dev-run review of the steer queue: - A steered message sandwiched between two consecutive injections rendered an assistant avatar-only row: the turn's avatar column was unconditional, and such a turn has no items of its own (the model's continuation lands in the following turn's group). The row now renders only for running turns (activity indicator), turns with content, or turns carrying a terminal footer; source-regex pin added so the guard cannot be silently dropped. - The queue chip's zap and cancel buttons did not read as equal: the zap is now a blue outlined lightning (size 14, blue hover tint) and the cancel swaps the raw "×" text glyph for the same stroke-based X icon at the same size, so both buttons align optically. Verification: scheduled_tasks_unit, deepseek_conversation_timeline, eslint 0, fresh build:ui + full browser ui smoke (53/53) pass. Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
A mid-turn steered bubble parks a user item LAST while the turn's streaming output above it keeps changing the auto-follow effect's traits (streamingFollowHtml / runningFollowOutputLength added for the steer freeze fix) — the effect re-ran on every delta and the lastItem-is-user branch force-scrolled to the bottom and re-armed following each time, overriding the scroll listener's 'user scrolled up' state for the whole remainder of the steered turn. Extract the force decision into shouldForceScrollFollow (conversation-scroll.js): the user-item snap fires once per appended item (count change), never on streaming-trait changes. Verified red/green: without the fix the new pure-function assertions fail. Signed-off-by: asto <asto18089@126.com>
Two chip-settlement follow-ups from the r13 review: - A chat:steer_dropped stashed while the steer_chat invoke is still pending is the authoritative 'never delivered' terminal for that steer_id. When the zap then learns withdraw=not_pending, restore the text immediately (watchdog-expiry semantics) instead of holding it for the full 60s reconcile window. Extracted settleZapSkipResend to keep runQueuedZap under the cognitive-complexity gate. - The transcript_committed fallback settles only legacy steered chips (no engine-side id); skip its full load_session snapshot when the queue holds only steer_id chips, which settle authoritatively via chat:steer_committed/dropped. Every commit during a busy queue no longer pays an IPC round-trip that cannot settle anything. Regressions for both (red/green verified against the prior code); chat protocol hash recomputed (the extractor scans raw source, the fallback body wording is part of the digest). Signed-off-by: asto <asto18089@126.com>
|
P2 addressed — PR body rewritten for the final r13 tree. The body now describes the actual final state instead of the r11/r12-era narrative:
Author-side verification while rewriting: synced the CodeWhale submodule to r13 locally and re-ran |
zhuowp
left a comment
There was a problem hiding this comment.
Reviewed the current head d2ff5c3. The subscription-snapshot settlement isolation, lifecycle pairing, one-shot scroll snap, and early dropped-steer recovery are coherent and covered by targeted regressions. I also reran scheduled_tasks_unit plus the bridge protocol, DeepSeek timeline, conversation scroll, and multiagent contract suites locally; all passed. No additional blocking finding from this review. This approval does not override the current change request from another reviewer.
d2ff5c3 to
5baf381
Compare
|
@JensenChen28 P2 已闭合(head
|
Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
Mid-turn steer injections were the only user messages persisted with the
engine-baked trailing <turn_meta> block: admissions are replaced by their
UI display copies via transcript sanitization rules, but inject_steer
bypasses that path, so the durable transcript carried stale
date/workspace envelopes that a rebuilt engine replayed to the model and
the reload path leaked into bubbles.
- sanitize_messages now also strips the trailing <turn_meta> block from
external-user messages (no Input provenance line) after rule
replacement, so steers persist as the same display-copy shape as
admissions. Runtime-owned user turns (subagent handoff, shell
completion, memory recall) keep their envelope: the provenance line is
the display layer's internal-message signal.
- The reload projection identified steered bubbles by sniffing that
envelope; with the strip, the marker moves to an explicit
steered-messages sidecar ({pos, text}, save/get session commands,
remote-control scope wiring) recorded at every settlement path (chip,
late-commit after withdrawal, legacy fallback). Position capture
tail-aligns same-text matches against a load_session snapshot and
retries on each chat:transcript_committed, the event that announces
the persist; purgeSteerState clears pending captures. Reload marks
steeredMidTurn from the sidecar (pos + text verified against
compaction/edit drift) and keeps the envelope check as the fallback
for sessions persisted before this alignment.
- The web bridge implements no steer flow, so there is nothing to
mirror there; the transcript strip benefits both hosts.
Verified: cargo test (turn lifecycle incl. the new strip test, sessions
sidecar, protocol, remote-control manager) 228 green; eslint clean;
steered_messages_sidecar 4/4; deepseek_conversation_timeline,
scheduled_tasks_unit, pinvou_scene_sidecar, session buffer/nav,
authority-sync and web-access contract suites green; bridge protocol
hashes recomputed.
Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
default-run and autobins sat below the [package.metadata.cargo-shear] header in src-tauri/Cargo.toml, so TOML parsed them into the metadata table and cargo ignored both: dev mode (tauri's default `cargo run` devCommand) failed with "could not determine which binary to run". Move both keys back into [package] proper. Signed-off-by: luzeyang (INT) <lu.zeyang@h3c.com>
5baf381 to
aa2f027
Compare
|
补充修正(head |
|
Addendum — local
Net: local run agrees with CI — no regression attributable to this PR. |
Background
Busy sessions previously could only queue until the end of the turn: main has no interrupt-and-send entry point (
git grep interruptAndSend origin/mainis empty — the chain is introduced by this PR), and the engine side had no deterministic cancel handshake or steer disposition semantics. This PR completes the whole chain: queued sends / mid-turn steer injection / ⚡ instant interrupt.Changes
1. Engine seam (app-side glue over the foundation's steer/cancel primitives) —
feat(assistant): adopt r11 steer withdrawal and cancel modes+ hardeningEngine::cancel_current_with_mode, a thin wrapper over the atomiccancel_with_mode(InterruptKeepInbox / StopDropInbox), retires the two-step write; idle / already-switched-turn stops re-issue StopDropInbox so parked steers cannot escape the "stop = clear inbox" contractcancel_generationreturnsCancelOutcome{generation, terminal}(terminal = claim path completed the terminal itself / reserve gate reopened / idle), which the frontend uses to decide whether to wait forchat:done;pending_cancelreplay carries the CancelMode so ⚡ keeps its keepInbox semantics through the submit→TurnStarted window; cancel-time shell cleanup is scoped to the turn's own foreground shellse{gen}-steer-{n}), the forwarder stamps thesteer_committed/steer_droppedpayloads with the same generation, andwithdraw_steergeneration-checks before delegation — a stale chip's withdrawal can never retire the live engine's unrelatedsteer-1after an idle reclaim or model switchwithdraw_steerreturns the foundation'sretired / not_pendingoutcome projection2. Frontend chain —
feat(chat): add busy queue, mid-turn steer, interrupt send+ outcome hardeningwithdraw_steer, effective until the moment of injection) and ⚡ (interrupt & send now); the "clear draft" button was removed with the layout rework (intentional, see the product decision below); Stop stays visible while busychat:steer_committedmatches the chip by opaque steer_id and flips it into a bubble (CJK input no longer suffers from hash-encoding mismatches); events that arrive before the invoke resolves are stashed per session and settle on backfill; a legacy no-id fallback with tail-aligned matching covers older backendsretired(safe to resend) /not_pendingorwithdraw_timeout/withdraw_unreachable(never auto-resend; reconcile watchdog: late committed → bubble, dropped → silent, event lost → text restored after 60s). A ⚡ on a not-yet-backfilled chip awaits the steer settlement first and skips its own backfill withdrawal; concurrent ⚡ on the same session is rejected with an explicit notice. Text is never silently lost and the queue never hangsinterruptSendcapability; i18n covers all three languages3. Conversation presentation & lifecycle
4. Steer persistence alignment (new this round) —
fix(chat): persist steers as display copies<turn_meta>block (admissions are replaced by their UI display copies via transcript sanitization rules;inject_steerbypasses that path), so the durable transcript carried stale date/workspace envelopes that a rebuilt engine replayed to the model.sanitize_messagesnow also strips the trailing envelope from external-user messages (noInput provenanceline), so steers persist as the same display-copy shape as admissions; runtime-owned user turns (subagent handoff, shell completion, memory recall) keep their envelope because the provenance line is the display layer's internal-message signal{pos, text}, newsave/get_session_steered_messagescommands with remote-control scope wiring) recorded at every settlement path, captured tail-aligned against aload_sessionsnapshot withchat:transcript_committedas the retry point. Reload markssteeredMidTurnfrom the sidecar (pos + text verified against compaction/edit drift); the envelope check stays only as the fallback for sessions persisted before this alignment5. Fork register & guards —
docs(fork): record r11 steer lifecycle modifications(zh + en, kept current through r13)pinvou-cli/Cargo.lockis a deterministic stale-lock regeneration to the 0.9.0 manifests, kept in-PR by author decision6. Drive-by build fix (flagged, splittable on request) —
fix(build): restore cargo default-run placementdefault-run/autobinsinsrc-tauri/Cargo.tomlsat below the[package.metadata.cargo-shear]header (on main as well), so cargo silently ignored both and dev mode's defaultcargo runfailed with "could not determine which binary to run". One-line-class move back into[package]; disclosed here because it is not steer-related — happy to split into its own PR if reviewers preferProduct decision: removing the "clear draft" button
Intentional, not an omission: the management semantics of an unsent draft are the text editing itself (select-all delete / direct rewrite); mainstream agent products like Claude Code and Codex have no standalone "clear draft" entry in their composer area. Undoing queued messages is handled by the per-entry × (× manages queued chips, not the draft — the two responsibilities were always distinct). This PR's always-visible Stop while busy + queue overlay layout aligns with that mainstream practice.
Known limitations
SteerCommittedbypasses turn admission and only emitschat:steer_committed(payload carries only{session_id, steer_id}, no content), and the web bridge has no steer listener; a remote observer sees the AI reply to a message that "never appeared", recovering after a full reload. Desktop bubbles are rendered by the local chip flip and are unaffected. A follow-up PR can evaluate emitting a content-bearingchat:user_messagein theSteerCommittedbranch.Verification
On the final tree (
aa2f027c7, linear overa610119d2):sanitize_strips_steer_turn_meta_tail_but_keeps_internal_envelopes, steered/scene sidecar normalization, sessions store, protocol and remote-control manager tests (0 failures across 254 targeted tests);cargo fmtcleansteered_messages_sidecar.test.js4/4 (settle→capture→sidecar record, tail-alignment against same-text history, persist-race retry, purge);deepseek_conversation_timeline,scheduled_tasks_unit,pinvou_scene_sidecar, session buffer/nav, authority-sync, web-access contract suites green; bridge domain protocol + contract green (chat/orchestration hashes recomputed with the test's own extractor); eslint cleansync-version.mjs --check(0.9.0 consistent),fork-guard.sh --fastgreen,architecture-guard.pygreend2ff5c33was fully green (rust-test / windows-rust-test / rust-lint / frontend-test / fast-gate incl. verify-public-submodule / required-gate / Gitleaks / version / commit gates); the same matrix runs on this head on pushReview status
All BLOCKER/MAJOR/P0/P1 items from the review rounds are substantively landed (root-cause verification, CJK steer correlation, deterministic cancel handshake, stop-clears-inbox backstop, restore paths, withdraw outcome semantics incl. timeout/unreachable fail-closed, cross-generation id stamps, subscription/lifecycle presentation fixes); JensenChen28's current-main integration P1 is resolved (rebased onto
a610119d2, merge-clean, r13 gitlink inherited unchanged) and the PR-body accuracy P2 is addressed by this rewrite; zhuowp's diagnostics-language P2 was translated in round 11; asto18089's latest re-audits are incorporated or answered in-thread. Remaining known-accepted items are documented above and in the review thread (remote observer visibility, model-tier steer stacking, and the small follow-ups agreed per round).