tmex-cli 0.5.1 → 0.6.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/runtime/server.js +966 -561
- package/package.json +1 -1
- package/resources/fe-dist/assets/DevicePage-BGPoxSI1.js +36 -0
- package/resources/fe-dist/assets/DevicePage-BGPoxSI1.js.map +1 -0
- package/resources/fe-dist/assets/{DevicesPage-_g8yXgKX.js → DevicesPage-ImyBiwEt.js} +2 -2
- package/resources/fe-dist/assets/{DevicesPage-_g8yXgKX.js.map → DevicesPage-ImyBiwEt.js.map} +1 -1
- package/resources/fe-dist/assets/{SettingsPage-Bi8Kez5a.js → SettingsPage-CyMAR4ec.js} +2 -2
- package/resources/fe-dist/assets/{SettingsPage-Bi8Kez5a.js.map → SettingsPage-CyMAR4ec.js.map} +1 -1
- package/resources/fe-dist/assets/{index-kXpemMRY.js → index-qxM5NVWd.js} +3 -3
- package/resources/fe-dist/assets/{index-kXpemMRY.js.map → index-qxM5NVWd.js.map} +1 -1
- package/resources/fe-dist/assets/{select-v4N0NzCz.js → select-CALI7PWC.js} +2 -2
- package/resources/fe-dist/assets/{select-v4N0NzCz.js.map → select-CALI7PWC.js.map} +1 -1
- package/resources/fe-dist/assets/{switch-BJu9baQM.js → switch-CkhOAzoZ.js} +2 -2
- package/resources/fe-dist/assets/{switch-BJu9baQM.js.map → switch-CkhOAzoZ.js.map} +1 -1
- package/resources/fe-dist/assets/{useValueChanged-D47LrxaP.js → useValueChanged-WtzSkCNQ.js} +2 -2
- package/resources/fe-dist/assets/{useValueChanged-D47LrxaP.js.map → useValueChanged-WtzSkCNQ.js.map} +1 -1
- package/resources/fe-dist/index.html +1 -1
- package/resources/fe-dist/assets/DevicePage-CQuQ0yeD.js +0 -36
- package/resources/fe-dist/assets/DevicePage-CQuQ0yeD.js.map +0 -1
package/dist/runtime/server.js
CHANGED
|
@@ -20282,7 +20282,7 @@ var require_lib3 = __commonJS((exports, module) => {
|
|
|
20282
20282
|
|
|
20283
20283
|
// src/runtime/server.ts
|
|
20284
20284
|
import { existsSync as existsSync4 } from "fs";
|
|
20285
|
-
import { extname, join as
|
|
20285
|
+
import { extname, join as join4, normalize, resolve as resolve2, sep } from "path";
|
|
20286
20286
|
|
|
20287
20287
|
// ../../apps/gateway/src/crypto/errors.ts
|
|
20288
20288
|
function contextLabel(context) {
|
|
@@ -52139,7 +52139,6 @@ class EventNotifier {
|
|
|
52139
52139
|
var eventNotifier = new EventNotifier;
|
|
52140
52140
|
|
|
52141
52141
|
// ../../apps/gateway/src/tmux-client/local-external-connection.ts
|
|
52142
|
-
import { mkdirSync, rmSync } from "fs";
|
|
52143
52142
|
import { homedir } from "os";
|
|
52144
52143
|
|
|
52145
52144
|
// ../../apps/gateway/src/ws/error-classify.ts
|
|
@@ -52531,156 +52530,297 @@ function buildLocalTmuxEnv(resolvedPath, baseEnv = process.env) {
|
|
|
52531
52530
|
return nextEnv;
|
|
52532
52531
|
}
|
|
52533
52532
|
|
|
52534
|
-
// ../../apps/gateway/src/tmux-client/
|
|
52535
|
-
|
|
52536
|
-
|
|
52533
|
+
// ../../apps/gateway/src/tmux-client/capture-history.ts
|
|
52534
|
+
var PANE_SCREEN_INFO_FORMAT = "#{alternate_on} #{cursor_x} #{cursor_y} #{pane_height}";
|
|
52535
|
+
function parsePaneScreenInfo(stdout) {
|
|
52536
|
+
const parts = stdout.trim().split(/\s+/);
|
|
52537
|
+
const toInt = (value) => {
|
|
52538
|
+
if (value === undefined) {
|
|
52539
|
+
return null;
|
|
52540
|
+
}
|
|
52541
|
+
const parsed = Number.parseInt(value, 10);
|
|
52542
|
+
return Number.isNaN(parsed) || parsed < 0 ? null : parsed;
|
|
52543
|
+
};
|
|
52544
|
+
return {
|
|
52545
|
+
alternateScreen: parts[0] === "1",
|
|
52546
|
+
cursorX: toInt(parts[1]),
|
|
52547
|
+
cursorY: toInt(parts[2]),
|
|
52548
|
+
paneHeight: toInt(parts[3])
|
|
52549
|
+
};
|
|
52537
52550
|
}
|
|
52538
|
-
function
|
|
52539
|
-
|
|
52551
|
+
function appendCursorRestore(history, info) {
|
|
52552
|
+
const { cursorX, cursorY, paneHeight } = info;
|
|
52553
|
+
if (cursorX === null || cursorY === null || paneHeight === null || paneHeight < 1) {
|
|
52554
|
+
return history;
|
|
52555
|
+
}
|
|
52556
|
+
const trimmed = history.endsWith(`
|
|
52557
|
+
`) ? history.slice(0, -1) : history;
|
|
52558
|
+
if (info.alternateScreen) {
|
|
52559
|
+
return `${trimmed}\x1B[${cursorY + 1};${cursorX + 1}H`;
|
|
52560
|
+
}
|
|
52561
|
+
const up = Math.min(Math.max(0, paneHeight - 1 - cursorY), paneHeight - 1);
|
|
52562
|
+
const moveUp = up > 0 ? `\x1B[${up}A` : "";
|
|
52563
|
+
return `${trimmed}${moveUp}\x1B[${cursorX + 1}G`;
|
|
52540
52564
|
}
|
|
52541
52565
|
|
|
52542
|
-
// ../../apps/gateway/src/tmux-client/
|
|
52543
|
-
|
|
52544
|
-
|
|
52545
|
-
var
|
|
52546
|
-
var
|
|
52547
|
-
|
|
52548
|
-
|
|
52549
|
-
|
|
52550
|
-
|
|
52551
|
-
|
|
52552
|
-
|
|
52553
|
-
|
|
52554
|
-
|
|
52555
|
-
|
|
52556
|
-
|
|
52557
|
-
|
|
52558
|
-
|
|
52559
|
-
|
|
52560
|
-
|
|
52561
|
-
|
|
52562
|
-
|
|
52563
|
-
|
|
52564
|
-
|
|
52565
|
-
|
|
52566
|
-
|
|
52567
|
-
|
|
52568
|
-
|
|
52569
|
-
|
|
52566
|
+
// ../../apps/gateway/src/tmux-client/control-mode-parser.ts
|
|
52567
|
+
var decoder = new TextDecoder;
|
|
52568
|
+
var MAX_LINE_BYTES = 4 * 1024 * 1024;
|
|
52569
|
+
var MAX_BLOCK_BODY_LINES = 1000;
|
|
52570
|
+
var BYTE_LF = 10;
|
|
52571
|
+
var BYTE_SPACE = 32;
|
|
52572
|
+
var BYTE_PERCENT = 37;
|
|
52573
|
+
var BYTE_BACKSLASH = 92;
|
|
52574
|
+
var KNOWN_NOTIFICATION_TYPES = new Set([
|
|
52575
|
+
"client-detached",
|
|
52576
|
+
"client-session-changed",
|
|
52577
|
+
"config-error",
|
|
52578
|
+
"continue",
|
|
52579
|
+
"layout-change",
|
|
52580
|
+
"message",
|
|
52581
|
+
"pane-mode-changed",
|
|
52582
|
+
"paste-buffer-changed",
|
|
52583
|
+
"paste-buffer-deleted",
|
|
52584
|
+
"pause",
|
|
52585
|
+
"session-changed",
|
|
52586
|
+
"session-renamed",
|
|
52587
|
+
"session-window-changed",
|
|
52588
|
+
"sessions-changed",
|
|
52589
|
+
"subscription-changed",
|
|
52590
|
+
"unlinked-window-add",
|
|
52591
|
+
"unlinked-window-close",
|
|
52592
|
+
"unlinked-window-renamed",
|
|
52593
|
+
"window-add",
|
|
52594
|
+
"window-close",
|
|
52595
|
+
"window-pane-changed",
|
|
52596
|
+
"window-renamed"
|
|
52597
|
+
]);
|
|
52598
|
+
function isOctalDigit(byte) {
|
|
52599
|
+
return byte >= 48 && byte <= 55;
|
|
52600
|
+
}
|
|
52601
|
+
function unescapeControlModeData(line, start, onInvalidEscape) {
|
|
52602
|
+
const result = new Uint8Array(line.length - start);
|
|
52603
|
+
let written = 0;
|
|
52604
|
+
let index = start;
|
|
52605
|
+
while (index < line.length) {
|
|
52606
|
+
const byte = line[index];
|
|
52607
|
+
if (byte !== BYTE_BACKSLASH) {
|
|
52608
|
+
result[written] = byte;
|
|
52609
|
+
written += 1;
|
|
52610
|
+
index += 1;
|
|
52611
|
+
continue;
|
|
52570
52612
|
}
|
|
52571
|
-
|
|
52613
|
+
const d1 = line[index + 1];
|
|
52614
|
+
const d2 = line[index + 2];
|
|
52615
|
+
const d3 = line[index + 3];
|
|
52616
|
+
if (d1 !== undefined && d2 !== undefined && d3 !== undefined && isOctalDigit(d1) && isOctalDigit(d2) && isOctalDigit(d3)) {
|
|
52617
|
+
result[written] = d1 - 48 << 6 | d2 - 48 << 3 | d3 - 48;
|
|
52618
|
+
written += 1;
|
|
52619
|
+
index += 4;
|
|
52620
|
+
continue;
|
|
52621
|
+
}
|
|
52622
|
+
onInvalidEscape?.();
|
|
52623
|
+
result[written] = byte;
|
|
52624
|
+
written += 1;
|
|
52625
|
+
index += 1;
|
|
52626
|
+
}
|
|
52627
|
+
return result.subarray(0, written);
|
|
52572
52628
|
}
|
|
52573
|
-
|
|
52574
|
-
|
|
52575
|
-
|
|
52576
|
-
|
|
52577
|
-
|
|
52578
|
-
|
|
52579
|
-
|
|
52580
|
-
|
|
52581
|
-
|
|
52582
|
-
|
|
52583
|
-
|
|
52584
|
-
cvvis=\\E[?12;25h, dch=\\E[%p1%dP, dch1=\\E[P, dim=\\E[2m,
|
|
52585
|
-
dl=\\E[%p1%dM, dl1=\\E[M, dsl=\\E]2;\\007, ech=\\E[%p1%dX,
|
|
52586
|
-
ed=\\E[J, el=\\E[K, el1=\\E[1K, flash=\\E[?5h$<100/>\\E[?5l,
|
|
52587
|
-
fsl=^G, home=\\E[H, hpa=\\E[%i%p1%dG, ht=^I, hts=\\EH,
|
|
52588
|
-
ich=\\E[%p1%d@, ich1=\\E[@, il=\\E[%p1%dL, il1=\\E[L, ind=\\n,
|
|
52589
|
-
indn=\\E[%p1%dS,
|
|
52590
|
-
initc=\\E]4;%p1%d;rgb:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\\E\\\\,
|
|
52591
|
-
invis=\\E[8m, kDC=\\E[3;2~, kEND=\\E[1;2F, kHOM=\\E[1;2H,
|
|
52592
|
-
kIC=\\E[2;2~, kLFT=\\E[1;2D, kNXT=\\E[6;2~, kPRV=\\E[5;2~,
|
|
52593
|
-
kRIT=\\E[1;2C, kbs=^?, kcbt=\\E[Z, kcub1=\\EOD, kcud1=\\EOB,
|
|
52594
|
-
kcuf1=\\EOC, kcuu1=\\EOA, kdch1=\\E[3~, kend=\\EOF, kent=\\EOM,
|
|
52595
|
-
kf1=\\EOP, kf10=\\E[21~, kf11=\\E[23~, kf12=\\E[24~,
|
|
52596
|
-
kf13=\\E[1;2P, kf14=\\E[1;2Q, kf15=\\E[1;2R, kf16=\\E[1;2S,
|
|
52597
|
-
kf17=\\E[15;2~, kf18=\\E[17;2~, kf19=\\E[18;2~, kf2=\\EOQ,
|
|
52598
|
-
kf20=\\E[19;2~, kf21=\\E[20;2~, kf22=\\E[21;2~,
|
|
52599
|
-
kf23=\\E[23;2~, kf24=\\E[24;2~, kf25=\\E[1;5P, kf26=\\E[1;5Q,
|
|
52600
|
-
kf27=\\E[1;5R, kf28=\\E[1;5S, kf29=\\E[15;5~, kf3=\\EOR,
|
|
52601
|
-
kf30=\\E[17;5~, kf31=\\E[18;5~, kf32=\\E[19;5~,
|
|
52602
|
-
kf33=\\E[20;5~, kf34=\\E[21;5~, kf35=\\E[23;5~,
|
|
52603
|
-
kf36=\\E[24;5~, kf37=\\E[1;6P, kf38=\\E[1;6Q, kf39=\\E[1;6R,
|
|
52604
|
-
kf4=\\EOS, kf40=\\E[1;6S, kf41=\\E[15;6~, kf42=\\E[17;6~,
|
|
52605
|
-
kf43=\\E[18;6~, kf44=\\E[19;6~, kf45=\\E[20;6~,
|
|
52606
|
-
kf46=\\E[21;6~, kf47=\\E[23;6~, kf48=\\E[24;6~,
|
|
52607
|
-
kf49=\\E[1;3P, kf5=\\E[15~, kf50=\\E[1;3Q, kf51=\\E[1;3R,
|
|
52608
|
-
kf52=\\E[1;3S, kf53=\\E[15;3~, kf54=\\E[17;3~,
|
|
52609
|
-
kf55=\\E[18;3~, kf56=\\E[19;3~, kf57=\\E[20;3~,
|
|
52610
|
-
kf58=\\E[21;3~, kf59=\\E[23;3~, kf6=\\E[17~, kf60=\\E[24;3~,
|
|
52611
|
-
kf61=\\E[1;4P, kf62=\\E[1;4Q, kf63=\\E[1;4R, kf7=\\E[18~,
|
|
52612
|
-
kf8=\\E[19~, kf9=\\E[20~, khome=\\EOH, kich1=\\E[2~,
|
|
52613
|
-
kind=\\E[1;2B, kmous=\\E[<, knp=\\E[6~, kpp=\\E[5~,
|
|
52614
|
-
kri=\\E[1;2A, oc=\\E]104\\007, op=\\E[39;49m, rc=\\E8,
|
|
52615
|
-
rep=%p1%c\\E[%p2%{1}%-%db, rev=\\E[7m, ri=\\EM,
|
|
52616
|
-
rin=\\E[%p1%dT, ritm=\\E[23m, rmacs=\\E(B, rmam=\\E[?7l,
|
|
52617
|
-
rmcup=\\E[?1049l, rmir=\\E[4l, rmkx=\\E[?1l\\E>, rmso=\\E[27m,
|
|
52618
|
-
rmul=\\E[24m, rs1=\\E]\\E\\\\\\Ec, sc=\\E7,
|
|
52619
|
-
setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
|
|
52620
|
-
setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,
|
|
52621
|
-
sgr=%?%p9%t\\E(0%e\\E(B%;\\E[0%?%p6%t;1%;%?%p5%t;2%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m,
|
|
52622
|
-
sgr0=\\E(B\\E[m, sitm=\\E[3m, smacs=\\E(0, smam=\\E[?7h,
|
|
52623
|
-
smcup=\\E[?1049h, smir=\\E[4h, smkx=\\E[?1h\\E=, smso=\\E[7m,
|
|
52624
|
-
smul=\\E[4m, tbc=\\E[3g, tsl=\\E]2;, u6=\\E[%i%d;%dR, u7=\\E[6n,
|
|
52625
|
-
u8=\\E[?%[;0123456789]c, u9=\\E[c, vpa=\\E[%i%p1%dd,
|
|
52626
|
-
BD=\\E[?2004l, BE=\\E[?2004h, Clmg=\\E[s,
|
|
52627
|
-
Cmg=\\E[%i%p1%d;%p2%ds, Dsmg=\\E[?69l, E3=\\E[3J,
|
|
52628
|
-
Enmg=\\E[?69h, Ms=\\E]52;%p1%s;%p2%s\\007, PE=\\E[201~,
|
|
52629
|
-
PS=\\E[200~, RV=\\E[>c, Se=\\E[2 q,
|
|
52630
|
-
Setulc=\\E[58:2::%p1%{65536}%/%d:%p1%{256}%/%{255}%&%d:%p1%{255}%&%d%;m,
|
|
52631
|
-
Smulx=\\E[4:%p1%dm, Ss=\\E[%p1%d q,
|
|
52632
|
-
Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;,
|
|
52633
|
-
XM=\\E[?1006;1000%?%p1%{1}%=%th%el%;, XR=\\E[>0q,
|
|
52634
|
-
fd=\\E[?1004l, fe=\\E[?1004h, kDC3=\\E[3;3~, kDC4=\\E[3;4~,
|
|
52635
|
-
kDC5=\\E[3;5~, kDC6=\\E[3;6~, kDC7=\\E[3;7~, kDN=\\E[1;2B,
|
|
52636
|
-
kDN3=\\E[1;3B, kDN4=\\E[1;4B, kDN5=\\E[1;5B, kDN6=\\E[1;6B,
|
|
52637
|
-
kDN7=\\E[1;7B, kEND3=\\E[1;3F, kEND4=\\E[1;4F,
|
|
52638
|
-
kEND5=\\E[1;5F, kEND6=\\E[1;6F, kEND7=\\E[1;7F,
|
|
52639
|
-
kHOM3=\\E[1;3H, kHOM4=\\E[1;4H, kHOM5=\\E[1;5H,
|
|
52640
|
-
kHOM6=\\E[1;6H, kHOM7=\\E[1;7H, kIC3=\\E[2;3~, kIC4=\\E[2;4~,
|
|
52641
|
-
kIC5=\\E[2;5~, kIC6=\\E[2;6~, kIC7=\\E[2;7~, kLFT3=\\E[1;3D,
|
|
52642
|
-
kLFT4=\\E[1;4D, kLFT5=\\E[1;5D, kLFT6=\\E[1;6D,
|
|
52643
|
-
kLFT7=\\E[1;7D, kNXT3=\\E[6;3~, kNXT4=\\E[6;4~,
|
|
52644
|
-
kNXT5=\\E[6;5~, kNXT6=\\E[6;6~, kNXT7=\\E[6;7~,
|
|
52645
|
-
kPRV3=\\E[5;3~, kPRV4=\\E[5;4~, kPRV5=\\E[5;5~,
|
|
52646
|
-
kPRV6=\\E[5;6~, kPRV7=\\E[5;7~, kRIT3=\\E[1;3C,
|
|
52647
|
-
kRIT4=\\E[1;4C, kRIT5=\\E[1;5C, kRIT6=\\E[1;6C,
|
|
52648
|
-
kRIT7=\\E[1;7C, kUP=\\E[1;2A, kUP3=\\E[1;3A, kUP4=\\E[1;4A,
|
|
52649
|
-
kUP5=\\E[1;5A, kUP6=\\E[1;6A, kUP7=\\E[1;7A, kxIN=\\E[I,
|
|
52650
|
-
kxOUT=\\E[O, rmxx=\\E[29m, rv=\\E\\\\[[0-9]+;[0-9]+;[0-9]+c,
|
|
52651
|
-
setrgbb=\\E[48:2:%p1%d:%p2%d:%p3%dm,
|
|
52652
|
-
setrgbf=\\E[38:2:%p1%d:%p2%d:%p3%dm, smxx=\\E[9m,
|
|
52653
|
-
xm=\\E[<%i%p3%d;%p1%d;%p2%d;%?%p4%tM%em%;,
|
|
52654
|
-
xr=\\EP>\\\\|[ -~]+a\\E\\\\,
|
|
52655
|
-
`;
|
|
52656
|
-
var HEREDOC_MARKER = "TMEX_TERMINFO_EOF";
|
|
52657
|
-
function buildEnsureGhosttyTerminfoScript() {
|
|
52658
|
-
return [
|
|
52659
|
-
"if ! infocmp xterm-ghostty >/dev/null 2>&1; then",
|
|
52660
|
-
`tic -x - <<'${HEREDOC_MARKER}' >/dev/null 2>&1`,
|
|
52661
|
-
XTERM_GHOSTTY_TERMINFO_SOURCE.trimEnd(),
|
|
52662
|
-
HEREDOC_MARKER,
|
|
52663
|
-
"fi",
|
|
52664
|
-
"infocmp xterm-ghostty >/dev/null 2>&1"
|
|
52665
|
-
].join(`
|
|
52666
|
-
`);
|
|
52629
|
+
function concatChunks(chunks, totalLength) {
|
|
52630
|
+
if (chunks.length === 1) {
|
|
52631
|
+
return chunks[0];
|
|
52632
|
+
}
|
|
52633
|
+
const merged = new Uint8Array(totalLength);
|
|
52634
|
+
let offset = 0;
|
|
52635
|
+
for (const chunk2 of chunks) {
|
|
52636
|
+
merged.set(chunk2, offset);
|
|
52637
|
+
offset += chunk2.length;
|
|
52638
|
+
}
|
|
52639
|
+
return merged;
|
|
52667
52640
|
}
|
|
52668
|
-
|
|
52669
|
-
|
|
52670
|
-
|
|
52671
|
-
|
|
52672
|
-
|
|
52673
|
-
|
|
52674
|
-
|
|
52675
|
-
|
|
52676
|
-
|
|
52677
|
-
|
|
52641
|
+
function createControlModeParser(callbacks) {
|
|
52642
|
+
let pendingChunks = [];
|
|
52643
|
+
let pendingLength = 0;
|
|
52644
|
+
let discardingOversizedLine = false;
|
|
52645
|
+
let warnedOversizedLine = false;
|
|
52646
|
+
let warnedInvalidEscape = false;
|
|
52647
|
+
let warnedUnexpectedLine = false;
|
|
52648
|
+
let currentBlock = null;
|
|
52649
|
+
function warnInvalidEscape() {
|
|
52650
|
+
if (!warnedInvalidEscape) {
|
|
52651
|
+
warnedInvalidEscape = true;
|
|
52652
|
+
console.warn("[tmex] control mode parser met invalid escape sequence, passing through");
|
|
52653
|
+
}
|
|
52678
52654
|
}
|
|
52679
|
-
|
|
52655
|
+
function findByte(line, byte, from) {
|
|
52656
|
+
for (let index = from;index < line.length; index += 1) {
|
|
52657
|
+
if (line[index] === byte) {
|
|
52658
|
+
return index;
|
|
52659
|
+
}
|
|
52660
|
+
}
|
|
52661
|
+
return -1;
|
|
52662
|
+
}
|
|
52663
|
+
function decodeRange(line, start, end) {
|
|
52664
|
+
return decoder.decode(line.subarray(start, end));
|
|
52665
|
+
}
|
|
52666
|
+
function handleOutputLine(line, payloadStart) {
|
|
52667
|
+
const paneEnd = findByte(line, BYTE_SPACE, payloadStart);
|
|
52668
|
+
if (paneEnd < 0) {
|
|
52669
|
+
return;
|
|
52670
|
+
}
|
|
52671
|
+
const paneId = decodeRange(line, payloadStart, paneEnd);
|
|
52672
|
+
callbacks.onOutput(paneId, unescapeControlModeData(line, paneEnd + 1, warnInvalidEscape));
|
|
52673
|
+
}
|
|
52674
|
+
function handleExtendedOutputLine(line, payloadStart) {
|
|
52675
|
+
const paneEnd = findByte(line, BYTE_SPACE, payloadStart);
|
|
52676
|
+
if (paneEnd < 0) {
|
|
52677
|
+
return;
|
|
52678
|
+
}
|
|
52679
|
+
const paneId = decodeRange(line, payloadStart, paneEnd);
|
|
52680
|
+
for (let index = paneEnd;index + 2 < line.length; index += 1) {
|
|
52681
|
+
if (line[index] === BYTE_SPACE && line[index + 1] === 58 && line[index + 2] === BYTE_SPACE) {
|
|
52682
|
+
callbacks.onOutput(paneId, unescapeControlModeData(line, index + 3, warnInvalidEscape));
|
|
52683
|
+
return;
|
|
52684
|
+
}
|
|
52685
|
+
}
|
|
52686
|
+
}
|
|
52687
|
+
function handleLine(line) {
|
|
52688
|
+
if (line.length === 0) {
|
|
52689
|
+
return;
|
|
52690
|
+
}
|
|
52691
|
+
if (line[0] !== BYTE_PERCENT) {
|
|
52692
|
+
if (currentBlock) {
|
|
52693
|
+
if (currentBlock.lines.length < MAX_BLOCK_BODY_LINES) {
|
|
52694
|
+
currentBlock.lines.push(decoder.decode(line));
|
|
52695
|
+
}
|
|
52696
|
+
return;
|
|
52697
|
+
}
|
|
52698
|
+
if (!warnedUnexpectedLine) {
|
|
52699
|
+
warnedUnexpectedLine = true;
|
|
52700
|
+
console.warn(`[tmex] control mode parser ignored unexpected line: ${decoder.decode(line.subarray(0, 80))}`);
|
|
52701
|
+
}
|
|
52702
|
+
return;
|
|
52703
|
+
}
|
|
52704
|
+
const typeEnd = findByte(line, BYTE_SPACE, 0);
|
|
52705
|
+
const type = typeEnd < 0 ? decodeRange(line, 1, line.length) : decodeRange(line, 1, typeEnd);
|
|
52706
|
+
const argsStart = typeEnd < 0 ? line.length : typeEnd + 1;
|
|
52707
|
+
switch (type) {
|
|
52708
|
+
case "output":
|
|
52709
|
+
handleOutputLine(line, argsStart);
|
|
52710
|
+
return;
|
|
52711
|
+
case "extended-output":
|
|
52712
|
+
handleExtendedOutputLine(line, argsStart);
|
|
52713
|
+
return;
|
|
52714
|
+
case "begin": {
|
|
52715
|
+
if (currentBlock) {
|
|
52716
|
+
callbacks.onBlockEnd?.(currentBlock);
|
|
52717
|
+
}
|
|
52718
|
+
currentBlock = {
|
|
52719
|
+
args: decodeRange(line, argsStart, line.length),
|
|
52720
|
+
isError: false,
|
|
52721
|
+
lines: []
|
|
52722
|
+
};
|
|
52723
|
+
return;
|
|
52724
|
+
}
|
|
52725
|
+
case "end":
|
|
52726
|
+
case "error": {
|
|
52727
|
+
if (!currentBlock) {
|
|
52728
|
+
return;
|
|
52729
|
+
}
|
|
52730
|
+
const args = decodeRange(line, argsStart, line.length);
|
|
52731
|
+
if (args !== currentBlock.args) {
|
|
52732
|
+
console.warn(`[tmex] control mode block guard mismatch: begin "${currentBlock.args}" vs ${type} "${args}"`);
|
|
52733
|
+
}
|
|
52734
|
+
currentBlock.isError = type === "error";
|
|
52735
|
+
callbacks.onBlockEnd?.(currentBlock);
|
|
52736
|
+
currentBlock = null;
|
|
52737
|
+
return;
|
|
52738
|
+
}
|
|
52739
|
+
case "exit": {
|
|
52740
|
+
const reason = argsStart < line.length ? decodeRange(line, argsStart, line.length) : null;
|
|
52741
|
+
callbacks.onExit(reason);
|
|
52742
|
+
return;
|
|
52743
|
+
}
|
|
52744
|
+
default: {
|
|
52745
|
+
if (currentBlock && !KNOWN_NOTIFICATION_TYPES.has(type)) {
|
|
52746
|
+
if (currentBlock.lines.length < MAX_BLOCK_BODY_LINES) {
|
|
52747
|
+
currentBlock.lines.push(decoder.decode(line));
|
|
52748
|
+
}
|
|
52749
|
+
return;
|
|
52750
|
+
}
|
|
52751
|
+
callbacks.onNotification({
|
|
52752
|
+
type,
|
|
52753
|
+
args: decodeRange(line, argsStart, line.length),
|
|
52754
|
+
raw: decoder.decode(line)
|
|
52755
|
+
});
|
|
52756
|
+
return;
|
|
52757
|
+
}
|
|
52758
|
+
}
|
|
52759
|
+
}
|
|
52760
|
+
function takePendingLine(tail) {
|
|
52761
|
+
if (pendingLength === 0) {
|
|
52762
|
+
return tail;
|
|
52763
|
+
}
|
|
52764
|
+
pendingChunks.push(tail);
|
|
52765
|
+
const line = concatChunks(pendingChunks, pendingLength + tail.length);
|
|
52766
|
+
pendingChunks = [];
|
|
52767
|
+
pendingLength = 0;
|
|
52768
|
+
return line;
|
|
52769
|
+
}
|
|
52770
|
+
return {
|
|
52771
|
+
push(chunk2) {
|
|
52772
|
+
let start = 0;
|
|
52773
|
+
while (start <= chunk2.length) {
|
|
52774
|
+
const newlineIndex = findByte(chunk2, BYTE_LF, start);
|
|
52775
|
+
if (newlineIndex < 0) {
|
|
52776
|
+
break;
|
|
52777
|
+
}
|
|
52778
|
+
const tail = chunk2.subarray(start, newlineIndex);
|
|
52779
|
+
if (discardingOversizedLine) {
|
|
52780
|
+
discardingOversizedLine = false;
|
|
52781
|
+
pendingChunks = [];
|
|
52782
|
+
pendingLength = 0;
|
|
52783
|
+
} else {
|
|
52784
|
+
handleLine(takePendingLine(tail));
|
|
52785
|
+
}
|
|
52786
|
+
start = newlineIndex + 1;
|
|
52787
|
+
}
|
|
52788
|
+
if (start < chunk2.length) {
|
|
52789
|
+
const rest = chunk2.subarray(start);
|
|
52790
|
+
if (discardingOversizedLine) {
|
|
52791
|
+
return;
|
|
52792
|
+
}
|
|
52793
|
+
if (pendingLength + rest.length > MAX_LINE_BYTES) {
|
|
52794
|
+
if (!warnedOversizedLine) {
|
|
52795
|
+
warnedOversizedLine = true;
|
|
52796
|
+
console.warn("[tmex] control mode parser dropped oversized line");
|
|
52797
|
+
}
|
|
52798
|
+
discardingOversizedLine = true;
|
|
52799
|
+
pendingChunks = [];
|
|
52800
|
+
pendingLength = 0;
|
|
52801
|
+
return;
|
|
52802
|
+
}
|
|
52803
|
+
pendingChunks.push(rest);
|
|
52804
|
+
pendingLength += rest.length;
|
|
52805
|
+
}
|
|
52806
|
+
},
|
|
52807
|
+
end() {
|
|
52808
|
+
if (discardingOversizedLine || pendingLength === 0) {
|
|
52809
|
+
discardingOversizedLine = false;
|
|
52810
|
+
pendingChunks = [];
|
|
52811
|
+
pendingLength = 0;
|
|
52812
|
+
return;
|
|
52813
|
+
}
|
|
52814
|
+
const line = concatChunks(pendingChunks, pendingLength);
|
|
52815
|
+
pendingChunks = [];
|
|
52816
|
+
pendingLength = 0;
|
|
52817
|
+
handleLine(line);
|
|
52818
|
+
}
|
|
52819
|
+
};
|
|
52680
52820
|
}
|
|
52681
52821
|
|
|
52682
52822
|
// ../../apps/gateway/src/tmux-client/pane-stream-parser.ts
|
|
52683
|
-
var
|
|
52823
|
+
var decoder2 = new TextDecoder;
|
|
52684
52824
|
var MAX_OSC_KIND_BYTES = 16;
|
|
52685
52825
|
var MAX_OSC_PAYLOAD_BYTES = 8 * 1024;
|
|
52686
52826
|
var MAX_DCS_PASSTHROUGH_BYTES = 64 * 1024;
|
|
@@ -52714,14 +52854,14 @@ function createPaneStreamParser(options) {
|
|
|
52714
52854
|
return true;
|
|
52715
52855
|
}
|
|
52716
52856
|
function emitTitle(bytes) {
|
|
52717
|
-
const title =
|
|
52857
|
+
const title = decoder2.decode(new Uint8Array(bytes)).trim();
|
|
52718
52858
|
if (!title) {
|
|
52719
52859
|
return;
|
|
52720
52860
|
}
|
|
52721
52861
|
options.onTitle(title);
|
|
52722
52862
|
}
|
|
52723
52863
|
function emitOsc() {
|
|
52724
|
-
const payload =
|
|
52864
|
+
const payload = decoder2.decode(new Uint8Array(oscPayloadBytes));
|
|
52725
52865
|
switch (oscKind) {
|
|
52726
52866
|
case "0":
|
|
52727
52867
|
case "1":
|
|
@@ -53020,7 +53160,245 @@ function createPaneStreamParser(options) {
|
|
|
53020
53160
|
};
|
|
53021
53161
|
}
|
|
53022
53162
|
|
|
53163
|
+
// ../../apps/gateway/src/tmux-client/control-mode-subscription.ts
|
|
53164
|
+
var STRUCTURE_DEBOUNCE_MS = 150;
|
|
53165
|
+
var STRUCTURE_NOTIFICATION_TYPES = new Set([
|
|
53166
|
+
"layout-change",
|
|
53167
|
+
"session-renamed",
|
|
53168
|
+
"session-window-changed",
|
|
53169
|
+
"sessions-changed",
|
|
53170
|
+
"unlinked-window-add",
|
|
53171
|
+
"unlinked-window-close",
|
|
53172
|
+
"unlinked-window-renamed",
|
|
53173
|
+
"window-add",
|
|
53174
|
+
"window-close",
|
|
53175
|
+
"window-pane-changed",
|
|
53176
|
+
"window-renamed"
|
|
53177
|
+
]);
|
|
53178
|
+
function createControlModeSubscription(callbacks) {
|
|
53179
|
+
const paneParsers = new Map;
|
|
53180
|
+
let structureTimer = null;
|
|
53181
|
+
let lastStructureEmitAt = 0;
|
|
53182
|
+
let disposed = false;
|
|
53183
|
+
function getPaneParser(paneId) {
|
|
53184
|
+
const existing = paneParsers.get(paneId);
|
|
53185
|
+
if (existing) {
|
|
53186
|
+
return existing;
|
|
53187
|
+
}
|
|
53188
|
+
const parser2 = createPaneStreamParser({
|
|
53189
|
+
onTitle: (title) => callbacks.onTitle(paneId, title),
|
|
53190
|
+
onBell: () => callbacks.onBell(paneId),
|
|
53191
|
+
onNotification: (notification) => callbacks.onNotification(paneId, notification)
|
|
53192
|
+
});
|
|
53193
|
+
paneParsers.set(paneId, parser2);
|
|
53194
|
+
return parser2;
|
|
53195
|
+
}
|
|
53196
|
+
function scheduleStructureChanged() {
|
|
53197
|
+
if (disposed) {
|
|
53198
|
+
return;
|
|
53199
|
+
}
|
|
53200
|
+
const now = Date.now();
|
|
53201
|
+
if (structureTimer) {
|
|
53202
|
+
return;
|
|
53203
|
+
}
|
|
53204
|
+
if (now - lastStructureEmitAt >= STRUCTURE_DEBOUNCE_MS) {
|
|
53205
|
+
lastStructureEmitAt = now;
|
|
53206
|
+
callbacks.onStructureChanged();
|
|
53207
|
+
return;
|
|
53208
|
+
}
|
|
53209
|
+
structureTimer = setTimeout(() => {
|
|
53210
|
+
structureTimer = null;
|
|
53211
|
+
if (disposed) {
|
|
53212
|
+
return;
|
|
53213
|
+
}
|
|
53214
|
+
lastStructureEmitAt = Date.now();
|
|
53215
|
+
callbacks.onStructureChanged();
|
|
53216
|
+
}, STRUCTURE_DEBOUNCE_MS - (now - lastStructureEmitAt));
|
|
53217
|
+
}
|
|
53218
|
+
function handleNotification(notification) {
|
|
53219
|
+
if (STRUCTURE_NOTIFICATION_TYPES.has(notification.type)) {
|
|
53220
|
+
scheduleStructureChanged();
|
|
53221
|
+
}
|
|
53222
|
+
}
|
|
53223
|
+
const parser = createControlModeParser({
|
|
53224
|
+
onOutput: (paneId, data) => {
|
|
53225
|
+
const output = getPaneParser(paneId).push(data);
|
|
53226
|
+
if (output.length > 0) {
|
|
53227
|
+
callbacks.onTerminalOutput(paneId, output);
|
|
53228
|
+
}
|
|
53229
|
+
},
|
|
53230
|
+
onNotification: handleNotification,
|
|
53231
|
+
onExit: (reason) => callbacks.onExit(reason),
|
|
53232
|
+
onBlockEnd: (block) => callbacks.onBlockEnd?.(block)
|
|
53233
|
+
});
|
|
53234
|
+
return {
|
|
53235
|
+
push(chunk2) {
|
|
53236
|
+
if (disposed) {
|
|
53237
|
+
return;
|
|
53238
|
+
}
|
|
53239
|
+
parser.push(chunk2);
|
|
53240
|
+
},
|
|
53241
|
+
end() {
|
|
53242
|
+
if (disposed) {
|
|
53243
|
+
return;
|
|
53244
|
+
}
|
|
53245
|
+
parser.end();
|
|
53246
|
+
},
|
|
53247
|
+
prunePanes(validPaneIds) {
|
|
53248
|
+
for (const paneId of Array.from(paneParsers.keys())) {
|
|
53249
|
+
if (!validPaneIds.has(paneId)) {
|
|
53250
|
+
paneParsers.delete(paneId);
|
|
53251
|
+
}
|
|
53252
|
+
}
|
|
53253
|
+
},
|
|
53254
|
+
dispose() {
|
|
53255
|
+
disposed = true;
|
|
53256
|
+
if (structureTimer) {
|
|
53257
|
+
clearTimeout(structureTimer);
|
|
53258
|
+
structureTimer = null;
|
|
53259
|
+
}
|
|
53260
|
+
paneParsers.clear();
|
|
53261
|
+
}
|
|
53262
|
+
};
|
|
53263
|
+
}
|
|
53264
|
+
|
|
53265
|
+
// ../../apps/gateway/src/tmux-client/ghostty-terminfo.ts
|
|
53266
|
+
var XTERM_GHOSTTY_TERMINFO_SOURCE = `xterm-ghostty|ghostty|Ghostty,
|
|
53267
|
+
am, bce, ccc, hs, km, mc5i, mir, msgr, npc, xenl, AX, Su, Tc, XT, fullkbd,
|
|
53268
|
+
colors#0x100, cols#80, it#8, lines#24, pairs#0x7fff,
|
|
53269
|
+
acsc=++\\,\\,--..00\`\`aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
|
|
53270
|
+
bel=^G, blink=\\E[5m, bold=\\E[1m, cbt=\\E[Z, civis=\\E[?25l,
|
|
53271
|
+
clear=\\E[H\\E[2J, cnorm=\\E[?12l\\E[?25h, cr=\\r,
|
|
53272
|
+
csr=\\E[%i%p1%d;%p2%dr, cub=\\E[%p1%dD, cub1=^H,
|
|
53273
|
+
cud=\\E[%p1%dB, cud1=\\n, cuf=\\E[%p1%dC, cuf1=\\E[C,
|
|
53274
|
+
cup=\\E[%i%p1%d;%p2%dH, cuu=\\E[%p1%dA, cuu1=\\E[A,
|
|
53275
|
+
cvvis=\\E[?12;25h, dch=\\E[%p1%dP, dch1=\\E[P, dim=\\E[2m,
|
|
53276
|
+
dl=\\E[%p1%dM, dl1=\\E[M, dsl=\\E]2;\\007, ech=\\E[%p1%dX,
|
|
53277
|
+
ed=\\E[J, el=\\E[K, el1=\\E[1K, flash=\\E[?5h$<100/>\\E[?5l,
|
|
53278
|
+
fsl=^G, home=\\E[H, hpa=\\E[%i%p1%dG, ht=^I, hts=\\EH,
|
|
53279
|
+
ich=\\E[%p1%d@, ich1=\\E[@, il=\\E[%p1%dL, il1=\\E[L, ind=\\n,
|
|
53280
|
+
indn=\\E[%p1%dS,
|
|
53281
|
+
initc=\\E]4;%p1%d;rgb:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\\E\\\\,
|
|
53282
|
+
invis=\\E[8m, kDC=\\E[3;2~, kEND=\\E[1;2F, kHOM=\\E[1;2H,
|
|
53283
|
+
kIC=\\E[2;2~, kLFT=\\E[1;2D, kNXT=\\E[6;2~, kPRV=\\E[5;2~,
|
|
53284
|
+
kRIT=\\E[1;2C, kbs=^?, kcbt=\\E[Z, kcub1=\\EOD, kcud1=\\EOB,
|
|
53285
|
+
kcuf1=\\EOC, kcuu1=\\EOA, kdch1=\\E[3~, kend=\\EOF, kent=\\EOM,
|
|
53286
|
+
kf1=\\EOP, kf10=\\E[21~, kf11=\\E[23~, kf12=\\E[24~,
|
|
53287
|
+
kf13=\\E[1;2P, kf14=\\E[1;2Q, kf15=\\E[1;2R, kf16=\\E[1;2S,
|
|
53288
|
+
kf17=\\E[15;2~, kf18=\\E[17;2~, kf19=\\E[18;2~, kf2=\\EOQ,
|
|
53289
|
+
kf20=\\E[19;2~, kf21=\\E[20;2~, kf22=\\E[21;2~,
|
|
53290
|
+
kf23=\\E[23;2~, kf24=\\E[24;2~, kf25=\\E[1;5P, kf26=\\E[1;5Q,
|
|
53291
|
+
kf27=\\E[1;5R, kf28=\\E[1;5S, kf29=\\E[15;5~, kf3=\\EOR,
|
|
53292
|
+
kf30=\\E[17;5~, kf31=\\E[18;5~, kf32=\\E[19;5~,
|
|
53293
|
+
kf33=\\E[20;5~, kf34=\\E[21;5~, kf35=\\E[23;5~,
|
|
53294
|
+
kf36=\\E[24;5~, kf37=\\E[1;6P, kf38=\\E[1;6Q, kf39=\\E[1;6R,
|
|
53295
|
+
kf4=\\EOS, kf40=\\E[1;6S, kf41=\\E[15;6~, kf42=\\E[17;6~,
|
|
53296
|
+
kf43=\\E[18;6~, kf44=\\E[19;6~, kf45=\\E[20;6~,
|
|
53297
|
+
kf46=\\E[21;6~, kf47=\\E[23;6~, kf48=\\E[24;6~,
|
|
53298
|
+
kf49=\\E[1;3P, kf5=\\E[15~, kf50=\\E[1;3Q, kf51=\\E[1;3R,
|
|
53299
|
+
kf52=\\E[1;3S, kf53=\\E[15;3~, kf54=\\E[17;3~,
|
|
53300
|
+
kf55=\\E[18;3~, kf56=\\E[19;3~, kf57=\\E[20;3~,
|
|
53301
|
+
kf58=\\E[21;3~, kf59=\\E[23;3~, kf6=\\E[17~, kf60=\\E[24;3~,
|
|
53302
|
+
kf61=\\E[1;4P, kf62=\\E[1;4Q, kf63=\\E[1;4R, kf7=\\E[18~,
|
|
53303
|
+
kf8=\\E[19~, kf9=\\E[20~, khome=\\EOH, kich1=\\E[2~,
|
|
53304
|
+
kind=\\E[1;2B, kmous=\\E[<, knp=\\E[6~, kpp=\\E[5~,
|
|
53305
|
+
kri=\\E[1;2A, oc=\\E]104\\007, op=\\E[39;49m, rc=\\E8,
|
|
53306
|
+
rep=%p1%c\\E[%p2%{1}%-%db, rev=\\E[7m, ri=\\EM,
|
|
53307
|
+
rin=\\E[%p1%dT, ritm=\\E[23m, rmacs=\\E(B, rmam=\\E[?7l,
|
|
53308
|
+
rmcup=\\E[?1049l, rmir=\\E[4l, rmkx=\\E[?1l\\E>, rmso=\\E[27m,
|
|
53309
|
+
rmul=\\E[24m, rs1=\\E]\\E\\\\\\Ec, sc=\\E7,
|
|
53310
|
+
setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
|
|
53311
|
+
setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,
|
|
53312
|
+
sgr=%?%p9%t\\E(0%e\\E(B%;\\E[0%?%p6%t;1%;%?%p5%t;2%;%?%p2%t;4%;%?%p1%p3%|%t;7%;%?%p4%t;5%;%?%p7%t;8%;m,
|
|
53313
|
+
sgr0=\\E(B\\E[m, sitm=\\E[3m, smacs=\\E(0, smam=\\E[?7h,
|
|
53314
|
+
smcup=\\E[?1049h, smir=\\E[4h, smkx=\\E[?1h\\E=, smso=\\E[7m,
|
|
53315
|
+
smul=\\E[4m, tbc=\\E[3g, tsl=\\E]2;, u6=\\E[%i%d;%dR, u7=\\E[6n,
|
|
53316
|
+
u8=\\E[?%[;0123456789]c, u9=\\E[c, vpa=\\E[%i%p1%dd,
|
|
53317
|
+
BD=\\E[?2004l, BE=\\E[?2004h, Clmg=\\E[s,
|
|
53318
|
+
Cmg=\\E[%i%p1%d;%p2%ds, Dsmg=\\E[?69l, E3=\\E[3J,
|
|
53319
|
+
Enmg=\\E[?69h, Ms=\\E]52;%p1%s;%p2%s\\007, PE=\\E[201~,
|
|
53320
|
+
PS=\\E[200~, RV=\\E[>c, Se=\\E[2 q,
|
|
53321
|
+
Setulc=\\E[58:2::%p1%{65536}%/%d:%p1%{256}%/%{255}%&%d:%p1%{255}%&%d%;m,
|
|
53322
|
+
Smulx=\\E[4:%p1%dm, Ss=\\E[%p1%d q,
|
|
53323
|
+
Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;,
|
|
53324
|
+
XM=\\E[?1006;1000%?%p1%{1}%=%th%el%;, XR=\\E[>0q,
|
|
53325
|
+
fd=\\E[?1004l, fe=\\E[?1004h, kDC3=\\E[3;3~, kDC4=\\E[3;4~,
|
|
53326
|
+
kDC5=\\E[3;5~, kDC6=\\E[3;6~, kDC7=\\E[3;7~, kDN=\\E[1;2B,
|
|
53327
|
+
kDN3=\\E[1;3B, kDN4=\\E[1;4B, kDN5=\\E[1;5B, kDN6=\\E[1;6B,
|
|
53328
|
+
kDN7=\\E[1;7B, kEND3=\\E[1;3F, kEND4=\\E[1;4F,
|
|
53329
|
+
kEND5=\\E[1;5F, kEND6=\\E[1;6F, kEND7=\\E[1;7F,
|
|
53330
|
+
kHOM3=\\E[1;3H, kHOM4=\\E[1;4H, kHOM5=\\E[1;5H,
|
|
53331
|
+
kHOM6=\\E[1;6H, kHOM7=\\E[1;7H, kIC3=\\E[2;3~, kIC4=\\E[2;4~,
|
|
53332
|
+
kIC5=\\E[2;5~, kIC6=\\E[2;6~, kIC7=\\E[2;7~, kLFT3=\\E[1;3D,
|
|
53333
|
+
kLFT4=\\E[1;4D, kLFT5=\\E[1;5D, kLFT6=\\E[1;6D,
|
|
53334
|
+
kLFT7=\\E[1;7D, kNXT3=\\E[6;3~, kNXT4=\\E[6;4~,
|
|
53335
|
+
kNXT5=\\E[6;5~, kNXT6=\\E[6;6~, kNXT7=\\E[6;7~,
|
|
53336
|
+
kPRV3=\\E[5;3~, kPRV4=\\E[5;4~, kPRV5=\\E[5;5~,
|
|
53337
|
+
kPRV6=\\E[5;6~, kPRV7=\\E[5;7~, kRIT3=\\E[1;3C,
|
|
53338
|
+
kRIT4=\\E[1;4C, kRIT5=\\E[1;5C, kRIT6=\\E[1;6C,
|
|
53339
|
+
kRIT7=\\E[1;7C, kUP=\\E[1;2A, kUP3=\\E[1;3A, kUP4=\\E[1;4A,
|
|
53340
|
+
kUP5=\\E[1;5A, kUP6=\\E[1;6A, kUP7=\\E[1;7A, kxIN=\\E[I,
|
|
53341
|
+
kxOUT=\\E[O, rmxx=\\E[29m, rv=\\E\\\\[[0-9]+;[0-9]+;[0-9]+c,
|
|
53342
|
+
setrgbb=\\E[48:2:%p1%d:%p2%d:%p3%dm,
|
|
53343
|
+
setrgbf=\\E[38:2:%p1%d:%p2%d:%p3%dm, smxx=\\E[9m,
|
|
53344
|
+
xm=\\E[<%i%p3%d;%p1%d;%p2%d;%?%p4%tM%em%;,
|
|
53345
|
+
xr=\\EP>\\\\|[ -~]+a\\E\\\\,
|
|
53346
|
+
`;
|
|
53347
|
+
var HEREDOC_MARKER = "TMEX_TERMINFO_EOF";
|
|
53348
|
+
function buildEnsureGhosttyTerminfoScript() {
|
|
53349
|
+
return [
|
|
53350
|
+
"if ! infocmp xterm-ghostty >/dev/null 2>&1; then",
|
|
53351
|
+
`tic -x - <<'${HEREDOC_MARKER}' >/dev/null 2>&1`,
|
|
53352
|
+
XTERM_GHOSTTY_TERMINFO_SOURCE.trimEnd(),
|
|
53353
|
+
HEREDOC_MARKER,
|
|
53354
|
+
"fi",
|
|
53355
|
+
"infocmp xterm-ghostty >/dev/null 2>&1"
|
|
53356
|
+
].join(`
|
|
53357
|
+
`);
|
|
53358
|
+
}
|
|
53359
|
+
|
|
53360
|
+
// ../../apps/gateway/src/tmux-client/input-encoder.ts
|
|
53361
|
+
var encoder = new TextEncoder;
|
|
53362
|
+
var SEND_KEYS_HEX_CHUNK_BYTES = 256;
|
|
53363
|
+
function encodeInputToHexChunks(input, chunkBytes = SEND_KEYS_HEX_CHUNK_BYTES) {
|
|
53364
|
+
const bytes = encoder.encode(input);
|
|
53365
|
+
const chunks = [];
|
|
53366
|
+
for (let offset = 0;offset < bytes.length; offset += chunkBytes) {
|
|
53367
|
+
const chunk2 = bytes.slice(offset, offset + chunkBytes);
|
|
53368
|
+
chunks.push(Array.from(chunk2, (byte) => byte.toString(16).padStart(2, "0")));
|
|
53369
|
+
}
|
|
53370
|
+
return chunks;
|
|
53371
|
+
}
|
|
53372
|
+
|
|
53373
|
+
// ../../apps/gateway/src/tmux-client/tmux-version.ts
|
|
53374
|
+
var MIN_CONTROL_MODE_VERSION = { major: 3, minor: 0 };
|
|
53375
|
+
function parseTmuxVersion(versionOutput) {
|
|
53376
|
+
const match = versionOutput.match(/(\d+)\.(\d+)/);
|
|
53377
|
+
if (!match) {
|
|
53378
|
+
return null;
|
|
53379
|
+
}
|
|
53380
|
+
return {
|
|
53381
|
+
major: Number.parseInt(match[1], 10),
|
|
53382
|
+
minor: Number.parseInt(match[2], 10)
|
|
53383
|
+
};
|
|
53384
|
+
}
|
|
53385
|
+
function isControlModeSupported(version2) {
|
|
53386
|
+
if (!version2) {
|
|
53387
|
+
return true;
|
|
53388
|
+
}
|
|
53389
|
+
if (version2.major !== MIN_CONTROL_MODE_VERSION.major) {
|
|
53390
|
+
return version2.major > MIN_CONTROL_MODE_VERSION.major;
|
|
53391
|
+
}
|
|
53392
|
+
return version2.minor >= MIN_CONTROL_MODE_VERSION.minor;
|
|
53393
|
+
}
|
|
53394
|
+
|
|
53023
53395
|
// ../../apps/gateway/src/tmux-client/local-external-connection.ts
|
|
53396
|
+
var CONTROL_MAX_RESTARTS = 3;
|
|
53397
|
+
var CONTROL_RESTART_DELAY_MS = 500;
|
|
53398
|
+
var CONTROL_STABLE_RESET_MS = 1e4;
|
|
53399
|
+
var CONTROL_STDERR_TAIL_LIMIT = 2048;
|
|
53400
|
+
var CONTROL_ATTACH_READY_TIMEOUT_MS = 3000;
|
|
53401
|
+
var PARKING_WINDOW_NAME = "tmex-park";
|
|
53024
53402
|
function hasRenderableTerminalContent(value) {
|
|
53025
53403
|
return value.trim().length > 0;
|
|
53026
53404
|
}
|
|
@@ -53048,6 +53426,26 @@ function defaultRun(argv) {
|
|
|
53048
53426
|
}).catch(reject);
|
|
53049
53427
|
});
|
|
53050
53428
|
}
|
|
53429
|
+
function defaultSpawnControlClient(argv) {
|
|
53430
|
+
const subprocess = Bun.spawn(argv, {
|
|
53431
|
+
env: buildLocalTmuxEnv(getLocalShellPath()),
|
|
53432
|
+
stdin: "pipe",
|
|
53433
|
+
stdout: "pipe",
|
|
53434
|
+
stderr: "pipe"
|
|
53435
|
+
});
|
|
53436
|
+
const stdin = subprocess.stdin;
|
|
53437
|
+
return {
|
|
53438
|
+
stdout: subprocess.stdout,
|
|
53439
|
+
stderr: subprocess.stderr,
|
|
53440
|
+
exited: subprocess.exited,
|
|
53441
|
+
kill: () => {
|
|
53442
|
+
try {
|
|
53443
|
+
stdin?.end();
|
|
53444
|
+
} catch {}
|
|
53445
|
+
subprocess.kill();
|
|
53446
|
+
}
|
|
53447
|
+
};
|
|
53448
|
+
}
|
|
53051
53449
|
|
|
53052
53450
|
class LocalExternalTmuxConnection {
|
|
53053
53451
|
deviceId;
|
|
@@ -53062,30 +53460,27 @@ class LocalExternalTmuxConnection {
|
|
|
53062
53460
|
pendingPaneTitles = new Map;
|
|
53063
53461
|
snapshotSession = null;
|
|
53064
53462
|
snapshotWindows = new Map;
|
|
53065
|
-
paneReaders = new Map;
|
|
53066
|
-
pipeTransition = Promise.resolve();
|
|
53067
53463
|
inputTransition = Promise.resolve();
|
|
53068
|
-
hookReadAbort = null;
|
|
53069
|
-
hookBuffer = "";
|
|
53070
53464
|
bellDedup = new Map;
|
|
53071
53465
|
closeNotified = false;
|
|
53072
53466
|
cleanupPromise = null;
|
|
53073
|
-
|
|
53074
|
-
|
|
53075
|
-
|
|
53076
|
-
|
|
53077
|
-
|
|
53467
|
+
controlProcess = null;
|
|
53468
|
+
controlSubscription = null;
|
|
53469
|
+
controlStartedAt = 0;
|
|
53470
|
+
controlRestartCount = 0;
|
|
53471
|
+
controlStderrTail = "";
|
|
53078
53472
|
constructor(options, inputDeps = {}) {
|
|
53079
53473
|
this.deviceId = options.deviceId;
|
|
53080
53474
|
this.callbacks = options;
|
|
53081
53475
|
this.deps = {
|
|
53082
|
-
|
|
53476
|
+
enableSubscription: inputDeps.enableSubscription ?? true,
|
|
53083
53477
|
getDevice: inputDeps.getDevice ?? ((deviceId) => getDeviceById(deviceId)),
|
|
53084
53478
|
run: inputDeps.run ?? defaultRun,
|
|
53085
53479
|
ensureGhosttyTerminfo: inputDeps.ensureGhosttyTerminfo ?? (async () => {
|
|
53086
53480
|
const result = await this.deps.run(["/bin/sh", "-c", buildEnsureGhosttyTerminfoScript()]);
|
|
53087
53481
|
return result.exitCode === 0;
|
|
53088
|
-
})
|
|
53482
|
+
}),
|
|
53483
|
+
spawnControlClient: inputDeps.spawnControlClient ?? defaultSpawnControlClient
|
|
53089
53484
|
};
|
|
53090
53485
|
}
|
|
53091
53486
|
async connect() {
|
|
@@ -53099,16 +53494,13 @@ class LocalExternalTmuxConnection {
|
|
|
53099
53494
|
throw new Error(`LocalExternalTmuxConnection only supports local device: ${this.deviceId}`);
|
|
53100
53495
|
}
|
|
53101
53496
|
this.sessionName = this.device.session?.trim() || "tmex";
|
|
53102
|
-
this.
|
|
53103
|
-
|
|
53104
|
-
|
|
53105
|
-
gatewayPid: process.pid
|
|
53106
|
-
});
|
|
53107
|
-
this.ensureRuntimeDirs();
|
|
53497
|
+
if (this.deps.enableSubscription) {
|
|
53498
|
+
await this.assertControlModeSupport();
|
|
53499
|
+
}
|
|
53108
53500
|
await this.ensureSession();
|
|
53109
53501
|
await this.configureSessionOptions();
|
|
53110
|
-
if (this.deps.
|
|
53111
|
-
await this.
|
|
53502
|
+
if (this.deps.enableSubscription) {
|
|
53503
|
+
await this.startControlClient();
|
|
53112
53504
|
}
|
|
53113
53505
|
this.connected = true;
|
|
53114
53506
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
@@ -53125,11 +53517,7 @@ class LocalExternalTmuxConnection {
|
|
|
53125
53517
|
}
|
|
53126
53518
|
this.manualDisconnect = true;
|
|
53127
53519
|
this.connected = false;
|
|
53128
|
-
this.
|
|
53129
|
-
if (this.deps.enableHooks) {
|
|
53130
|
-
this.stopHooks();
|
|
53131
|
-
}
|
|
53132
|
-
rmSync(this.fsPaths.rootDir, { recursive: true, force: true });
|
|
53520
|
+
this.stopControlClient();
|
|
53133
53521
|
}
|
|
53134
53522
|
requestSnapshot() {
|
|
53135
53523
|
this.requestSnapshotInternal();
|
|
@@ -53241,7 +53629,14 @@ class LocalExternalTmuxConnection {
|
|
|
53241
53629
|
"extended-keys-format",
|
|
53242
53630
|
"csi-u"
|
|
53243
53631
|
]);
|
|
53244
|
-
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "
|
|
53632
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "off"]);
|
|
53633
|
+
await this.runTmuxAllowFailure([
|
|
53634
|
+
"set-option",
|
|
53635
|
+
"-t",
|
|
53636
|
+
this.sessionName,
|
|
53637
|
+
"destroy-unattached",
|
|
53638
|
+
"off"
|
|
53639
|
+
]);
|
|
53245
53640
|
const termProgram = config.tmuxTermProgram.trim();
|
|
53246
53641
|
if (termProgram && termProgram.toLowerCase() !== "off") {
|
|
53247
53642
|
await this.runTmuxAllowFailure([
|
|
@@ -53262,86 +53657,204 @@ class LocalExternalTmuxConnection {
|
|
|
53262
53657
|
}
|
|
53263
53658
|
}
|
|
53264
53659
|
}
|
|
53265
|
-
|
|
53266
|
-
|
|
53267
|
-
|
|
53268
|
-
|
|
53660
|
+
async assertControlModeSupport() {
|
|
53661
|
+
const result = await this.runTmuxAllowFailure(["-V"]);
|
|
53662
|
+
if (result.exitCode !== 0) {
|
|
53663
|
+
return;
|
|
53664
|
+
}
|
|
53665
|
+
const version2 = parseTmuxVersion(result.stdout.trim());
|
|
53666
|
+
if (!isControlModeSupported(version2)) {
|
|
53667
|
+
throw new Error(`tmux ${version2?.major}.${version2?.minor} is too old for tmex (control mode requires tmux >= 3.0)`);
|
|
53668
|
+
}
|
|
53269
53669
|
}
|
|
53270
|
-
async
|
|
53271
|
-
this.
|
|
53272
|
-
|
|
53273
|
-
rmSync(fifoPath, { force: true });
|
|
53274
|
-
await this.runShell(`mkfifo ${quoteShellArg(fifoPath)}`);
|
|
53275
|
-
const readerProcess = Bun.spawn(["/bin/sh", "-lc", `tail -n +1 -f ${quoteShellArg(fifoPath)}`], {
|
|
53276
|
-
stdout: "pipe",
|
|
53277
|
-
stderr: "pipe"
|
|
53278
|
-
});
|
|
53279
|
-
const reader = readerProcess.stdout.getReader();
|
|
53280
|
-
(async () => {
|
|
53281
|
-
try {
|
|
53282
|
-
while (true) {
|
|
53283
|
-
const chunk2 = await reader.read();
|
|
53284
|
-
if (chunk2.done) {
|
|
53285
|
-
break;
|
|
53286
|
-
}
|
|
53287
|
-
this.handleHookChunk(new TextDecoder().decode(chunk2.value));
|
|
53288
|
-
}
|
|
53289
|
-
} catch (error) {
|
|
53290
|
-
if (!this.manualDisconnect && !shouldIgnoreReaderAbortError(error)) {
|
|
53291
|
-
this.callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
53292
|
-
}
|
|
53293
|
-
}
|
|
53294
|
-
})();
|
|
53295
|
-
this.hookReadAbort = () => {
|
|
53296
|
-
reader.releaseLock();
|
|
53297
|
-
readerProcess.kill();
|
|
53298
|
-
rmSync(fifoPath, { force: true });
|
|
53299
|
-
};
|
|
53300
|
-
await this.installHook("pane-exited", ["pane-exited", "#{window_id}", "#{pane_id}"]);
|
|
53301
|
-
await this.installHook("pane-died", ["pane-died", "#{window_id}", "#{pane_id}"]);
|
|
53302
|
-
await this.installHook("after-new-window", ["refresh"]);
|
|
53303
|
-
await this.installHook("after-split-window", ["refresh"]);
|
|
53304
|
-
}
|
|
53305
|
-
async stopHooks() {
|
|
53306
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-exited"]);
|
|
53307
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-died"]);
|
|
53308
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-new-window"]);
|
|
53309
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-split-window"]);
|
|
53310
|
-
this.hookReadAbort?.();
|
|
53311
|
-
this.hookReadAbort = null;
|
|
53312
|
-
this.hookBuffer = "";
|
|
53313
|
-
}
|
|
53314
|
-
async installHook(hookName, fields) {
|
|
53315
|
-
const fifoPath = this.fsPaths.hookFifoPath;
|
|
53316
|
-
const innerScript = `printf '%s\\t%s\\t%s\\n' ${fields.map((field) => quoteShellArg(field)).join(" ")} >> ${quoteShellArg(fifoPath)}`;
|
|
53317
|
-
await this.runTmux([
|
|
53318
|
-
"set-hook",
|
|
53670
|
+
async createParkingWindow() {
|
|
53671
|
+
const result = await this.runTmuxAllowFailure([
|
|
53672
|
+
"new-window",
|
|
53319
53673
|
"-t",
|
|
53320
53674
|
this.sessionName,
|
|
53321
|
-
|
|
53322
|
-
|
|
53675
|
+
"-n",
|
|
53676
|
+
PARKING_WINDOW_NAME,
|
|
53677
|
+
"-P",
|
|
53678
|
+
"-F",
|
|
53679
|
+
"#{window_id}",
|
|
53680
|
+
"sleep 30"
|
|
53323
53681
|
]);
|
|
53682
|
+
if (result.exitCode !== 0) {
|
|
53683
|
+
console.warn(`[local] failed to create parking window on ${this.deviceId}, attaching without focus shield`);
|
|
53684
|
+
return null;
|
|
53685
|
+
}
|
|
53686
|
+
return result.stdout.trim() || null;
|
|
53324
53687
|
}
|
|
53325
|
-
|
|
53326
|
-
|
|
53327
|
-
|
|
53328
|
-
|
|
53329
|
-
|
|
53330
|
-
|
|
53331
|
-
|
|
53688
|
+
async removeParkingWindow(windowId) {
|
|
53689
|
+
if (!windowId) {
|
|
53690
|
+
return;
|
|
53691
|
+
}
|
|
53692
|
+
await this.runTmuxAllowFailure(["last-window", "-t", this.sessionName]);
|
|
53693
|
+
await this.runTmuxAllowFailure(["kill-window", "-t", windowId]);
|
|
53694
|
+
}
|
|
53695
|
+
async startControlClient() {
|
|
53696
|
+
let attachReadyResolve = null;
|
|
53697
|
+
const attachReady = new Promise((resolve) => {
|
|
53698
|
+
attachReadyResolve = resolve;
|
|
53699
|
+
});
|
|
53700
|
+
const parkingWindowId = await this.createParkingWindow();
|
|
53701
|
+
let proc;
|
|
53702
|
+
try {
|
|
53703
|
+
proc = this.spawnControlClientProcess(() => {
|
|
53704
|
+
attachReadyResolve?.();
|
|
53705
|
+
attachReadyResolve = null;
|
|
53706
|
+
});
|
|
53707
|
+
await Promise.race([
|
|
53708
|
+
attachReady,
|
|
53709
|
+
new Promise((resolve) => setTimeout(resolve, CONTROL_ATTACH_READY_TIMEOUT_MS))
|
|
53710
|
+
]);
|
|
53711
|
+
} finally {
|
|
53712
|
+
await this.removeParkingWindow(parkingWindowId);
|
|
53713
|
+
}
|
|
53714
|
+
if (this.controlProcess !== proc) {
|
|
53715
|
+
const message = this.controlStderrTail.trim() || "tmux control client exited during attach";
|
|
53716
|
+
console.warn(`[local] tmux control client died during attach on ${this.deviceId}: ${message}`);
|
|
53717
|
+
throw new Error(message);
|
|
53718
|
+
}
|
|
53719
|
+
}
|
|
53720
|
+
spawnControlClientProcess(onAttachReady) {
|
|
53721
|
+
const subscription = createControlModeSubscription({
|
|
53722
|
+
onTerminalOutput: (paneId, data) => {
|
|
53723
|
+
this.callbacks.onTerminalOutput(paneId, data);
|
|
53724
|
+
},
|
|
53725
|
+
onTitle: (paneId, title) => {
|
|
53726
|
+
this.pendingPaneTitles.set(paneId, title);
|
|
53727
|
+
this.requestSnapshot();
|
|
53728
|
+
},
|
|
53729
|
+
onBell: (paneId) => {
|
|
53730
|
+
this.recordBell(paneId);
|
|
53731
|
+
},
|
|
53732
|
+
onNotification: (paneId, notification) => {
|
|
53733
|
+
this.emitNotification(paneId, notification);
|
|
53734
|
+
},
|
|
53735
|
+
onStructureChanged: () => {
|
|
53736
|
+
this.requestSnapshot();
|
|
53737
|
+
},
|
|
53738
|
+
onExit: () => {},
|
|
53739
|
+
onBlockEnd: () => {
|
|
53740
|
+
onAttachReady();
|
|
53332
53741
|
}
|
|
53333
|
-
|
|
53334
|
-
|
|
53335
|
-
|
|
53336
|
-
|
|
53742
|
+
});
|
|
53743
|
+
const proc = this.deps.spawnControlClient([
|
|
53744
|
+
"tmux",
|
|
53745
|
+
"-C",
|
|
53746
|
+
"attach-session",
|
|
53747
|
+
"-t",
|
|
53748
|
+
this.sessionName
|
|
53749
|
+
]);
|
|
53750
|
+
this.controlProcess = proc;
|
|
53751
|
+
this.controlSubscription = subscription;
|
|
53752
|
+
this.controlStartedAt = Date.now();
|
|
53753
|
+
this.controlStderrTail = "";
|
|
53754
|
+
this.pumpControlStdout(proc, subscription);
|
|
53755
|
+
this.pumpControlStderr(proc);
|
|
53756
|
+
proc.exited.then((exitCode) => {
|
|
53757
|
+
this.handleControlClientExit(proc, exitCode);
|
|
53758
|
+
}).catch(() => {
|
|
53759
|
+
this.handleControlClientExit(proc, -1);
|
|
53760
|
+
});
|
|
53761
|
+
return proc;
|
|
53762
|
+
}
|
|
53763
|
+
async pumpControlStdout(proc, subscription) {
|
|
53764
|
+
const reader = proc.stdout.getReader();
|
|
53765
|
+
try {
|
|
53766
|
+
while (true) {
|
|
53767
|
+
const chunk2 = await reader.read();
|
|
53768
|
+
if (chunk2.done || this.controlProcess !== proc) {
|
|
53769
|
+
break;
|
|
53770
|
+
}
|
|
53771
|
+
subscription.push(chunk2.value);
|
|
53337
53772
|
}
|
|
53338
|
-
|
|
53339
|
-
if (
|
|
53340
|
-
|
|
53773
|
+
} catch (error) {
|
|
53774
|
+
if (!this.manualDisconnect && !shouldIgnoreReaderAbortError(error)) {
|
|
53775
|
+
this.callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
53341
53776
|
}
|
|
53342
|
-
|
|
53343
|
-
|
|
53777
|
+
}
|
|
53778
|
+
subscription.end();
|
|
53779
|
+
}
|
|
53780
|
+
async pumpControlStderr(proc) {
|
|
53781
|
+
const reader = proc.stderr.getReader();
|
|
53782
|
+
const decoder3 = new TextDecoder;
|
|
53783
|
+
try {
|
|
53784
|
+
while (true) {
|
|
53785
|
+
const chunk2 = await reader.read();
|
|
53786
|
+
if (chunk2.done) {
|
|
53787
|
+
break;
|
|
53788
|
+
}
|
|
53789
|
+
if (this.controlProcess === proc) {
|
|
53790
|
+
this.controlStderrTail = (this.controlStderrTail + decoder3.decode(chunk2.value)).slice(-CONTROL_STDERR_TAIL_LIMIT);
|
|
53791
|
+
}
|
|
53344
53792
|
}
|
|
53793
|
+
} catch {}
|
|
53794
|
+
}
|
|
53795
|
+
stopControlClient() {
|
|
53796
|
+
const proc = this.controlProcess;
|
|
53797
|
+
this.controlProcess = null;
|
|
53798
|
+
this.controlSubscription?.dispose();
|
|
53799
|
+
this.controlSubscription = null;
|
|
53800
|
+
proc?.kill();
|
|
53801
|
+
}
|
|
53802
|
+
handleControlClientExit(proc, exitCode) {
|
|
53803
|
+
if (this.controlProcess !== proc) {
|
|
53804
|
+
return;
|
|
53805
|
+
}
|
|
53806
|
+
this.controlProcess = null;
|
|
53807
|
+
this.controlSubscription?.dispose();
|
|
53808
|
+
this.controlSubscription = null;
|
|
53809
|
+
if (!this.connected || this.manualDisconnect) {
|
|
53810
|
+
return;
|
|
53811
|
+
}
|
|
53812
|
+
this.reconnectControlClient(exitCode);
|
|
53813
|
+
}
|
|
53814
|
+
async reconnectControlClient(exitCode) {
|
|
53815
|
+
if (Date.now() - this.controlStartedAt > CONTROL_STABLE_RESET_MS) {
|
|
53816
|
+
this.controlRestartCount = 0;
|
|
53817
|
+
}
|
|
53818
|
+
this.controlRestartCount += 1;
|
|
53819
|
+
const stderrMessage = this.controlStderrTail.trim();
|
|
53820
|
+
if (this.controlRestartCount > CONTROL_MAX_RESTARTS) {
|
|
53821
|
+
const message = stderrMessage || `tmux control client exited repeatedly (last code ${exitCode})`;
|
|
53822
|
+
console.warn(`[local] tmux control client gave up on ${this.deviceId}: ${message}`);
|
|
53823
|
+
this.notifyRuntimeError(message);
|
|
53824
|
+
this.shutdownInternal(true);
|
|
53825
|
+
return;
|
|
53826
|
+
}
|
|
53827
|
+
console.warn(`[local] tmux control client exited (code ${exitCode}) on ${this.deviceId}, reconnecting (attempt ${this.controlRestartCount})`);
|
|
53828
|
+
await new Promise((resolve) => setTimeout(resolve, CONTROL_RESTART_DELAY_MS * this.controlRestartCount));
|
|
53829
|
+
if (!this.connected || this.manualDisconnect) {
|
|
53830
|
+
return;
|
|
53831
|
+
}
|
|
53832
|
+
const probe = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
|
|
53833
|
+
if (probe.exitCode !== 0) {
|
|
53834
|
+
const message = probe.stderr.trim() || probe.stdout.trim() || "tmux session gone";
|
|
53835
|
+
console.warn(`[local] tmux session gone on ${this.deviceId}: ${message}`);
|
|
53836
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
53837
|
+
lastSeenAt: new Date().toISOString(),
|
|
53838
|
+
tmuxAvailable: false,
|
|
53839
|
+
lastError: message
|
|
53840
|
+
});
|
|
53841
|
+
this.shutdownInternal(true);
|
|
53842
|
+
return;
|
|
53843
|
+
}
|
|
53844
|
+
if (!this.connected || this.manualDisconnect) {
|
|
53845
|
+
return;
|
|
53846
|
+
}
|
|
53847
|
+
try {
|
|
53848
|
+
await this.startControlClient();
|
|
53849
|
+
} catch (error) {
|
|
53850
|
+
console.warn(`[local] control client restart failed on ${this.deviceId}:`, error);
|
|
53851
|
+
return;
|
|
53852
|
+
}
|
|
53853
|
+
this.requestSnapshot();
|
|
53854
|
+
if (this.activePaneId) {
|
|
53855
|
+
this.capturePaneHistory(this.activePaneId).catch(() => {
|
|
53856
|
+
return;
|
|
53857
|
+
});
|
|
53345
53858
|
}
|
|
53346
53859
|
}
|
|
53347
53860
|
async runAndRefresh(argv, allowTargetMissing = false) {
|
|
@@ -53381,13 +53894,13 @@ class LocalExternalTmuxConnection {
|
|
|
53381
53894
|
await this.requestSnapshotInternal();
|
|
53382
53895
|
}
|
|
53383
53896
|
async capturePaneHistory(paneId) {
|
|
53384
|
-
const
|
|
53385
|
-
const alternateScreen =
|
|
53386
|
-
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-N", "-p"], true)).stdout;
|
|
53387
|
-
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-N", "-p", "-q"], true)).stdout;
|
|
53897
|
+
const screenInfo = parsePaneScreenInfo((await this.runTmux(["display-message", "-p", "-t", paneId, PANE_SCREEN_INFO_FORMAT], true)).stdout);
|
|
53898
|
+
const alternateScreen = screenInfo.alternateScreen;
|
|
53899
|
+
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-J", "-N", "-p"], true)).stdout;
|
|
53900
|
+
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-J", "-N", "-p", "-q"], true)).stdout;
|
|
53388
53901
|
const history = alternateScreen ? hasRenderableTerminalContent(normal) ? normal : alternate : normal || alternate;
|
|
53389
53902
|
if (history) {
|
|
53390
|
-
this.callbacks.onTerminalHistory(paneId, history, alternateScreen);
|
|
53903
|
+
this.callbacks.onTerminalHistory(paneId, appendCursorRestore(history, screenInfo), alternateScreen);
|
|
53391
53904
|
}
|
|
53392
53905
|
}
|
|
53393
53906
|
async requestSnapshotInternal() {
|
|
@@ -53438,7 +53951,7 @@ ${panesRes.stderr}`;
|
|
|
53438
53951
|
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
53439
53952
|
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
53440
53953
|
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
53441
|
-
|
|
53954
|
+
this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
|
|
53442
53955
|
this.emitSnapshot();
|
|
53443
53956
|
}
|
|
53444
53957
|
parseSnapshotSession(lines) {
|
|
@@ -53564,108 +54077,6 @@ ${panesRes.stderr}`;
|
|
|
53564
54077
|
getExpectedPaneIds() {
|
|
53565
54078
|
return Array.from(this.snapshotWindows.values()).sort((left, right) => left.index - right.index).flatMap((window2) => window2.panes.map((pane) => pane.id));
|
|
53566
54079
|
}
|
|
53567
|
-
async startPipeForPaneNow(paneId) {
|
|
53568
|
-
if (this.paneReaders.has(paneId)) {
|
|
53569
|
-
return;
|
|
53570
|
-
}
|
|
53571
|
-
const fifoPath = this.fsPaths.paneFifoPath(paneId);
|
|
53572
|
-
this.ensureRuntimeDirs();
|
|
53573
|
-
rmSync(fifoPath, { force: true });
|
|
53574
|
-
await this.runShell(`mkfifo ${quoteShellArg(fifoPath)}`);
|
|
53575
|
-
const parser = createPaneStreamParser({
|
|
53576
|
-
onTitle: (title) => {
|
|
53577
|
-
this.pendingPaneTitles.set(paneId, title);
|
|
53578
|
-
this.requestSnapshot();
|
|
53579
|
-
},
|
|
53580
|
-
onBell: () => {
|
|
53581
|
-
this.recordBell(paneId);
|
|
53582
|
-
},
|
|
53583
|
-
onNotification: (notification) => {
|
|
53584
|
-
this.emitNotification(paneId, notification);
|
|
53585
|
-
}
|
|
53586
|
-
});
|
|
53587
|
-
const readerProcess = Bun.spawn(["/bin/sh", "-lc", `cat ${quoteShellArg(fifoPath)}`], {
|
|
53588
|
-
stdout: "pipe",
|
|
53589
|
-
stderr: "pipe"
|
|
53590
|
-
});
|
|
53591
|
-
const reader = readerProcess.stdout.getReader();
|
|
53592
|
-
const stopReader = () => {
|
|
53593
|
-
reader.releaseLock();
|
|
53594
|
-
readerProcess.kill();
|
|
53595
|
-
rmSync(fifoPath, { force: true });
|
|
53596
|
-
};
|
|
53597
|
-
this.paneReaders.set(paneId, { paneId, fifoPath, stopReader });
|
|
53598
|
-
(async () => {
|
|
53599
|
-
try {
|
|
53600
|
-
while (true) {
|
|
53601
|
-
const chunk2 = await reader.read();
|
|
53602
|
-
if (chunk2.done) {
|
|
53603
|
-
break;
|
|
53604
|
-
}
|
|
53605
|
-
const output = parser.push(chunk2.value);
|
|
53606
|
-
if (output.length > 0) {
|
|
53607
|
-
this.callbacks.onTerminalOutput(paneId, output);
|
|
53608
|
-
}
|
|
53609
|
-
}
|
|
53610
|
-
} catch (error) {
|
|
53611
|
-
if (!this.manualDisconnect && !shouldIgnoreReaderAbortError(error)) {
|
|
53612
|
-
this.callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
53613
|
-
}
|
|
53614
|
-
}
|
|
53615
|
-
})();
|
|
53616
|
-
try {
|
|
53617
|
-
await this.runTmux(["pipe-pane", "-O", "-t", paneId, `cat >${fifoPath}`]);
|
|
53618
|
-
} catch (error) {
|
|
53619
|
-
this.paneReaders.delete(paneId);
|
|
53620
|
-
stopReader();
|
|
53621
|
-
throw error;
|
|
53622
|
-
}
|
|
53623
|
-
}
|
|
53624
|
-
async stopPipeForPaneNow(paneId) {
|
|
53625
|
-
const handle = this.paneReaders.get(paneId);
|
|
53626
|
-
if (!handle) {
|
|
53627
|
-
return;
|
|
53628
|
-
}
|
|
53629
|
-
this.paneReaders.delete(paneId);
|
|
53630
|
-
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
53631
|
-
handle.stopReader();
|
|
53632
|
-
}
|
|
53633
|
-
async syncPipeReaders() {
|
|
53634
|
-
const expectedPaneIds = this.getExpectedPaneIds();
|
|
53635
|
-
const expectedSet = new Set(expectedPaneIds);
|
|
53636
|
-
await this.queuePipeTransition(async () => {
|
|
53637
|
-
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
53638
|
-
if (!expectedSet.has(paneId)) {
|
|
53639
|
-
await this.stopPipeForPaneNow(paneId);
|
|
53640
|
-
}
|
|
53641
|
-
}
|
|
53642
|
-
for (const paneId of expectedPaneIds) {
|
|
53643
|
-
if (!this.paneReaders.has(paneId)) {
|
|
53644
|
-
await this.startPipeForPaneNow(paneId);
|
|
53645
|
-
}
|
|
53646
|
-
}
|
|
53647
|
-
});
|
|
53648
|
-
}
|
|
53649
|
-
async stopAllPipeReaders() {
|
|
53650
|
-
await this.queuePipeTransition(async () => {
|
|
53651
|
-
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
53652
|
-
await this.stopPipeForPaneNow(paneId);
|
|
53653
|
-
}
|
|
53654
|
-
});
|
|
53655
|
-
}
|
|
53656
|
-
queuePipeTransition(task) {
|
|
53657
|
-
const next = this.pipeTransition.catch(() => {
|
|
53658
|
-
return;
|
|
53659
|
-
}).then(task);
|
|
53660
|
-
this.pipeTransition = next;
|
|
53661
|
-
return next;
|
|
53662
|
-
}
|
|
53663
|
-
async runShell(command) {
|
|
53664
|
-
const result = await this.deps.run(["/bin/sh", "-lc", command]);
|
|
53665
|
-
if (result.exitCode !== 0) {
|
|
53666
|
-
throw new Error(result.stderr.trim() || `shell command failed: ${command}`);
|
|
53667
|
-
}
|
|
53668
|
-
}
|
|
53669
54080
|
async runTmux(argv, allowTargetMissing = false) {
|
|
53670
54081
|
const result = await this.runTmuxAllowFailure(argv);
|
|
53671
54082
|
if (result.exitCode === 0) {
|
|
@@ -53722,17 +54133,7 @@ ${panesRes.stderr}`;
|
|
|
53722
54133
|
}
|
|
53723
54134
|
this.connected = false;
|
|
53724
54135
|
this.cleanupPromise = (async () => {
|
|
53725
|
-
|
|
53726
|
-
return;
|
|
53727
|
-
});
|
|
53728
|
-
if (this.deps.enableHooks) {
|
|
53729
|
-
await this.stopHooks().catch(() => {
|
|
53730
|
-
return;
|
|
53731
|
-
});
|
|
53732
|
-
}
|
|
53733
|
-
try {
|
|
53734
|
-
rmSync(this.fsPaths.rootDir, { recursive: true, force: true });
|
|
53735
|
-
} catch {}
|
|
54136
|
+
this.stopControlClient();
|
|
53736
54137
|
})();
|
|
53737
54138
|
await this.cleanupPromise;
|
|
53738
54139
|
this.cleanupPromise = null;
|
|
@@ -53756,9 +54157,17 @@ ${panesRes.stderr}`;
|
|
|
53756
54157
|
// ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
|
|
53757
54158
|
var import_ssh2 = __toESM(require_lib3(), 1);
|
|
53758
54159
|
|
|
54160
|
+
// ../../apps/gateway/src/tmux-client/command-builder.ts
|
|
54161
|
+
function quoteShellArg(value) {
|
|
54162
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
54163
|
+
}
|
|
54164
|
+
function joinShellArgs(argv) {
|
|
54165
|
+
return argv.map((arg) => quoteShellArg(arg)).join(" ");
|
|
54166
|
+
}
|
|
54167
|
+
|
|
53759
54168
|
// ../../apps/gateway/src/tmux-client/ssh-connect-config.ts
|
|
53760
54169
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
53761
|
-
import { join as
|
|
54170
|
+
import { join as join3 } from "path";
|
|
53762
54171
|
|
|
53763
54172
|
// ../../apps/gateway/src/tmux/ssh-auth.ts
|
|
53764
54173
|
function normalizeEnvValue(value) {
|
|
@@ -53811,7 +54220,7 @@ function expandHomePath(value, env) {
|
|
|
53811
54220
|
return env.HOME?.trim() || trimmed;
|
|
53812
54221
|
}
|
|
53813
54222
|
if (trimmed.startsWith("~/") && env.HOME?.trim()) {
|
|
53814
|
-
return
|
|
54223
|
+
return join3(env.HOME.trim(), trimmed.slice(2));
|
|
53815
54224
|
}
|
|
53816
54225
|
return trimmed;
|
|
53817
54226
|
}
|
|
@@ -54094,6 +54503,12 @@ function hasRenderableTerminalContent2(value) {
|
|
|
54094
54503
|
var BELL_DEDUP_WINDOW_MS2 = 200;
|
|
54095
54504
|
var COMMAND_SENTINEL = "\x1ETMEX_END ";
|
|
54096
54505
|
var SNAPSHOT_FIELD_SEPARATOR = "|";
|
|
54506
|
+
var CONTROL_MAX_RESTARTS2 = 3;
|
|
54507
|
+
var CONTROL_RESTART_DELAY_MS2 = 500;
|
|
54508
|
+
var CONTROL_STABLE_RESET_MS2 = 1e4;
|
|
54509
|
+
var CONTROL_STDERR_TAIL_LIMIT2 = 2048;
|
|
54510
|
+
var CONTROL_ATTACH_READY_TIMEOUT_MS2 = 3000;
|
|
54511
|
+
var PARKING_WINDOW_NAME2 = "tmex-park";
|
|
54097
54512
|
function splitSnapshotFields(line, fieldCount) {
|
|
54098
54513
|
const parts = line.split(SNAPSHOT_FIELD_SEPARATOR);
|
|
54099
54514
|
if (parts.length <= fieldCount) {
|
|
@@ -54134,16 +54549,12 @@ class SshExternalTmuxConnection {
|
|
|
54134
54549
|
pendingPaneTitles = new Map;
|
|
54135
54550
|
snapshotSession = null;
|
|
54136
54551
|
snapshotWindows = new Map;
|
|
54137
|
-
paneReaders = new Map;
|
|
54138
|
-
pipeTransition = Promise.resolve();
|
|
54139
|
-
hookReadAbort = null;
|
|
54140
|
-
hookBuffer = "";
|
|
54141
54552
|
bellDedup = new Map;
|
|
54142
|
-
|
|
54143
|
-
|
|
54144
|
-
|
|
54145
|
-
|
|
54146
|
-
|
|
54553
|
+
controlChannel = null;
|
|
54554
|
+
controlSubscription = null;
|
|
54555
|
+
controlStartedAt = 0;
|
|
54556
|
+
controlRestartCount = 0;
|
|
54557
|
+
controlStderrTail = "";
|
|
54147
54558
|
sshClient = null;
|
|
54148
54559
|
commandStream = null;
|
|
54149
54560
|
commandStdoutBuffer = "";
|
|
@@ -54171,17 +54582,11 @@ class SshExternalTmuxConnection {
|
|
|
54171
54582
|
throw new Error(`SshExternalTmuxConnection only supports ssh device: ${this.deviceId}`);
|
|
54172
54583
|
}
|
|
54173
54584
|
this.sessionName = this.device.session?.trim() || "tmex";
|
|
54174
|
-
this.fsPaths = createRuntimeFsPaths({
|
|
54175
|
-
deviceId: this.deviceId,
|
|
54176
|
-
sessionName: this.sessionName,
|
|
54177
|
-
gatewayPid: process.pid
|
|
54178
|
-
});
|
|
54179
54585
|
await this.connectSshClient();
|
|
54180
54586
|
await this.openCommandChannel();
|
|
54181
|
-
await this.ensureRemoteRuntimeDirs();
|
|
54182
54587
|
await this.ensureSession();
|
|
54183
54588
|
await this.configureSessionOptions();
|
|
54184
|
-
await this.
|
|
54589
|
+
await this.startControlClient();
|
|
54185
54590
|
this.connected = true;
|
|
54186
54591
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
54187
54592
|
lastSeenAt: new Date().toISOString(),
|
|
@@ -54371,17 +54776,16 @@ class SshExternalTmuxConnection {
|
|
|
54371
54776
|
}
|
|
54372
54777
|
this.tmuxBin = parsed.tmuxBin;
|
|
54373
54778
|
this.remoteHomeDir = parsed.homeDir;
|
|
54374
|
-
|
|
54375
|
-
|
|
54376
|
-
|
|
54377
|
-
|
|
54378
|
-
|
|
54379
|
-
|
|
54380
|
-
|
|
54381
|
-
|
|
54382
|
-
|
|
54383
|
-
|
|
54384
|
-
`));
|
|
54779
|
+
const version2 = parseTmuxVersion(parsed.tmuxVersion);
|
|
54780
|
+
if (!isControlModeSupported(version2)) {
|
|
54781
|
+
const message = `remote tmux too old for tmex (control mode requires tmux >= 3.0, found ${parsed.tmuxVersion || "unknown"})`;
|
|
54782
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
54783
|
+
lastSeenAt: new Date().toISOString(),
|
|
54784
|
+
tmuxAvailable: false,
|
|
54785
|
+
lastError: message
|
|
54786
|
+
});
|
|
54787
|
+
throw new Error(message);
|
|
54788
|
+
}
|
|
54385
54789
|
}
|
|
54386
54790
|
async ensureSession() {
|
|
54387
54791
|
const exists3 = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
|
|
@@ -54408,7 +54812,14 @@ class SshExternalTmuxConnection {
|
|
|
54408
54812
|
"extended-keys-format",
|
|
54409
54813
|
"csi-u"
|
|
54410
54814
|
]);
|
|
54411
|
-
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "
|
|
54815
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "off"]);
|
|
54816
|
+
await this.runTmuxAllowFailure([
|
|
54817
|
+
"set-option",
|
|
54818
|
+
"-t",
|
|
54819
|
+
this.sessionName,
|
|
54820
|
+
"destroy-unattached",
|
|
54821
|
+
"off"
|
|
54822
|
+
]);
|
|
54412
54823
|
const termProgram = config.tmuxTermProgram.trim();
|
|
54413
54824
|
if (termProgram && termProgram.toLowerCase() !== "off") {
|
|
54414
54825
|
await this.runTmuxAllowFailure([
|
|
@@ -54437,71 +54848,167 @@ class SshExternalTmuxConnection {
|
|
|
54437
54848
|
return false;
|
|
54438
54849
|
}
|
|
54439
54850
|
}
|
|
54440
|
-
async
|
|
54441
|
-
await this.
|
|
54442
|
-
|
|
54443
|
-
|
|
54444
|
-
|
|
54851
|
+
async createParkingWindow() {
|
|
54852
|
+
const result = await this.runTmuxAllowFailure([
|
|
54853
|
+
"new-window",
|
|
54854
|
+
"-t",
|
|
54855
|
+
this.sessionName,
|
|
54856
|
+
"-n",
|
|
54857
|
+
PARKING_WINDOW_NAME2,
|
|
54858
|
+
"-P",
|
|
54859
|
+
"-F",
|
|
54860
|
+
"#{window_id}",
|
|
54861
|
+
"sleep 30"
|
|
54862
|
+
]);
|
|
54863
|
+
if (result.exitCode !== 0) {
|
|
54864
|
+
console.warn(`[ssh] failed to create parking window on ${this.deviceId}, attaching without focus shield`);
|
|
54865
|
+
return null;
|
|
54866
|
+
}
|
|
54867
|
+
return result.stdout.trim() || null;
|
|
54868
|
+
}
|
|
54869
|
+
async removeParkingWindow(windowId) {
|
|
54870
|
+
if (!windowId) {
|
|
54871
|
+
return;
|
|
54872
|
+
}
|
|
54873
|
+
await this.runTmuxAllowFailure(["last-window", "-t", this.sessionName]);
|
|
54874
|
+
await this.runTmuxAllowFailure(["kill-window", "-t", windowId]);
|
|
54875
|
+
}
|
|
54876
|
+
async startControlClient() {
|
|
54877
|
+
let attachReadyResolve = null;
|
|
54878
|
+
const attachReady = new Promise((resolve) => {
|
|
54879
|
+
attachReadyResolve = resolve;
|
|
54880
|
+
});
|
|
54881
|
+
const parkingWindowId = await this.createParkingWindow();
|
|
54882
|
+
let handle;
|
|
54883
|
+
try {
|
|
54884
|
+
handle = await this.openControlChannel(() => {
|
|
54885
|
+
attachReadyResolve?.();
|
|
54886
|
+
attachReadyResolve = null;
|
|
54887
|
+
});
|
|
54888
|
+
await Promise.race([
|
|
54889
|
+
attachReady,
|
|
54890
|
+
new Promise((resolve) => setTimeout(resolve, CONTROL_ATTACH_READY_TIMEOUT_MS2))
|
|
54891
|
+
]);
|
|
54892
|
+
} finally {
|
|
54893
|
+
await this.removeParkingWindow(parkingWindowId);
|
|
54894
|
+
}
|
|
54895
|
+
if (this.controlChannel !== handle) {
|
|
54896
|
+
throw new Error(this.controlStderrTail.trim() || "tmux control client channel closed during attach");
|
|
54897
|
+
}
|
|
54898
|
+
}
|
|
54899
|
+
async openControlChannel(onAttachReady) {
|
|
54900
|
+
const subscription = createControlModeSubscription({
|
|
54901
|
+
onTerminalOutput: (paneId, data) => {
|
|
54902
|
+
this.callbacks.onTerminalOutput(paneId, data);
|
|
54903
|
+
},
|
|
54904
|
+
onTitle: (paneId, title) => {
|
|
54905
|
+
this.pendingPaneTitles.set(paneId, title);
|
|
54906
|
+
this.requestSnapshot();
|
|
54907
|
+
},
|
|
54908
|
+
onBell: (paneId) => {
|
|
54909
|
+
this.recordBell(paneId);
|
|
54910
|
+
},
|
|
54911
|
+
onNotification: (paneId, notification) => {
|
|
54912
|
+
this.emitNotification(paneId, notification);
|
|
54913
|
+
},
|
|
54914
|
+
onStructureChanged: () => {
|
|
54915
|
+
this.requestSnapshot();
|
|
54916
|
+
},
|
|
54917
|
+
onExit: () => {},
|
|
54918
|
+
onBlockEnd: () => {
|
|
54919
|
+
onAttachReady();
|
|
54920
|
+
}
|
|
54921
|
+
});
|
|
54922
|
+
const handle = { stop: () => {} };
|
|
54923
|
+
this.controlChannel = handle;
|
|
54924
|
+
this.controlSubscription = subscription;
|
|
54925
|
+
this.controlStartedAt = Date.now();
|
|
54926
|
+
this.controlStderrTail = "";
|
|
54927
|
+
const stopReader = await this.openReaderChannel(`exec ${quoteShellArg(this.tmuxBin)} -C attach-session -t ${quoteShellArg(this.sessionName)}`, {
|
|
54445
54928
|
onData: (data) => {
|
|
54446
|
-
this.
|
|
54929
|
+
if (this.controlChannel === handle) {
|
|
54930
|
+
subscription.push(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
|
54931
|
+
}
|
|
54447
54932
|
},
|
|
54448
|
-
|
|
54449
|
-
if (this.
|
|
54450
|
-
|
|
54933
|
+
onStderr: (data) => {
|
|
54934
|
+
if (this.controlChannel === handle) {
|
|
54935
|
+
this.controlStderrTail = (this.controlStderrTail + data.toString()).slice(-CONTROL_STDERR_TAIL_LIMIT2);
|
|
54451
54936
|
}
|
|
54452
|
-
|
|
54453
|
-
|
|
54937
|
+
},
|
|
54938
|
+
onClose: () => {
|
|
54939
|
+
this.handleControlChannelClose(handle);
|
|
54454
54940
|
}
|
|
54455
54941
|
});
|
|
54456
|
-
|
|
54457
|
-
|
|
54458
|
-
this.runShellAllowFailure(`rm -f ${quoteShellArg(fifoPath)}`);
|
|
54459
|
-
};
|
|
54460
|
-
await this.installHook("pane-exited", ["pane-exited", "#{window_id}", "#{pane_id}"]);
|
|
54461
|
-
await this.installHook("pane-died", ["pane-died", "#{window_id}", "#{pane_id}"]);
|
|
54462
|
-
await this.installHook("after-new-window", ["refresh"]);
|
|
54463
|
-
await this.installHook("after-split-window", ["refresh"]);
|
|
54464
|
-
}
|
|
54465
|
-
async stopHooks() {
|
|
54466
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-exited"]);
|
|
54467
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-died"]);
|
|
54468
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-new-window"]);
|
|
54469
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-split-window"]);
|
|
54470
|
-
this.hookReadAbort?.();
|
|
54471
|
-
this.hookReadAbort = null;
|
|
54472
|
-
this.hookBuffer = "";
|
|
54473
|
-
}
|
|
54474
|
-
async installHook(hookName, fields) {
|
|
54475
|
-
const fifoPath = this.fsPaths.hookFifoPath;
|
|
54476
|
-
const innerScript = `printf '%s\\t%s\\t%s\\n' ${fields.map((field) => quoteShellArg(field)).join(" ")} >> ${quoteShellArg(fifoPath)}`;
|
|
54477
|
-
await this.runTmux([
|
|
54478
|
-
"set-hook",
|
|
54479
|
-
"-t",
|
|
54480
|
-
this.sessionName,
|
|
54481
|
-
hookName,
|
|
54482
|
-
`run-shell -b ${quoteShellArg(innerScript)}`
|
|
54483
|
-
]);
|
|
54942
|
+
handle.stop = stopReader;
|
|
54943
|
+
return handle;
|
|
54484
54944
|
}
|
|
54485
|
-
|
|
54486
|
-
this.
|
|
54487
|
-
|
|
54488
|
-
|
|
54489
|
-
|
|
54490
|
-
|
|
54945
|
+
stopControlClient() {
|
|
54946
|
+
const handle = this.controlChannel;
|
|
54947
|
+
this.controlChannel = null;
|
|
54948
|
+
this.controlSubscription?.dispose();
|
|
54949
|
+
this.controlSubscription = null;
|
|
54950
|
+
handle?.stop();
|
|
54951
|
+
}
|
|
54952
|
+
handleControlChannelClose(handle) {
|
|
54953
|
+
if (this.controlChannel !== handle) {
|
|
54954
|
+
return;
|
|
54955
|
+
}
|
|
54956
|
+
this.controlChannel = null;
|
|
54957
|
+
this.controlSubscription?.dispose();
|
|
54958
|
+
this.controlSubscription = null;
|
|
54959
|
+
if (!this.connected || this.manualDisconnect) {
|
|
54960
|
+
return;
|
|
54961
|
+
}
|
|
54962
|
+
this.reconnectControlClient();
|
|
54963
|
+
}
|
|
54964
|
+
async reconnectControlClient() {
|
|
54965
|
+
if (Date.now() - this.controlStartedAt > CONTROL_STABLE_RESET_MS2) {
|
|
54966
|
+
this.controlRestartCount = 0;
|
|
54967
|
+
}
|
|
54968
|
+
this.controlRestartCount += 1;
|
|
54969
|
+
const stderrMessage = this.controlStderrTail.trim();
|
|
54970
|
+
if (this.controlRestartCount > CONTROL_MAX_RESTARTS2) {
|
|
54971
|
+
const message = stderrMessage || "tmux control client channel closed repeatedly";
|
|
54972
|
+
console.warn(`[ssh] tmux control client gave up on ${this.deviceId}: ${message}`);
|
|
54973
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
54974
|
+
lastSeenAt: new Date().toISOString(),
|
|
54975
|
+
tmuxAvailable: false,
|
|
54976
|
+
lastError: message
|
|
54977
|
+
});
|
|
54978
|
+
this.shutdownInternal(true);
|
|
54979
|
+
return;
|
|
54980
|
+
}
|
|
54981
|
+
console.warn(`[ssh] tmux control client channel closed on ${this.deviceId}, reconnecting (attempt ${this.controlRestartCount})`);
|
|
54982
|
+
await new Promise((resolve) => setTimeout(resolve, CONTROL_RESTART_DELAY_MS2 * this.controlRestartCount));
|
|
54983
|
+
if (!this.connected || this.manualDisconnect) {
|
|
54984
|
+
return;
|
|
54985
|
+
}
|
|
54986
|
+
const probe = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
|
|
54987
|
+
if (probe.exitCode !== 0) {
|
|
54988
|
+
const message = probe.stderr.trim() || probe.stdout.trim() || "tmux session gone";
|
|
54989
|
+
console.warn(`[ssh] tmux session gone on ${this.deviceId}: ${message}`);
|
|
54990
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
54991
|
+
lastSeenAt: new Date().toISOString(),
|
|
54992
|
+
tmuxAvailable: false,
|
|
54993
|
+
lastError: message
|
|
54994
|
+
});
|
|
54995
|
+
this.shutdownInternal(true);
|
|
54996
|
+
return;
|
|
54997
|
+
}
|
|
54998
|
+
if (!this.connected || this.manualDisconnect) {
|
|
54999
|
+
return;
|
|
55000
|
+
}
|
|
55001
|
+
try {
|
|
55002
|
+
await this.startControlClient();
|
|
55003
|
+
} catch (error) {
|
|
55004
|
+
console.warn(`[ssh] control client restart failed on ${this.deviceId}:`, error);
|
|
55005
|
+
return;
|
|
55006
|
+
}
|
|
55007
|
+
this.requestSnapshot();
|
|
55008
|
+
if (this.activePaneId) {
|
|
55009
|
+
this.capturePaneHistory(this.activePaneId).catch(() => {
|
|
54491
55010
|
return;
|
|
54492
|
-
}
|
|
54493
|
-
const line = this.hookBuffer.slice(0, newlineIndex).trim();
|
|
54494
|
-
this.hookBuffer = this.hookBuffer.slice(newlineIndex + 1);
|
|
54495
|
-
if (!line) {
|
|
54496
|
-
continue;
|
|
54497
|
-
}
|
|
54498
|
-
const [type, windowId, paneId] = line.split("\t");
|
|
54499
|
-
if (type === "bell") {
|
|
54500
|
-
continue;
|
|
54501
|
-
}
|
|
54502
|
-
if (type === "pane-exited" || type === "pane-died" || type === "refresh") {
|
|
54503
|
-
this.requestSnapshot();
|
|
54504
|
-
}
|
|
55011
|
+
});
|
|
54505
55012
|
}
|
|
54506
55013
|
}
|
|
54507
55014
|
async runAndRefresh(argv, allowTargetMissing = false) {
|
|
@@ -54541,13 +55048,13 @@ class SshExternalTmuxConnection {
|
|
|
54541
55048
|
await this.requestSnapshotInternal();
|
|
54542
55049
|
}
|
|
54543
55050
|
async capturePaneHistory(paneId) {
|
|
54544
|
-
const
|
|
54545
|
-
const alternateScreen =
|
|
54546
|
-
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-N", "-p"], true, 30000)).stdout;
|
|
54547
|
-
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-N", "-p", "-q"], true, 30000)).stdout;
|
|
55051
|
+
const screenInfo = parsePaneScreenInfo((await this.runTmux(["display-message", "-p", "-t", paneId, PANE_SCREEN_INFO_FORMAT], true)).stdout);
|
|
55052
|
+
const alternateScreen = screenInfo.alternateScreen;
|
|
55053
|
+
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-J", "-N", "-p"], true, 30000)).stdout;
|
|
55054
|
+
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-J", "-N", "-p", "-q"], true, 30000)).stdout;
|
|
54548
55055
|
const history = alternateScreen ? hasRenderableTerminalContent2(normal) ? normal : alternate : normal || alternate;
|
|
54549
55056
|
if (history) {
|
|
54550
|
-
this.callbacks.onTerminalHistory(paneId, history, alternateScreen);
|
|
55057
|
+
this.callbacks.onTerminalHistory(paneId, appendCursorRestore(history, screenInfo), alternateScreen);
|
|
54551
55058
|
}
|
|
54552
55059
|
}
|
|
54553
55060
|
async requestSnapshotInternal() {
|
|
@@ -54598,7 +55105,7 @@ ${panesRes.stderr}`;
|
|
|
54598
55105
|
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
54599
55106
|
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
54600
55107
|
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
54601
|
-
|
|
55108
|
+
this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
|
|
54602
55109
|
this.emitSnapshot();
|
|
54603
55110
|
}
|
|
54604
55111
|
parseSnapshotSession(lines) {
|
|
@@ -54724,104 +55231,6 @@ ${panesRes.stderr}`;
|
|
|
54724
55231
|
getExpectedPaneIds() {
|
|
54725
55232
|
return Array.from(this.snapshotWindows.values()).sort((left, right) => left.index - right.index).flatMap((window2) => window2.panes.map((pane) => pane.id));
|
|
54726
55233
|
}
|
|
54727
|
-
async startPipeForPaneNow(paneId) {
|
|
54728
|
-
if (this.paneReaders.has(paneId)) {
|
|
54729
|
-
return;
|
|
54730
|
-
}
|
|
54731
|
-
const fifoPath = this.fsPaths.paneFifoPath(paneId);
|
|
54732
|
-
await this.ensureRemoteRuntimeDirs();
|
|
54733
|
-
await this.runShell(`rm -f ${quoteShellArg(fifoPath)} && mkfifo ${quoteShellArg(fifoPath)} && chmod 600 ${quoteShellArg(fifoPath)}`);
|
|
54734
|
-
const parser = createPaneStreamParser({
|
|
54735
|
-
onTitle: (title) => {
|
|
54736
|
-
this.pendingPaneTitles.set(paneId, title);
|
|
54737
|
-
this.requestSnapshot();
|
|
54738
|
-
},
|
|
54739
|
-
onBell: () => {
|
|
54740
|
-
this.recordBell(paneId);
|
|
54741
|
-
},
|
|
54742
|
-
onNotification: (notification) => {
|
|
54743
|
-
this.emitNotification(paneId, notification);
|
|
54744
|
-
}
|
|
54745
|
-
});
|
|
54746
|
-
const stopReader = await this.openReaderChannel(`exec cat ${quoteShellArg(fifoPath)}`, {
|
|
54747
|
-
onData: (raw) => {
|
|
54748
|
-
const output = parser.push(raw);
|
|
54749
|
-
if (output.length > 0) {
|
|
54750
|
-
this.callbacks.onTerminalOutput(paneId, output);
|
|
54751
|
-
}
|
|
54752
|
-
},
|
|
54753
|
-
onClose: () => {
|
|
54754
|
-
if (this.manualDisconnect) {
|
|
54755
|
-
return;
|
|
54756
|
-
}
|
|
54757
|
-
const existing = this.paneReaders.get(paneId);
|
|
54758
|
-
if (!existing) {
|
|
54759
|
-
return;
|
|
54760
|
-
}
|
|
54761
|
-
console.warn(`[ssh] pane reader channel closed for ${paneId}, resync on next snapshot`);
|
|
54762
|
-
this.paneReaders.delete(paneId);
|
|
54763
|
-
this.runShellAllowFailure(`rm -f ${quoteShellArg(existing.fifoPath)}`);
|
|
54764
|
-
this.requestSnapshot();
|
|
54765
|
-
}
|
|
54766
|
-
});
|
|
54767
|
-
const handle = {
|
|
54768
|
-
paneId,
|
|
54769
|
-
fifoPath,
|
|
54770
|
-
stopReader: () => {
|
|
54771
|
-
stopReader();
|
|
54772
|
-
this.runShellAllowFailure(`rm -f ${quoteShellArg(fifoPath)}`);
|
|
54773
|
-
}
|
|
54774
|
-
};
|
|
54775
|
-
this.paneReaders.set(paneId, handle);
|
|
54776
|
-
try {
|
|
54777
|
-
await this.runTmux(["pipe-pane", "-O", "-t", paneId, `cat >${fifoPath}`]);
|
|
54778
|
-
} catch (error) {
|
|
54779
|
-
this.paneReaders.delete(paneId);
|
|
54780
|
-
handle.stopReader();
|
|
54781
|
-
throw error;
|
|
54782
|
-
}
|
|
54783
|
-
}
|
|
54784
|
-
async stopPipeForPaneNow(paneId) {
|
|
54785
|
-
const handle = this.paneReaders.get(paneId);
|
|
54786
|
-
if (!handle) {
|
|
54787
|
-
return;
|
|
54788
|
-
}
|
|
54789
|
-
this.paneReaders.delete(paneId);
|
|
54790
|
-
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
54791
|
-
handle.stopReader();
|
|
54792
|
-
}
|
|
54793
|
-
async syncPipeReaders() {
|
|
54794
|
-
const expectedPaneIds = this.getExpectedPaneIds();
|
|
54795
|
-
const expectedSet = new Set(expectedPaneIds);
|
|
54796
|
-
await this.queuePipeTransition(async () => {
|
|
54797
|
-
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
54798
|
-
if (!expectedSet.has(paneId)) {
|
|
54799
|
-
await this.stopPipeForPaneNow(paneId);
|
|
54800
|
-
}
|
|
54801
|
-
}
|
|
54802
|
-
for (const paneId of expectedPaneIds) {
|
|
54803
|
-
if (!this.paneReaders.has(paneId)) {
|
|
54804
|
-
await this.startPipeForPaneNow(paneId);
|
|
54805
|
-
}
|
|
54806
|
-
}
|
|
54807
|
-
});
|
|
54808
|
-
}
|
|
54809
|
-
async stopAllPipeReaders() {
|
|
54810
|
-
await this.queuePipeTransition(async () => {
|
|
54811
|
-
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
54812
|
-
await this.stopPipeForPaneNow(paneId);
|
|
54813
|
-
}
|
|
54814
|
-
});
|
|
54815
|
-
}
|
|
54816
|
-
queuePipeTransition(task) {
|
|
54817
|
-
const next = this.pipeTransition.catch(() => {
|
|
54818
|
-
return;
|
|
54819
|
-
}).then(task);
|
|
54820
|
-
this.pipeTransition = next.catch(() => {
|
|
54821
|
-
return;
|
|
54822
|
-
});
|
|
54823
|
-
return next;
|
|
54824
|
-
}
|
|
54825
55234
|
async runTmux(argv, allowTargetMissing = false, timeoutMs = 1e4) {
|
|
54826
55235
|
const result = await this.runTmuxAllowFailure(argv, timeoutMs);
|
|
54827
55236
|
if (result.exitCode === 0) {
|
|
@@ -54949,6 +55358,10 @@ printf '\\036TMEX_END %s %d\\036\\n' ${quoteShellArg(commandId)} $?
|
|
|
54949
55358
|
options.onData(data);
|
|
54950
55359
|
});
|
|
54951
55360
|
stream.stderr.on("data", (data) => {
|
|
55361
|
+
if (options.onStderr) {
|
|
55362
|
+
options.onStderr(data);
|
|
55363
|
+
return;
|
|
55364
|
+
}
|
|
54952
55365
|
if (!this.manualDisconnect) {
|
|
54953
55366
|
this.callbacks.onError(new Error(data.toString().trim() || "SSH reader stderr output"));
|
|
54954
55367
|
}
|
|
@@ -54993,15 +55406,7 @@ printf '\\036TMEX_END %s %d\\036\\n' ${quoteShellArg(commandId)} $?
|
|
|
54993
55406
|
}
|
|
54994
55407
|
this.connected = false;
|
|
54995
55408
|
this.cleanupPromise = (async () => {
|
|
54996
|
-
|
|
54997
|
-
return;
|
|
54998
|
-
});
|
|
54999
|
-
await this.stopHooks().catch(() => {
|
|
55000
|
-
return;
|
|
55001
|
-
});
|
|
55002
|
-
await this.runShellAllowFailure(`rm -rf ${quoteShellArg(this.fsPaths.rootDir)}`).catch(() => {
|
|
55003
|
-
return;
|
|
55004
|
-
});
|
|
55409
|
+
this.stopControlClient();
|
|
55005
55410
|
this.rejectPendingCommand(new Error("SSH command channel closed"));
|
|
55006
55411
|
this.commandStream?.end();
|
|
55007
55412
|
this.commandStream?.close();
|
|
@@ -57816,7 +58221,7 @@ async function serveFrontend(req, staticRoot) {
|
|
|
57816
58221
|
if (!requestedPath) {
|
|
57817
58222
|
return new Response(t3("runtime.forbidden"), { status: 403 });
|
|
57818
58223
|
}
|
|
57819
|
-
const indexPath =
|
|
58224
|
+
const indexPath = join4(staticRoot, "index.html");
|
|
57820
58225
|
const targetPath = existsSync4(requestedPath) ? requestedPath : indexPath;
|
|
57821
58226
|
if (!existsSync4(targetPath)) {
|
|
57822
58227
|
return new Response(t3("runtime.frontendMissing"), { status: 500 });
|