Skip to content

fix(kimi): 修复多行输入未提交 - #788

Merged
deepcoldy merged 1 commit into
deepcoldy:masterfrom
acllm:fix/kimi-multiline-input
Aug 10, 2026
Merged

fix(kimi): 修复多行输入未提交#788
deepcoldy merged 1 commit into
deepcoldy:masterfrom
acllm:fix/kimi-multiline-input

Conversation

@acllm

@acllm acllm commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

问题

Kimi Code TUI 收到 Botmux 首条消息时,旧适配器通过普通 sendText 写入完整 routing prompt。该 prompt 包含多行 XML、技能上下文和用户消息;在生产规模下,Kimi 会把这段快速输入视为仍未结束的 paste burst,随后发送的 Enter 被吸收为换行,内容留在输入框中。冷启动时还存在一个更窄的窗口:TUI 第一次显示 composer 后立即写入,首条消息仍可能被初始化过程丢弃。用户侧表现为消息发出后没有回应、仍显示 No session yet

修复

  • tmux / Zellij / ZMX 等带 pasteText 能力的后端统一走 backend bracketed-paste 传输,再单独发送 Enter。
    • tmux 实际使用 load-buffer stdin + paste-buffer -p,避免把完整 prompt 放进 send-keys argv,同时支持大输入。
    • Zellij / ZMX 继续复用其已有 bracketed-paste 实现。
  • raw PTY 保留显式 \x1b[200~...\x1b[201~ 回退。
  • 每个 backend 实例的首次 writeInput 在 paste 前等待 250ms,让冷启动 composer 完成初始化;同一 backend 的后续输入不增加该等待。
  • 保持旧 adapter 的 assume-issued 返回语义:false 或副作用后抛错都不转换为 clean non-submit,避免 queued activation 自动重放造成正文重复或同一 turn 执行两次。
  • 补充 64 KiB 大输入、首次写入和歧义传输场景测试。

影响面

  • 仅修改 src/adapters/cli/kimi.ts 与对应输入测试。
  • 未修改公共 worker、公共 backend、其它 CLI adapter、IM 路由或会话恢复逻辑。
  • Kimi 启动参数、模型选择、native session 恢复保持不变。
  • 首写 settle 按 backend 对象身份记录:fresh spawn / backend replacement 各自执行一次,warm follow-up 不重复等待。

验证

  • pnpm build
    • TypeScript、Dashboard bundle、build audit 全部通过。
  • pnpm vitest run --project unit test/write-input.test.ts test/tmux-backend-input.test.ts test/tmux-pipe-backend.test.ts
    • 3 个文件、185 条测试全部通过。
  • blocking review 回归:
    • paste 产生副作用后返回 false:只粘贴一次,返回 undefined
    • Enter 产生副作用后返回 false:只发送一次 Enter,返回 undefined
    • paste 产生副作用后抛异常:不追加正文、不返回 clean non-submit;
    • raw PTY write() 返回 false:仍保持 assume-issued,不触发自动重放;
    • 64 KiB prompt 必须走 pasteTextsendText 调用为 0;
    • 同一 backend 首次写入有 settle,第二次写入不重复 settle。
  • Kimi Code CLI 0.34.0 + tmux 2.8 真实 TUI:
    • 14,439 Bytes 脱敏 production-shaped prompt:修复前无响应,修复后模型响应、sendTextCalls=0
    • 约 30 KiB prompt:通过 paste-buffer 正常响应;
    • 冷启动时序矩阵(4,390+ Bytes 脱敏首条 prompt):ready 后外部等待 250/1000/2000ms 三组均建立 native session 并返回 sentinel;
    • 最终代码验证:外部等待 0ms,仅依赖 adapter 内 250ms 首写 settle,首条即建立 native session 并返回 sentinel。
  • pnpm test
    • 13,974 条通过,10 条跳过,4 条失败;
    • 2 条 Codex App runner timeout 单独重跑均通过;
    • 另外 2 条为本机 tmux-backend-env shell wrapper 与 adopted Pi tmux fixture,已在最新 origin/master 独立 worktree、同机同命令稳定复现,与本 PR 两个改动文件无关。

实证截图

修复前:首轮多行 prompt 留在输入框,仍为 No session yet

Kimi pre-fix prompt stuck

修复后:同一脱敏 prompt 一次提交,模型返回结果

Kimi patched prompt submitted

Copilot AI lite review requested due to automatic review settings August 8, 2026 02:25
@acllm
acllm requested a review from deepcoldy as a code owner August 8, 2026 02:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

该 PR 修复 Kimi Code TUI 在收到 Botmux 首条包含多行内容的 routing prompt 时,因换行走按键事件路径而导致“未作为一条消息提交”的问题;通过统一输入提交流程,确保多行内容以一次性提交的方式进入 Kimi,从而避免用户侧“消息发出后无回应 / 仍显示 No session yet”的现象。

Changes:

  • Kimi 适配器的输入写入统一改为显式 bracketed paste(\x1b[200~...\x1b[201~)后再单独发送 Enter,并在文本写入/Enter 明确失败或异常时返回 { submitted: false }
  • 补充 Kimi 在单行、多行、Unicode、以及发送失败(Enter 被拒绝 / 传输抛异常)场景下的单元测试覆盖。

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
test/write-input.test.ts 将 Kimi 纳入 writeInput 行为矩阵,并新增对 bracketed paste + Enter 及失败返回的测试用例。
src/adapters/cli/kimi.ts Kimi writeInput 改为显式 bracketed paste + Enter,增加失败/异常路径的 { submitted: false } 返回。

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

感谢修复。括号粘贴 + 独立 Enter 的主修复方向是正确的,与 coco / claude / codex / oh-my-pi 处理多行 TUI 输入的既有做法一致。但新加的「提交结果判定」语义有一个 blocking 问题,合并前需要修正。

🔴 Blocking:把「歧义的传输失败」当成「干净的未提交」,会导致重复提交

背景:当前 master(合入 #597 后)的 PtyHandle 契约已明确——sendText / sendSpecialKeys 返回 false 只表示无法确认,字节仍可能已经落地;它不是「零字节送达」的证据。

本 PR 在 writeInput 里把两个 === false 分支和外层 catch 都归类成 { submitted: false }。对首条消息而言(#597 会给它带 queuedActivationToken),worker 收到 submitted:false 后会调用 requeueUnsubmittedQueuedActivation,把同一条消息重新入队,下一次 flush 再跑一遍 writeInput。于是有三条真实的重复路径:

  1. sendText 已经把 bracketed paste 写进输入框、但因确认超时返回 false → 本轮不发 Enter;重试再粘一次 → 输入框里出现两份正文,一起提交。
  2. Enter 已经落地、但返回 false → 第一轮其实已经开始执行;重试 → 同一个用户 turn 执行两次
  3. 写调用先产生副作用再抛异常(例如 sendText 已写入后抛确认超时)→ 外层 catch 同样归成 {submitted:false} → 同样触发上面的双发。

这是本 PR 新引入的回归:改动前的 adapter 全程返回 void(assume-issued 语义),从不返回 submitted:false,因此从不触发 requeue、也就不会重复提交。

对照 oh-my-pi:它虽然也返回 {submitted:false},但额外有 composerDirty 状态 + 失败后用 Ctrl+C 清输入框、并在未清理干净前阻止继续追加新正文的恢复层。本 PR 没有这一层,所以这里的 {submitted:false} 重试并不安全。

🟡 次要:raw PTY 分支的 write() 返回值未检查(两分支语义不对称)

else 分支里两次 pty.write(...) 都没有检查返回值;#597 之后 write() 在进程已退出时也会返回 false,此时 adapter 仍然返回 {submitted:true}——等于零字节送达却报成功。这一点与其它 raw adapter(pi / gemini / hermes)现状一致、属既有共性问题,本 PR 不强制在此处修;但既然 tmux 分支加了失败检测,建议 raw 分支也一并检查,避免两条分支语义分叉。

建议修法(按推荐顺序)

  1. 最小收敛(推荐):保留括号粘贴 + 独立 Enter,撤回新增的两个 === false 判定和外层 catch → {submitted:false},整体恢复成旧 adapter 的 assume-issued(全程 void)语义。注意:只改其一不够——只回退 === false、保留 catch,side-effect-then-throw 仍会双发(两者是彼此独立的触发向量)。
  2. 若要保留传输失败检测:先确认 Kimi 的 Ctrl+C 清输入框 / 运行中取消语义,再做 oh-my-pi 式的 dirty-composer 状态机。
  3. 通用正确解:在 worker / backend 层引入显式的 ambiguous disposition(禁止把歧义结果当 clean non-submit 自动重试)——但这是公共层改造,会横向影响所有 CLI / 后端,不建议塞进这个两文件 PR,应另开 PR 并做横向测试。

验收用例(重要)

只补「sendText 返回 false → 期望 {submitted:false}」的测试不足以验收,反而会把上面的错误语义固化。请至少覆盖以下断言「不会重复提交」的用例:

  • sendText 产生副作用后返回 false → 不得把同一正文粘贴两次;
  • Enter 产生副作用后返回 false → 不得重复执行同一 turn;
  • sendText 产生副作用后抛异常 → 同上(覆盖 catch 路径);
  • raw PTY write() 返回 false → 不得返回 submitted:true

其它

  • 建议先 rebase 到最新 master 再改:当前分支落后 master 3 个 commit,其中 #597 正是引入「首条消息带 queuedActivationToken → 失败即 requeue」的那次改动,也是上面 blocking 问题成立的前提。

@acllm
acllm force-pushed the fix/kimi-multiline-input branch 2 times, most recently from 15e01ca to e02349c Compare August 8, 2026 18:01
acllm added a commit to acllm/botmux that referenced this pull request Aug 8, 2026
为 PR deepcoldy#788 保存完全脱敏的最终修复前后真实 Kimi TUI 对照截图,不进入产品代码分支。

Co-authored-by: TRAE CLI <noreply@bytedance.com>
@acllm

acllm commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@deepcoldy @alexander2618 已按 blocking review 完成整改并更新到 e02349cf,请重新 review。

Blocking 逐项关闭

  1. 撤回 clean non-submit 判定

    • Kimi adapter 不再把 transport falsecatch 转成 { submitted: false }
    • 成功、false、副作用后抛错都保持 assume-issued(undefined),因此 worker 不会调用 queued activation 的 clean-non-submit 重入队路径。
  2. 改用 backend pasteText

    • tmux 走 load-buffer stdin + paste-buffer -p,避免大 prompt 经过 send-keys argv;
    • raw PTY 仍显式 bracketed paste;
    • 未改公共 backend 或其它 CLI。
  3. review 指定的四类验收均已覆盖

    • paste 产生副作用后返回 false:正文只写一次;
    • Enter 产生副作用后返回 false:Enter 只发一次;
    • paste 产生副作用后抛异常:不追加正文;
    • raw PTY write 返回 false:不返回 clean non-submit;
    • 四类结果均为 undefined,不会触发自动重放。
  4. 已 rebase 最新 master

实测结果

  • pnpm build:通过;
  • Kimi/后端聚焦测试:184/184 通过;
  • 真实 Kimi 0.34.0 + tmux 2.8,14,439 Bytes 完全脱敏首轮 prompt:
    • 修复前 oldResponded=false
    • 修复后 newResponded=truesendTextCalls=0
  • 约 30 KiB prompt 通过 paste-buffer 正常响应;64 KiB 输入由单测锁定必须走 pasteText

修复前:

Kimi pre-fix prompt stuck

修复后:

Kimi patched prompt submitted

全量单测为 13,973 passed / 10 skipped / 4 failed:2 条 Codex runner timeout 单跑通过;剩余 2 条已在最新 origin/master 独立 worktree 同机复现,分别是 shell wrapper 与 adopted Pi tmux fixture,不涉及本 PR 的 Kimi adapter/test 文件。

将 Kimi 的 tmux 输入改为 paste-buffer bracketed paste,避免多行 routing prompt 被 TUI 吞掉提交,并规避大 prompt 经 send-keys argv 传输的长度上限。每个 backend 首次写入前额外等待 250ms,让冷启动 composer 完成初始化;后续输入不增加延迟。保持 assume-issued 返回语义:传输返回 false 或副作用后抛错均不标记 clean non-submit,防止 queued activation 自动重放导致重复执行。保留 raw PTY 回退,并补齐 64KB 大输入、首次写入与歧义传输回归测试。

Co-authored-by: TRAE CLI <noreply@bytedance.com>
@acllm
acllm force-pushed the fix/kimi-multiline-input branch from e02349c to 6d7693e Compare August 8, 2026 18:15
@acllm

acllm commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@deepcoldy @alexander2618 补充一次 live daemon 验收后的闭环,head 已更新为 6d7693e3

  • 部署 e02349cf 后,真实飞书/Kimi 会话出现“冷启动首条未建立 native session、同一会话第二条成功”的边界;日志显示首个 Prompt detected 后仅 16ms 就开始写入。
  • 我没有把第二条成功误报为完成,而是继续做真实冷启动延迟矩阵:4,390+ Bytes 脱敏首条 prompt,ready 后额外等待 250/1000/2000ms 三组全部建立 native session 并返回 sentinel。
  • 最终修复按仓库已有 Claude/Cursor 首写 warmup 模式,用 WeakSet<PtyHandle> 为每个 backend 的首次 Kimi writeInput 增加 250ms settle;同一 backend 后续输入不增加延迟。
  • 最终代码再次以“外部等待 0ms”跑真实冷启动:仅依赖 adapter 内 250ms settle,首条即建立 native session并返回 sentinel。

验证更新:

  • build 通过;
  • Kimi/后端聚焦测试 185/185 通过;
  • 全量 13,974 passed / 10 skipped / 4 failed;2 条 Codex runner timeout 单跑通过,另 2 条已在最新 master 独立 worktree复现。

blocking review 的 exactly-once 收敛保持不变:仍返回 assume-issued undefined,不会把歧义 transport 结果转换成 clean non-submit 自动重放。

@acllm

acllm commented Aug 8, 2026

Copy link
Copy Markdown
Contributor Author

@deepcoldy @alexander2618 最终 live daemon + 飞书真实首条验收已通过:

  • 已部署 head 6d7693e3 到当前 checkout daemon;
  • 关闭前一测试会话后,由飞书创建全新 Kimi session 5bb7d3c3
  • 第一条即为多行 production routing prompt(不是 follow-up);
  • Kimi 建立 native session,并通过 botmux send 回传唯一 sentinel:KIMI_FIRST_TURN_PASS_1786213078
  • 飞书回执消息:om_x100b684360f0e8a0b3449e23fff2e25
  • 日志中首次 ready 后进入最终 adapter 首写 settle,首条直接提交并执行,无第二条触发。

至此 bracketed paste、exactly-once assume-issued、冷启动首写时序三项均完成真实端到端闭环,请重新 review / 合并。

@acllm
acllm requested a review from deepcoldy August 9, 2026 04:44
@deepcoldy

Copy link
Copy Markdown
Owner

同学可以帮填下这个 https://bytedance.larkoffice.com/wiki/WJ1nwWbtxi89erkNGNbcgkt9nUe
后面会自动拉pr review群

@deepcoldy deepcoldy left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

复审通过:作者 force-push 6d7693e38 已按最小方案①修好 blocking——writeInput 全程恢复 assume-issued(返 undefined,无 {submitted:false}/{submitted:true}),worker 不再把歧义传输结果当 clean non-submit 自动重放,double-send 回归封闭。

已在最新 master(47ead6e)合并树上独立复核:build 通过;write-input/cli-adapters 聚焦测试全绿;变异验证(将 catch{return;} 回退为 {submitted:false} 时对应测试立即变红)确认新测试能拦住原双发向量。

herdr 后端 pasteText 为无 marker 的 literal write,该后端多行仍可能被拆——但与旧版本行为一致、非本 PR 回归,production 默认 tmux 主路径已修复并有 E2E 闭环;建议后续把描述里的后端表述收窄为明确列举 tmux/Zellij/ZMX 并注明 herdr 限制(纯文案,不阻断)。

双人复核一致 APPROVE。

@deepcoldy
deepcoldy merged commit 059f80a into deepcoldy:master Aug 10, 2026
@github-actions

Copy link
Copy Markdown

🚀 Released in v3.12.1

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.

3 participants