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) } /**