@@ -7,6 +7,24 @@ export function normalizePositiveLimit(value: number | undefined, fallback: numb
|
7 | 7 | |
8 | 8 | export const SESSION_TOOL_STDERR_TAIL_BYTES = 64 * 1024; |
9 | 9 | |
| 10 | +function decodeUtf8TextTail(buffer: Buffer, maxBytes: number): string { |
| 11 | +const chars = Array.from(buffer.toString("utf8")); |
| 12 | +const kept: string[] = []; |
| 13 | +let bytes = 0; |
| 14 | + |
| 15 | +for (let i = chars.length - 1; i >= 0; i--) { |
| 16 | +const char = chars[i] ?? ""; |
| 17 | +const charBytes = Buffer.byteLength(char, "utf8"); |
| 18 | +if (bytes + charBytes > maxBytes) { |
| 19 | +break; |
| 20 | +} |
| 21 | +kept.push(char); |
| 22 | +bytes += charBytes; |
| 23 | +} |
| 24 | + |
| 25 | +return kept.toReversed().join(""); |
| 26 | +} |
| 27 | + |
10 | 28 | export function appendBoundedTextTail( |
11 | 29 | current: string, |
12 | 30 | chunk: Buffer | string, |
@@ -15,7 +33,7 @@ export function appendBoundedTextTail(
|
15 | 33 | const effectiveMaxBytes = normalizePositiveLimit(maxBytes, SESSION_TOOL_STDERR_TAIL_BYTES); |
16 | 34 | const chunkBuffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk); |
17 | 35 | if (chunkBuffer.byteLength >= effectiveMaxBytes) { |
18 | | -return chunkBuffer.subarray(chunkBuffer.byteLength - effectiveMaxBytes).toString("utf8"); |
| 36 | +return decodeUtf8TextTail(chunkBuffer, effectiveMaxBytes); |
19 | 37 | } |
20 | 38 | |
21 | 39 | const currentBuffer = Buffer.from(current); |
@@ -26,5 +44,5 @@ export function appendBoundedTextTail(
|
26 | 44 | |
27 | 45 | const currentTailBytes = Math.max(0, effectiveMaxBytes - chunkBuffer.byteLength); |
28 | 46 | const currentTail = currentBuffer.subarray(currentBuffer.byteLength - currentTailBytes); |
29 | | -return Buffer.concat([currentTail, chunkBuffer], effectiveMaxBytes).toString("utf8"); |
| 47 | +return decodeUtf8TextTail(Buffer.concat([currentTail, chunkBuffer]), effectiveMaxBytes); |
30 | 48 | } |