tmex-cli 0.5.1 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/server.js +925 -553
- package/package.json +1 -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,264 @@ 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/control-mode-parser.ts
|
|
52534
|
+
var decoder = new TextDecoder;
|
|
52535
|
+
var MAX_LINE_BYTES = 4 * 1024 * 1024;
|
|
52536
|
+
var MAX_BLOCK_BODY_LINES = 1000;
|
|
52537
|
+
var BYTE_LF = 10;
|
|
52538
|
+
var BYTE_SPACE = 32;
|
|
52539
|
+
var BYTE_PERCENT = 37;
|
|
52540
|
+
var BYTE_BACKSLASH = 92;
|
|
52541
|
+
var KNOWN_NOTIFICATION_TYPES = new Set([
|
|
52542
|
+
"client-detached",
|
|
52543
|
+
"client-session-changed",
|
|
52544
|
+
"config-error",
|
|
52545
|
+
"continue",
|
|
52546
|
+
"layout-change",
|
|
52547
|
+
"message",
|
|
52548
|
+
"pane-mode-changed",
|
|
52549
|
+
"paste-buffer-changed",
|
|
52550
|
+
"paste-buffer-deleted",
|
|
52551
|
+
"pause",
|
|
52552
|
+
"session-changed",
|
|
52553
|
+
"session-renamed",
|
|
52554
|
+
"session-window-changed",
|
|
52555
|
+
"sessions-changed",
|
|
52556
|
+
"subscription-changed",
|
|
52557
|
+
"unlinked-window-add",
|
|
52558
|
+
"unlinked-window-close",
|
|
52559
|
+
"unlinked-window-renamed",
|
|
52560
|
+
"window-add",
|
|
52561
|
+
"window-close",
|
|
52562
|
+
"window-pane-changed",
|
|
52563
|
+
"window-renamed"
|
|
52564
|
+
]);
|
|
52565
|
+
function isOctalDigit(byte) {
|
|
52566
|
+
return byte >= 48 && byte <= 55;
|
|
52567
|
+
}
|
|
52568
|
+
function unescapeControlModeData(line, start, onInvalidEscape) {
|
|
52569
|
+
const result = new Uint8Array(line.length - start);
|
|
52570
|
+
let written = 0;
|
|
52571
|
+
let index = start;
|
|
52572
|
+
while (index < line.length) {
|
|
52573
|
+
const byte = line[index];
|
|
52574
|
+
if (byte !== BYTE_BACKSLASH) {
|
|
52575
|
+
result[written] = byte;
|
|
52576
|
+
written += 1;
|
|
52577
|
+
index += 1;
|
|
52578
|
+
continue;
|
|
52579
|
+
}
|
|
52580
|
+
const d1 = line[index + 1];
|
|
52581
|
+
const d2 = line[index + 2];
|
|
52582
|
+
const d3 = line[index + 3];
|
|
52583
|
+
if (d1 !== undefined && d2 !== undefined && d3 !== undefined && isOctalDigit(d1) && isOctalDigit(d2) && isOctalDigit(d3)) {
|
|
52584
|
+
result[written] = d1 - 48 << 6 | d2 - 48 << 3 | d3 - 48;
|
|
52585
|
+
written += 1;
|
|
52586
|
+
index += 4;
|
|
52587
|
+
continue;
|
|
52588
|
+
}
|
|
52589
|
+
onInvalidEscape?.();
|
|
52590
|
+
result[written] = byte;
|
|
52591
|
+
written += 1;
|
|
52592
|
+
index += 1;
|
|
52593
|
+
}
|
|
52594
|
+
return result.subarray(0, written);
|
|
52537
52595
|
}
|
|
52538
|
-
function
|
|
52539
|
-
|
|
52596
|
+
function concatChunks(chunks, totalLength) {
|
|
52597
|
+
if (chunks.length === 1) {
|
|
52598
|
+
return chunks[0];
|
|
52599
|
+
}
|
|
52600
|
+
const merged = new Uint8Array(totalLength);
|
|
52601
|
+
let offset = 0;
|
|
52602
|
+
for (const chunk2 of chunks) {
|
|
52603
|
+
merged.set(chunk2, offset);
|
|
52604
|
+
offset += chunk2.length;
|
|
52605
|
+
}
|
|
52606
|
+
return merged;
|
|
52540
52607
|
}
|
|
52541
|
-
|
|
52542
|
-
|
|
52543
|
-
|
|
52544
|
-
|
|
52545
|
-
|
|
52546
|
-
|
|
52547
|
-
|
|
52548
|
-
|
|
52549
|
-
|
|
52550
|
-
|
|
52551
|
-
|
|
52552
|
-
|
|
52553
|
-
|
|
52554
|
-
}
|
|
52555
|
-
function
|
|
52556
|
-
|
|
52557
|
-
|
|
52558
|
-
|
|
52559
|
-
|
|
52560
|
-
|
|
52561
|
-
|
|
52562
|
-
|
|
52608
|
+
function createControlModeParser(callbacks) {
|
|
52609
|
+
let pendingChunks = [];
|
|
52610
|
+
let pendingLength = 0;
|
|
52611
|
+
let discardingOversizedLine = false;
|
|
52612
|
+
let warnedOversizedLine = false;
|
|
52613
|
+
let warnedInvalidEscape = false;
|
|
52614
|
+
let warnedUnexpectedLine = false;
|
|
52615
|
+
let currentBlock = null;
|
|
52616
|
+
function warnInvalidEscape() {
|
|
52617
|
+
if (!warnedInvalidEscape) {
|
|
52618
|
+
warnedInvalidEscape = true;
|
|
52619
|
+
console.warn("[tmex] control mode parser met invalid escape sequence, passing through");
|
|
52620
|
+
}
|
|
52621
|
+
}
|
|
52622
|
+
function findByte(line, byte, from) {
|
|
52623
|
+
for (let index = from;index < line.length; index += 1) {
|
|
52624
|
+
if (line[index] === byte) {
|
|
52625
|
+
return index;
|
|
52626
|
+
}
|
|
52627
|
+
}
|
|
52628
|
+
return -1;
|
|
52629
|
+
}
|
|
52630
|
+
function decodeRange(line, start, end) {
|
|
52631
|
+
return decoder.decode(line.subarray(start, end));
|
|
52632
|
+
}
|
|
52633
|
+
function handleOutputLine(line, payloadStart) {
|
|
52634
|
+
const paneEnd = findByte(line, BYTE_SPACE, payloadStart);
|
|
52635
|
+
if (paneEnd < 0) {
|
|
52636
|
+
return;
|
|
52637
|
+
}
|
|
52638
|
+
const paneId = decodeRange(line, payloadStart, paneEnd);
|
|
52639
|
+
callbacks.onOutput(paneId, unescapeControlModeData(line, paneEnd + 1, warnInvalidEscape));
|
|
52640
|
+
}
|
|
52641
|
+
function handleExtendedOutputLine(line, payloadStart) {
|
|
52642
|
+
const paneEnd = findByte(line, BYTE_SPACE, payloadStart);
|
|
52643
|
+
if (paneEnd < 0) {
|
|
52644
|
+
return;
|
|
52645
|
+
}
|
|
52646
|
+
const paneId = decodeRange(line, payloadStart, paneEnd);
|
|
52647
|
+
for (let index = paneEnd;index + 2 < line.length; index += 1) {
|
|
52648
|
+
if (line[index] === BYTE_SPACE && line[index + 1] === 58 && line[index + 2] === BYTE_SPACE) {
|
|
52649
|
+
callbacks.onOutput(paneId, unescapeControlModeData(line, index + 3, warnInvalidEscape));
|
|
52650
|
+
return;
|
|
52651
|
+
}
|
|
52652
|
+
}
|
|
52653
|
+
}
|
|
52654
|
+
function handleLine(line) {
|
|
52655
|
+
if (line.length === 0) {
|
|
52656
|
+
return;
|
|
52657
|
+
}
|
|
52658
|
+
if (line[0] !== BYTE_PERCENT) {
|
|
52659
|
+
if (currentBlock) {
|
|
52660
|
+
if (currentBlock.lines.length < MAX_BLOCK_BODY_LINES) {
|
|
52661
|
+
currentBlock.lines.push(decoder.decode(line));
|
|
52662
|
+
}
|
|
52663
|
+
return;
|
|
52664
|
+
}
|
|
52665
|
+
if (!warnedUnexpectedLine) {
|
|
52666
|
+
warnedUnexpectedLine = true;
|
|
52667
|
+
console.warn(`[tmex] control mode parser ignored unexpected line: ${decoder.decode(line.subarray(0, 80))}`);
|
|
52668
|
+
}
|
|
52669
|
+
return;
|
|
52670
|
+
}
|
|
52671
|
+
const typeEnd = findByte(line, BYTE_SPACE, 0);
|
|
52672
|
+
const type = typeEnd < 0 ? decodeRange(line, 1, line.length) : decodeRange(line, 1, typeEnd);
|
|
52673
|
+
const argsStart = typeEnd < 0 ? line.length : typeEnd + 1;
|
|
52674
|
+
switch (type) {
|
|
52675
|
+
case "output":
|
|
52676
|
+
handleOutputLine(line, argsStart);
|
|
52677
|
+
return;
|
|
52678
|
+
case "extended-output":
|
|
52679
|
+
handleExtendedOutputLine(line, argsStart);
|
|
52680
|
+
return;
|
|
52681
|
+
case "begin": {
|
|
52682
|
+
if (currentBlock) {
|
|
52683
|
+
callbacks.onBlockEnd?.(currentBlock);
|
|
52684
|
+
}
|
|
52685
|
+
currentBlock = {
|
|
52686
|
+
args: decodeRange(line, argsStart, line.length),
|
|
52687
|
+
isError: false,
|
|
52688
|
+
lines: []
|
|
52689
|
+
};
|
|
52690
|
+
return;
|
|
52691
|
+
}
|
|
52692
|
+
case "end":
|
|
52693
|
+
case "error": {
|
|
52694
|
+
if (!currentBlock) {
|
|
52695
|
+
return;
|
|
52696
|
+
}
|
|
52697
|
+
const args = decodeRange(line, argsStart, line.length);
|
|
52698
|
+
if (args !== currentBlock.args) {
|
|
52699
|
+
console.warn(`[tmex] control mode block guard mismatch: begin "${currentBlock.args}" vs ${type} "${args}"`);
|
|
52700
|
+
}
|
|
52701
|
+
currentBlock.isError = type === "error";
|
|
52702
|
+
callbacks.onBlockEnd?.(currentBlock);
|
|
52703
|
+
currentBlock = null;
|
|
52704
|
+
return;
|
|
52705
|
+
}
|
|
52706
|
+
case "exit": {
|
|
52707
|
+
const reason = argsStart < line.length ? decodeRange(line, argsStart, line.length) : null;
|
|
52708
|
+
callbacks.onExit(reason);
|
|
52709
|
+
return;
|
|
52710
|
+
}
|
|
52711
|
+
default: {
|
|
52712
|
+
if (currentBlock && !KNOWN_NOTIFICATION_TYPES.has(type)) {
|
|
52713
|
+
if (currentBlock.lines.length < MAX_BLOCK_BODY_LINES) {
|
|
52714
|
+
currentBlock.lines.push(decoder.decode(line));
|
|
52715
|
+
}
|
|
52716
|
+
return;
|
|
52717
|
+
}
|
|
52718
|
+
callbacks.onNotification({
|
|
52719
|
+
type,
|
|
52720
|
+
args: decodeRange(line, argsStart, line.length),
|
|
52721
|
+
raw: decoder.decode(line)
|
|
52722
|
+
});
|
|
52723
|
+
return;
|
|
52724
|
+
}
|
|
52725
|
+
}
|
|
52726
|
+
}
|
|
52727
|
+
function takePendingLine(tail) {
|
|
52728
|
+
if (pendingLength === 0) {
|
|
52729
|
+
return tail;
|
|
52730
|
+
}
|
|
52731
|
+
pendingChunks.push(tail);
|
|
52732
|
+
const line = concatChunks(pendingChunks, pendingLength + tail.length);
|
|
52733
|
+
pendingChunks = [];
|
|
52734
|
+
pendingLength = 0;
|
|
52735
|
+
return line;
|
|
52736
|
+
}
|
|
52563
52737
|
return {
|
|
52564
|
-
|
|
52565
|
-
|
|
52566
|
-
|
|
52567
|
-
|
|
52568
|
-
|
|
52569
|
-
|
|
52738
|
+
push(chunk2) {
|
|
52739
|
+
let start = 0;
|
|
52740
|
+
while (start <= chunk2.length) {
|
|
52741
|
+
const newlineIndex = findByte(chunk2, BYTE_LF, start);
|
|
52742
|
+
if (newlineIndex < 0) {
|
|
52743
|
+
break;
|
|
52744
|
+
}
|
|
52745
|
+
const tail = chunk2.subarray(start, newlineIndex);
|
|
52746
|
+
if (discardingOversizedLine) {
|
|
52747
|
+
discardingOversizedLine = false;
|
|
52748
|
+
pendingChunks = [];
|
|
52749
|
+
pendingLength = 0;
|
|
52750
|
+
} else {
|
|
52751
|
+
handleLine(takePendingLine(tail));
|
|
52752
|
+
}
|
|
52753
|
+
start = newlineIndex + 1;
|
|
52754
|
+
}
|
|
52755
|
+
if (start < chunk2.length) {
|
|
52756
|
+
const rest = chunk2.subarray(start);
|
|
52757
|
+
if (discardingOversizedLine) {
|
|
52758
|
+
return;
|
|
52759
|
+
}
|
|
52760
|
+
if (pendingLength + rest.length > MAX_LINE_BYTES) {
|
|
52761
|
+
if (!warnedOversizedLine) {
|
|
52762
|
+
warnedOversizedLine = true;
|
|
52763
|
+
console.warn("[tmex] control mode parser dropped oversized line");
|
|
52764
|
+
}
|
|
52765
|
+
discardingOversizedLine = true;
|
|
52766
|
+
pendingChunks = [];
|
|
52767
|
+
pendingLength = 0;
|
|
52768
|
+
return;
|
|
52769
|
+
}
|
|
52770
|
+
pendingChunks.push(rest);
|
|
52771
|
+
pendingLength += rest.length;
|
|
52772
|
+
}
|
|
52773
|
+
},
|
|
52774
|
+
end() {
|
|
52775
|
+
if (discardingOversizedLine || pendingLength === 0) {
|
|
52776
|
+
discardingOversizedLine = false;
|
|
52777
|
+
pendingChunks = [];
|
|
52778
|
+
pendingLength = 0;
|
|
52779
|
+
return;
|
|
52780
|
+
}
|
|
52781
|
+
const line = concatChunks(pendingChunks, pendingLength);
|
|
52782
|
+
pendingChunks = [];
|
|
52783
|
+
pendingLength = 0;
|
|
52784
|
+
handleLine(line);
|
|
52570
52785
|
}
|
|
52571
52786
|
};
|
|
52572
52787
|
}
|
|
52573
52788
|
|
|
52574
|
-
// ../../apps/gateway/src/tmux-client/ghostty-terminfo.ts
|
|
52575
|
-
var XTERM_GHOSTTY_TERMINFO_SOURCE = `xterm-ghostty|ghostty|Ghostty,
|
|
52576
|
-
am, bce, ccc, hs, km, mc5i, mir, msgr, npc, xenl, AX, Su, Tc, XT, fullkbd,
|
|
52577
|
-
colors#0x100, cols#80, it#8, lines#24, pairs#0x7fff,
|
|
52578
|
-
acsc=++\\,\\,--..00\`\`aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
|
|
52579
|
-
bel=^G, blink=\\E[5m, bold=\\E[1m, cbt=\\E[Z, civis=\\E[?25l,
|
|
52580
|
-
clear=\\E[H\\E[2J, cnorm=\\E[?12l\\E[?25h, cr=\\r,
|
|
52581
|
-
csr=\\E[%i%p1%d;%p2%dr, cub=\\E[%p1%dD, cub1=^H,
|
|
52582
|
-
cud=\\E[%p1%dB, cud1=\\n, cuf=\\E[%p1%dC, cuf1=\\E[C,
|
|
52583
|
-
cup=\\E[%i%p1%d;%p2%dH, cuu=\\E[%p1%dA, cuu1=\\E[A,
|
|
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
|
-
`);
|
|
52667
|
-
}
|
|
52668
|
-
|
|
52669
|
-
// ../../apps/gateway/src/tmux-client/input-encoder.ts
|
|
52670
|
-
var encoder = new TextEncoder;
|
|
52671
|
-
var SEND_KEYS_HEX_CHUNK_BYTES = 256;
|
|
52672
|
-
function encodeInputToHexChunks(input, chunkBytes = SEND_KEYS_HEX_CHUNK_BYTES) {
|
|
52673
|
-
const bytes = encoder.encode(input);
|
|
52674
|
-
const chunks = [];
|
|
52675
|
-
for (let offset = 0;offset < bytes.length; offset += chunkBytes) {
|
|
52676
|
-
const chunk2 = bytes.slice(offset, offset + chunkBytes);
|
|
52677
|
-
chunks.push(Array.from(chunk2, (byte) => byte.toString(16).padStart(2, "0")));
|
|
52678
|
-
}
|
|
52679
|
-
return chunks;
|
|
52680
|
-
}
|
|
52681
|
-
|
|
52682
52789
|
// ../../apps/gateway/src/tmux-client/pane-stream-parser.ts
|
|
52683
|
-
var
|
|
52790
|
+
var decoder2 = new TextDecoder;
|
|
52684
52791
|
var MAX_OSC_KIND_BYTES = 16;
|
|
52685
52792
|
var MAX_OSC_PAYLOAD_BYTES = 8 * 1024;
|
|
52686
52793
|
var MAX_DCS_PASSTHROUGH_BYTES = 64 * 1024;
|
|
@@ -52714,14 +52821,14 @@ function createPaneStreamParser(options) {
|
|
|
52714
52821
|
return true;
|
|
52715
52822
|
}
|
|
52716
52823
|
function emitTitle(bytes) {
|
|
52717
|
-
const title =
|
|
52824
|
+
const title = decoder2.decode(new Uint8Array(bytes)).trim();
|
|
52718
52825
|
if (!title) {
|
|
52719
52826
|
return;
|
|
52720
52827
|
}
|
|
52721
52828
|
options.onTitle(title);
|
|
52722
52829
|
}
|
|
52723
52830
|
function emitOsc() {
|
|
52724
|
-
const payload =
|
|
52831
|
+
const payload = decoder2.decode(new Uint8Array(oscPayloadBytes));
|
|
52725
52832
|
switch (oscKind) {
|
|
52726
52833
|
case "0":
|
|
52727
52834
|
case "1":
|
|
@@ -53020,7 +53127,245 @@ function createPaneStreamParser(options) {
|
|
|
53020
53127
|
};
|
|
53021
53128
|
}
|
|
53022
53129
|
|
|
53130
|
+
// ../../apps/gateway/src/tmux-client/control-mode-subscription.ts
|
|
53131
|
+
var STRUCTURE_DEBOUNCE_MS = 150;
|
|
53132
|
+
var STRUCTURE_NOTIFICATION_TYPES = new Set([
|
|
53133
|
+
"layout-change",
|
|
53134
|
+
"session-renamed",
|
|
53135
|
+
"session-window-changed",
|
|
53136
|
+
"sessions-changed",
|
|
53137
|
+
"unlinked-window-add",
|
|
53138
|
+
"unlinked-window-close",
|
|
53139
|
+
"unlinked-window-renamed",
|
|
53140
|
+
"window-add",
|
|
53141
|
+
"window-close",
|
|
53142
|
+
"window-pane-changed",
|
|
53143
|
+
"window-renamed"
|
|
53144
|
+
]);
|
|
53145
|
+
function createControlModeSubscription(callbacks) {
|
|
53146
|
+
const paneParsers = new Map;
|
|
53147
|
+
let structureTimer = null;
|
|
53148
|
+
let lastStructureEmitAt = 0;
|
|
53149
|
+
let disposed = false;
|
|
53150
|
+
function getPaneParser(paneId) {
|
|
53151
|
+
const existing = paneParsers.get(paneId);
|
|
53152
|
+
if (existing) {
|
|
53153
|
+
return existing;
|
|
53154
|
+
}
|
|
53155
|
+
const parser2 = createPaneStreamParser({
|
|
53156
|
+
onTitle: (title) => callbacks.onTitle(paneId, title),
|
|
53157
|
+
onBell: () => callbacks.onBell(paneId),
|
|
53158
|
+
onNotification: (notification) => callbacks.onNotification(paneId, notification)
|
|
53159
|
+
});
|
|
53160
|
+
paneParsers.set(paneId, parser2);
|
|
53161
|
+
return parser2;
|
|
53162
|
+
}
|
|
53163
|
+
function scheduleStructureChanged() {
|
|
53164
|
+
if (disposed) {
|
|
53165
|
+
return;
|
|
53166
|
+
}
|
|
53167
|
+
const now = Date.now();
|
|
53168
|
+
if (structureTimer) {
|
|
53169
|
+
return;
|
|
53170
|
+
}
|
|
53171
|
+
if (now - lastStructureEmitAt >= STRUCTURE_DEBOUNCE_MS) {
|
|
53172
|
+
lastStructureEmitAt = now;
|
|
53173
|
+
callbacks.onStructureChanged();
|
|
53174
|
+
return;
|
|
53175
|
+
}
|
|
53176
|
+
structureTimer = setTimeout(() => {
|
|
53177
|
+
structureTimer = null;
|
|
53178
|
+
if (disposed) {
|
|
53179
|
+
return;
|
|
53180
|
+
}
|
|
53181
|
+
lastStructureEmitAt = Date.now();
|
|
53182
|
+
callbacks.onStructureChanged();
|
|
53183
|
+
}, STRUCTURE_DEBOUNCE_MS - (now - lastStructureEmitAt));
|
|
53184
|
+
}
|
|
53185
|
+
function handleNotification(notification) {
|
|
53186
|
+
if (STRUCTURE_NOTIFICATION_TYPES.has(notification.type)) {
|
|
53187
|
+
scheduleStructureChanged();
|
|
53188
|
+
}
|
|
53189
|
+
}
|
|
53190
|
+
const parser = createControlModeParser({
|
|
53191
|
+
onOutput: (paneId, data) => {
|
|
53192
|
+
const output = getPaneParser(paneId).push(data);
|
|
53193
|
+
if (output.length > 0) {
|
|
53194
|
+
callbacks.onTerminalOutput(paneId, output);
|
|
53195
|
+
}
|
|
53196
|
+
},
|
|
53197
|
+
onNotification: handleNotification,
|
|
53198
|
+
onExit: (reason) => callbacks.onExit(reason),
|
|
53199
|
+
onBlockEnd: (block) => callbacks.onBlockEnd?.(block)
|
|
53200
|
+
});
|
|
53201
|
+
return {
|
|
53202
|
+
push(chunk2) {
|
|
53203
|
+
if (disposed) {
|
|
53204
|
+
return;
|
|
53205
|
+
}
|
|
53206
|
+
parser.push(chunk2);
|
|
53207
|
+
},
|
|
53208
|
+
end() {
|
|
53209
|
+
if (disposed) {
|
|
53210
|
+
return;
|
|
53211
|
+
}
|
|
53212
|
+
parser.end();
|
|
53213
|
+
},
|
|
53214
|
+
prunePanes(validPaneIds) {
|
|
53215
|
+
for (const paneId of Array.from(paneParsers.keys())) {
|
|
53216
|
+
if (!validPaneIds.has(paneId)) {
|
|
53217
|
+
paneParsers.delete(paneId);
|
|
53218
|
+
}
|
|
53219
|
+
}
|
|
53220
|
+
},
|
|
53221
|
+
dispose() {
|
|
53222
|
+
disposed = true;
|
|
53223
|
+
if (structureTimer) {
|
|
53224
|
+
clearTimeout(structureTimer);
|
|
53225
|
+
structureTimer = null;
|
|
53226
|
+
}
|
|
53227
|
+
paneParsers.clear();
|
|
53228
|
+
}
|
|
53229
|
+
};
|
|
53230
|
+
}
|
|
53231
|
+
|
|
53232
|
+
// ../../apps/gateway/src/tmux-client/ghostty-terminfo.ts
|
|
53233
|
+
var XTERM_GHOSTTY_TERMINFO_SOURCE = `xterm-ghostty|ghostty|Ghostty,
|
|
53234
|
+
am, bce, ccc, hs, km, mc5i, mir, msgr, npc, xenl, AX, Su, Tc, XT, fullkbd,
|
|
53235
|
+
colors#0x100, cols#80, it#8, lines#24, pairs#0x7fff,
|
|
53236
|
+
acsc=++\\,\\,--..00\`\`aaffgghhiijjkkllmmnnooppqqrrssttuuvvwwxxyyzz{{||}}~~,
|
|
53237
|
+
bel=^G, blink=\\E[5m, bold=\\E[1m, cbt=\\E[Z, civis=\\E[?25l,
|
|
53238
|
+
clear=\\E[H\\E[2J, cnorm=\\E[?12l\\E[?25h, cr=\\r,
|
|
53239
|
+
csr=\\E[%i%p1%d;%p2%dr, cub=\\E[%p1%dD, cub1=^H,
|
|
53240
|
+
cud=\\E[%p1%dB, cud1=\\n, cuf=\\E[%p1%dC, cuf1=\\E[C,
|
|
53241
|
+
cup=\\E[%i%p1%d;%p2%dH, cuu=\\E[%p1%dA, cuu1=\\E[A,
|
|
53242
|
+
cvvis=\\E[?12;25h, dch=\\E[%p1%dP, dch1=\\E[P, dim=\\E[2m,
|
|
53243
|
+
dl=\\E[%p1%dM, dl1=\\E[M, dsl=\\E]2;\\007, ech=\\E[%p1%dX,
|
|
53244
|
+
ed=\\E[J, el=\\E[K, el1=\\E[1K, flash=\\E[?5h$<100/>\\E[?5l,
|
|
53245
|
+
fsl=^G, home=\\E[H, hpa=\\E[%i%p1%dG, ht=^I, hts=\\EH,
|
|
53246
|
+
ich=\\E[%p1%d@, ich1=\\E[@, il=\\E[%p1%dL, il1=\\E[L, ind=\\n,
|
|
53247
|
+
indn=\\E[%p1%dS,
|
|
53248
|
+
initc=\\E]4;%p1%d;rgb:%p2%{255}%*%{1000}%/%2.2X/%p3%{255}%*%{1000}%/%2.2X/%p4%{255}%*%{1000}%/%2.2X\\E\\\\,
|
|
53249
|
+
invis=\\E[8m, kDC=\\E[3;2~, kEND=\\E[1;2F, kHOM=\\E[1;2H,
|
|
53250
|
+
kIC=\\E[2;2~, kLFT=\\E[1;2D, kNXT=\\E[6;2~, kPRV=\\E[5;2~,
|
|
53251
|
+
kRIT=\\E[1;2C, kbs=^?, kcbt=\\E[Z, kcub1=\\EOD, kcud1=\\EOB,
|
|
53252
|
+
kcuf1=\\EOC, kcuu1=\\EOA, kdch1=\\E[3~, kend=\\EOF, kent=\\EOM,
|
|
53253
|
+
kf1=\\EOP, kf10=\\E[21~, kf11=\\E[23~, kf12=\\E[24~,
|
|
53254
|
+
kf13=\\E[1;2P, kf14=\\E[1;2Q, kf15=\\E[1;2R, kf16=\\E[1;2S,
|
|
53255
|
+
kf17=\\E[15;2~, kf18=\\E[17;2~, kf19=\\E[18;2~, kf2=\\EOQ,
|
|
53256
|
+
kf20=\\E[19;2~, kf21=\\E[20;2~, kf22=\\E[21;2~,
|
|
53257
|
+
kf23=\\E[23;2~, kf24=\\E[24;2~, kf25=\\E[1;5P, kf26=\\E[1;5Q,
|
|
53258
|
+
kf27=\\E[1;5R, kf28=\\E[1;5S, kf29=\\E[15;5~, kf3=\\EOR,
|
|
53259
|
+
kf30=\\E[17;5~, kf31=\\E[18;5~, kf32=\\E[19;5~,
|
|
53260
|
+
kf33=\\E[20;5~, kf34=\\E[21;5~, kf35=\\E[23;5~,
|
|
53261
|
+
kf36=\\E[24;5~, kf37=\\E[1;6P, kf38=\\E[1;6Q, kf39=\\E[1;6R,
|
|
53262
|
+
kf4=\\EOS, kf40=\\E[1;6S, kf41=\\E[15;6~, kf42=\\E[17;6~,
|
|
53263
|
+
kf43=\\E[18;6~, kf44=\\E[19;6~, kf45=\\E[20;6~,
|
|
53264
|
+
kf46=\\E[21;6~, kf47=\\E[23;6~, kf48=\\E[24;6~,
|
|
53265
|
+
kf49=\\E[1;3P, kf5=\\E[15~, kf50=\\E[1;3Q, kf51=\\E[1;3R,
|
|
53266
|
+
kf52=\\E[1;3S, kf53=\\E[15;3~, kf54=\\E[17;3~,
|
|
53267
|
+
kf55=\\E[18;3~, kf56=\\E[19;3~, kf57=\\E[20;3~,
|
|
53268
|
+
kf58=\\E[21;3~, kf59=\\E[23;3~, kf6=\\E[17~, kf60=\\E[24;3~,
|
|
53269
|
+
kf61=\\E[1;4P, kf62=\\E[1;4Q, kf63=\\E[1;4R, kf7=\\E[18~,
|
|
53270
|
+
kf8=\\E[19~, kf9=\\E[20~, khome=\\EOH, kich1=\\E[2~,
|
|
53271
|
+
kind=\\E[1;2B, kmous=\\E[<, knp=\\E[6~, kpp=\\E[5~,
|
|
53272
|
+
kri=\\E[1;2A, oc=\\E]104\\007, op=\\E[39;49m, rc=\\E8,
|
|
53273
|
+
rep=%p1%c\\E[%p2%{1}%-%db, rev=\\E[7m, ri=\\EM,
|
|
53274
|
+
rin=\\E[%p1%dT, ritm=\\E[23m, rmacs=\\E(B, rmam=\\E[?7l,
|
|
53275
|
+
rmcup=\\E[?1049l, rmir=\\E[4l, rmkx=\\E[?1l\\E>, rmso=\\E[27m,
|
|
53276
|
+
rmul=\\E[24m, rs1=\\E]\\E\\\\\\Ec, sc=\\E7,
|
|
53277
|
+
setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m,
|
|
53278
|
+
setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m,
|
|
53279
|
+
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,
|
|
53280
|
+
sgr0=\\E(B\\E[m, sitm=\\E[3m, smacs=\\E(0, smam=\\E[?7h,
|
|
53281
|
+
smcup=\\E[?1049h, smir=\\E[4h, smkx=\\E[?1h\\E=, smso=\\E[7m,
|
|
53282
|
+
smul=\\E[4m, tbc=\\E[3g, tsl=\\E]2;, u6=\\E[%i%d;%dR, u7=\\E[6n,
|
|
53283
|
+
u8=\\E[?%[;0123456789]c, u9=\\E[c, vpa=\\E[%i%p1%dd,
|
|
53284
|
+
BD=\\E[?2004l, BE=\\E[?2004h, Clmg=\\E[s,
|
|
53285
|
+
Cmg=\\E[%i%p1%d;%p2%ds, Dsmg=\\E[?69l, E3=\\E[3J,
|
|
53286
|
+
Enmg=\\E[?69h, Ms=\\E]52;%p1%s;%p2%s\\007, PE=\\E[201~,
|
|
53287
|
+
PS=\\E[200~, RV=\\E[>c, Se=\\E[2 q,
|
|
53288
|
+
Setulc=\\E[58:2::%p1%{65536}%/%d:%p1%{256}%/%{255}%&%d:%p1%{255}%&%d%;m,
|
|
53289
|
+
Smulx=\\E[4:%p1%dm, Ss=\\E[%p1%d q,
|
|
53290
|
+
Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;,
|
|
53291
|
+
XM=\\E[?1006;1000%?%p1%{1}%=%th%el%;, XR=\\E[>0q,
|
|
53292
|
+
fd=\\E[?1004l, fe=\\E[?1004h, kDC3=\\E[3;3~, kDC4=\\E[3;4~,
|
|
53293
|
+
kDC5=\\E[3;5~, kDC6=\\E[3;6~, kDC7=\\E[3;7~, kDN=\\E[1;2B,
|
|
53294
|
+
kDN3=\\E[1;3B, kDN4=\\E[1;4B, kDN5=\\E[1;5B, kDN6=\\E[1;6B,
|
|
53295
|
+
kDN7=\\E[1;7B, kEND3=\\E[1;3F, kEND4=\\E[1;4F,
|
|
53296
|
+
kEND5=\\E[1;5F, kEND6=\\E[1;6F, kEND7=\\E[1;7F,
|
|
53297
|
+
kHOM3=\\E[1;3H, kHOM4=\\E[1;4H, kHOM5=\\E[1;5H,
|
|
53298
|
+
kHOM6=\\E[1;6H, kHOM7=\\E[1;7H, kIC3=\\E[2;3~, kIC4=\\E[2;4~,
|
|
53299
|
+
kIC5=\\E[2;5~, kIC6=\\E[2;6~, kIC7=\\E[2;7~, kLFT3=\\E[1;3D,
|
|
53300
|
+
kLFT4=\\E[1;4D, kLFT5=\\E[1;5D, kLFT6=\\E[1;6D,
|
|
53301
|
+
kLFT7=\\E[1;7D, kNXT3=\\E[6;3~, kNXT4=\\E[6;4~,
|
|
53302
|
+
kNXT5=\\E[6;5~, kNXT6=\\E[6;6~, kNXT7=\\E[6;7~,
|
|
53303
|
+
kPRV3=\\E[5;3~, kPRV4=\\E[5;4~, kPRV5=\\E[5;5~,
|
|
53304
|
+
kPRV6=\\E[5;6~, kPRV7=\\E[5;7~, kRIT3=\\E[1;3C,
|
|
53305
|
+
kRIT4=\\E[1;4C, kRIT5=\\E[1;5C, kRIT6=\\E[1;6C,
|
|
53306
|
+
kRIT7=\\E[1;7C, kUP=\\E[1;2A, kUP3=\\E[1;3A, kUP4=\\E[1;4A,
|
|
53307
|
+
kUP5=\\E[1;5A, kUP6=\\E[1;6A, kUP7=\\E[1;7A, kxIN=\\E[I,
|
|
53308
|
+
kxOUT=\\E[O, rmxx=\\E[29m, rv=\\E\\\\[[0-9]+;[0-9]+;[0-9]+c,
|
|
53309
|
+
setrgbb=\\E[48:2:%p1%d:%p2%d:%p3%dm,
|
|
53310
|
+
setrgbf=\\E[38:2:%p1%d:%p2%d:%p3%dm, smxx=\\E[9m,
|
|
53311
|
+
xm=\\E[<%i%p3%d;%p1%d;%p2%d;%?%p4%tM%em%;,
|
|
53312
|
+
xr=\\EP>\\\\|[ -~]+a\\E\\\\,
|
|
53313
|
+
`;
|
|
53314
|
+
var HEREDOC_MARKER = "TMEX_TERMINFO_EOF";
|
|
53315
|
+
function buildEnsureGhosttyTerminfoScript() {
|
|
53316
|
+
return [
|
|
53317
|
+
"if ! infocmp xterm-ghostty >/dev/null 2>&1; then",
|
|
53318
|
+
`tic -x - <<'${HEREDOC_MARKER}' >/dev/null 2>&1`,
|
|
53319
|
+
XTERM_GHOSTTY_TERMINFO_SOURCE.trimEnd(),
|
|
53320
|
+
HEREDOC_MARKER,
|
|
53321
|
+
"fi",
|
|
53322
|
+
"infocmp xterm-ghostty >/dev/null 2>&1"
|
|
53323
|
+
].join(`
|
|
53324
|
+
`);
|
|
53325
|
+
}
|
|
53326
|
+
|
|
53327
|
+
// ../../apps/gateway/src/tmux-client/input-encoder.ts
|
|
53328
|
+
var encoder = new TextEncoder;
|
|
53329
|
+
var SEND_KEYS_HEX_CHUNK_BYTES = 256;
|
|
53330
|
+
function encodeInputToHexChunks(input, chunkBytes = SEND_KEYS_HEX_CHUNK_BYTES) {
|
|
53331
|
+
const bytes = encoder.encode(input);
|
|
53332
|
+
const chunks = [];
|
|
53333
|
+
for (let offset = 0;offset < bytes.length; offset += chunkBytes) {
|
|
53334
|
+
const chunk2 = bytes.slice(offset, offset + chunkBytes);
|
|
53335
|
+
chunks.push(Array.from(chunk2, (byte) => byte.toString(16).padStart(2, "0")));
|
|
53336
|
+
}
|
|
53337
|
+
return chunks;
|
|
53338
|
+
}
|
|
53339
|
+
|
|
53340
|
+
// ../../apps/gateway/src/tmux-client/tmux-version.ts
|
|
53341
|
+
var MIN_CONTROL_MODE_VERSION = { major: 3, minor: 0 };
|
|
53342
|
+
function parseTmuxVersion(versionOutput) {
|
|
53343
|
+
const match = versionOutput.match(/(\d+)\.(\d+)/);
|
|
53344
|
+
if (!match) {
|
|
53345
|
+
return null;
|
|
53346
|
+
}
|
|
53347
|
+
return {
|
|
53348
|
+
major: Number.parseInt(match[1], 10),
|
|
53349
|
+
minor: Number.parseInt(match[2], 10)
|
|
53350
|
+
};
|
|
53351
|
+
}
|
|
53352
|
+
function isControlModeSupported(version2) {
|
|
53353
|
+
if (!version2) {
|
|
53354
|
+
return true;
|
|
53355
|
+
}
|
|
53356
|
+
if (version2.major !== MIN_CONTROL_MODE_VERSION.major) {
|
|
53357
|
+
return version2.major > MIN_CONTROL_MODE_VERSION.major;
|
|
53358
|
+
}
|
|
53359
|
+
return version2.minor >= MIN_CONTROL_MODE_VERSION.minor;
|
|
53360
|
+
}
|
|
53361
|
+
|
|
53023
53362
|
// ../../apps/gateway/src/tmux-client/local-external-connection.ts
|
|
53363
|
+
var CONTROL_MAX_RESTARTS = 3;
|
|
53364
|
+
var CONTROL_RESTART_DELAY_MS = 500;
|
|
53365
|
+
var CONTROL_STABLE_RESET_MS = 1e4;
|
|
53366
|
+
var CONTROL_STDERR_TAIL_LIMIT = 2048;
|
|
53367
|
+
var CONTROL_ATTACH_READY_TIMEOUT_MS = 3000;
|
|
53368
|
+
var PARKING_WINDOW_NAME = "tmex-park";
|
|
53024
53369
|
function hasRenderableTerminalContent(value) {
|
|
53025
53370
|
return value.trim().length > 0;
|
|
53026
53371
|
}
|
|
@@ -53048,6 +53393,26 @@ function defaultRun(argv) {
|
|
|
53048
53393
|
}).catch(reject);
|
|
53049
53394
|
});
|
|
53050
53395
|
}
|
|
53396
|
+
function defaultSpawnControlClient(argv) {
|
|
53397
|
+
const subprocess = Bun.spawn(argv, {
|
|
53398
|
+
env: buildLocalTmuxEnv(getLocalShellPath()),
|
|
53399
|
+
stdin: "pipe",
|
|
53400
|
+
stdout: "pipe",
|
|
53401
|
+
stderr: "pipe"
|
|
53402
|
+
});
|
|
53403
|
+
const stdin = subprocess.stdin;
|
|
53404
|
+
return {
|
|
53405
|
+
stdout: subprocess.stdout,
|
|
53406
|
+
stderr: subprocess.stderr,
|
|
53407
|
+
exited: subprocess.exited,
|
|
53408
|
+
kill: () => {
|
|
53409
|
+
try {
|
|
53410
|
+
stdin?.end();
|
|
53411
|
+
} catch {}
|
|
53412
|
+
subprocess.kill();
|
|
53413
|
+
}
|
|
53414
|
+
};
|
|
53415
|
+
}
|
|
53051
53416
|
|
|
53052
53417
|
class LocalExternalTmuxConnection {
|
|
53053
53418
|
deviceId;
|
|
@@ -53062,30 +53427,27 @@ class LocalExternalTmuxConnection {
|
|
|
53062
53427
|
pendingPaneTitles = new Map;
|
|
53063
53428
|
snapshotSession = null;
|
|
53064
53429
|
snapshotWindows = new Map;
|
|
53065
|
-
paneReaders = new Map;
|
|
53066
|
-
pipeTransition = Promise.resolve();
|
|
53067
53430
|
inputTransition = Promise.resolve();
|
|
53068
|
-
hookReadAbort = null;
|
|
53069
|
-
hookBuffer = "";
|
|
53070
53431
|
bellDedup = new Map;
|
|
53071
53432
|
closeNotified = false;
|
|
53072
53433
|
cleanupPromise = null;
|
|
53073
|
-
|
|
53074
|
-
|
|
53075
|
-
|
|
53076
|
-
|
|
53077
|
-
|
|
53434
|
+
controlProcess = null;
|
|
53435
|
+
controlSubscription = null;
|
|
53436
|
+
controlStartedAt = 0;
|
|
53437
|
+
controlRestartCount = 0;
|
|
53438
|
+
controlStderrTail = "";
|
|
53078
53439
|
constructor(options, inputDeps = {}) {
|
|
53079
53440
|
this.deviceId = options.deviceId;
|
|
53080
53441
|
this.callbacks = options;
|
|
53081
53442
|
this.deps = {
|
|
53082
|
-
|
|
53443
|
+
enableSubscription: inputDeps.enableSubscription ?? true,
|
|
53083
53444
|
getDevice: inputDeps.getDevice ?? ((deviceId) => getDeviceById(deviceId)),
|
|
53084
53445
|
run: inputDeps.run ?? defaultRun,
|
|
53085
53446
|
ensureGhosttyTerminfo: inputDeps.ensureGhosttyTerminfo ?? (async () => {
|
|
53086
53447
|
const result = await this.deps.run(["/bin/sh", "-c", buildEnsureGhosttyTerminfoScript()]);
|
|
53087
53448
|
return result.exitCode === 0;
|
|
53088
|
-
})
|
|
53449
|
+
}),
|
|
53450
|
+
spawnControlClient: inputDeps.spawnControlClient ?? defaultSpawnControlClient
|
|
53089
53451
|
};
|
|
53090
53452
|
}
|
|
53091
53453
|
async connect() {
|
|
@@ -53099,16 +53461,13 @@ class LocalExternalTmuxConnection {
|
|
|
53099
53461
|
throw new Error(`LocalExternalTmuxConnection only supports local device: ${this.deviceId}`);
|
|
53100
53462
|
}
|
|
53101
53463
|
this.sessionName = this.device.session?.trim() || "tmex";
|
|
53102
|
-
this.
|
|
53103
|
-
|
|
53104
|
-
|
|
53105
|
-
gatewayPid: process.pid
|
|
53106
|
-
});
|
|
53107
|
-
this.ensureRuntimeDirs();
|
|
53464
|
+
if (this.deps.enableSubscription) {
|
|
53465
|
+
await this.assertControlModeSupport();
|
|
53466
|
+
}
|
|
53108
53467
|
await this.ensureSession();
|
|
53109
53468
|
await this.configureSessionOptions();
|
|
53110
|
-
if (this.deps.
|
|
53111
|
-
await this.
|
|
53469
|
+
if (this.deps.enableSubscription) {
|
|
53470
|
+
await this.startControlClient();
|
|
53112
53471
|
}
|
|
53113
53472
|
this.connected = true;
|
|
53114
53473
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
@@ -53125,11 +53484,7 @@ class LocalExternalTmuxConnection {
|
|
|
53125
53484
|
}
|
|
53126
53485
|
this.manualDisconnect = true;
|
|
53127
53486
|
this.connected = false;
|
|
53128
|
-
this.
|
|
53129
|
-
if (this.deps.enableHooks) {
|
|
53130
|
-
this.stopHooks();
|
|
53131
|
-
}
|
|
53132
|
-
rmSync(this.fsPaths.rootDir, { recursive: true, force: true });
|
|
53487
|
+
this.stopControlClient();
|
|
53133
53488
|
}
|
|
53134
53489
|
requestSnapshot() {
|
|
53135
53490
|
this.requestSnapshotInternal();
|
|
@@ -53241,7 +53596,14 @@ class LocalExternalTmuxConnection {
|
|
|
53241
53596
|
"extended-keys-format",
|
|
53242
53597
|
"csi-u"
|
|
53243
53598
|
]);
|
|
53244
|
-
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "
|
|
53599
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "off"]);
|
|
53600
|
+
await this.runTmuxAllowFailure([
|
|
53601
|
+
"set-option",
|
|
53602
|
+
"-t",
|
|
53603
|
+
this.sessionName,
|
|
53604
|
+
"destroy-unattached",
|
|
53605
|
+
"off"
|
|
53606
|
+
]);
|
|
53245
53607
|
const termProgram = config.tmuxTermProgram.trim();
|
|
53246
53608
|
if (termProgram && termProgram.toLowerCase() !== "off") {
|
|
53247
53609
|
await this.runTmuxAllowFailure([
|
|
@@ -53262,86 +53624,204 @@ class LocalExternalTmuxConnection {
|
|
|
53262
53624
|
}
|
|
53263
53625
|
}
|
|
53264
53626
|
}
|
|
53265
|
-
|
|
53266
|
-
|
|
53267
|
-
|
|
53268
|
-
|
|
53627
|
+
async assertControlModeSupport() {
|
|
53628
|
+
const result = await this.runTmuxAllowFailure(["-V"]);
|
|
53629
|
+
if (result.exitCode !== 0) {
|
|
53630
|
+
return;
|
|
53631
|
+
}
|
|
53632
|
+
const version2 = parseTmuxVersion(result.stdout.trim());
|
|
53633
|
+
if (!isControlModeSupported(version2)) {
|
|
53634
|
+
throw new Error(`tmux ${version2?.major}.${version2?.minor} is too old for tmex (control mode requires tmux >= 3.0)`);
|
|
53635
|
+
}
|
|
53269
53636
|
}
|
|
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",
|
|
53637
|
+
async createParkingWindow() {
|
|
53638
|
+
const result = await this.runTmuxAllowFailure([
|
|
53639
|
+
"new-window",
|
|
53319
53640
|
"-t",
|
|
53320
53641
|
this.sessionName,
|
|
53321
|
-
|
|
53322
|
-
|
|
53642
|
+
"-n",
|
|
53643
|
+
PARKING_WINDOW_NAME,
|
|
53644
|
+
"-P",
|
|
53645
|
+
"-F",
|
|
53646
|
+
"#{window_id}",
|
|
53647
|
+
"sleep 30"
|
|
53323
53648
|
]);
|
|
53649
|
+
if (result.exitCode !== 0) {
|
|
53650
|
+
console.warn(`[local] failed to create parking window on ${this.deviceId}, attaching without focus shield`);
|
|
53651
|
+
return null;
|
|
53652
|
+
}
|
|
53653
|
+
return result.stdout.trim() || null;
|
|
53324
53654
|
}
|
|
53325
|
-
|
|
53326
|
-
|
|
53327
|
-
|
|
53328
|
-
|
|
53329
|
-
|
|
53330
|
-
|
|
53331
|
-
|
|
53655
|
+
async removeParkingWindow(windowId) {
|
|
53656
|
+
if (!windowId) {
|
|
53657
|
+
return;
|
|
53658
|
+
}
|
|
53659
|
+
await this.runTmuxAllowFailure(["last-window", "-t", this.sessionName]);
|
|
53660
|
+
await this.runTmuxAllowFailure(["kill-window", "-t", windowId]);
|
|
53661
|
+
}
|
|
53662
|
+
async startControlClient() {
|
|
53663
|
+
let attachReadyResolve = null;
|
|
53664
|
+
const attachReady = new Promise((resolve) => {
|
|
53665
|
+
attachReadyResolve = resolve;
|
|
53666
|
+
});
|
|
53667
|
+
const parkingWindowId = await this.createParkingWindow();
|
|
53668
|
+
let proc;
|
|
53669
|
+
try {
|
|
53670
|
+
proc = this.spawnControlClientProcess(() => {
|
|
53671
|
+
attachReadyResolve?.();
|
|
53672
|
+
attachReadyResolve = null;
|
|
53673
|
+
});
|
|
53674
|
+
await Promise.race([
|
|
53675
|
+
attachReady,
|
|
53676
|
+
new Promise((resolve) => setTimeout(resolve, CONTROL_ATTACH_READY_TIMEOUT_MS))
|
|
53677
|
+
]);
|
|
53678
|
+
} finally {
|
|
53679
|
+
await this.removeParkingWindow(parkingWindowId);
|
|
53680
|
+
}
|
|
53681
|
+
if (this.controlProcess !== proc) {
|
|
53682
|
+
const message = this.controlStderrTail.trim() || "tmux control client exited during attach";
|
|
53683
|
+
console.warn(`[local] tmux control client died during attach on ${this.deviceId}: ${message}`);
|
|
53684
|
+
throw new Error(message);
|
|
53685
|
+
}
|
|
53686
|
+
}
|
|
53687
|
+
spawnControlClientProcess(onAttachReady) {
|
|
53688
|
+
const subscription = createControlModeSubscription({
|
|
53689
|
+
onTerminalOutput: (paneId, data) => {
|
|
53690
|
+
this.callbacks.onTerminalOutput(paneId, data);
|
|
53691
|
+
},
|
|
53692
|
+
onTitle: (paneId, title) => {
|
|
53693
|
+
this.pendingPaneTitles.set(paneId, title);
|
|
53694
|
+
this.requestSnapshot();
|
|
53695
|
+
},
|
|
53696
|
+
onBell: (paneId) => {
|
|
53697
|
+
this.recordBell(paneId);
|
|
53698
|
+
},
|
|
53699
|
+
onNotification: (paneId, notification) => {
|
|
53700
|
+
this.emitNotification(paneId, notification);
|
|
53701
|
+
},
|
|
53702
|
+
onStructureChanged: () => {
|
|
53703
|
+
this.requestSnapshot();
|
|
53704
|
+
},
|
|
53705
|
+
onExit: () => {},
|
|
53706
|
+
onBlockEnd: () => {
|
|
53707
|
+
onAttachReady();
|
|
53332
53708
|
}
|
|
53333
|
-
|
|
53334
|
-
|
|
53335
|
-
|
|
53336
|
-
|
|
53709
|
+
});
|
|
53710
|
+
const proc = this.deps.spawnControlClient([
|
|
53711
|
+
"tmux",
|
|
53712
|
+
"-C",
|
|
53713
|
+
"attach-session",
|
|
53714
|
+
"-t",
|
|
53715
|
+
this.sessionName
|
|
53716
|
+
]);
|
|
53717
|
+
this.controlProcess = proc;
|
|
53718
|
+
this.controlSubscription = subscription;
|
|
53719
|
+
this.controlStartedAt = Date.now();
|
|
53720
|
+
this.controlStderrTail = "";
|
|
53721
|
+
this.pumpControlStdout(proc, subscription);
|
|
53722
|
+
this.pumpControlStderr(proc);
|
|
53723
|
+
proc.exited.then((exitCode) => {
|
|
53724
|
+
this.handleControlClientExit(proc, exitCode);
|
|
53725
|
+
}).catch(() => {
|
|
53726
|
+
this.handleControlClientExit(proc, -1);
|
|
53727
|
+
});
|
|
53728
|
+
return proc;
|
|
53729
|
+
}
|
|
53730
|
+
async pumpControlStdout(proc, subscription) {
|
|
53731
|
+
const reader = proc.stdout.getReader();
|
|
53732
|
+
try {
|
|
53733
|
+
while (true) {
|
|
53734
|
+
const chunk2 = await reader.read();
|
|
53735
|
+
if (chunk2.done || this.controlProcess !== proc) {
|
|
53736
|
+
break;
|
|
53737
|
+
}
|
|
53738
|
+
subscription.push(chunk2.value);
|
|
53337
53739
|
}
|
|
53338
|
-
|
|
53339
|
-
if (
|
|
53340
|
-
|
|
53740
|
+
} catch (error) {
|
|
53741
|
+
if (!this.manualDisconnect && !shouldIgnoreReaderAbortError(error)) {
|
|
53742
|
+
this.callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
53341
53743
|
}
|
|
53342
|
-
|
|
53343
|
-
|
|
53744
|
+
}
|
|
53745
|
+
subscription.end();
|
|
53746
|
+
}
|
|
53747
|
+
async pumpControlStderr(proc) {
|
|
53748
|
+
const reader = proc.stderr.getReader();
|
|
53749
|
+
const decoder3 = new TextDecoder;
|
|
53750
|
+
try {
|
|
53751
|
+
while (true) {
|
|
53752
|
+
const chunk2 = await reader.read();
|
|
53753
|
+
if (chunk2.done) {
|
|
53754
|
+
break;
|
|
53755
|
+
}
|
|
53756
|
+
if (this.controlProcess === proc) {
|
|
53757
|
+
this.controlStderrTail = (this.controlStderrTail + decoder3.decode(chunk2.value)).slice(-CONTROL_STDERR_TAIL_LIMIT);
|
|
53758
|
+
}
|
|
53344
53759
|
}
|
|
53760
|
+
} catch {}
|
|
53761
|
+
}
|
|
53762
|
+
stopControlClient() {
|
|
53763
|
+
const proc = this.controlProcess;
|
|
53764
|
+
this.controlProcess = null;
|
|
53765
|
+
this.controlSubscription?.dispose();
|
|
53766
|
+
this.controlSubscription = null;
|
|
53767
|
+
proc?.kill();
|
|
53768
|
+
}
|
|
53769
|
+
handleControlClientExit(proc, exitCode) {
|
|
53770
|
+
if (this.controlProcess !== proc) {
|
|
53771
|
+
return;
|
|
53772
|
+
}
|
|
53773
|
+
this.controlProcess = null;
|
|
53774
|
+
this.controlSubscription?.dispose();
|
|
53775
|
+
this.controlSubscription = null;
|
|
53776
|
+
if (!this.connected || this.manualDisconnect) {
|
|
53777
|
+
return;
|
|
53778
|
+
}
|
|
53779
|
+
this.reconnectControlClient(exitCode);
|
|
53780
|
+
}
|
|
53781
|
+
async reconnectControlClient(exitCode) {
|
|
53782
|
+
if (Date.now() - this.controlStartedAt > CONTROL_STABLE_RESET_MS) {
|
|
53783
|
+
this.controlRestartCount = 0;
|
|
53784
|
+
}
|
|
53785
|
+
this.controlRestartCount += 1;
|
|
53786
|
+
const stderrMessage = this.controlStderrTail.trim();
|
|
53787
|
+
if (this.controlRestartCount > CONTROL_MAX_RESTARTS) {
|
|
53788
|
+
const message = stderrMessage || `tmux control client exited repeatedly (last code ${exitCode})`;
|
|
53789
|
+
console.warn(`[local] tmux control client gave up on ${this.deviceId}: ${message}`);
|
|
53790
|
+
this.notifyRuntimeError(message);
|
|
53791
|
+
this.shutdownInternal(true);
|
|
53792
|
+
return;
|
|
53793
|
+
}
|
|
53794
|
+
console.warn(`[local] tmux control client exited (code ${exitCode}) on ${this.deviceId}, reconnecting (attempt ${this.controlRestartCount})`);
|
|
53795
|
+
await new Promise((resolve) => setTimeout(resolve, CONTROL_RESTART_DELAY_MS * this.controlRestartCount));
|
|
53796
|
+
if (!this.connected || this.manualDisconnect) {
|
|
53797
|
+
return;
|
|
53798
|
+
}
|
|
53799
|
+
const probe = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
|
|
53800
|
+
if (probe.exitCode !== 0) {
|
|
53801
|
+
const message = probe.stderr.trim() || probe.stdout.trim() || "tmux session gone";
|
|
53802
|
+
console.warn(`[local] tmux session gone on ${this.deviceId}: ${message}`);
|
|
53803
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
53804
|
+
lastSeenAt: new Date().toISOString(),
|
|
53805
|
+
tmuxAvailable: false,
|
|
53806
|
+
lastError: message
|
|
53807
|
+
});
|
|
53808
|
+
this.shutdownInternal(true);
|
|
53809
|
+
return;
|
|
53810
|
+
}
|
|
53811
|
+
if (!this.connected || this.manualDisconnect) {
|
|
53812
|
+
return;
|
|
53813
|
+
}
|
|
53814
|
+
try {
|
|
53815
|
+
await this.startControlClient();
|
|
53816
|
+
} catch (error) {
|
|
53817
|
+
console.warn(`[local] control client restart failed on ${this.deviceId}:`, error);
|
|
53818
|
+
return;
|
|
53819
|
+
}
|
|
53820
|
+
this.requestSnapshot();
|
|
53821
|
+
if (this.activePaneId) {
|
|
53822
|
+
this.capturePaneHistory(this.activePaneId).catch(() => {
|
|
53823
|
+
return;
|
|
53824
|
+
});
|
|
53345
53825
|
}
|
|
53346
53826
|
}
|
|
53347
53827
|
async runAndRefresh(argv, allowTargetMissing = false) {
|
|
@@ -53438,7 +53918,7 @@ ${panesRes.stderr}`;
|
|
|
53438
53918
|
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
53439
53919
|
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
53440
53920
|
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
53441
|
-
|
|
53921
|
+
this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
|
|
53442
53922
|
this.emitSnapshot();
|
|
53443
53923
|
}
|
|
53444
53924
|
parseSnapshotSession(lines) {
|
|
@@ -53564,108 +54044,6 @@ ${panesRes.stderr}`;
|
|
|
53564
54044
|
getExpectedPaneIds() {
|
|
53565
54045
|
return Array.from(this.snapshotWindows.values()).sort((left, right) => left.index - right.index).flatMap((window2) => window2.panes.map((pane) => pane.id));
|
|
53566
54046
|
}
|
|
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
54047
|
async runTmux(argv, allowTargetMissing = false) {
|
|
53670
54048
|
const result = await this.runTmuxAllowFailure(argv);
|
|
53671
54049
|
if (result.exitCode === 0) {
|
|
@@ -53722,17 +54100,7 @@ ${panesRes.stderr}`;
|
|
|
53722
54100
|
}
|
|
53723
54101
|
this.connected = false;
|
|
53724
54102
|
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 {}
|
|
54103
|
+
this.stopControlClient();
|
|
53736
54104
|
})();
|
|
53737
54105
|
await this.cleanupPromise;
|
|
53738
54106
|
this.cleanupPromise = null;
|
|
@@ -53756,9 +54124,17 @@ ${panesRes.stderr}`;
|
|
|
53756
54124
|
// ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
|
|
53757
54125
|
var import_ssh2 = __toESM(require_lib3(), 1);
|
|
53758
54126
|
|
|
54127
|
+
// ../../apps/gateway/src/tmux-client/command-builder.ts
|
|
54128
|
+
function quoteShellArg(value) {
|
|
54129
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
54130
|
+
}
|
|
54131
|
+
function joinShellArgs(argv) {
|
|
54132
|
+
return argv.map((arg) => quoteShellArg(arg)).join(" ");
|
|
54133
|
+
}
|
|
54134
|
+
|
|
53759
54135
|
// ../../apps/gateway/src/tmux-client/ssh-connect-config.ts
|
|
53760
54136
|
import { existsSync as existsSync2, readFileSync } from "fs";
|
|
53761
|
-
import { join as
|
|
54137
|
+
import { join as join3 } from "path";
|
|
53762
54138
|
|
|
53763
54139
|
// ../../apps/gateway/src/tmux/ssh-auth.ts
|
|
53764
54140
|
function normalizeEnvValue(value) {
|
|
@@ -53811,7 +54187,7 @@ function expandHomePath(value, env) {
|
|
|
53811
54187
|
return env.HOME?.trim() || trimmed;
|
|
53812
54188
|
}
|
|
53813
54189
|
if (trimmed.startsWith("~/") && env.HOME?.trim()) {
|
|
53814
|
-
return
|
|
54190
|
+
return join3(env.HOME.trim(), trimmed.slice(2));
|
|
53815
54191
|
}
|
|
53816
54192
|
return trimmed;
|
|
53817
54193
|
}
|
|
@@ -54094,6 +54470,12 @@ function hasRenderableTerminalContent2(value) {
|
|
|
54094
54470
|
var BELL_DEDUP_WINDOW_MS2 = 200;
|
|
54095
54471
|
var COMMAND_SENTINEL = "\x1ETMEX_END ";
|
|
54096
54472
|
var SNAPSHOT_FIELD_SEPARATOR = "|";
|
|
54473
|
+
var CONTROL_MAX_RESTARTS2 = 3;
|
|
54474
|
+
var CONTROL_RESTART_DELAY_MS2 = 500;
|
|
54475
|
+
var CONTROL_STABLE_RESET_MS2 = 1e4;
|
|
54476
|
+
var CONTROL_STDERR_TAIL_LIMIT2 = 2048;
|
|
54477
|
+
var CONTROL_ATTACH_READY_TIMEOUT_MS2 = 3000;
|
|
54478
|
+
var PARKING_WINDOW_NAME2 = "tmex-park";
|
|
54097
54479
|
function splitSnapshotFields(line, fieldCount) {
|
|
54098
54480
|
const parts = line.split(SNAPSHOT_FIELD_SEPARATOR);
|
|
54099
54481
|
if (parts.length <= fieldCount) {
|
|
@@ -54134,16 +54516,12 @@ class SshExternalTmuxConnection {
|
|
|
54134
54516
|
pendingPaneTitles = new Map;
|
|
54135
54517
|
snapshotSession = null;
|
|
54136
54518
|
snapshotWindows = new Map;
|
|
54137
|
-
paneReaders = new Map;
|
|
54138
|
-
pipeTransition = Promise.resolve();
|
|
54139
|
-
hookReadAbort = null;
|
|
54140
|
-
hookBuffer = "";
|
|
54141
54519
|
bellDedup = new Map;
|
|
54142
|
-
|
|
54143
|
-
|
|
54144
|
-
|
|
54145
|
-
|
|
54146
|
-
|
|
54520
|
+
controlChannel = null;
|
|
54521
|
+
controlSubscription = null;
|
|
54522
|
+
controlStartedAt = 0;
|
|
54523
|
+
controlRestartCount = 0;
|
|
54524
|
+
controlStderrTail = "";
|
|
54147
54525
|
sshClient = null;
|
|
54148
54526
|
commandStream = null;
|
|
54149
54527
|
commandStdoutBuffer = "";
|
|
@@ -54171,17 +54549,11 @@ class SshExternalTmuxConnection {
|
|
|
54171
54549
|
throw new Error(`SshExternalTmuxConnection only supports ssh device: ${this.deviceId}`);
|
|
54172
54550
|
}
|
|
54173
54551
|
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
54552
|
await this.connectSshClient();
|
|
54180
54553
|
await this.openCommandChannel();
|
|
54181
|
-
await this.ensureRemoteRuntimeDirs();
|
|
54182
54554
|
await this.ensureSession();
|
|
54183
54555
|
await this.configureSessionOptions();
|
|
54184
|
-
await this.
|
|
54556
|
+
await this.startControlClient();
|
|
54185
54557
|
this.connected = true;
|
|
54186
54558
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
54187
54559
|
lastSeenAt: new Date().toISOString(),
|
|
@@ -54371,17 +54743,16 @@ class SshExternalTmuxConnection {
|
|
|
54371
54743
|
}
|
|
54372
54744
|
this.tmuxBin = parsed.tmuxBin;
|
|
54373
54745
|
this.remoteHomeDir = parsed.homeDir;
|
|
54374
|
-
|
|
54375
|
-
|
|
54376
|
-
|
|
54377
|
-
|
|
54378
|
-
|
|
54379
|
-
|
|
54380
|
-
|
|
54381
|
-
|
|
54382
|
-
|
|
54383
|
-
|
|
54384
|
-
`));
|
|
54746
|
+
const version2 = parseTmuxVersion(parsed.tmuxVersion);
|
|
54747
|
+
if (!isControlModeSupported(version2)) {
|
|
54748
|
+
const message = `remote tmux too old for tmex (control mode requires tmux >= 3.0, found ${parsed.tmuxVersion || "unknown"})`;
|
|
54749
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
54750
|
+
lastSeenAt: new Date().toISOString(),
|
|
54751
|
+
tmuxAvailable: false,
|
|
54752
|
+
lastError: message
|
|
54753
|
+
});
|
|
54754
|
+
throw new Error(message);
|
|
54755
|
+
}
|
|
54385
54756
|
}
|
|
54386
54757
|
async ensureSession() {
|
|
54387
54758
|
const exists3 = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
|
|
@@ -54408,7 +54779,14 @@ class SshExternalTmuxConnection {
|
|
|
54408
54779
|
"extended-keys-format",
|
|
54409
54780
|
"csi-u"
|
|
54410
54781
|
]);
|
|
54411
|
-
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "
|
|
54782
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "off"]);
|
|
54783
|
+
await this.runTmuxAllowFailure([
|
|
54784
|
+
"set-option",
|
|
54785
|
+
"-t",
|
|
54786
|
+
this.sessionName,
|
|
54787
|
+
"destroy-unattached",
|
|
54788
|
+
"off"
|
|
54789
|
+
]);
|
|
54412
54790
|
const termProgram = config.tmuxTermProgram.trim();
|
|
54413
54791
|
if (termProgram && termProgram.toLowerCase() !== "off") {
|
|
54414
54792
|
await this.runTmuxAllowFailure([
|
|
@@ -54437,71 +54815,167 @@ class SshExternalTmuxConnection {
|
|
|
54437
54815
|
return false;
|
|
54438
54816
|
}
|
|
54439
54817
|
}
|
|
54440
|
-
async
|
|
54441
|
-
await this.
|
|
54442
|
-
|
|
54443
|
-
|
|
54444
|
-
|
|
54818
|
+
async createParkingWindow() {
|
|
54819
|
+
const result = await this.runTmuxAllowFailure([
|
|
54820
|
+
"new-window",
|
|
54821
|
+
"-t",
|
|
54822
|
+
this.sessionName,
|
|
54823
|
+
"-n",
|
|
54824
|
+
PARKING_WINDOW_NAME2,
|
|
54825
|
+
"-P",
|
|
54826
|
+
"-F",
|
|
54827
|
+
"#{window_id}",
|
|
54828
|
+
"sleep 30"
|
|
54829
|
+
]);
|
|
54830
|
+
if (result.exitCode !== 0) {
|
|
54831
|
+
console.warn(`[ssh] failed to create parking window on ${this.deviceId}, attaching without focus shield`);
|
|
54832
|
+
return null;
|
|
54833
|
+
}
|
|
54834
|
+
return result.stdout.trim() || null;
|
|
54835
|
+
}
|
|
54836
|
+
async removeParkingWindow(windowId) {
|
|
54837
|
+
if (!windowId) {
|
|
54838
|
+
return;
|
|
54839
|
+
}
|
|
54840
|
+
await this.runTmuxAllowFailure(["last-window", "-t", this.sessionName]);
|
|
54841
|
+
await this.runTmuxAllowFailure(["kill-window", "-t", windowId]);
|
|
54842
|
+
}
|
|
54843
|
+
async startControlClient() {
|
|
54844
|
+
let attachReadyResolve = null;
|
|
54845
|
+
const attachReady = new Promise((resolve) => {
|
|
54846
|
+
attachReadyResolve = resolve;
|
|
54847
|
+
});
|
|
54848
|
+
const parkingWindowId = await this.createParkingWindow();
|
|
54849
|
+
let handle;
|
|
54850
|
+
try {
|
|
54851
|
+
handle = await this.openControlChannel(() => {
|
|
54852
|
+
attachReadyResolve?.();
|
|
54853
|
+
attachReadyResolve = null;
|
|
54854
|
+
});
|
|
54855
|
+
await Promise.race([
|
|
54856
|
+
attachReady,
|
|
54857
|
+
new Promise((resolve) => setTimeout(resolve, CONTROL_ATTACH_READY_TIMEOUT_MS2))
|
|
54858
|
+
]);
|
|
54859
|
+
} finally {
|
|
54860
|
+
await this.removeParkingWindow(parkingWindowId);
|
|
54861
|
+
}
|
|
54862
|
+
if (this.controlChannel !== handle) {
|
|
54863
|
+
throw new Error(this.controlStderrTail.trim() || "tmux control client channel closed during attach");
|
|
54864
|
+
}
|
|
54865
|
+
}
|
|
54866
|
+
async openControlChannel(onAttachReady) {
|
|
54867
|
+
const subscription = createControlModeSubscription({
|
|
54868
|
+
onTerminalOutput: (paneId, data) => {
|
|
54869
|
+
this.callbacks.onTerminalOutput(paneId, data);
|
|
54870
|
+
},
|
|
54871
|
+
onTitle: (paneId, title) => {
|
|
54872
|
+
this.pendingPaneTitles.set(paneId, title);
|
|
54873
|
+
this.requestSnapshot();
|
|
54874
|
+
},
|
|
54875
|
+
onBell: (paneId) => {
|
|
54876
|
+
this.recordBell(paneId);
|
|
54877
|
+
},
|
|
54878
|
+
onNotification: (paneId, notification) => {
|
|
54879
|
+
this.emitNotification(paneId, notification);
|
|
54880
|
+
},
|
|
54881
|
+
onStructureChanged: () => {
|
|
54882
|
+
this.requestSnapshot();
|
|
54883
|
+
},
|
|
54884
|
+
onExit: () => {},
|
|
54885
|
+
onBlockEnd: () => {
|
|
54886
|
+
onAttachReady();
|
|
54887
|
+
}
|
|
54888
|
+
});
|
|
54889
|
+
const handle = { stop: () => {} };
|
|
54890
|
+
this.controlChannel = handle;
|
|
54891
|
+
this.controlSubscription = subscription;
|
|
54892
|
+
this.controlStartedAt = Date.now();
|
|
54893
|
+
this.controlStderrTail = "";
|
|
54894
|
+
const stopReader = await this.openReaderChannel(`exec ${quoteShellArg(this.tmuxBin)} -C attach-session -t ${quoteShellArg(this.sessionName)}`, {
|
|
54445
54895
|
onData: (data) => {
|
|
54446
|
-
this.
|
|
54896
|
+
if (this.controlChannel === handle) {
|
|
54897
|
+
subscription.push(new Uint8Array(data.buffer, data.byteOffset, data.byteLength));
|
|
54898
|
+
}
|
|
54447
54899
|
},
|
|
54448
|
-
|
|
54449
|
-
if (this.
|
|
54450
|
-
|
|
54900
|
+
onStderr: (data) => {
|
|
54901
|
+
if (this.controlChannel === handle) {
|
|
54902
|
+
this.controlStderrTail = (this.controlStderrTail + data.toString()).slice(-CONTROL_STDERR_TAIL_LIMIT2);
|
|
54451
54903
|
}
|
|
54452
|
-
|
|
54453
|
-
|
|
54904
|
+
},
|
|
54905
|
+
onClose: () => {
|
|
54906
|
+
this.handleControlChannelClose(handle);
|
|
54454
54907
|
}
|
|
54455
54908
|
});
|
|
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
|
-
]);
|
|
54909
|
+
handle.stop = stopReader;
|
|
54910
|
+
return handle;
|
|
54484
54911
|
}
|
|
54485
|
-
|
|
54486
|
-
this.
|
|
54487
|
-
|
|
54488
|
-
|
|
54489
|
-
|
|
54490
|
-
|
|
54912
|
+
stopControlClient() {
|
|
54913
|
+
const handle = this.controlChannel;
|
|
54914
|
+
this.controlChannel = null;
|
|
54915
|
+
this.controlSubscription?.dispose();
|
|
54916
|
+
this.controlSubscription = null;
|
|
54917
|
+
handle?.stop();
|
|
54918
|
+
}
|
|
54919
|
+
handleControlChannelClose(handle) {
|
|
54920
|
+
if (this.controlChannel !== handle) {
|
|
54921
|
+
return;
|
|
54922
|
+
}
|
|
54923
|
+
this.controlChannel = null;
|
|
54924
|
+
this.controlSubscription?.dispose();
|
|
54925
|
+
this.controlSubscription = null;
|
|
54926
|
+
if (!this.connected || this.manualDisconnect) {
|
|
54927
|
+
return;
|
|
54928
|
+
}
|
|
54929
|
+
this.reconnectControlClient();
|
|
54930
|
+
}
|
|
54931
|
+
async reconnectControlClient() {
|
|
54932
|
+
if (Date.now() - this.controlStartedAt > CONTROL_STABLE_RESET_MS2) {
|
|
54933
|
+
this.controlRestartCount = 0;
|
|
54934
|
+
}
|
|
54935
|
+
this.controlRestartCount += 1;
|
|
54936
|
+
const stderrMessage = this.controlStderrTail.trim();
|
|
54937
|
+
if (this.controlRestartCount > CONTROL_MAX_RESTARTS2) {
|
|
54938
|
+
const message = stderrMessage || "tmux control client channel closed repeatedly";
|
|
54939
|
+
console.warn(`[ssh] tmux control client gave up on ${this.deviceId}: ${message}`);
|
|
54940
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
54941
|
+
lastSeenAt: new Date().toISOString(),
|
|
54942
|
+
tmuxAvailable: false,
|
|
54943
|
+
lastError: message
|
|
54944
|
+
});
|
|
54945
|
+
this.shutdownInternal(true);
|
|
54946
|
+
return;
|
|
54947
|
+
}
|
|
54948
|
+
console.warn(`[ssh] tmux control client channel closed on ${this.deviceId}, reconnecting (attempt ${this.controlRestartCount})`);
|
|
54949
|
+
await new Promise((resolve) => setTimeout(resolve, CONTROL_RESTART_DELAY_MS2 * this.controlRestartCount));
|
|
54950
|
+
if (!this.connected || this.manualDisconnect) {
|
|
54951
|
+
return;
|
|
54952
|
+
}
|
|
54953
|
+
const probe = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
|
|
54954
|
+
if (probe.exitCode !== 0) {
|
|
54955
|
+
const message = probe.stderr.trim() || probe.stdout.trim() || "tmux session gone";
|
|
54956
|
+
console.warn(`[ssh] tmux session gone on ${this.deviceId}: ${message}`);
|
|
54957
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
54958
|
+
lastSeenAt: new Date().toISOString(),
|
|
54959
|
+
tmuxAvailable: false,
|
|
54960
|
+
lastError: message
|
|
54961
|
+
});
|
|
54962
|
+
this.shutdownInternal(true);
|
|
54963
|
+
return;
|
|
54964
|
+
}
|
|
54965
|
+
if (!this.connected || this.manualDisconnect) {
|
|
54966
|
+
return;
|
|
54967
|
+
}
|
|
54968
|
+
try {
|
|
54969
|
+
await this.startControlClient();
|
|
54970
|
+
} catch (error) {
|
|
54971
|
+
console.warn(`[ssh] control client restart failed on ${this.deviceId}:`, error);
|
|
54972
|
+
return;
|
|
54973
|
+
}
|
|
54974
|
+
this.requestSnapshot();
|
|
54975
|
+
if (this.activePaneId) {
|
|
54976
|
+
this.capturePaneHistory(this.activePaneId).catch(() => {
|
|
54491
54977
|
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
|
-
}
|
|
54978
|
+
});
|
|
54505
54979
|
}
|
|
54506
54980
|
}
|
|
54507
54981
|
async runAndRefresh(argv, allowTargetMissing = false) {
|
|
@@ -54598,7 +55072,7 @@ ${panesRes.stderr}`;
|
|
|
54598
55072
|
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
54599
55073
|
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
54600
55074
|
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
54601
|
-
|
|
55075
|
+
this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
|
|
54602
55076
|
this.emitSnapshot();
|
|
54603
55077
|
}
|
|
54604
55078
|
parseSnapshotSession(lines) {
|
|
@@ -54724,104 +55198,6 @@ ${panesRes.stderr}`;
|
|
|
54724
55198
|
getExpectedPaneIds() {
|
|
54725
55199
|
return Array.from(this.snapshotWindows.values()).sort((left, right) => left.index - right.index).flatMap((window2) => window2.panes.map((pane) => pane.id));
|
|
54726
55200
|
}
|
|
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
55201
|
async runTmux(argv, allowTargetMissing = false, timeoutMs = 1e4) {
|
|
54826
55202
|
const result = await this.runTmuxAllowFailure(argv, timeoutMs);
|
|
54827
55203
|
if (result.exitCode === 0) {
|
|
@@ -54949,6 +55325,10 @@ printf '\\036TMEX_END %s %d\\036\\n' ${quoteShellArg(commandId)} $?
|
|
|
54949
55325
|
options.onData(data);
|
|
54950
55326
|
});
|
|
54951
55327
|
stream.stderr.on("data", (data) => {
|
|
55328
|
+
if (options.onStderr) {
|
|
55329
|
+
options.onStderr(data);
|
|
55330
|
+
return;
|
|
55331
|
+
}
|
|
54952
55332
|
if (!this.manualDisconnect) {
|
|
54953
55333
|
this.callbacks.onError(new Error(data.toString().trim() || "SSH reader stderr output"));
|
|
54954
55334
|
}
|
|
@@ -54993,15 +55373,7 @@ printf '\\036TMEX_END %s %d\\036\\n' ${quoteShellArg(commandId)} $?
|
|
|
54993
55373
|
}
|
|
54994
55374
|
this.connected = false;
|
|
54995
55375
|
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
|
-
});
|
|
55376
|
+
this.stopControlClient();
|
|
55005
55377
|
this.rejectPendingCommand(new Error("SSH command channel closed"));
|
|
55006
55378
|
this.commandStream?.end();
|
|
55007
55379
|
this.commandStream?.close();
|
|
@@ -57816,7 +58188,7 @@ async function serveFrontend(req, staticRoot) {
|
|
|
57816
58188
|
if (!requestedPath) {
|
|
57817
58189
|
return new Response(t3("runtime.forbidden"), { status: 403 });
|
|
57818
58190
|
}
|
|
57819
|
-
const indexPath =
|
|
58191
|
+
const indexPath = join4(staticRoot, "index.html");
|
|
57820
58192
|
const targetPath = existsSync4(requestedPath) ? requestedPath : indexPath;
|
|
57821
58193
|
if (!existsSync4(targetPath)) {
|
|
57822
58194
|
return new Response(t3("runtime.frontendMissing"), { status: 500 });
|