Skip to content

feat(chat): busy queue, mid-turn steer, zap send - #308

Merged
asto18089 merged 25 commits into
Pinvou:mainfrom
qiuYliangM:feat/ai-runtime-conversation-insertion
Sep 2, 2026
Merged

feat(chat): busy queue, mid-turn steer, zap send#308
asto18089 merged 25 commits into
Pinvou:mainfrom
qiuYliangM:feat/ai-runtime-conversation-insertion

Conversation

@qiuYliangM

@qiuYliangM qiuYliangM commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

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/main is 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.

History note: the branch was rewritten once at maintainer suggestion (byte-identical tree, plus review-suggested comments). What remains on top of the three base commits is the accumulated follow-up work from the review rounds — outcome-gated withdrawal, restore-path hardening, cross-generation steer-id stamps, presentation fixes, persistence alignment — plus semantic merges of main. The final head is a linear 25-commit series over current main (a610119d2, the 0.9.0 release bump), 42 files changed (+6293/−393). The CodeWhale gitlink inherits main's r13 (f853f8f1) unchanged — this PR advances no submodule pointer; the steer/cancel primitives it builds on landed in the foundation at r11, and the r12/r13 baseline updates arrived through the merges of main.

Changes

1. Engine seam (app-side glue over the foundation's steer/cancel primitives)feat(assistant): adopt r11 steer withdrawal and cancel modes + hardening

  • Engine::cancel_current_with_mode, a thin wrapper over the atomic cancel_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" contract
  • cancel_generation returns CancelOutcome{generation, terminal} (terminal = claim path completed the terminal itself / reserve gate reopened / idle), which the frontend uses to decide whether to wait for chat:done; pending_cancel replay 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 shells
  • Generation-scoped steer ids: the pool stamps each spawning engine's process-monotonic incarnation sequence onto foundation ordinal ids (e{gen}-steer-{n}), the forwarder stamps the steer_committed/steer_dropped payloads with the same generation, and withdraw_steer generation-checks before delegation — a stale chip's withdrawal can never retire the live engine's unrelated steer-1 after an idle reclaim or model switch
  • withdraw_steer returns the foundation's retired / not_pending outcome projection

2. Frontend chainfeat(chat): add busy queue, mid-turn steer, interrupt send + outcome hardening

  • While busy, Send becomes a queued send; queued messages enter the overlay, each chip with its own × (real withdraw_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 busy
  • Mid-turn steer injection: chat:steer_committed matches 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 backends
  • Every await is bounded and every outcome is fail-closed: 25s transport timeouts on steer / cancel / withdraw; the withdraw outcome is four-way — retired (safe to resend) / not_pending or withdraw_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 hangs
  • Steer intermediates are purged with session deletion / LRU eviction; failure notices route by sid; the remote-control access-policy whitelist gains the steer events (test-locked); web hides ⚡ behind the interruptSend capability; i18n covers all three languages

3. Conversation presentation & lifecycle

  • Subscription safety: the steer settlement handle was moved out of the subscription-visible chat slice (a promise in the slice poisoned the snapshot validator and froze streaming text/thinking until the chip settled)
  • Steer turns hydrate correctly across reloads (timeline grouping, no phantom terminal badges for content-less injected turns), avatar-only rows between consecutive injections are hidden, queue chip × / ⚡ read as visually equal actions with tooltips matching behavior, and a mid-turn steered bubble snaps to the bottom exactly once without overriding the user's scrolled-up state

4. Steer persistence alignment (new this round)fix(chat): persist steers as display copies

  • 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; inject_steer bypasses that path), so the durable transcript carried stale date/workspace envelopes that a rebuilt engine replayed to the model. sanitize_messages now also strips the trailing envelope from external-user messages (no Input provenance line), 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
  • The reload projection previously identified steered bubbles by sniffing that envelope; the marker moves to an explicit steered-messages sidecar ({pos, text}, new save/get_session_steered_messages commands with remote-control scope wiring) recorded at every settlement path, captured tail-aligned against a load_session snapshot with chat:transcript_committed as the retry point. Reload marks steeredMidTurn from the sidecar (pos + text verified against compaction/edit drift); the envelope check stays only as the fallback for sessions persisted before this alignment

5. Fork register & guardsdocs(fork): record r11 steer lifecycle modifications (zh + en, kept current through r13)

  • fork-guard carries the steer/cancel fingerprints plus both sides' T2 entries from the r12 merge; pinvou-cli/Cargo.lock is a deterministic stale-lock regeneration to the 0.9.0 manifests, kept in-PR by author decision

6. Drive-by build fix (flagged, splittable on request)fix(build): restore cargo default-run placement

  • default-run / autobins in src-tauri/Cargo.toml sat below the [package.metadata.cargo-shear] header (on main as well), so cargo silently ignored both and dev mode's default cargo run failed 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 prefer

Product 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

  • Remote/web observers temporarily cannot see steer-injected messages: SteerCommitted bypasses turn admission and only emits chat: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-bearing chat:user_message in the SteerCommitted branch.
  • Stacking several queued messages into one running turn injects them all as consecutive user messages; lighter model tiers may answer only the most recent one and deprioritize earlier tasks (observed live with glm-5.3-flash: the first question's investigation was started, then abandoned when later steers landed). This is a model-tier trade-off of the mid-turn steer design, not a delivery defect — every message verifiably reaches the engine in FIFO order. Switching to a heavier model or queueing to the next turn (the attachment path already does this) avoids it.

Verification

On the final tree (aa2f027c7, linear over a610119d2):

  • Rust: targeted suites green — turn-lifecycle incl. the new 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 fmt clean
  • Frontend: new steered_messages_sidecar.test.js 4/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 clean
  • Repo gates: sync-version.mjs --check (0.9.0 consistent), fork-guard.sh --fast green, architecture-guard.py green
  • CI on the previous head d2ff5c33 was 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 push
  • Regression coverage across the series: interrupt waits and generation filtering, steer stash settlement, watchdog degrade / not_pending / timeout / unreachable-withdraw reconciliation, zap on not-backfilled chips, concurrent ⚡ rejection, generation-collision and cross-generation withdrawal refusal, early dropped-steer restore, frozen-streaming subscription guard, avatar-row / scroll-snap / hydration pins, transcript fallback tail-backscan, steer persistence display-copy alignment, pending-enable smoke

Review 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).

@qiuYliangM
qiuYliangM force-pushed the feat/ai-runtime-conversation-insertion branch from 89c2c49 to 1713a32 Compare August 17, 2026 18:15

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢这个 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_hashUTF-8 字节做 FNV-1a(CodeWhale turn_loop.rs:4271content.trim().bytes());前端 steerContentHashUTF-16 码元bridge/chat-events.js:338charCodeAt)。实测「中文」两侧哈希不同;任何非 ASCII 输入必不匹配
  • 后果:chat:steer_committed 永远匹配不到 chip(committed 场景靠 transcript_committed 兜底侥幸存活);chat:steer_dropped 没有任何兜底——按 ⏹ 停止后中文 chip 永久悬挂;且 flushQueued 遇队首 steered chip 直接 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_runningshell.rs:2259)会杀掉本 session manager 内全部 Running 进程——包括之前轮次用户刻意转后台的任务(task_shell_start 的跨 turn 语义)、以及设计上与父轮取消解耦的后台子智能体 shell(subagent/mod.rsbackground_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),不 drain rx_steer channel 残留;同时 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.rs reclaim_engine_entry)与 Op::Shutdown 销毁 pending_steers 与 channel 残留时不发任何 SteerDropped;Op::SyncSessionengine.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::cancelis_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::commandsprotocol_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.attachmentschat.js:807)→ 同一附件随下一条消息重复发送
  • M-11 Stop 按钮回归:main 上 busy 恒显 Stop;新代码 busy && !hasDraftTextChatView.jsx:2042)——生成中打了字就无法「停止但保留草稿」,必须先清空草稿。
  • M-12 Web 端 ⚡ 可见但静默 no-op:web bridge 无 interruptAndSenddomain-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:802else {} 死代码;steer() 未使用的 queuedItem 参数;chat.js:490:699 注释互相矛盾且描述已废弃设计。
  • interruptAndSendstate.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_sessiontranscript_revision 字段,与现实契约脱节。
  • ChatView.jsx:2057 中文硬编码 fallback(键已存在,实际是死代码,但违反 i18n 公约且与同行 t.queueMsg 写法自相矛盾)。
  • FNV-1a 已是仓内第 4 份实现(CodeWhale user_constitution.rs:1289、app memory/util.rs:62 已有)。
  • 5s waitForChatDone 超时对长 tool chain 的取消 unwind 偏短;超时先于 listen Promise resolve 时监听器泄漏一个(有 resolved 守卫,无功能危害)。
  • CancelOutcome doc 注释「目标轮已自然结束(mismatch)⇒ terminal=true」与实现不符(mismatch+新轮已 reserve 时实际为 false,方向无害:多等 5s)。

修复优先级建议

  1. B-1(哈希/改 opaque id)+ M-3/M-4(drop 与 evict/SyncSession 覆盖)——steer 协议的根。
  2. M-6(terminal 判定改读闸门状态)——⚡ 可靠性的根。
  3. M-1(一行)+ M-9/M-10(静默丢失三连)。
  4. 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),建议先在那里收敛引擎语义,父仓再对齐。

qiuYliangM pushed a commit to qiuYliangM/pinvou-agent that referenced this pull request Aug 19, 2026
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>
asto18089 pushed a commit that referenced this pull request Aug 19, 2026
#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>
@asto18089

Copy link
Copy Markdown
Collaborator

审查结论:高质量、根因全部属实且完全解决;已帮修 2 处前端失败路径 MAJOR,另有 2 处 MAJOR 与 1 个产品变更待作者确认

感谢这个 PR——mid-turn 注入/打断链路的设计整体优雅(steer 语义全在底座 EngineHandle,app 侧薄封装;opaque steer_id 替代 content_hash 关联;is_reserve_gate_open_for 的三分支闸门判定 + P0-B「chat:done ⇒ 槽位已释放」契约),五条声称的根因经逐条验证全部属实且在声称范围内完全解决,fork 边界与登记自洽。rebase 到最新 main 已完成(分支里有 9 个已合入 main 的旧拷贝提交已 drop,净差异零丢失),全量门禁绿。

已代修并推送(8cf6be0f)

MAJOR-1:steer invoke 挂起 → 文字永久丢失 + 排队区被悬挂 chip 阻塞bridge/chat.js
前端 invoke 无传输层超时,引擎任务卡住(不死、不 drain)时 steer_chat 永不结算:输入框已清空无法恢复,chip steered:true, steerId:null 卡在队头使 flushQueued 永久让路;若引擎恢复并 commit,事件带 steer_id 但 chip 的 steerId 为 null 匹配不上,stashSteerEvent 永久暂存。
→ 修复:steer() 补 25s Promise.race 兜底(与 waitForChatDone 对齐),超时走既有失败恢复(移除 chip、恢复输入框、提示)。

MAJOR-2:⚡ 失败恢复的 steered chip 回插队头 → 排队区可卡死bridge/chat.js
撤回已发给引擎,但引擎只在下一轮 drain 时才发 steer_dropped 结算;而 chat 发送已失败、flushQueued 被 steered 队头挡住——下一轮永远不来,用户必须手动再 ⚡/× 才能解锁。
→ 修复:失败恢复时把 chip 降级为非 steered(撤回已发出,引擎侧不会再注入;残留由迟到的 steer_committed 补气泡,去重已有处理),由 flushQueued 正常消费。

两个修复均带回归测试(假时钟触发超时恢复 / 降级 chip 被 chat:done flush 消费),interruptQueuedFailureRestoresChipToQueue 断言同步更新;前端 165 测试 + Rust 1254 测试 + fork-guard + architecture-guard 全绿。

待作者处理的缺陷

MAJOR-3:引擎重启/崩溃时未注入的 steer 静默丢失——底座 shutdown 的 try_send(SteerDropped) 在 forwarder 已退出时会丢事件,前端也未监听引擎重建来清算 steered chip。有 ⚡/× 手动逃生口故不算阻断,但「按 steer_id 结算」的承诺在引擎死亡路径上不成立,建议至少加一条提示或状态重查。

MAJOR-4:interruptAndSend 25s 超时兜底后不复查状态直接 doSendFor——超时后直接发送,若闸门实际未开会撞 session_turn_in_progress 并 throw;UI 的 ⚡ 路径有 catch 恢复,但该函数已导出到 bridge.chat 供远控/其他宿主使用,调用方不接异常时消息静默丢失。建议超时后先重查 busy/闸门状态再决定发送或显式失败。

需要确认的产品变更(超出「打断链路修复」范围)

桌面输入区的**「清空草稿」按钮(Trash2)被整体删除**,清空入口仅剩平板语音悬浮模式。这是 busy-恒显-Stop 布局重构的直接结果,但属于独立的用户可见产品变化——请确认是有意为之(若有意,建议在 PR 描述补充说明;若无意,需要恢复)。

测试缺口(非阻断,建议补齐)

  • terminal=false → waitForChatDone 分支零覆盖(现有 ⚡ 测试都 mock cancel_generationterminal:true);25s 超时恢复、会话切换时 steer chip 清理、停止按钮(keepInbox=false)清空 chip + 提示同样无测试。
  • followupQueuedUntilScheduledInitialTurnTerminal 改写时静默删掉了两条仍有价值的旧断言(busy steer 期间不出现多余 assistant placeholder、flush 后 busy turn 归属),建议补回等价断言。
  • protocol_tests.rs 的新增只是命令名清单扫描,steer_chat/withdraw_steer 在 Rust 层无行为测试(AppHandle 依赖导致无法单测是客观原因,但应如实记为缺口)。

小问题(NIT)

  • PR 描述「⚡ 与发送按钮并排」与实现不符——⚡ 已挪到每个排队 chip 上(实现更好,建议更新描述)。
  • handle.rs:176 注释残留内部标签 (P0-A),与登记声明的上游中性实现不符,回馈上游前应清理。
  • fork-guard.sh:34 失败提示文案仍写「r7 公开维护 head」,已过期。
  • docs/fork-modifications.md 中英标题层级不一致(zh H3 / en H2)。

其余验证:CodeWhale 4 个新提交全部主题内(steer 原语/测试构造/fmt/评审修复),11 条新指纹逐条存在,EXPECTED_COMMITS=13 与 gitlink 一致;allowlist 声称属实(access-policy.json 权威准入、RUST_FORWARDED_EVENTS 仅去重),无第三处清单遗漏;i18n 三语齐全;无重复造轮子、无必须拆分的无关改动。

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已代修 MAJOR-1/2 并推送 8cf6be0(含回归测试,全量门禁绿);MAJOR-3/4 与「清空草稿按钮删除」待作者处理/确认,详见完整审查评论。

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

已代修 MAJOR-1/2 并推送 8cf6be0(含回归测试,全量门禁绿);MAJOR-3/4 与「清空草稿按钮删除」待作者处理/确认,详见完整审查评论。

@asto18089
asto18089 force-pushed the feat/ai-runtime-conversation-insertion branch from 7c21bfe to 8cf6be0 Compare August 19, 2026 13:02
asto18089 pushed a commit that referenced this pull request Aug 19, 2026
#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>
@asto18089
asto18089 force-pushed the feat/ai-runtime-conversation-insertion branch from 8cf6be0 to 72cdf44 Compare August 19, 2026 13:08
@asto18089

Copy link
Copy Markdown
Collaborator

补充:推送后 main 又合入了 #292/#316/#301(与 PR 有 7 个文件交集),已再次 rebase 到 9a5ff79 并重新推送,最终提交为 72cdf44(上一条评论中的 8cf6be0 已被覆盖,修复内容完全一致;chat 协议哈希已按新基线重算为 6918230f,behavior_telemetry 的 lib.rs/protocol_tests 冲突已保全双方合并)。本地全量门禁复验:前端 203 过 / Rust 1249 过 / fork-guard / architecture-guard 全绿,CI 已重新触发。

@asto18089
asto18089 force-pushed the feat/ai-runtime-conversation-insertion branch from 72cdf44 to e0464fb Compare August 19, 2026 13:32
@asto18089

Copy link
Copy Markdown
Collaborator

CI 状态补充说明:最终 head 为 e0464fb(在 72cdf44 基础上补了 protocol_tests.rs 的 cargo fmt,修复我上一轮推送引入的 rust-lint 失败)。当前 PR Check 中 fast-gate「公开底座 gitlink 可达性」失败是存量问题(作者此前的推送同样失败):gitlink a5ef9eb9 目前只存在于 CodeWhale 的 refs/pull/16/head,尚未发布为公开 tag,而 verify-public-submodule.sh 要求 gitlink 等于 pinvou-v0.9.5-r7(或后续 tag)的解引用值。合并前需要先在 Pinvou/CodeWhale 合并 #16 并发布新 tag(如 r8),届时 fast-gate 才能通过。其余门禁(frontend-test / windows-rust-test / rust-lint / changes / commit-message / version-consistency)全绿;rust-test 在普通 PR 上按设计跳过(需 ci:full-rust 标签,merge queue 会跑)。

asto18089 pushed a commit to qiuYliangM/pinvou-agent that referenced this pull request Aug 19, 2026
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>
@asto18089
asto18089 force-pushed the feat/ai-runtime-conversation-insertion branch from e0464fb to b27bd56 Compare August 19, 2026 14:10

@zhuowp zhuowp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

复审结论:当前仍需修改。

  • P1(集成/发布门禁):父仓 gitlink 为 a5ef9eb91a7c9180e1327e2b80ec6f0500b82a98,但公开 pinvou-v0.9.5-r7 仍解引用为 a36e6cd533024cfe5724bae21875aea42b2ed87ascripts/verify-public-submodule.sh 因此失败,线上 fast-gaterequired-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 也成功。当前阻塞为上述三项。

qiuYliangM pushed a commit to qiuYliangM/pinvou-agent that referenced this pull request Aug 25, 2026
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>
@qiuYliangM
qiuYliangM force-pushed the feat/ai-runtime-conversation-insertion branch from b27bd56 to 632af49 Compare August 25, 2026 11:57
@qiuYliangM

Copy link
Copy Markdown
Collaborator Author

三项阻塞已全部处理(分支已 rebase 到最新 main 并推送):

  • P1(集成/发布门禁):CodeWhale#16 已合入 pinvou3-clean 并公开发布。期间底座又前进了两个修复(fix: 升级 serde_with 至 3.21.0 #17 edit-last-turn 边界、fix: 移除未使用且存在漏洞的 React Router 依赖 #19 固定采样路由 compaction),公开基线现为 pinvou-v0.9.5-r10feb8761ae。父仓 gitlink、verify-public-submodule.sh 锁定 tag、fork-guard 拓扑(v0.9.5 之上 14 commits)三者已对齐 r10,fast-gate 的公开 gitlink 可达性应恢复。
    • 注意:Draft: add design mode composer UI shell #16 合并版与候选链存在 API 差异(评审修复重构)——set_steer_keep_inbox 两步写法退役,改为 cancel_with_mode(reason, CancelMode::{InterruptKeepInbox, StopDropInbox}) 原子发布 steer 处置与 cancel token;撤回语义同步为有界集合 + 恰好一次 settle。父仓 EnginePool::cancel 已适配(新增 Engine::cancel_current_with_mode),评测/headless 的 pool.cancel 调用补齐停止语义实参;fork-guard steer 指纹与 docs/fork-modifications(中英)登记已按合并版实际代码逐条核对。
  • P1(main 冲突):已 rebase 到最新 origin/main(含 perf: lazy-loading round 2 — i18n per-language chunks, bridge init parallelization, panel-level lazy loading, knowledge model first-use load #341 i18n 惰性拆分、fix(chat): preserve authoritative edit-last-turn boundaries #320 编辑边界、fix(codex): preserve selected model label on first native turn #349 等)。bridge_domain_protocol.test.mjs 等冲突逐个解决:i18n 新增文案移植到按语言 chunk(三语 parity 测试通过)、interruptSendacpCodeMode 能力并存、steer 测试与 webFollowup 测试并集、fork-guard 取 r10 拓扑与合并版指纹、协议哈希按最终合并树全量重算。
  • P2(公开 API 超时语义):已修。waitForChatDone 现在返回是否真正观察到终态;25s 兜底超时且会话仍 busy 时 interruptAndSend 显式 reject、绝不调用 chat(此时发送会稳定撞 session_turn_in_progress),消息由调用方既有恢复路径保留;仅在会话已不 busy(监听器错过事件但 turn 实际结束)时才继续发送。新增公开 API 回归测试 interruptAndSendTimeoutWhileBusyFailsWithoutSending:假时钟触发 25s 超时仍 busy → 断言 reject、零 chat 调用、终态后重试同一条消息成功。

验证: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 通过。

@qiuYliangM

Copy link
Copy Markdown
Collaborator Author

跟进 8-19 审查中待作者处理的各项(分支已 rebase 到最新 main):

  • MAJOR-4(interruptAndSend 超时兜底):与 zhuowp 复审的 P2 同根,已修复——waitForChatDone 返回是否观察到终态,25s 超时且仍 busy 时显式 reject、不盲发(公开 API 调用方不接异常也不会静默丢消息),并补了公开 API 回归测试 interruptAndSendTimeoutWhileBusyFailsWithoutSending
  • MAJOR-3(引擎重启/崩溃时未注入 steer):底座 Draft: add design mode composer UI shell #16 合并版已带 Drop for Enginetry_send best-effort 兜底(engine_drop_reports_unconsumed_steers_best_effort 回归),覆盖宿主 evict/reclaim 直接丢弃引擎的路径。forwarder 已退出导致事件丢失的残留面本 PR 不引入前端重查逻辑(有 ⚡/× 手动逃生口),建议另开 issue 跟进引擎重建时的排队 chip 清算。
  • 「清空草稿」按钮删除:有意为之——busy 恒显 Stop 布局重构的一部分(保留草稿 + Stop 常显,排队消息统一进浮层管理,清空入口由逐条 × 替代),PR 描述已补充说明。
  • NIT:PR 描述已更新(⚡ 在排队 chip 上,非与发送按钮并排);handle.rs(P0-A) 注释在 Draft: add design mode composer UI shell #16 合并版中已不存在;fork-guard 文案已随基线更新到 r10;fork-modifications 中英标题层级已对齐。
  • 底座对齐:CodeWhale#16 合并版用 cancel_with_mode(reason, CancelMode) 原子 API 替代了候选链的 set_steer_keep_inbox,父仓已适配;gitlink 前进到公开基线 pinvou-v0.9.5-r10feb8761ae,含 Draft: add design mode composer UI shell #16/fix: 升级 serde_with 至 3.21.0 #17/fix: 移除未使用且存在漏洞的 React Router 依赖 #19),登记/指纹/verify 脚本同步。

asto18089 pushed a commit that referenced this pull request Aug 25, 2026
#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>
@asto18089
asto18089 force-pushed the feat/ai-runtime-conversation-insertion branch from 632af49 to 7beef3b Compare August 25, 2026 15:27
@asto18089

Copy link
Copy Markdown
Collaborator

第三轮审查结论:根因全部落地、架构与测试质量高;已代修 2 处 CI 必红 + 5 处前端/文档缺陷并推送(head 7beef3b18

先说结论:本轮(r10 适配 + P2 修复 + rebase)质量是三轮里最好的。上一轮的 4 个 MAJOR 全部得到实质处理:M-4 超时显式 reject(含高质量公开 API 回归)、M-3 由底座 Drop for Engine try_send 兜底覆盖(engine_drop_reports_unconsumed_steers_best_effort 回归确认存在);我上轮代修的 M-1/M2 经逐 commit 比对在历史重写后原文保留cancel_current_with_mode 对 r10 cancel_with_mode 的适配是忠实薄封装,is_reserve_gate_open_for 闸门判据(以「闸门重开」而非「终态已认领」为准)与 P0-B「chat:done ⇒ 槽位已释放」契约在 forwarder/engine 三处终态路径全部对齐,无两步写法残留。远控白名单双清单接线正确、新事件 payload 不含消息文本、steer_chat/withdraw_steer 未进入 allowed_commands,安全面无扩大。i18n 三语 parity 全过。rebase 到最新 main(3dd2a085b)零冲突,本地全量验证绿。

但发现 2 处会让必需门禁必红的问题(均已代修)

  1. fast-gate 必红scripts/tests/test_ci_gate_policy.pytest_pr_submodule_verifier_strictly_matches_the_published_tag 仍断言 PINVOU_CODEWHALE_TAG="pinvou-v0.9.5-r9"——verify 脚本推进到 r10 后该策略测试未同步(它不在 PR 改动的 27 个文件里,PR 描述的验证清单也没覆盖 scripts/tests)。本地复现 81 tests 1 failure 正是它,线上 fast-gate 日志确认同一断言。已改为 r10。
  2. commit-message 必红adapt to r10 atomic cancel_with_mode steer disposition(54)、fail interruptAndSend explicitly when cancel wait times out while busy(70)、align r10 public baseline and recompute bridge protocol hashes(62)三个 subject 超过 50 字符硬门禁。已 reword(正文不动)。required-gate 的红是这两者的聚合。

本轮代修并已推送的缺陷(含回归测试)

  • steer 失败恢复的队列路由chat.js):原实现 filter 的是 working-set 队列——invoke 最长 25s 才超时,期间用户切走会话后,悬挂 chip(steered:true, steerId:null)永久卡死后台会话队头,flushQueued 让路、该会话排队消息全部饿死。已改按 sid 路由(steeredQueueFor)。
  • steer/handleSend 失败恢复无条件覆盖输入框chat.js + ChatView.jsx):25s 等待期间用户新打的字会被旧文本整体冲掉。已加判空守卫:输入框为空才回填,非空时 steer 路径把 chip 原地降级为纯本地排队(文字不丢、×/⚡ 仍可用),handleSend 路径退化为 prefill 追加。
  • busy steer 分支直接赋值 state.composerDraft = "":不同步会话 buffer(切走再切回复活已发送文字),且后台会话的 steer 会清掉当前活跃会话的输入框。已改 setComposerDraft("") + sid 守卫。
  • 排队浮层无高度上限:大量排队消息可顶出视口。已加 max-h-[40vh] overflow-y-auto
  • busy Send morph 文案失真(三语):原「本轮结束后自动发送」与桌面 steer 注入当前轮的实际行为矛盾,已改中性表述(zh「发送到当前对话」)。
  • 文档 r10 同步fork-policy.md/.en.md 整体停在 r9(基线行/drift 数字/已合并 PR 列表)、fork-modifications.en.md 页首残留 r9、zh 验证段 37 passed 应为 41(r10 实测 41 条 forkguard)。
  • 回归测试 ×3:steerTimeoutDoesNotClobberFreshInputsteerTimeoutWhileSwitchedAwayUnblocksBackgroundQueue(后台队列解除卡死 + flush 消费断言)。

代修后全量验证:scheduled_tasks_unit(含新回归)/ bridge_domain_protocol / web_bridge_domain_contract / bridge_domain_contract / ui_language_coverage / eslint / fork-guard --fast / architecture-guard / verify-public-submodule / scripts/tests 81 项 / validate-commit-msg / cargo fmt 全绿。

留给作者的三件事

  1. 「清空草稿」按钮:PR 描述称「清空入口由逐条 × 替代」,但 × 删的是已提交进排队区的 chip,清空草稿删的是输入框里未发送的文本——二者语义不同,不可互替。桌面主布局用户失去唯一的一键清空入口(仅剩 tabletVoiceMode 悬浮区的按钮)。建议恢复按钮(handleClearInput 本体仍在)或在描述里给出真实的替代路径。
  2. 空闲/已切轮窗口下 stop 不发布 StopDropInboxengine_pool.rs):turn 自然结束、lifecycle 空闲但前端 busy 未复位(正是 PR 自述的窗口)时按 ⏹,阶段一的 cancel_current_with_mode(StopDropInbox) 被 idle 守卫跳过,上一轮 keep 保留的 parked steer 逃过「停止=清空」契约、下一轮照常注入且无 chat:steer_dropped。窗口窄故非阻断,建议 idle 分支也调一次 cancel_current_with_mode(StopDropInbox)(对空闲 token 幂等)。
  3. 测试缺口(主打的 P0-B 只有失败路径被测):所有 ⚡ 测试的 cancel_generation mock 都返回 terminal:true 绕过事件等待,waitForChatDone成功路径(收到匹配 generation 的 chat:done → 继续发送)与 generation 过滤(迟到的旧轮 done 不得提前解锁)零覆盖;pendingSteerEvents 事件早于 invoke 返回的暂存竞态同样零覆盖。建议补这两条。

记录在案(不阻断,建议 follow-up issue)

  • steered chip 对「事件永不到达」(invoke 成功后引擎死亡/forwarder 退出)无看门狗,队头悬挂需手动 × 自救——与上轮 M-3 同族,作者已建议开 issue 跟进引擎重建清算;
  • CodeWhale gitlink r9→r10(fix: 移除未使用且存在漏洞的 React Router 依赖 #19 compaction 修复)目前藏在 7a5304788(mid-turn inject)里且 r9 已含本 PR 全部所需 API——建议后续基线跳变独立成 commit 并在描述显式声明(本次登记/指纹齐全,不再要求拆分);
  • 其余 MINOR/NIT(⚡ 失败双重提示、chip 文本截断无 title、远控停止默认清空桌面 inbox 的跨端语义提示、双白名单无一致性守护测试、withdrawSteerChip fire-and-forget 不回滚 withdrawnSteers、cancel_current_with_mode 硬编码 CancelReason::User)已在审查笔记中记录,可另开小 PR 清理。

@asto18089 asto18089 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

第三轮审查完成(详见 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 仅剩三项待作者处理:

  1. 「清空草稿」按钮移除的决策依据(× 删的是排队 chip,不能替代清空输入框草稿)——恢复或说明;
  2. 空闲/已切轮窗口下 stop 不发布 StopDropInbox(parked steer 逃过清空契约,修法一行);
  3. 补 P0-B 事件驱动等待的成功路径 + pendingSteerEvents 先到事件竞态的测试覆盖。

@qiuYliangM

Copy link
Copy Markdown
Collaborator Author

三件事跟进:

  1. 「清空草稿」按钮:坚持删除,有意为之。主流 Agent 产品(Claude Code、Codex 等)输入区均无独立「清空草稿」按钮——未发送草稿的管理语义就是文本编辑本身(全选删除/直接改写),不需要专门入口;已排队消息的撤销由逐条 × 承担。本 PR 的 busy 恒显 Stop + 排队浮层布局与这一主流做法对齐,PR 描述已如实说明该用户可见变化。
  2. 空闲/已切轮窗口 stop 不发布 StopDropInbox:已修(engine_pool.rs)。EnginePool::cancel!keep_inbox && target.is_none() 且复查仍空闲时,对在场 engine 补发一次 cancel_current_with_mode(StopDropInbox)——底座仅抬高 drop_through_generation 屏障 retire parked steer(无 parked 时 no-op,对空闲 token 无副作用),上一轮 keep 保留的 parked steer 不再逃过「停止=清空」契约。仅停止路径做(打断的 keepInbox 本就要保留);即便与刚 reserve 的新轮竞速,⏹ 语义本就是停止该会话一切生成,方向一致。
  3. 测试缺口:已补两条回归——interruptAndSendWaitsForMatchingGenerationDone(waitForChatDone 成功路径 + generation 过滤:旧轮 done 不解锁零发送、匹配 done 解锁并恰好发送一次;用 scheduled 会话豁免跨端权威对账闸,聚焦 wait 监听器语义)与 steerCommittedBeforeBackfillSettlesViaStash(steer_committed 早于 invoke 返回 → 按 session 暂存 → steerId 回填立即结算、chip 转气泡不悬挂)。

验证:scheduled_tasks_unit 全绿(含打断链路全部回归)、cargo check --all-targets / cargo fmt --check / eslint 通过。

@qiuYliangM
qiuYliangM force-pushed the feat/ai-runtime-conversation-insertion branch from b5f143f to 8de684d Compare August 26, 2026 03:53
qiuYliangM pushed a commit to qiuYliangM/pinvou-agent that referenced this pull request Aug 26, 2026
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 zhuowp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本轮审核结论:Request changes

发现 3 个仍存在于当前 head 8de684d 的问题:

  1. [P1] ⚡ 瞬发可能重复投递同一条 steer。 withdrawSteerChip() fire-and-forget 调用 withdraw_steer 后立即执行 cancel + 新 chat;后端返回 Result<(), String>,无法区分“撤回成功”和“已经 committed”。若 committed 事件尚未到 UI,原 steer 已进入 transcript,新 chat 又会发送一次;后续气泡去重不能撤销模型已收到的重复指令。建议后端返回明确的 withdrawal outcome,前端 await 后再决定是否重发。

  2. [P1] steer_chat 超时后的晚成功会丢失 steerId 并可能重复投递。 Promise.race 超时后恢复输入框,但原 invoke 后续 resolve 没有处理;引擎仍可能提交原消息,用户重试后形成第二次投递。建议为晚 resolve 增加补偿撤回,或提供可取消/可查询最终状态的协议。

  3. [P2] cancel 返回后才注册 chat:done 监听,可能固定假等待 25 秒。 terminal:false 时,事件若在 invoke 返回与监听注册之间到达,Tauri 分支没有 busy 预检,会一直等到兜底超时。建议 cancel 前预注册监听,或在注册前后复查 busy/generation。

本地定向验证:scheduled_tasks_unit.test.jsbridge_domain_protocol.test.mjsbridge_domain_contract.mjs 均通过;现有测试没有覆盖上述 committed/timeout 晚到竞态。

asto18089 pushed a commit to qiuYliangM/pinvou-agent that referenced this pull request Aug 26, 2026
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>
@asto18089
asto18089 force-pushed the feat/ai-runtime-conversation-insertion branch from 8de684d to 89b2267 Compare August 26, 2026 14:44
asto18089 and others added 11 commits September 1, 2026 18:35
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>
@qiuYliangM

Copy link
Copy Markdown
Collaborator Author

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:

  • Background: the false "never exported on main" claim is gone. Accurate statement: main had no interrupt-and-send entry point at all — interruptAndSend/interruptAndSendQueued are introduced by this PR (the "introduced but never exported" episode was this branch's own early-iteration bug, fixed in the sixth round; verified interruptAndSend does not exist anywhere on main@bc344974).
  • History note: replaced the stale per-round commit hash listing with the actual final state — a linear 22-commit series over bc344974, 35 files (+5680/−382), one rewrite + semantic merges of main at r12/r13.
  • Changes: restructured into the four blocks the final diff actually contains (engine seam incl. generation-scoped/incarnation-stamped steer ids; frontend chain with the four-way fail-closed withdraw outcome; the r13-window presentation/lifecycle fixes — subscription safety, hydration, avatar rows, queue buttons, scroll snap; fork register & guards). The gitlink claim is corrected: this PR advances no submodule pointer — the gitlink inherits main's r13 (f853f8f1) unchanged.
  • Verification: refreshed to current results — CI fully green on d2ff5c33 including rust-test and required-gate; locally re-confirmed the frontend suite (521 tests / 520 pass / 0 fail on a fresh build), fork-guard --fast (102 fingerprints), architecture guard, and commit-message convention over all 22 commits.
  • Review status updated to the eight-round state; the duplicate "Known limitations" section is merged.

Author-side verification while rewriting: synced the CodeWhale submodule to r13 locally and re-ran fork-guard --fast (green, including the new r13 eval fingerprints), architecture-guard.py, and validate-commit-msg over bc344974..d2ff5c33 (all 22 pass).

@zhuowp zhuowp left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@qiuYliangM
qiuYliangM force-pushed the feat/ai-runtime-conversation-insertion branch from d2ff5c3 to 5baf381 Compare September 2, 2026 02:36
@qiuYliangM

Copy link
Copy Markdown
Collaborator Author

@JensenChen28 P2 已闭合(head 5baf381fb,base = main tip a610119d2 0.9.0 release bump,force-with-lease 推送):

  • PR body 重写:Background / History note / Changes / Verification 已全面对齐最终树——gitlink 明确为继承 main 的 r13(f853f8f1)不变、不再出现 r11 推进或 r11→r12 的描述;提交数与文件统计刷新为 25 commits / 42 files (+6293/−393);Background 只保留可验证事实(git grep interruptAndSend origin/main 为空,链路由本 PR 引入);Verification 按当前 head 的实际门禁重写。
  • 本轮新增三个提交
    1. fix(chat): align steer persistence with admission display copies——steer 消息落盘对齐:sanitize_messages 统一剥离 external-user 的尾部 <turn_meta> 展示副本(runtime 内部消息的 provenance 信封保留),重载的 steeredMidTurn 标记迁移到显式 steered-messages sidecar({pos, text} 尾对齐捕获、transcript_committed 重试、压实漂移文本校验),旧会话信封检测降级为兜底。新增 steered_messages_sidecar.test.js 4/4 + Rust 剥离语义测试。
    2. chore(cli): sync Cargo.lock version bumps to 0.9.0——rebase 到 0.9.0 后从合并清单重生成 lockfile(sync-version.mjs --check 通过)。
    3. fix(build): restore cargo run default-run swallowed by metadata table——drive-by 构建修复:default-run/autobins[package.metadata.cargo-shear] 段吞掉(main 上同样存在),dev 模式 cargo run 报 "could not determine which binary to run"。非 steer 相关已在 body 中显式标注,如 reviewer 希望拆分独立 PR 请示下。
  • 门禁(最终树,本地):cargo targeted(turn-lifecycle 含新剥离测试 / sidecar / protocol / sessions / remote-control manager)0 失败;steered sidecar 4/4、conversation timeline、scheduled_tasks_unit、scene sidecar、web-access 契约、bridge 协议+域契约(哈希按合并树重算)、eslint 全绿;sync-version --check / fork-guard --fast / architecture-guard 通过;cargo fmt 干净。上一 head d2ff5c33 CI 全绿,本 head CI 已随推送触发。

luzeyang (INT) added 3 commits September 2, 2026 10:43
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>
@qiuYliangM
qiuYliangM force-pushed the feat/ai-runtime-conversation-insertion branch from 5baf381 to aa2f027 Compare September 2, 2026 02:46
@qiuYliangM

Copy link
Copy Markdown
Collaborator Author

补充修正(head aa2f027c7):上一轮的三个新提交缺 DCO Signed-off-by 尾注,且两个主题超出 50 字符上限,导致 Signed-off commitscommit-message 两门红。已重签并缩短主题(fix(chat): persist steers as display copies / fix(build): restore cargo default-run placement),scripts/validate-commit-msg.py --range origin/main HEAD 本地通过,内容与 diff 不变。

@qiuYliangM

Copy link
Copy Markdown
Collaborator Author

Addendum — local cargo test --lib numbers on the final r13 tree (my earlier verification loop finished after the body was patched, so the body cites CI; adding the local figures here for completeness):

  • Serial full run: 1796 passed / 3 failed / 12 ignored, where the 3 (bridge::engine_config_for_session_keeps_isolation_without_prompt_variance, safety_deny_rules::sensitive_shell_reads_are_denied_across_spellings, marketplace::install_tencent_docs_raw_authorization_env_headers_on_all_servers) are environment-flaky on this Windows box — each passes in isolation (reproduced, verified one-by-one), they only interfere under the full-suite run sharing the real ~/.pinvou3 state, and none of their files are in this PR's diff except platform/bridge.rs, whose delta is the PINVOU3_HOME isolation guard itself. Consistent with the known environmental set reported on other machines and with CI rust-test being green on the same commit.
  • A first concurrent run (npm suite in parallel) also produced 8 transient platform::download failures that vanish 10/10 when run serially — local-resource contention, not code.

Net: local run agrees with CI — no regression attributable to this PR.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants