fix(gateway): use cooked-mode readline for approval prompts on Windows#77
Open
knqiufan wants to merge 2 commits into
Open
fix(gateway): use cooked-mode readline for approval prompts on Windows#77knqiufan wants to merge 2 commits into
knqiufan wants to merge 2 commits into
Conversation
Collaborator
|
根因分析很扎实—— 合入前有两个必须处理的点:
一个留档的点:cooked mode 是全平台生效的,macOS/Linux 的审批 prompt 同样失去行内编辑——y/n 场景没问题;将来若有多字符输入的 prompt 复用这个工厂,需要重新评估。两点改完 @ 我们,这个和 #79 我们都想尽快合。 |
Windows console + `terminal: true` readline leaks keypress listeners across prompts (`rl.close()` does not reliably remove the listener on Windows). At the Nth prompt stdin has N accumulated listeners, which (a) synthesize spurious empty-line submits from focus/IME/mode-switch events and (b) echo a single keystroke N times via `_ttyWrite`. Result: prompts auto-advance after ~30s idle, and a single `y` becomes `yy`/`yyy`, which fails the decision parser and deadlocks the session. Switch both `promptForToolApproval` and `promptForUserClarification` to a factory that creates the readline interface with `terminal: false` (cooked mode). The OS handles line editing and delivers complete lines only on Enter — no raw mode, no keypress listener, no spurious events. Trade-off: no in-line editing (backspace, arrows) inside the prompt; acceptable for y/n approval where the user types one char + Enter. `disposeInteractiveReadline` also runs `input.pause()`, `setRawMode(false)`, and removes any stray `keypress` listeners as defensive cleanup for Windows hosts where residue was observed even with `terminal: false`. Closes stepfun-ai#76 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
knqiufan
force-pushed
the
fix/76-windows-approval-readline-leak
branch
from
July 17, 2026 18:26
b2fa820 to
7beb47d
Compare
Contributor
Author
|
@ZouR-Ma 感谢细致审阅,两个必须项都已处理:
已本地运行 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
关联 Issue
Closes #76
变更类型
影响范围
packages/protocol/(跨边界协议,需要同步 SDK)packages/core/的公开 APIsrc/gateway/(session authority 行为变更)AGENTS.md与.dependency-cruiser.cjs)变更说明(why,而非 what)
Windows console 下,
terminal: true创建的 readline 接口在rl.close()时无法可靠移除 stdin 上的keypresslistener。每次promptForToolApproval/promptForUserClarification进出都给 stdin 累加一个泄漏的 listener。到第 N 个 prompt 时,累积的 N 个 listener 既会从 Windows focus / IME / 终端模式切换事件合成出空行提交(rl.question()提前 resolve 成"",被解释为 deny,agent-loop 顺序推进到下一个 tool call —— 即「30 秒静默后 prompt 自动跳过」),又会把单次y通过每个 listener 的_ttyWrite各 echo 一次(终端显示yyy)。yy/yyy无法匹配决策解析,会话死锁。详见 #76 的根因分析。
关键决策:把 readline 接口从
terminal: true切到terminal: false(cooked mode)。OS 接管行编辑,只在用户按 Enter 时交付完整一行 —— 没有 raw mode、没有 keypress listener、没有伪空行事件。两个症状从源头一起消除。接受的折衷:prompt 内失去行内编辑(退格 / 方向键 / 历史)。对 y/n 审批场景可接受,用户只需输入一个字符 + Enter。
实现结构:抽出
src/gateway/interactive-readline.ts作为工厂(per-call 创建 + dispose),两个 prompt 函数在try/finally里使用。工厂在创建时快照已有的keypresslistener;dispose 只清理此后新增的差集,不再调用input.pause()或setRawMode(false),因此不会影响主 REPL 的长驻 readline。测试计划
src/gateway/interactive-readline.test.ts共 7 个用例,覆盖每次调用返回独立 interface、terminal: false不增长 keypress listener、dispose 安全可重入、循环 create/dispose 不累积 listener、debug 日志开启时不抛错,以及 dispose 不影响既存 listener。pnpm vitest run src/gateway/interactive-readline.test.ts全绿。pnpm step "list all .md files in the current directory"—— 修复前 ~30s 静默后会自动刷出下一条 prompt,且按一次y显示yyy;修复后只出现一条 prompt,静默 30s+ 不自动跳过,按一次y+ Enter 单字符显示并正常推进到下一个 tool call。pnpm step(无参数,REPL 模式)—— 主输入框与 approval 流程互不干扰。vitest.config.ts的coverage.include已加入src/gateway/interactive-readline.ts。自检清单
pnpm check已通过(test / lint / dep-guard / deadcode / tsc / format).test.tsvi.skipIf/describe.runIf,未硬编码 platform 判断 —— 本改动不引入新的平台分支,行为对所有平台一致(只是 Windows 上更需要)vitest.config.ts的coverage.include--no-verify绕过钩子