@code-yeongyu/senpi-tui 2026.10.19 → 2026.10.21-1
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/autocomplete.js +2 -1
- package/dist/kitty-graphics-probe.d.ts +21 -0
- package/dist/kitty-graphics-probe.js +37 -0
- package/dist/terminal-capabilities.d.ts +2 -0
- package/dist/terminal-image.d.ts +3 -1
- package/dist/terminal-image.js +25 -2
- package/dist/tui.d.ts +4 -0
- package/dist/tui.js +44 -1
- package/package.json +1 -1
package/dist/autocomplete.js
CHANGED
|
@@ -168,7 +168,8 @@ async function walkDirectoryWithFd(baseDir, fdPath, query, maxResults, signal, m
|
|
|
168
168
|
resolve(results);
|
|
169
169
|
};
|
|
170
170
|
const onAbort = () => {
|
|
171
|
-
|
|
171
|
+
// A child that never started has no pid; on Node < 24.22 / 26.10 its kill() signals our own process group.
|
|
172
|
+
if (child.pid !== undefined && child.exitCode === null) {
|
|
172
173
|
child.kill("SIGKILL");
|
|
173
174
|
}
|
|
174
175
|
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kitty graphics support probe for multiplexer panes whose environment says
|
|
3
|
+
* nothing about image support (herdr; senpi #3698).
|
|
4
|
+
*
|
|
5
|
+
* A 1x1 RGBA direct-transmission query (`a=q`) is written to the terminal; a
|
|
6
|
+
* terminal that implements the Kitty graphics protocol replies with an APC
|
|
7
|
+
* response naming the query's image id. Silence, an error payload, or a reply
|
|
8
|
+
* for another id all mean "no graphics" — the caller must then keep the text
|
|
9
|
+
* fallback instead of emitting graphics sequences blindly.
|
|
10
|
+
*/
|
|
11
|
+
export declare const KITTY_GRAPHICS_PROBE_TIMEOUT_MS = 500;
|
|
12
|
+
/** `AAAA` is base64 for four zero bytes: one 1x1 RGBA pixel. */
|
|
13
|
+
export declare function buildKittyGraphicsQuery(imageId: number): string;
|
|
14
|
+
export type KittyGraphicsProbeOutcome = "ok" | "err";
|
|
15
|
+
/**
|
|
16
|
+
* Classify one APC chunk as the answer to our query. Returns null when the
|
|
17
|
+
* chunk is not a Kitty graphics response for `imageId`, so unrelated APC
|
|
18
|
+
* traffic (graphics commands from other writers) is left untouched.
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseKittyGraphicsProbeResponse(data: string, imageId: number): KittyGraphicsProbeOutcome | null;
|
|
21
|
+
//# sourceMappingURL=kitty-graphics-probe.d.ts.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kitty graphics support probe for multiplexer panes whose environment says
|
|
3
|
+
* nothing about image support (herdr; senpi #3698).
|
|
4
|
+
*
|
|
5
|
+
* A 1x1 RGBA direct-transmission query (`a=q`) is written to the terminal; a
|
|
6
|
+
* terminal that implements the Kitty graphics protocol replies with an APC
|
|
7
|
+
* response naming the query's image id. Silence, an error payload, or a reply
|
|
8
|
+
* for another id all mean "no graphics" — the caller must then keep the text
|
|
9
|
+
* fallback instead of emitting graphics sequences blindly.
|
|
10
|
+
*/
|
|
11
|
+
export const KITTY_GRAPHICS_PROBE_TIMEOUT_MS = 500;
|
|
12
|
+
/** `AAAA` is base64 for four zero bytes: one 1x1 RGBA pixel. */
|
|
13
|
+
export function buildKittyGraphicsQuery(imageId) {
|
|
14
|
+
return `\x1b_Gi=${imageId},s=1,v=1,a=q,t=d,f=24;AAAA\x1b\\`;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Classify one APC chunk as the answer to our query. Returns null when the
|
|
18
|
+
* chunk is not a Kitty graphics response for `imageId`, so unrelated APC
|
|
19
|
+
* traffic (graphics commands from other writers) is left untouched.
|
|
20
|
+
*/
|
|
21
|
+
export function parseKittyGraphicsProbeResponse(data, imageId) {
|
|
22
|
+
const start = "\x1b_G";
|
|
23
|
+
const end = "\x1b\\";
|
|
24
|
+
if (!data.startsWith(start) || !data.endsWith(end))
|
|
25
|
+
return null;
|
|
26
|
+
const body = data.slice(start.length, -end.length);
|
|
27
|
+
const separator = body.indexOf(";");
|
|
28
|
+
if (separator === -1)
|
|
29
|
+
return null;
|
|
30
|
+
const keys = body.slice(0, separator);
|
|
31
|
+
const message = body.slice(separator + 1);
|
|
32
|
+
const idMatch = /(?:^|,)i=(\d+)(?:,|$)/.exec(keys);
|
|
33
|
+
if (!idMatch || Number.parseInt(idMatch[1] ?? "", 10) !== imageId)
|
|
34
|
+
return null;
|
|
35
|
+
return message.startsWith("OK") ? "ok" : "err";
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=kitty-graphics-probe.js.map
|
|
@@ -8,6 +8,8 @@ export interface TerminalCapabilities {
|
|
|
8
8
|
kittyUnicodePlaceholders?: boolean;
|
|
9
9
|
/** Shown next to the image placeholder when the terminal could show images after a user action. */
|
|
10
10
|
imageHint?: string;
|
|
11
|
+
/** Set when image support is still unknown from the environment: the TUI must query the terminal and record the answer via `setProbedImageProtocol`. */
|
|
12
|
+
imageProbe?: "kitty";
|
|
11
13
|
}
|
|
12
14
|
export interface DetectedTerminalCapabilities extends TerminalCapabilities {
|
|
13
15
|
cellDimensions?: {
|
package/dist/terminal-image.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { TerminalColorMode } from "./colors.ts";
|
|
2
|
-
import { type DetectedTerminalCapabilities, type TerminalCapabilities } from "./terminal-capabilities.ts";
|
|
2
|
+
import { type DetectedTerminalCapabilities, type ImageProtocol, type TerminalCapabilities } from "./terminal-capabilities.ts";
|
|
3
3
|
export type { ImageProtocol, TerminalCapabilities } from "./terminal-capabilities.ts";
|
|
4
4
|
export { outerKittyGraphicsMode } from "./terminal-capabilities.ts";
|
|
5
5
|
export interface TmuxPassthroughState {
|
|
@@ -27,6 +27,8 @@ export interface ImageRenderOptions {
|
|
|
27
27
|
}
|
|
28
28
|
export declare function getCellDimensions(): CellDimensions;
|
|
29
29
|
export declare function setCellDimensions(dims: CellDimensions): void;
|
|
30
|
+
/** Record the TUI probe's answer for probe-requesting terminals. `undefined` re-arms the probe. */
|
|
31
|
+
export declare function setProbedImageProtocol(protocol: ImageProtocol | undefined): void;
|
|
30
32
|
export declare function detectCapabilities(tmuxForwardsHyperlink?: () => boolean, tmuxPassthroughState?: () => TmuxPassthroughState): DetectedTerminalCapabilities;
|
|
31
33
|
export declare function getCapabilities(): TerminalCapabilities;
|
|
32
34
|
export declare function getTerminalColorMode(capabilities?: TerminalCapabilities): TerminalColorMode;
|
package/dist/terminal-image.js
CHANGED
|
@@ -7,6 +7,10 @@ import { isOldWindowsWezTerm, WEZTERM_WINDOWS_IMAGES_HINT } from "./wezterm-wind
|
|
|
7
7
|
export { outerKittyGraphicsMode } from "./terminal-capabilities.js";
|
|
8
8
|
let cachedCapabilities = null;
|
|
9
9
|
let capabilityOverrides = {};
|
|
10
|
+
// Answer of the TUI's Kitty graphics probe, recorded once per process. It
|
|
11
|
+
// outlives cachedCapabilities because focus events reset that cache and
|
|
12
|
+
// re-detect from the environment (senpi #3698).
|
|
13
|
+
let probedImageProtocol;
|
|
10
14
|
// Default cell dimensions - updated by TUI when terminal responds to query
|
|
11
15
|
let cellDimensions = { widthPx: 9, heightPx: 18 };
|
|
12
16
|
export function getCellDimensions() {
|
|
@@ -74,6 +78,17 @@ function detectCapabilitiesFromEnvironment(tmuxForwardsHyperlink) {
|
|
|
74
78
|
if (terminalEmulator === "jetbrains-jediterm") {
|
|
75
79
|
return { images: null, trueColor: true, hyperlinks: false };
|
|
76
80
|
}
|
|
81
|
+
// herdr panes: the environment says nothing about the outer terminal's graphics support, and herdr
|
|
82
|
+
// itself leaks graphics sequences when the outer terminal cannot render them. Detection defers to
|
|
83
|
+
// the TUI's Kitty graphics probe and latches its answer (senpi #3698).
|
|
84
|
+
if (termProgram === "herdr" || process.env.HERDR_ENV === "1") {
|
|
85
|
+
return {
|
|
86
|
+
images: probedImageProtocol ?? null,
|
|
87
|
+
trueColor: hasTrueColorHint,
|
|
88
|
+
hyperlinks: false,
|
|
89
|
+
...(probedImageProtocol === undefined ? { imageProbe: "kitty" } : {}),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
77
92
|
// Windows Terminal does not always set WT_SESSION, for example when it hosts
|
|
78
93
|
// a cmd.exe launched directly from Win+R. Modern Windows consoles support
|
|
79
94
|
// truecolor; keep hyperlinks off unless we positively detected support above.
|
|
@@ -89,9 +104,17 @@ function detectCapabilitiesFromEnvironment(tmuxForwardsHyperlink) {
|
|
|
89
104
|
function parseBooleanCapabilityOverride(value) {
|
|
90
105
|
return value === "1" ? true : value === "0" ? false : undefined;
|
|
91
106
|
}
|
|
92
|
-
|
|
107
|
+
/** Record the TUI probe's answer for probe-requesting terminals. `undefined` re-arms the probe. */
|
|
108
|
+
export function setProbedImageProtocol(protocol) {
|
|
109
|
+
if (probedImageProtocol === protocol)
|
|
110
|
+
return;
|
|
111
|
+
probedImageProtocol = protocol;
|
|
112
|
+
cachedCapabilities = null;
|
|
113
|
+
}
|
|
114
|
+
// The hint explains why detection turned images off; an explicit image choice makes it wrong. The
|
|
115
|
+
// probe flag is stripped for the same reason: an explicit choice settles the question, no probe needed.
|
|
93
116
|
function withoutImageHint(caps) {
|
|
94
|
-
const { imageHint: _imageHint, ...rest } = caps;
|
|
117
|
+
const { imageHint: _imageHint, imageProbe: _imageProbe, ...rest } = caps;
|
|
95
118
|
return rest;
|
|
96
119
|
}
|
|
97
120
|
export function detectCapabilities(tmuxForwardsHyperlink, tmuxPassthroughState) {
|
package/dist/tui.d.ts
CHANGED
|
@@ -363,6 +363,7 @@ export declare abstract class TuiBase extends Container {
|
|
|
363
363
|
* replies belong to the oldest one. Queries stay here after a timeout to collect late replies.
|
|
364
364
|
*/
|
|
365
365
|
private pendingTerminalColorQueries;
|
|
366
|
+
private pendingKittyGraphicsProbe?;
|
|
366
367
|
private terminalColorSchemeListeners;
|
|
367
368
|
private terminalColorSchemeNotificationsEnabled;
|
|
368
369
|
/** Directory for debug/crash logs. The fork keeps a concrete default (`~/.senpi/agent`) so PI_DEBUG_REDRAW and crash dumps stay in the agent directory. */
|
|
@@ -470,6 +471,9 @@ export declare abstract class TuiBase extends Container {
|
|
|
470
471
|
onTerminalColorSchemeChange(listener: (scheme: TerminalColorScheme) => void): () => void;
|
|
471
472
|
setTerminalColorSchemeNotifications(enabled: boolean): void;
|
|
472
473
|
private queryCellSize;
|
|
474
|
+
private probeKittyGraphics;
|
|
475
|
+
private consumeKittyGraphicsProbeResponse;
|
|
476
|
+
private resolveKittyGraphicsProbe;
|
|
473
477
|
stop(options?: TuiStopOptions): void;
|
|
474
478
|
/**
|
|
475
479
|
* While a reply streams, a frame that changes rows above the viewport would replay the whole scrollback
|
package/dist/tui.js
CHANGED
|
@@ -7,9 +7,10 @@ import * as os from "node:os";
|
|
|
7
7
|
import * as path from "node:path";
|
|
8
8
|
import { performance } from "node:perf_hooks";
|
|
9
9
|
import { isKeyRelease, matchesKey } from "./keys.js";
|
|
10
|
+
import { buildKittyGraphicsQuery, KITTY_GRAPHICS_PROBE_TIMEOUT_MS, parseKittyGraphicsProbeResponse, } from "./kitty-graphics-probe.js";
|
|
10
11
|
import { isMultiplexerSession, useLegacyMuxRender, viewportRenderEnabled } from "./mux.js";
|
|
11
12
|
import { parseOscColorResponse, parseTerminalColorSchemeReport, } from "./terminal-colors.js";
|
|
12
|
-
import { deleteKittyImage, getCapabilities, isImageLine, resetCapabilitiesCache, setCellDimensions, } from "./terminal-image.js";
|
|
13
|
+
import { allocateImageId, deleteKittyImage, getCapabilities, isImageLine, resetCapabilitiesCache, setCellDimensions, setProbedImageProtocol, } from "./terminal-image.js";
|
|
13
14
|
import { consumeTmuxFocusEvent, DISABLE_FOCUS_REPORTING, ENABLE_FOCUS_REPORTING } from "./tmux-focus.js";
|
|
14
15
|
import { extractSegments, normalizeTerminalOutput, sliceByColumn, sliceWithWidth, visibleWidth } from "./utils.js";
|
|
15
16
|
import { planViewportInsertScroll } from "./viewport-insert-scroll.js";
|
|
@@ -1242,6 +1243,7 @@ export class TuiBase extends Container {
|
|
|
1242
1243
|
this.terminal.write("\x1b[?2031h");
|
|
1243
1244
|
}
|
|
1244
1245
|
this.queryCellSize();
|
|
1246
|
+
this.probeKittyGraphics();
|
|
1245
1247
|
this.requestRender();
|
|
1246
1248
|
}
|
|
1247
1249
|
addInputListener(listener) {
|
|
@@ -1277,6 +1279,40 @@ export class TuiBase extends Container {
|
|
|
1277
1279
|
// Response format: CSI 6 ; height ; width t
|
|
1278
1280
|
this.terminal.write("\x1b[16t");
|
|
1279
1281
|
}
|
|
1282
|
+
probeKittyGraphics() {
|
|
1283
|
+
if (this.pendingKittyGraphicsProbe)
|
|
1284
|
+
return;
|
|
1285
|
+
if (getCapabilities().imageProbe !== "kitty")
|
|
1286
|
+
return;
|
|
1287
|
+
const imageId = allocateImageId();
|
|
1288
|
+
const timer = setTimeout(() => this.resolveKittyGraphicsProbe(false), KITTY_GRAPHICS_PROBE_TIMEOUT_MS);
|
|
1289
|
+
this.pendingKittyGraphicsProbe = { imageId, timer };
|
|
1290
|
+
this.terminal.write(buildKittyGraphicsQuery(imageId));
|
|
1291
|
+
}
|
|
1292
|
+
consumeKittyGraphicsProbeResponse(data) {
|
|
1293
|
+
const probe = this.pendingKittyGraphicsProbe;
|
|
1294
|
+
if (!probe)
|
|
1295
|
+
return false;
|
|
1296
|
+
const outcome = parseKittyGraphicsProbeResponse(data, probe.imageId);
|
|
1297
|
+
if (outcome === null)
|
|
1298
|
+
return false;
|
|
1299
|
+
this.resolveKittyGraphicsProbe(outcome === "ok");
|
|
1300
|
+
return true;
|
|
1301
|
+
}
|
|
1302
|
+
resolveKittyGraphicsProbe(supported) {
|
|
1303
|
+
const probe = this.pendingKittyGraphicsProbe;
|
|
1304
|
+
if (!probe)
|
|
1305
|
+
return;
|
|
1306
|
+
this.pendingKittyGraphicsProbe = undefined;
|
|
1307
|
+
clearTimeout(probe.timer);
|
|
1308
|
+
setProbedImageProtocol(supported ? "kitty" : null);
|
|
1309
|
+
if (this.stopped)
|
|
1310
|
+
return;
|
|
1311
|
+
if (supported)
|
|
1312
|
+
this.queryCellSize();
|
|
1313
|
+
this.invalidate();
|
|
1314
|
+
this.requestRender();
|
|
1315
|
+
}
|
|
1280
1316
|
stop(options = {}) {
|
|
1281
1317
|
this.stopped = true;
|
|
1282
1318
|
// The hold itself belongs to the turn (interactive mode sets and releases it); a stop()/start() handover
|
|
@@ -1287,6 +1323,10 @@ export class TuiBase extends Container {
|
|
|
1287
1323
|
this.renderRequested = false;
|
|
1288
1324
|
this.inputRenderPending = false;
|
|
1289
1325
|
this.cancelRenderTimer();
|
|
1326
|
+
if (this.pendingKittyGraphicsProbe) {
|
|
1327
|
+
clearTimeout(this.pendingKittyGraphicsProbe.timer);
|
|
1328
|
+
this.pendingKittyGraphicsProbe = undefined;
|
|
1329
|
+
}
|
|
1290
1330
|
if (this.terminalColorSchemeNotificationsEnabled) {
|
|
1291
1331
|
this.terminal.write("\x1b[?2031l");
|
|
1292
1332
|
}
|
|
@@ -1493,6 +1533,9 @@ export class TuiBase extends Container {
|
|
|
1493
1533
|
if (this.consumeTerminalColorSchemeReport(data)) {
|
|
1494
1534
|
return;
|
|
1495
1535
|
}
|
|
1536
|
+
if (this.consumeKittyGraphicsProbeResponse(data)) {
|
|
1537
|
+
return;
|
|
1538
|
+
}
|
|
1496
1539
|
// A key press means the user is at the bottom again. Terminal reports (mouse/wheel, OSC/DCS replies,
|
|
1497
1540
|
// DEC private reports, window and cell-size reports) are not the user and must not trigger it.
|
|
1498
1541
|
if (!isTerminalReport(data)) {
|
package/package.json
CHANGED