@cplieger/web-terminal-engine 2.1.0 → 2.2.0
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/jsr.json +1 -1
- package/package.json +1 -1
- package/src/connection.ts +1 -0
- package/src/keyboard.ts +247 -7
- package/src/modes.ts +17 -0
- package/src/status-stream.ts +6 -0
- package/src/types.ts +7 -0
- package/src/wire-binary.ts +10 -1
package/jsr.json
CHANGED
package/package.json
CHANGED
package/src/connection.ts
CHANGED
package/src/keyboard.ts
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
// vt screen tracks both bracketed paste (?2004) and DECCKM (?1) and
|
|
15
15
|
// emits a modes frame whenever they change.
|
|
16
16
|
|
|
17
|
-
import { isApplicationCursor, isBracketedPaste } from "./modes.js";
|
|
17
|
+
import { getKeyboardFlags, isApplicationCursor, isBracketedPaste } from "./modes.js";
|
|
18
18
|
|
|
19
19
|
/** Result of mapping a keyboard event. */
|
|
20
20
|
export type KeyboardResult =
|
|
@@ -25,14 +25,21 @@ export type KeyboardResult =
|
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* KeyboardModes is the mode state `mapKeyboardEvent` reads: DECCKM (application
|
|
28
|
-
* cursor keys)
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* cursor keys), DECKPAM (application keypad), and the kitty keyboard
|
|
29
|
+
* progressive-enhancement flags. Passed explicitly so the shared input maps
|
|
30
|
+
* against the active tab's modes and so the mapping is testable without mutating
|
|
31
|
+
* global state. The `modes` module namespace satisfies this structurally; a
|
|
32
|
+
* tabbed shell passes its active session's modes.
|
|
32
33
|
*/
|
|
33
34
|
export interface KeyboardModes {
|
|
34
35
|
isApplicationCursor: () => boolean;
|
|
35
36
|
isApplicationKeypad: () => boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Active kitty keyboard flags (bit0 disambiguate; higher bits reserved). When
|
|
39
|
+
* the disambiguate bit is set, non-text keys are encoded as kitty CSI-u
|
|
40
|
+
* sequences instead of the legacy encodings.
|
|
41
|
+
*/
|
|
42
|
+
getKeyboardFlags: () => number;
|
|
36
43
|
}
|
|
37
44
|
|
|
38
45
|
const ESC = "\x1b";
|
|
@@ -126,6 +133,214 @@ const KEYPAD_SS3: Record<string, string | undefined> = {
|
|
|
126
133
|
Enter: "M",
|
|
127
134
|
};
|
|
128
135
|
|
|
136
|
+
// -- Kitty keyboard protocol (progressive enhancement) ----------------------
|
|
137
|
+
// When the server reports an active kitty flag (modes.getKeyboardFlags), keys
|
|
138
|
+
// that do NOT produce text are encoded per the kitty protocol instead of the
|
|
139
|
+
// legacy encodings, so an app that enabled the protocol (e.g. Codex via
|
|
140
|
+
// crossterm) gets unambiguous key events. We honor the disambiguate flag (0x1)
|
|
141
|
+
// — report-event-types (0x2) / report-alternate-keys (0x4) / report-all (0x8) /
|
|
142
|
+
// text (0x10) are masked off server-side, so the CSI ?u query reports only 0x1
|
|
143
|
+
// and the encoder never has to emit them.
|
|
144
|
+
//
|
|
145
|
+
// Under disambiguate, text-producing keys still flow through the hidden
|
|
146
|
+
// textarea as text (this encoder returns "ignore" for them), matching both the
|
|
147
|
+
// spec (text keys stay text under 0x1) and our IME/composition model. Only
|
|
148
|
+
// Escape, ctrl/alt/meta combinations, and functional keys are re-encoded.
|
|
149
|
+
|
|
150
|
+
/** Kitty progressive-enhancement flag bits (mirror vt/kitty.go). */
|
|
151
|
+
const KITTY_DISAMBIGUATE = 1;
|
|
152
|
+
|
|
153
|
+
// Functional keys encoded as CSI 1;{mod}{letter} (the 1;{mod} is omitted when
|
|
154
|
+
// unmodified). Under kitty these use the CSI form (legacy uses SS3 for F1-F4)
|
|
155
|
+
// and even under DECCKM. F3 is deliberately absent — it has no letter form in
|
|
156
|
+
// the kitty table (CSI R collides with the Cursor Position Report), so it goes
|
|
157
|
+
// in KITTY_TILDE as 13 instead. Do not add F3 here.
|
|
158
|
+
const KITTY_LETTER: Record<string, string | undefined> = {
|
|
159
|
+
ArrowUp: "A",
|
|
160
|
+
ArrowDown: "B",
|
|
161
|
+
ArrowRight: "C",
|
|
162
|
+
ArrowLeft: "D",
|
|
163
|
+
Home: "H",
|
|
164
|
+
End: "F",
|
|
165
|
+
F1: "P",
|
|
166
|
+
F2: "Q",
|
|
167
|
+
F4: "S",
|
|
168
|
+
};
|
|
169
|
+
// Functional keys encoded as CSI {num};{mod}~ (the ;{mod} omitted when unmodified).
|
|
170
|
+
const KITTY_TILDE: Record<string, number | undefined> = {
|
|
171
|
+
Insert: 2,
|
|
172
|
+
Delete: 3,
|
|
173
|
+
PageUp: 5,
|
|
174
|
+
PageDown: 6,
|
|
175
|
+
F3: 13, // no letter form — CSI R conflicts with the cursor-position report
|
|
176
|
+
F5: 15,
|
|
177
|
+
F6: 17,
|
|
178
|
+
F7: 18,
|
|
179
|
+
F8: 19,
|
|
180
|
+
F9: 20,
|
|
181
|
+
F10: 21,
|
|
182
|
+
F11: 23,
|
|
183
|
+
F12: 24,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// US-layout base (unshifted) codepoints for the non-alphanumeric physical keys,
|
|
187
|
+
// keyed by ev.code. Used so a ctrl/alt+shift+symbol event reports the UNSHIFTED
|
|
188
|
+
// key code the spec mandates (e.g. Ctrl+Shift+; is CSI 59;6u using ';'=59, not
|
|
189
|
+
// ':'=58). Letters/digits are handled separately from ev.code; other keys fall
|
|
190
|
+
// back to the case-folded ev.key.
|
|
191
|
+
const KITTY_CODE_BASE: Record<string, number | undefined> = {
|
|
192
|
+
Semicolon: 59, // ;
|
|
193
|
+
Equal: 61, // =
|
|
194
|
+
Comma: 44, // ,
|
|
195
|
+
Minus: 45, // -
|
|
196
|
+
Period: 46, // .
|
|
197
|
+
Slash: 47, // /
|
|
198
|
+
Backquote: 96, // `
|
|
199
|
+
BracketLeft: 91, // [
|
|
200
|
+
Backslash: 92, // \
|
|
201
|
+
BracketRight: 93, // ]
|
|
202
|
+
Quote: 39, // '
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Keypad NON-TEXT keys under disambiguate, keyed by ev.key (the key's current
|
|
206
|
+
// function — NumLock flips a numpad key between its text digit, which stays text
|
|
207
|
+
// under 0x1, and these navigation functions). ev.code identifies it as a keypad
|
|
208
|
+
// key. Per the spec these get dedicated KP_* codes (57414-57427) so an app can
|
|
209
|
+
// tell them apart from the main navigation keys. NumpadEnter is included as
|
|
210
|
+
// KP_ENTER (57414) — it is a distinct key, NOT the legacy-byte main Enter.
|
|
211
|
+
// KP_BEGIN (Numpad5, NumLock off -> "Clear") is emitted as 57427 in the `u`
|
|
212
|
+
// form: both kitty and crossterm decode `CSI 57427 u`, whereas the spec's
|
|
213
|
+
// alternate `CSI E` letter form is kitty-only (crossterm/ratatui can't parse it).
|
|
214
|
+
// The KP_ DIGIT/operator codes (57399-57416) are deliberately absent — they are
|
|
215
|
+
// a report-all (0x8) feature; under 0x1 a NumLock-on numpad digit is plain text.
|
|
216
|
+
const KITTY_KEYPAD: Record<string, number | undefined> = {
|
|
217
|
+
ArrowLeft: 57417, // KP_LEFT
|
|
218
|
+
ArrowRight: 57418, // KP_RIGHT
|
|
219
|
+
ArrowUp: 57419, // KP_UP
|
|
220
|
+
ArrowDown: 57420, // KP_DOWN
|
|
221
|
+
PageUp: 57421, // KP_PAGE_UP
|
|
222
|
+
PageDown: 57422, // KP_PAGE_DOWN
|
|
223
|
+
Home: 57423, // KP_HOME
|
|
224
|
+
End: 57424, // KP_END
|
|
225
|
+
Insert: 57425, // KP_INSERT
|
|
226
|
+
Delete: 57426, // KP_DELETE
|
|
227
|
+
Clear: 57427, // KP_BEGIN
|
|
228
|
+
Enter: 57414, // KP_ENTER
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/** CSI 1;{mod}{letter}, omitting `1;{mod}` when there are no modifiers. */
|
|
232
|
+
function kittyLetterSeq(letter: string, ev: KeyboardEvent): string {
|
|
233
|
+
const m = modifiersDigit(ev);
|
|
234
|
+
return m === 1 ? `${ESC}[${letter}` : `${ESC}[1;${m}${letter}`;
|
|
235
|
+
}
|
|
236
|
+
/** CSI {num};{mod}~, omitting `;{mod}` when there are no modifiers. */
|
|
237
|
+
function kittyTildeSeq(num: number, ev: KeyboardEvent): string {
|
|
238
|
+
const m = modifiersDigit(ev);
|
|
239
|
+
return m === 1 ? `${ESC}[${num}~` : `${ESC}[${num};${m}~`;
|
|
240
|
+
}
|
|
241
|
+
/** CSI {num};{mod}u, omitting `;{mod}` when there are no modifiers. */
|
|
242
|
+
function kittyUSeq(num: number, ev: KeyboardEvent): string {
|
|
243
|
+
const m = modifiersDigit(ev);
|
|
244
|
+
return m === 1 ? `${ESC}[${num}u` : `${ESC}[${num};${m}u`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** True when any modifier (ctrl / alt / meta / shift) is held. */
|
|
248
|
+
function anyModifier(ev: KeyboardEvent): boolean {
|
|
249
|
+
return ev.ctrlKey || ev.altKey || ev.metaKey || ev.shiftKey;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* The kitty "unicode-key-code": the UNSHIFTED (lower-case / base-layout)
|
|
254
|
+
* codepoint of the key. Letters and digits are read from ev.code (the physical
|
|
255
|
+
* key), so a composed Alt/Option character on macOS still maps to its base key;
|
|
256
|
+
* other single-character keys fall back to the case-folded ev.key.
|
|
257
|
+
*/
|
|
258
|
+
function kittyKeyCodepoint(ev: KeyboardEvent): number {
|
|
259
|
+
const code = ev.code;
|
|
260
|
+
if (code.length === 4 && code.startsWith("Key")) {
|
|
261
|
+
return code.charCodeAt(3) + 32; // "KeyA".."KeyZ" -> 'a'..'z'
|
|
262
|
+
}
|
|
263
|
+
if (code.length === 6 && code.startsWith("Digit")) {
|
|
264
|
+
return code.charCodeAt(5); // "Digit0".."Digit9" -> '0'..'9'
|
|
265
|
+
}
|
|
266
|
+
const base = KITTY_CODE_BASE[code];
|
|
267
|
+
if (base !== undefined) {
|
|
268
|
+
return base; // symbol key -> unshifted base codepoint (spec: use the un-shifted key)
|
|
269
|
+
}
|
|
270
|
+
const k = ev.key;
|
|
271
|
+
return k.length === 1 ? (k.toLowerCase().codePointAt(0) ?? 0) : 0;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Encode a keydown under the kitty disambiguate flag (0x1). Returns "ignore"
|
|
276
|
+
* for text-producing keys so the hidden textarea still handles typing/IME.
|
|
277
|
+
* Called only when the flag is active; the modifier-only and local-scroll cases
|
|
278
|
+
* are handled by the shared preamble in mapKeyboardEvent (which skips its
|
|
279
|
+
* application-keypad SS3 branch under this flag, so keypad keys reach here).
|
|
280
|
+
*/
|
|
281
|
+
function encodeKittyDisambiguate(ev: KeyboardEvent): KeyboardResult {
|
|
282
|
+
const key = ev.key;
|
|
283
|
+
|
|
284
|
+
// Keypad NON-TEXT keys (NumLock-off navigation + NumpadEnter) get dedicated
|
|
285
|
+
// KP_* codes so an app can distinguish them from the main keys. Detected by
|
|
286
|
+
// ev.code (Numpad*); the specific code is chosen by ev.key. Text keypad keys
|
|
287
|
+
// (NumLock-on digits/operators) are NOT intercepted — they fall through to the
|
|
288
|
+
// printable path below (text when unmodified; the ASCII-codepoint CSI-u form
|
|
289
|
+
// when a ctrl/alt/meta modifier suppresses the text), because KP_ digit codes
|
|
290
|
+
// are a report-all (0x8) feature we don't honor.
|
|
291
|
+
if (ev.code.startsWith("Numpad")) {
|
|
292
|
+
const kp = KITTY_KEYPAD[key];
|
|
293
|
+
if (kp !== undefined) {
|
|
294
|
+
return { kind: "send", bytes: kittyUSeq(kp, ev) };
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
const letter = KITTY_LETTER[key];
|
|
299
|
+
if (letter !== undefined) {
|
|
300
|
+
return { kind: "send", bytes: kittyLetterSeq(letter, ev) };
|
|
301
|
+
}
|
|
302
|
+
const tnum = KITTY_TILDE[key];
|
|
303
|
+
if (tnum !== undefined) {
|
|
304
|
+
return { kind: "send", bytes: kittyTildeSeq(tnum, ev) };
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Enter / Tab / Backspace keep their legacy bytes when UNMODIFIED (so a user
|
|
308
|
+
// can still type `reset` if a program leaves the mode on after crashing);
|
|
309
|
+
// with modifiers they take the disambiguated CSI-u form.
|
|
310
|
+
if (key === "Enter") {
|
|
311
|
+
return { kind: "send", bytes: anyModifier(ev) ? kittyUSeq(13, ev) : "\r" };
|
|
312
|
+
}
|
|
313
|
+
if (key === "Tab") {
|
|
314
|
+
return { kind: "send", bytes: anyModifier(ev) ? kittyUSeq(9, ev) : "\t" };
|
|
315
|
+
}
|
|
316
|
+
if (key === "Backspace") {
|
|
317
|
+
return { kind: "send", bytes: anyModifier(ev) ? kittyUSeq(127, ev) : DEL };
|
|
318
|
+
}
|
|
319
|
+
if (key === "Escape") {
|
|
320
|
+
return { kind: "send", bytes: kittyUSeq(27, ev) };
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// Single-character keys: text stays text (deferred to the textarea) UNLESS a
|
|
324
|
+
// ctrl/alt/meta modifier is held, in which case the legacy encoding is
|
|
325
|
+
// ambiguous (Ctrl+I == Tab, Alt+x == ESC x, …) so we send the CSI-u form with
|
|
326
|
+
// the unshifted codepoint.
|
|
327
|
+
if (key.length === 1) {
|
|
328
|
+
if (ev.ctrlKey || ev.altKey || ev.metaKey) {
|
|
329
|
+
const cp = kittyKeyCodepoint(ev);
|
|
330
|
+
if (cp > 0) {
|
|
331
|
+
return { kind: "send", bytes: kittyUSeq(cp, ev) };
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return { kind: "ignore" };
|
|
335
|
+
}
|
|
336
|
+
return { kind: "ignore" };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/** True when the kitty disambiguate flag is active in the global mode state. */
|
|
340
|
+
function kittyDisambiguateActive(): boolean {
|
|
341
|
+
return (getKeyboardFlags() & KITTY_DISAMBIGUATE) !== 0;
|
|
342
|
+
}
|
|
343
|
+
|
|
129
344
|
/**
|
|
130
345
|
* mapKeyboardEvent converts a KeyboardEvent into the terminal action
|
|
131
346
|
* to take. Returns "ignore" when the event is purely a modifier press
|
|
@@ -156,6 +371,7 @@ export function mapKeyboardEvent(ev: KeyboardEvent, modes: KeyboardModes): Keybo
|
|
|
156
371
|
// NumpadSubtract, NumpadAdd, NumpadMultiply, NumpadDivide, NumpadEnter).
|
|
157
372
|
if (
|
|
158
373
|
modes.isApplicationKeypad() &&
|
|
374
|
+
(modes.getKeyboardFlags() & KITTY_DISAMBIGUATE) === 0 &&
|
|
159
375
|
!ev.ctrlKey &&
|
|
160
376
|
!ev.altKey &&
|
|
161
377
|
!ev.metaKey &&
|
|
@@ -188,6 +404,15 @@ export function mapKeyboardEvent(ev: KeyboardEvent, modes: KeyboardModes): Keybo
|
|
|
188
404
|
return { kind: "scroll-down" };
|
|
189
405
|
}
|
|
190
406
|
|
|
407
|
+
// Kitty keyboard protocol: once the app enables the disambiguate flag, encode
|
|
408
|
+
// non-text keys as CSI-u sequences (Esc, ctrl/alt combos, functional keys);
|
|
409
|
+
// text keys still defer to the textarea. Placed after the shared preamble
|
|
410
|
+
// (modifier-only, application keypad, local scroll) so those behave
|
|
411
|
+
// identically in both modes.
|
|
412
|
+
if ((modes.getKeyboardFlags() & KITTY_DISAMBIGUATE) !== 0) {
|
|
413
|
+
return encodeKittyDisambiguate(ev);
|
|
414
|
+
}
|
|
415
|
+
|
|
191
416
|
// Cursor keys — CSI form with optional modifiers (CSI 1;{m}{letter}).
|
|
192
417
|
const arrow = ARROW_LETTER[ev.key];
|
|
193
418
|
if (arrow !== undefined) {
|
|
@@ -555,6 +780,11 @@ export function bindMobileToolbar(opts: BindMobileToolbarOptions): MobileToolbar
|
|
|
555
780
|
}
|
|
556
781
|
|
|
557
782
|
function arrowSeq(letter: "A" | "B" | "C" | "D"): string {
|
|
783
|
+
// Under kitty disambiguate, arrows use the CSI form regardless of DECCKM
|
|
784
|
+
// (the protocol supersedes cursor-key mode). Otherwise DECCKM chooses SS3.
|
|
785
|
+
if (kittyDisambiguateActive()) {
|
|
786
|
+
return `${ESC}[${letter}`;
|
|
787
|
+
}
|
|
558
788
|
return isApplicationCursor() ? `${ESC}O${letter}` : `${ESC}[${letter}`;
|
|
559
789
|
}
|
|
560
790
|
|
|
@@ -619,7 +849,9 @@ export function bindMobileToolbar(opts: BindMobileToolbarOptions): MobileToolbar
|
|
|
619
849
|
on(escBtn, (e) => {
|
|
620
850
|
e.preventDefault();
|
|
621
851
|
setCtrlArmed(false);
|
|
622
|
-
|
|
852
|
+
// Under kitty disambiguate, Escape is CSI 27 u (so the app never has to
|
|
853
|
+
// disambiguate a lone ESC byte from the start of an escape sequence).
|
|
854
|
+
opts.send(kittyDisambiguateActive() ? `${ESC}[27u` : ESC);
|
|
623
855
|
});
|
|
624
856
|
|
|
625
857
|
function applyStickyCtrl(text: string): string {
|
|
@@ -627,8 +859,16 @@ export function bindMobileToolbar(opts: BindMobileToolbarOptions): MobileToolbar
|
|
|
627
859
|
return text;
|
|
628
860
|
}
|
|
629
861
|
if (text.length === 1) {
|
|
630
|
-
const ctrl = ctrlByteFor(text);
|
|
631
862
|
setCtrlArmed(false);
|
|
863
|
+
// Under kitty disambiguate, Ctrl+char is the CSI-u form (ctrl modifier =
|
|
864
|
+
// 5) with the unshifted codepoint, matching the physical-keyboard path.
|
|
865
|
+
if (kittyDisambiguateActive()) {
|
|
866
|
+
const cp = text.toLowerCase().codePointAt(0);
|
|
867
|
+
if (cp !== undefined) {
|
|
868
|
+
return `${ESC}[${cp};5u`;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
const ctrl = ctrlByteFor(text);
|
|
632
872
|
return ctrl ?? text;
|
|
633
873
|
}
|
|
634
874
|
// Multi-char input (paste, IME commit) — applying Ctrl to a string
|
package/src/modes.ts
CHANGED
|
@@ -23,6 +23,9 @@ let mouseSGR = false;
|
|
|
23
23
|
let mousePixels = false; // DEC 1016: report pixel coords instead of cell coords
|
|
24
24
|
let focusReporting = false;
|
|
25
25
|
let reverseVideo = false;
|
|
26
|
+
// Kitty keyboard progressive-enhancement flags (bit0 disambiguate, bit1
|
|
27
|
+
// event-types, bit2 alternate-keys). 0 = protocol disabled (legacy encoding).
|
|
28
|
+
let keyboardFlags = 0;
|
|
26
29
|
|
|
27
30
|
/**
|
|
28
31
|
* Update the cached mode state. Called by the consumer whenever the server
|
|
@@ -38,6 +41,7 @@ export function setModes(
|
|
|
38
41
|
appKeypad?: boolean,
|
|
39
42
|
revVideo?: boolean,
|
|
40
43
|
mPixels?: boolean,
|
|
44
|
+
kbdFlags?: number,
|
|
41
45
|
): void {
|
|
42
46
|
bracketedPaste = bracketed;
|
|
43
47
|
applicationCursor = appCursor;
|
|
@@ -59,6 +63,9 @@ export function setModes(
|
|
|
59
63
|
if (mPixels !== undefined) {
|
|
60
64
|
mousePixels = mPixels;
|
|
61
65
|
}
|
|
66
|
+
if (kbdFlags !== undefined) {
|
|
67
|
+
keyboardFlags = kbdFlags;
|
|
68
|
+
}
|
|
62
69
|
}
|
|
63
70
|
|
|
64
71
|
/** True when the server has DEC 2004 (bracketed paste) enabled. */
|
|
@@ -104,3 +111,13 @@ export function isApplicationKeypad(): boolean {
|
|
|
104
111
|
export function isReverseVideo(): boolean {
|
|
105
112
|
return reverseVideo;
|
|
106
113
|
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Kitty keyboard progressive-enhancement flags currently in effect (bit0
|
|
117
|
+
* disambiguate, bit1 report-event-types, bit2 report-alternate-keys). 0 means
|
|
118
|
+
* the protocol is disabled and keys use legacy encoding. Read by keyboard.ts to
|
|
119
|
+
* choose between legacy and kitty CSI-u encoding.
|
|
120
|
+
*/
|
|
121
|
+
export function getKeyboardFlags(): number {
|
|
122
|
+
return keyboardFlags;
|
|
123
|
+
}
|
package/src/status-stream.ts
CHANGED
|
@@ -14,6 +14,12 @@ export interface SessionStatus {
|
|
|
14
14
|
readonly createdAt: string;
|
|
15
15
|
/** true when the session is gone (closed or reaped); the consumer drops it. */
|
|
16
16
|
readonly removed?: boolean;
|
|
17
|
+
/** true once the session has emitted a genuine activity signal — OSC 9;4
|
|
18
|
+
* progress (kiro-cli, Claude Code, …) or a classified OSC 9 notification.
|
|
19
|
+
* Sticky for the session's life. Consumers reveal the per-tab activity dot
|
|
20
|
+
* only when this is set; a program that emits no OSC 9 signal (a plain shell)
|
|
21
|
+
* keeps its tab dot hidden. */
|
|
22
|
+
readonly reportsActivity?: boolean;
|
|
17
23
|
}
|
|
18
24
|
|
|
19
25
|
/** StatusStreamCallbacks are the consumer's hooks. Declared as function-typed
|
package/src/types.ts
CHANGED
|
@@ -133,6 +133,13 @@ export interface ModesMessage {
|
|
|
133
133
|
mousePixels: boolean;
|
|
134
134
|
/** Mouse tracking: 0=off, 1000=normal, 1002=button-event, 1003=any-event. */
|
|
135
135
|
mouseMode: number;
|
|
136
|
+
/**
|
|
137
|
+
* Kitty keyboard progressive-enhancement flags (bit0 disambiguate, bit1
|
|
138
|
+
* report-event-types, bit2 report-alternate-keys); 0 means the protocol is
|
|
139
|
+
* disabled and keys use legacy encoding. The keyboard encoder emits kitty
|
|
140
|
+
* CSI-u sequences for the set flags.
|
|
141
|
+
*/
|
|
142
|
+
keyboardFlags: number;
|
|
136
143
|
/** Server-confirmed bytesReceived for the input ACK protocol. */
|
|
137
144
|
inputAck?: number;
|
|
138
145
|
}
|
package/src/wire-binary.ts
CHANGED
|
@@ -29,8 +29,12 @@ const MSG_CLIPBOARD = 6;
|
|
|
29
29
|
* (e.g. a stale cached bundle after a breaking change) and warn rather than
|
|
30
30
|
* silently mis-decode. Bump on any breaking change to the binary frame layout
|
|
31
31
|
* or control-message shape. Mirrors `wireProtocolVersion` in wire_binary.go.
|
|
32
|
+
*
|
|
33
|
+
* v3: the modes frame gained a trailing kbdFlags byte (kitty keyboard
|
|
34
|
+
* protocol). Decoding is tolerant of a pre-v3 server (kbdFlags defaults to 0
|
|
35
|
+
* when the byte is absent), so the bump only drives the mismatch warning.
|
|
32
36
|
*/
|
|
33
|
-
export const WIRE_PROTOCOL_VERSION =
|
|
37
|
+
export const WIRE_PROTOCOL_VERSION = 3;
|
|
34
38
|
const MODE_FLAG_BRACKETED_PASTE = 1;
|
|
35
39
|
const MODE_FLAG_APP_CURSOR_KEYS = 2;
|
|
36
40
|
const MODE_FLAG_MOUSE_SGR = 4;
|
|
@@ -220,6 +224,10 @@ function decodeWireBinaryInner(buf: ArrayBuffer): ServerMessage | null {
|
|
|
220
224
|
if (msgType === MSG_MODES) {
|
|
221
225
|
const flags = c.u8();
|
|
222
226
|
const mouseMode = c.u16();
|
|
227
|
+
// kbdFlags (kitty keyboard protocol) was appended in wire protocol v3.
|
|
228
|
+
// Read it only if the frame carries it, so a pre-v3 server's shorter frame
|
|
229
|
+
// decodes with the protocol disabled rather than reading past the buffer.
|
|
230
|
+
const keyboardFlags = c.off < c.bytes.length ? c.u8() : 0;
|
|
223
231
|
const msg: ModesMessage = {
|
|
224
232
|
type: "modes",
|
|
225
233
|
bracketedPaste: (flags & MODE_FLAG_BRACKETED_PASTE) !== 0,
|
|
@@ -230,6 +238,7 @@ function decodeWireBinaryInner(buf: ArrayBuffer): ServerMessage | null {
|
|
|
230
238
|
reverseVideo: (flags & MODE_FLAG_REVERSE_VIDEO) !== 0,
|
|
231
239
|
mousePixels: (flags & MODE_FLAG_MOUSE_PIXELS) !== 0,
|
|
232
240
|
mouseMode,
|
|
241
|
+
keyboardFlags,
|
|
233
242
|
inputAck,
|
|
234
243
|
};
|
|
235
244
|
return msg;
|