@code-yeongyu/senpi-pty 2026.10.11 → 2026.10.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/screen-text.d.ts
CHANGED
|
@@ -4,5 +4,16 @@ export declare function normalizeDimension(value: number | undefined, fallback:
|
|
|
4
4
|
export declare function normalizeScrollback(value: number | undefined): number;
|
|
5
5
|
export declare function normalizeReplayHistoryLength(cols: number, rows: number, scrollback: number): number;
|
|
6
6
|
export declare function decodeInput(value: string | Uint8Array): string;
|
|
7
|
+
/**
|
|
8
|
+
* The last `maxLength` chars of `value`, sanitized, as a copy that does not keep `value` alive: a plain
|
|
9
|
+
* slice of an oversized chunk would pin the whole chunk in replay history (senpi#3170). The UTF-16
|
|
10
|
+
* round trip is lossless, lone surrogates included, and runs only when a chunk outgrows the cap.
|
|
11
|
+
*/
|
|
12
|
+
export declare function sanitizedTail(value: string, maxLength: number): string;
|
|
13
|
+
/**
|
|
14
|
+
* Replaces lone surrogates with U+FFFD. Valid text comes back as the same string, and repaired text is
|
|
15
|
+
* joined from slices: the screen keeps these strings as replay history, and a string grown one character
|
|
16
|
+
* at a time stays a rope of per-character nodes (senpi#3170).
|
|
17
|
+
*/
|
|
7
18
|
export declare function sanitizeString(value: string): string;
|
|
8
19
|
//# sourceMappingURL=screen-text.d.ts.map
|
package/dist/screen-text.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
1
2
|
const DEFAULT_SCROLLBACK = 1000;
|
|
2
3
|
const MIN_SIZE = 1;
|
|
3
4
|
const MAX_SIZE = 10000;
|
|
@@ -25,27 +26,40 @@ export function decodeInput(value) {
|
|
|
25
26
|
return value;
|
|
26
27
|
return new TextDecoder("utf-8", { fatal: false }).decode(value);
|
|
27
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* The last `maxLength` chars of `value`, sanitized, as a copy that does not keep `value` alive: a plain
|
|
31
|
+
* slice of an oversized chunk would pin the whole chunk in replay history (senpi#3170). The UTF-16
|
|
32
|
+
* round trip is lossless, lone surrogates included, and runs only when a chunk outgrows the cap.
|
|
33
|
+
*/
|
|
34
|
+
export function sanitizedTail(value, maxLength) {
|
|
35
|
+
const tail = Buffer.from(value.slice(-maxLength), "utf16le").toString("utf16le");
|
|
36
|
+
return sanitizeString(tail);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Replaces lone surrogates with U+FFFD. Valid text comes back as the same string, and repaired text is
|
|
40
|
+
* joined from slices: the screen keeps these strings as replay history, and a string grown one character
|
|
41
|
+
* at a time stays a rope of per-character nodes (senpi#3170).
|
|
42
|
+
*/
|
|
28
43
|
export function sanitizeString(value) {
|
|
29
|
-
|
|
44
|
+
const parts = [];
|
|
45
|
+
let copiedUpTo = 0;
|
|
30
46
|
for (let index = 0; index < value.length; index += 1) {
|
|
31
47
|
const code = value.charCodeAt(index);
|
|
32
|
-
if (code
|
|
48
|
+
if (code < 0xd800 || code > 0xdfff)
|
|
49
|
+
continue;
|
|
50
|
+
if (code <= 0xdbff) {
|
|
33
51
|
const next = value.charCodeAt(index + 1);
|
|
34
52
|
if (next >= 0xdc00 && next <= 0xdfff) {
|
|
35
|
-
output += value[index] + value[index + 1];
|
|
36
53
|
index += 1;
|
|
54
|
+
continue;
|
|
37
55
|
}
|
|
38
|
-
else {
|
|
39
|
-
output += "\uFFFD";
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
else if (code >= 0xdc00 && code <= 0xdfff) {
|
|
43
|
-
output += "\uFFFD";
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
output += value[index];
|
|
47
56
|
}
|
|
57
|
+
parts.push(value.slice(copiedUpTo, index), "\uFFFD");
|
|
58
|
+
copiedUpTo = index + 1;
|
|
48
59
|
}
|
|
49
|
-
|
|
60
|
+
if (parts.length === 0)
|
|
61
|
+
return value;
|
|
62
|
+
parts.push(value.slice(copiedUpTo));
|
|
63
|
+
return parts.join("");
|
|
50
64
|
}
|
|
51
65
|
//# sourceMappingURL=screen-text.js.map
|
package/dist/screen.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import xterm from "@xterm/headless";
|
|
2
2
|
import { MAX_PENDING_WRITE_CHARS, settleOperation, sharedSettler, } from "./screen-operations.js";
|
|
3
|
-
import { decodeInput, normalizeDimension, normalizeReplayHistoryLength, normalizeScrollback, readLine, sanitizeString, } from "./screen-text.js";
|
|
3
|
+
import { decodeInput, normalizeDimension, normalizeReplayHistoryLength, normalizeScrollback, readLine, sanitizedTail, sanitizeString, } from "./screen-text.js";
|
|
4
4
|
const XtermTerminal = xterm.Terminal;
|
|
5
5
|
const DEFAULT_COLS = 80;
|
|
6
6
|
const DEFAULT_ROWS = 24;
|
|
@@ -191,7 +191,7 @@ export class TerminalScreen {
|
|
|
191
191
|
boundReplayPayload(payload) {
|
|
192
192
|
if (payload.length <= this.maxReplayHistoryLength)
|
|
193
193
|
return payload;
|
|
194
|
-
return
|
|
194
|
+
return sanitizedTail(payload, this.maxReplayHistoryLength);
|
|
195
195
|
}
|
|
196
196
|
async drain() {
|
|
197
197
|
if (this.draining)
|
|
@@ -270,7 +270,7 @@ export class TerminalScreen {
|
|
|
270
270
|
const [onlyChunk] = this.history;
|
|
271
271
|
if (onlyChunk === undefined)
|
|
272
272
|
return;
|
|
273
|
-
const trimmed =
|
|
273
|
+
const trimmed = sanitizedTail(onlyChunk, this.maxReplayHistoryLength);
|
|
274
274
|
this.history[0] = trimmed;
|
|
275
275
|
this.historyLength = trimmed.length;
|
|
276
276
|
}
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-yeongyu/senpi-pty",
|
|
3
|
-
"version": "2026.10.
|
|
3
|
+
"version": "2026.10.12",
|
|
4
4
|
"description": "Typed PTY session loader for Senpi persistent terminals",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"clean": "shx rm -rf dist",
|
|
35
35
|
"build": "tsgo -p tsconfig.build.json",
|
|
36
|
-
"test": "vitest --run test",
|
|
36
|
+
"test": "node ../../scripts/run-tests.mjs vitest --run test",
|
|
37
37
|
"check:prebuild": "node native/check-prebuild-fresh.mjs",
|
|
38
38
|
"prepublishOnly": "npm run clean && npm run build"
|
|
39
39
|
},
|