Skip to content

Commit 6a7c698

Browse files
committed
fix: CJK backspace, remove ANSI-clear rerender that caused hang
1 parent 2084f56 commit 6a7c698

2 files changed

Lines changed: 34 additions & 12 deletions

File tree

src/cli/input.ts

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,8 +142,14 @@ export class CliInput extends EventEmitter {
142142
// Backspace
143143
if (chunk === "\x7f" || chunk === "\b") {
144144
if (this.buffer.length > 0) {
145+
const removed = this.buffer.slice(-1);
145146
this.buffer = this.buffer.slice(0, -1);
146-
process.stdout.write("\b \b");
147+
// CJK and other wide characters take 2 terminal columns
148+
if (this.isWideChar(removed)) {
149+
process.stdout.write("\b \b\b \b");
150+
} else {
151+
process.stdout.write("\b \b");
152+
}
147153
}
148154
return;
149155
}
@@ -259,4 +265,24 @@ export class CliInput extends EventEmitter {
259265
showInputPrompt(): void {
260266
this.showPrompt();
261267
}
268+
269+
/** Check if a character is full-width (CJK, emoji, etc.) */
270+
private isWideChar(ch: string): boolean {
271+
const code = ch.codePointAt(0) ?? 0;
272+
return (
273+
(code >= 0x1100 && code <= 0x115f) || // Hangul Jamo
274+
(code >= 0x2e80 && code <= 0x303e) || // CJK Radicals
275+
(code >= 0x3040 && code <= 0x33bf) || // Japanese
276+
(code >= 0x3400 && code <= 0x4dbf) || // CJK Unified Extension A
277+
(code >= 0x4e00 && code <= 0x9fff) || // CJK Unified
278+
(code >= 0xa960 && code <= 0xa97c) || // Hangul Jamo Extended-A
279+
(code >= 0xac00 && code <= 0xd7a3) || // Hangul Syllables
280+
(code >= 0xf900 && code <= 0xfaff) || // CJK Compatibility
281+
(code >= 0xfe30 && code <= 0xfe6b) || // CJK Compatibility Forms
282+
(code >= 0xff01 && code <= 0xff60) || // Fullwidth Forms
283+
(code >= 0xffe0 && code <= 0xffe6) || // Fullwidth Signs
284+
(code >= 0x1f000 && code <= 0x1fbff) || // Emoji & Symbols
285+
(code >= 0x20000 && code <= 0x2ffff) // CJK Extension B+
286+
);
287+
}
262288
}

src/index.ts

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -123,27 +123,23 @@ async function main() {
123123
workspaceDir,
124124
callbacks: {
125125
onAssistantText: (text) => {
126-
// Called when streaming is complete — render markdown
127126
spinner.stop();
128127
if (text.trim() === "HEARTBEAT_OK") return;
129128

130129
if (streamingStarted) {
131-
// We already streamed raw text, now re-render with markdown
132-
// Clear the streamed output and replace with rendered version
133-
process.stdout.write("\r\x1b[J"); // clear from cursor
134-
process.stdout.write("\n");
130+
// Text was already streamed to terminal, just finish with newline
131+
process.stdout.write("\n\n");
132+
} else {
133+
// Non-streamed response (e.g. short tool-only reply): render with markdown
134+
const rendered = renderMarkdown(text);
135+
process.stdout.write(chalk.green("🦐 ") + rendered + "\n");
135136
}
136-
137-
// Render markdown for pretty display
138-
const rendered = renderMarkdown(text);
139-
process.stdout.write(chalk.green("🦐 ") + rendered + "\n");
140137
streamingStarted = false;
141138
},
142139
onTextChunk: (chunk) => {
143-
// Streaming: show raw text as it arrives
144140
spinner.stop();
145141
if (!streamingStarted) {
146-
process.stdout.write(chalk.green("🦐 "));
142+
process.stdout.write(chalk.green("\n🦐 "));
147143
streamingStarted = true;
148144
}
149145
process.stdout.write(chunk);

0 commit comments

Comments
 (0)