From afc341fb845e69914acbe69190a52164912e85f6 Mon Sep 17 00:00:00 2001 From: cheho Date: Tue, 22 Sep 2026 17:58:23 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=90=9B=20fix(vscode):=20=E5=AE=A1?= =?UTF-8?q?=E6=9F=A5=E5=8F=8D=E9=A6=88=E6=89=93=E8=BF=9B=E4=BC=9A=E8=AF=9D?= =?UTF-8?q?=E5=90=8E=E8=87=AA=E5=8A=A8=E5=8F=91=E9=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/panel/handlers/sessionSubmit.test.ts | 17 +++++++++++++++++ vscode/src/panel/handlers/sessionSubmit.ts | 18 ++++++++++++++++++ vscode/src/panel/handlers/sessions.ts | 4 ++-- vscode/src/panel/handlers/terminals.ts | 11 ++++------- 4 files changed, 41 insertions(+), 9 deletions(-) create mode 100644 vscode/src/panel/handlers/sessionSubmit.test.ts create mode 100644 vscode/src/panel/handlers/sessionSubmit.ts diff --git a/vscode/src/panel/handlers/sessionSubmit.test.ts b/vscode/src/panel/handlers/sessionSubmit.test.ts new file mode 100644 index 0000000..1bde9a8 --- /dev/null +++ b/vscode/src/panel/handlers/sessionSubmit.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'vitest' +import { buildSessionSubmitPayload } from './sessionSubmit' + +describe('buildSessionSubmitPayload', () => { + it('换行留在粘贴括号里,发送用的回车在括号外面', () => { + const payload = buildSessionSubmitPayload('第一行\n第二行') + expect(payload.startsWith('\u001b[200~')).toBe(true) + expect(payload.endsWith('\u001b[201~\r')).toBe(true) + expect(payload).toContain('第一行\n第二行') + }) + + it('正文里的转义字符会被去掉,避免提前结束粘贴', () => { + expect(buildSessionSubmitPayload('看到\u001b[201~就截断')).toBe( + '\u001b[200~看到[201~就截断\u001b[201~\r', + ) + }) +}) diff --git a/vscode/src/panel/handlers/sessionSubmit.ts b/vscode/src/panel/handlers/sessionSubmit.ts new file mode 100644 index 0000000..94bb3e3 --- /dev/null +++ b/vscode/src/panel/handlers/sessionSubmit.ts @@ -0,0 +1,18 @@ +/** + * 把要打进会话输入框的正文收成一次「粘贴 + 回车」。 + * + * 编辑器往终端写字时,会把所有换行改成回车字符。会话收到一大段字时, + * 不会把这些回车当成「发送」,而是整段放进输入框。框里只要还留着这种回车, + * 第一次按回车只是把它们清掉,消息不会发出去,人得再按一次。 + * + * 粘贴括号里的回车会被还原成换行。括号结束之后紧跟的那个回车才是发送, + * 而且必须和正文在同一次写入里:分开写的话,长正文还没写完,回车可能插进正文中间。 + * 正文里的转义字符要去掉,否则会把粘贴提前截断。 + */ +const PASTE_START = '\u001b[200~' +const PASTE_END = '\u001b[201~' + +export function buildSessionSubmitPayload(text: string): string { + const body = text.replaceAll('\u001b', '') + return `${PASTE_START}${body}${PASTE_END}\r` +} diff --git a/vscode/src/panel/handlers/sessions.ts b/vscode/src/panel/handlers/sessions.ts index 3d26ba0..2a298f7 100644 --- a/vscode/src/panel/handlers/sessions.ts +++ b/vscode/src/panel/handlers/sessions.ts @@ -516,8 +516,8 @@ async function triggerAutoReviewTabUnlocked(panel: KanbanWebviewPanel, opts: { model: settings.codexModel, reasoningEffort: settings.codexReasoningEffort, })) - // codex resume rebuilds the conversation from the jsonl rollout - // before the TUI accepts input; 8s covers a cold codex start. + // codex resume 要先从记录里把对话拼回来,拼完才吃得进字。 + // 已经开着的审查终端不用等;冷启动留 8 秒,够它起来又不至于拖很久。 setTimeout(() => { submitToSessionTerminal(terminal, 'PR 更新了,再审一下') }, 8000) diff --git a/vscode/src/panel/handlers/terminals.ts b/vscode/src/panel/handlers/terminals.ts index 5076faa..8860c55 100644 --- a/vscode/src/panel/handlers/terminals.ts +++ b/vscode/src/panel/handlers/terminals.ts @@ -8,6 +8,7 @@ import { TabInputTerminal, window } from 'vscode' import { logger } from '../../logging/logger' import { getSettings } from '../../settings/store' import { existingTerminalGroupColumn, terminalEditorColumn } from '../terminalColumn' +import { buildSessionSubmitPayload } from './sessionSubmit' /** 一个工单能开的会话终端种类;每种对应详情面板里的一行会话 id。 */ export type SessionTabKind = 'brainstorm' | 'implement' | 'review' | 'test' | 'prototype' @@ -76,15 +77,11 @@ export function injectIntoImplTerminal(panel: KanbanWebviewPanel, issueNumber: n } /** - * 往正在跑 cc / codex TUI 的终端里提交一条消息。 - * - * sendText 把文本当成逐字敲键盘,TUI 靠输入速度猜是不是粘贴,猜成粘贴后 - * 会把紧跟的回车也吞成换行,消息只停在输入框。用括号粘贴标记 - * (\x1B[200~ … \x1B[201~) 明确告诉 TUI 粘贴从哪到哪,之后的 \r 才被当成提交键。 + * 往正在跑 cc / codex 的终端里提交一条消息。 + * 正文和回车的拼法见 {@link buildSessionSubmitPayload}。 */ export function submitToSessionTerminal(terminal: Terminal, text: string): void { - terminal.sendText(`\x1B[200~${text}\x1B[201~`, false) - terminal.sendText('\r', false) + terminal.sendText(buildSessionSubmitPayload(text), false) } /**