Skip to content

fix(tui): AI 输出中排队消息不再自动注入,输入框贴底(排队语义 + 渲染顺序) - #40

Closed
yq04 wants to merge 2 commits into
huiliyi37:mainfrom
yq04:fix/queue-confirm-input-bottomline
Closed

fix(tui): AI 输出中排队消息不再自动注入,输入框贴底(排队语义 + 渲染顺序)#40
yq04 wants to merge 2 commits into
huiliyi37:mainfrom
yq04:fix/queue-confirm-input-bottomline

Conversation

@yq04

@yq04 yq04 commented Aug 16, 2026

Copy link
Copy Markdown

Summary

修复 TUI 端的两个交互问题:

  1. AI 输出期间提交的消息显示「⏳ 已排队」却实际已被自动发送 —— 排队语义与界面文案矛盾
  2. 输入框的极限位置不在界面最下方 —— 输入框下方还有状态行等辅助行,滚动到底时输入框无法贴底

问题 1:排队消息在工具边界被自动注入

复现

AI 输出过程中发送普通消息 → 界面显示 ⏳ 已排队: "..." → 下一个工具回合消息以 [User guidance] 格式注入给 AI,AI 立即执行。用户以为消息在排队,实际已发出。

根因

src/tui/engine/app.tsonSteerDrain 回调无优先级过滤地 drain 整个 steer 队列:

onSteerDrain: () => this.steerBuffer.drain(),  // 修复前:所有排队消息(含普通聊天)在工具边界注入

AI 输出期间每个工具回合都会触发该回调,把普通消息格式化为 [User guidance — 用户新指令...] 注入给正在运行的 AI。代码注释自己承认「此时仍显示已排队是界面在撒谎」。

修复

对齐 kimi-cli / codex 的共识(普通消息在 AI 输出期间只入队,turn 结束后才处理):

  • onSteerDrain 改为 drain('next'):只注入紧急意图(halt=now / redirect=next,即用户主动喊停/改方向,steer 特性保留);普通消息(guidance/question/augment/ack → later 级)留在队列
  • notifyRunSettled 在 run 自然结束(isFinal)时把队列残留回填输入框(复用既有 backfillSteerToInput 机制),提示「⏮ 已把 N 条排队消息拉回输入框」,由用户再次按 Enter 确认后发送
  • 守护中断(watchdog/convergence)自动续跑路径经 guardianAbortPending 标志区分,不回填、不打断续跑流程

参考实现

  • kimi-cli(MoonshotAI/kimi-cli):streaming 期间 Enter 只入队 _queued_messages,注释明确「messages waiting to be sent after the turn ends」;turn 结束后 drain 作为新 turn 发送;Ctrl+S 才是显式立即注入
  • codex(openai/codex):队列消息在 agent turn 结束后 maybe_send_next_queued_input() 自动发送

问题 2:输入框不在界面最下方

复现

界面滚动到底时,屏幕最底行是状态行(metrics + 权限模式),输入框在其上方——输入框的极限位置不在屏幕最下方(与 Claude Code 的输入框贴底惯例不符)。

根因

renderLive 的 chrome 段中,输入框(topBorder + inputLines + botBorder)渲染在中游,其后仍渲染图片摘要、状态行(formatPermissionModeLine metrics + 权限模式)、@file 诊断、上下文提示、slash 提示、文件补全候选。

修复

调整 chrome 段渲染顺序:全部辅助行移到输入框上方,输入框成为 live region 最后一行。reservedTail / chromeStart 计算不变(行数不变,仅顺序变化),不影响定高视口逻辑。

测试

新增

  • src/tui/engine/__tests__/steer-queue-confirm.test.ts(4 条契约):
    1. 工具边界不注入普通排队消息(修复前 RED:onSteerDrain 返回 [User guidance...] 注入文本、队列被清空)
    2. 工具边界仍注入紧急意图(halt 即时 steer 保留)
    3. run 正常结束后队列残留回填输入框、steerBuffer 清空(修复前 RED)
    4. 回填后再次 Enter → 消息作为新 run 提交(修复前 RED)
  • src/tui/engine/__tests__/input-bottom-line.test.ts(2 条契约):
    1. 输入框 botBorder 为渲染帧最后一行(修复前 RED:botBorder 后仍有状态行 test ⚡- 0s ... ⏵ auto-safe
    2. 状态行(metrics/权限模式)位于输入框上方

回归

  • user-commit-paths / abort-interrupt-ux / abort-resubmit / slash-passthrough / queue-lane:38/38 通过(含「守护中断 settle 不回填」既有契约)
  • fixed-viewport / live-engine-ghost-render / live-engine / input-* / 新增测试:133/133 通过
  • TUI 引擎域全量:522 测试 520 通过(1 个预存在失败 approval-key 工作区路径判定,Windows 环境问题,在原始上游 HEAD 同样复现)
  • typecheck:app.ts 零错误(src/server/__tests__/session-search.test.ts 的 8 个 TS2322 为预存在错误,与本次改动无关)

变更文件

  • src/tui/engine/app.ts(onSteerDrain 优先级过滤 + notifyRunSettled 自然结束回填 + chrome 段顺序调整)
  • src/tui/engine/__tests__/steer-queue-confirm.test.ts(新增)
  • src/tui/engine/__tests__/input-bottom-line.test.ts(新增)

验证建议(本地)

npm install
npm run typecheck
npx tsx --test src/tui/engine/__tests__/steer-queue-confirm.test.ts src/tui/engine/__tests__/input-bottom-line.test.ts

真实终端交互:npm start 后 AI 输出中发送消息 → 应看到「⏳ 已排队」且 AI 不被打断;回合结束后消息回填输入框,按 Enter 才发送。滚动到底时输入框应贴屏幕最底部。

yq04 added 2 commits August 16, 2026 17:34
问题:AI 输出期间提交的普通消息显示「⏳ 已排队」却在下一个工具边界被
onSteerDrain 无过滤 drain 成 [User guidance] 注入给 AI——界面文案与真实
行为矛盾(界面撒谎)。

修复(对齐 kimi-cli/codex:普通消息只入队,turn 结束后交还用户):
- onSteerDrain 改为 drain('next'):仅注入紧急意图(halt=now / redirect=next,
  steer 特性保留);普通消息(later 级)留在队列
- notifyRunSettled:run 自然结束(isFinal)时队列残留回填输入框,用户再次
  Enter 才发送(复用 backfillSteerToInput);守护中断(watchdog/convergence)
  经 guardianAbortPending 标志区分,不回填(保留自动续跑流程)
- 新增 steer-queue-confirm.test.ts:4 条契约测试(不注入/紧急注入保留/
  结束回填/回填后 Enter 提交),修复前 1/3/4 RED

回归:user-commit-paths / abort-interrupt-ux / abort-resubmit /
slash-passthrough / queue-lane 全部通过(38/38),typecheck app.ts 无新增错误。
问题:renderLive chrome 段中,输入框(topBorder+inputLines+botBorder)渲染在
中游,其后仍有图片摘要/状态行(metrics+权限模式)/@file 诊断/上下文提示/
slash 提示/文件补全——滚动到底时屏幕最底行是状态行,输入框极限位置不在
界面最下方(与 Claude Code 的输入框贴底惯例不符)。

修复:调整 chrome 段渲染顺序——全部辅助行移到输入框上方,输入框成为
live region 最后一行。reservedTail/chromeStart 计算不变(行数不变仅顺序变)。

新增 input-bottom-line.test.ts:2 条契约测试(botBorder 为最后一行 / 状态行
在输入框上方),修复前 RED(实测 botBorder 后仍有状态行)。

回归:fixed-viewport / live-engine-ghost-render / live-engine / input-* /
steer-queue-confirm 全部通过(133/133),typecheck app.ts 无错误。
@huiliyi37

Copy link
Copy Markdown
Owner

已回合 dev 主线(cherry-pick 保留作者署名):b88375c06(排队消息不自动注入 + run 结束回填)+ 02cd6413a(输入框贴底)。

验证:typecheck 通过;新增 6 条契约全绿(steer-queue-confirm 4 + input-bottom-line 2);TUI 域 2213 测试 2212 过 / 0 失败(1 为既有 todo 占位)。

内容将随下次 sync 进入公开仓 main。感谢贡献——根因分析(kimi-cli/codex 对照)与 RED→GREEN 测试基线都很扎实。

@huiliyi37 huiliyi37 closed this Aug 16, 2026
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.

2 participants