tmex-cli 0.8.2 → 0.9.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/cli-node.js +3 -0
- package/dist/runtime/server.js +207 -77
- package/package.json +1 -1
package/dist/cli-node.js
CHANGED
|
@@ -556,6 +556,7 @@ After=network.target
|
|
|
556
556
|
|
|
557
557
|
[Service]
|
|
558
558
|
Type=simple
|
|
559
|
+
KillMode=process
|
|
559
560
|
WorkingDirectory=${escapedInstallDir}
|
|
560
561
|
SyslogIdentifier=tmex
|
|
561
562
|
StandardOutput=journal
|
|
@@ -595,6 +596,8 @@ function buildLaunchdPlist({
|
|
|
595
596
|
<true/>
|
|
596
597
|
<key>KeepAlive</key>
|
|
597
598
|
<true/>
|
|
599
|
+
<key>AbandonProcessGroup</key>
|
|
600
|
+
<true/>
|
|
598
601
|
<key>StandardOutPath</key>
|
|
599
602
|
<string>${escapeXml(join3(installDir, "tmex.log"))}</string>
|
|
600
603
|
<key>StandardErrorPath</key>
|
package/dist/runtime/server.js
CHANGED
|
@@ -32413,10 +32413,16 @@ var telegramBotChats = sqliteTable("telegram_bot_chats", {
|
|
|
32413
32413
|
// ../../apps/gateway/src/db/client.ts
|
|
32414
32414
|
var sqliteClient = null;
|
|
32415
32415
|
var db = null;
|
|
32416
|
+
function applyPragmas(database) {
|
|
32417
|
+
database.run("PRAGMA foreign_keys = ON");
|
|
32418
|
+
database.run("PRAGMA journal_mode = WAL");
|
|
32419
|
+
database.run("PRAGMA busy_timeout = 5000");
|
|
32420
|
+
database.run("PRAGMA synchronous = NORMAL");
|
|
32421
|
+
}
|
|
32416
32422
|
function ensureSqliteClient() {
|
|
32417
32423
|
if (!sqliteClient) {
|
|
32418
32424
|
sqliteClient = new Database2(config.databaseUrl);
|
|
32419
|
-
sqliteClient
|
|
32425
|
+
applyPragmas(sqliteClient);
|
|
32420
32426
|
}
|
|
32421
32427
|
return sqliteClient;
|
|
32422
32428
|
}
|
|
@@ -95733,6 +95739,9 @@ var TMEX_INJECTED_ENV_EXACT = new Set(["NODE_ENV", "DATABASE_URL", "GATEWAY_PORT
|
|
|
95733
95739
|
function isTmexInjectedEnvKey(key) {
|
|
95734
95740
|
return key.startsWith("TMEX_") || TMEX_INJECTED_ENV_EXACT.has(key);
|
|
95735
95741
|
}
|
|
95742
|
+
function isUtf8Locale(value) {
|
|
95743
|
+
return typeof value === "string" && /utf-?8/i.test(value);
|
|
95744
|
+
}
|
|
95736
95745
|
function buildLocalTmuxEnv(resolvedPath, baseEnv = process.env) {
|
|
95737
95746
|
const nextEnv = {};
|
|
95738
95747
|
for (const [key, value] of Object.entries(baseEnv)) {
|
|
@@ -95744,8 +95753,8 @@ function buildLocalTmuxEnv(resolvedPath, baseEnv = process.env) {
|
|
|
95744
95753
|
if (resolvedPath) {
|
|
95745
95754
|
nextEnv.PATH = resolvedPath;
|
|
95746
95755
|
}
|
|
95747
|
-
if (!nextEnv.
|
|
95748
|
-
nextEnv.
|
|
95756
|
+
if (!isUtf8Locale(nextEnv.LC_ALL) && (nextEnv.LC_ALL || !isUtf8Locale(nextEnv.LC_CTYPE) && !isUtf8Locale(nextEnv.LANG))) {
|
|
95757
|
+
nextEnv.LC_ALL = "C.UTF-8";
|
|
95749
95758
|
}
|
|
95750
95759
|
return nextEnv;
|
|
95751
95760
|
}
|
|
@@ -96625,6 +96634,76 @@ function encodeInputToHexChunks(input, chunkBytes = SEND_KEYS_HEX_CHUNK_BYTES) {
|
|
|
96625
96634
|
return chunks;
|
|
96626
96635
|
}
|
|
96627
96636
|
|
|
96637
|
+
// ../../apps/gateway/src/tmux-client/snapshot-format.ts
|
|
96638
|
+
var SNAPSHOT_FIELD_SEPARATOR = "|";
|
|
96639
|
+
var TMUX_SESSION_ID_PATTERN = /^\$\d+$/;
|
|
96640
|
+
var TMUX_WINDOW_ID_PATTERN = /^@\d+$/;
|
|
96641
|
+
var TMUX_PANE_ID_PATTERN = /^%\d+$/;
|
|
96642
|
+
function isTmuxSessionId(value) {
|
|
96643
|
+
return typeof value === "string" && TMUX_SESSION_ID_PATTERN.test(value);
|
|
96644
|
+
}
|
|
96645
|
+
function isTmuxWindowId(value) {
|
|
96646
|
+
return typeof value === "string" && TMUX_WINDOW_ID_PATTERN.test(value);
|
|
96647
|
+
}
|
|
96648
|
+
function isTmuxPaneId(value) {
|
|
96649
|
+
return typeof value === "string" && TMUX_PANE_ID_PATTERN.test(value);
|
|
96650
|
+
}
|
|
96651
|
+
function parseSnapshotInteger(value) {
|
|
96652
|
+
if (typeof value !== "string" || !/^\d+$/.test(value)) {
|
|
96653
|
+
return null;
|
|
96654
|
+
}
|
|
96655
|
+
return Number.parseInt(value, 10);
|
|
96656
|
+
}
|
|
96657
|
+
function formatSnapshotRowForLog(line, limit = 160) {
|
|
96658
|
+
if (line.length <= limit) {
|
|
96659
|
+
return line;
|
|
96660
|
+
}
|
|
96661
|
+
return `${line.slice(0, Math.max(0, limit - 3))}...`;
|
|
96662
|
+
}
|
|
96663
|
+
function splitSnapshotFields(line, fieldCount) {
|
|
96664
|
+
const parts = line.split(SNAPSHOT_FIELD_SEPARATOR);
|
|
96665
|
+
if (parts.length <= fieldCount) {
|
|
96666
|
+
return parts;
|
|
96667
|
+
}
|
|
96668
|
+
if (fieldCount === 2) {
|
|
96669
|
+
return [parts[0] ?? "", parts.slice(1).join(SNAPSHOT_FIELD_SEPARATOR)];
|
|
96670
|
+
}
|
|
96671
|
+
if (fieldCount === 4) {
|
|
96672
|
+
return [
|
|
96673
|
+
parts[0] ?? "",
|
|
96674
|
+
parts[1] ?? "",
|
|
96675
|
+
parts.slice(2, -1).join(SNAPSHOT_FIELD_SEPARATOR),
|
|
96676
|
+
parts.at(-1) ?? ""
|
|
96677
|
+
];
|
|
96678
|
+
}
|
|
96679
|
+
if (fieldCount === 8) {
|
|
96680
|
+
return [
|
|
96681
|
+
parts[0] ?? "",
|
|
96682
|
+
parts[1] ?? "",
|
|
96683
|
+
parts[2] ?? "",
|
|
96684
|
+
parts.slice(3, -4).join(SNAPSHOT_FIELD_SEPARATOR),
|
|
96685
|
+
parts.at(-4) ?? "",
|
|
96686
|
+
parts.at(-3) ?? "",
|
|
96687
|
+
parts.at(-2) ?? "",
|
|
96688
|
+
parts.at(-1) ?? ""
|
|
96689
|
+
];
|
|
96690
|
+
}
|
|
96691
|
+
if (fieldCount === 9) {
|
|
96692
|
+
return [
|
|
96693
|
+
parts[0] ?? "",
|
|
96694
|
+
parts[1] ?? "",
|
|
96695
|
+
parts[2] ?? "",
|
|
96696
|
+
parts.slice(3, -5).join(SNAPSHOT_FIELD_SEPARATOR),
|
|
96697
|
+
parts.at(-5) ?? "",
|
|
96698
|
+
parts.at(-4) ?? "",
|
|
96699
|
+
parts.at(-3) ?? "",
|
|
96700
|
+
parts.at(-2) ?? "",
|
|
96701
|
+
parts.at(-1) ?? ""
|
|
96702
|
+
];
|
|
96703
|
+
}
|
|
96704
|
+
return parts;
|
|
96705
|
+
}
|
|
96706
|
+
|
|
96628
96707
|
// ../../apps/gateway/src/tmux-client/target-missing.ts
|
|
96629
96708
|
class TmuxTargetMissingError extends Error {
|
|
96630
96709
|
constructor(message) {
|
|
@@ -96848,7 +96927,7 @@ class LocalExternalTmuxConnection {
|
|
|
96848
96927
|
if (!this.connected) {
|
|
96849
96928
|
return;
|
|
96850
96929
|
}
|
|
96851
|
-
this.runAndRefresh(["select-window", "-t", windowId]).catch((error51) => {
|
|
96930
|
+
this.runAndRefresh(["select-window", "-t", windowId], true).catch((error51) => {
|
|
96852
96931
|
this.callbacks.onError(error51);
|
|
96853
96932
|
});
|
|
96854
96933
|
}
|
|
@@ -97272,14 +97351,14 @@ class LocalExternalTmuxConnection {
|
|
|
97272
97351
|
"-p",
|
|
97273
97352
|
"-t",
|
|
97274
97353
|
this.sessionName,
|
|
97275
|
-
"#{session_id}
|
|
97354
|
+
["#{session_id}", "#{session_name}"].join(SNAPSHOT_FIELD_SEPARATOR)
|
|
97276
97355
|
]),
|
|
97277
97356
|
this.runTmuxAllowFailure([
|
|
97278
97357
|
"list-windows",
|
|
97279
97358
|
"-t",
|
|
97280
97359
|
this.sessionName,
|
|
97281
97360
|
"-F",
|
|
97282
|
-
"#{window_id}
|
|
97361
|
+
["#{window_id}", "#{window_index}", "#{window_name}", "#{window_active}"].join(SNAPSHOT_FIELD_SEPARATOR)
|
|
97283
97362
|
]),
|
|
97284
97363
|
this.runTmuxAllowFailure([
|
|
97285
97364
|
"list-panes",
|
|
@@ -97287,7 +97366,17 @@ class LocalExternalTmuxConnection {
|
|
|
97287
97366
|
"-t",
|
|
97288
97367
|
this.sessionName,
|
|
97289
97368
|
"-F",
|
|
97290
|
-
|
|
97369
|
+
[
|
|
97370
|
+
"#{pane_id}",
|
|
97371
|
+
"#{window_id}",
|
|
97372
|
+
"#{pane_index}",
|
|
97373
|
+
"#{pane_title}",
|
|
97374
|
+
"#{pane_active}",
|
|
97375
|
+
"#{pane_width}",
|
|
97376
|
+
"#{pane_height}",
|
|
97377
|
+
"#{window_active}",
|
|
97378
|
+
"#{pane_current_command}"
|
|
97379
|
+
].join(SNAPSHOT_FIELD_SEPARATOR)
|
|
97291
97380
|
])
|
|
97292
97381
|
]);
|
|
97293
97382
|
if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
|
|
@@ -97311,6 +97400,7 @@ ${panesRes.stderr}`;
|
|
|
97311
97400
|
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
97312
97401
|
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
97313
97402
|
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
97403
|
+
this.discardInvalidSnapshot();
|
|
97314
97404
|
this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
|
|
97315
97405
|
this.emitSnapshot();
|
|
97316
97406
|
}
|
|
@@ -97320,9 +97410,11 @@ ${panesRes.stderr}`;
|
|
|
97320
97410
|
if (!line.trim()) {
|
|
97321
97411
|
continue;
|
|
97322
97412
|
}
|
|
97323
|
-
const [id, name24] = line
|
|
97324
|
-
if (id) {
|
|
97413
|
+
const [id, name24] = splitSnapshotFields(line, 2);
|
|
97414
|
+
if (isTmuxSessionId(id)) {
|
|
97325
97415
|
this.snapshotSession = { id, name: name24 ?? "" };
|
|
97416
|
+
} else {
|
|
97417
|
+
console.warn(`[local] ignoring invalid tmux session id on ${this.deviceId}: ${id ?? ""}`);
|
|
97326
97418
|
}
|
|
97327
97419
|
return;
|
|
97328
97420
|
}
|
|
@@ -97333,18 +97425,19 @@ ${panesRes.stderr}`;
|
|
|
97333
97425
|
if (!line.trim()) {
|
|
97334
97426
|
continue;
|
|
97335
97427
|
}
|
|
97336
|
-
const [id, indexRaw, name24, activeRaw] = line
|
|
97337
|
-
|
|
97428
|
+
const [id, indexRaw, name24, activeRaw] = splitSnapshotFields(line, 4);
|
|
97429
|
+
const index = parseSnapshotInteger(indexRaw);
|
|
97430
|
+
if (!isTmuxWindowId(id) || index === null || !this.isSnapshotFlag(activeRaw)) {
|
|
97431
|
+
console.warn(`[local] ignoring invalid tmux window snapshot row on ${this.deviceId}: ${formatSnapshotRowForLog(line)}`);
|
|
97338
97432
|
continue;
|
|
97339
97433
|
}
|
|
97340
|
-
const index = Number.parseInt(indexRaw ?? "", 10);
|
|
97341
97434
|
const active = activeRaw === "1";
|
|
97342
97435
|
if (active) {
|
|
97343
97436
|
this.activeWindowId = id;
|
|
97344
97437
|
}
|
|
97345
97438
|
this.snapshotWindows.set(id, {
|
|
97346
97439
|
id,
|
|
97347
|
-
index
|
|
97440
|
+
index,
|
|
97348
97441
|
name: name24 ?? "",
|
|
97349
97442
|
active,
|
|
97350
97443
|
panes: []
|
|
@@ -97359,22 +97452,23 @@ ${panesRes.stderr}`;
|
|
|
97359
97452
|
if (!line.trim()) {
|
|
97360
97453
|
continue;
|
|
97361
97454
|
}
|
|
97362
|
-
const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw, windowActiveRaw, currentCommandRaw] = line
|
|
97363
|
-
|
|
97455
|
+
const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw, windowActiveRaw, currentCommandRaw] = splitSnapshotFields(line, 9);
|
|
97456
|
+
const index = parseSnapshotInteger(indexRaw);
|
|
97457
|
+
const width = parseSnapshotInteger(widthRaw);
|
|
97458
|
+
const height = parseSnapshotInteger(heightRaw);
|
|
97459
|
+
if (!isTmuxPaneId(paneId) || !isTmuxWindowId(windowId) || index === null || width === null || height === null || !this.isSnapshotFlag(activeRaw) || !this.isSnapshotFlag(windowActiveRaw)) {
|
|
97460
|
+
console.warn(`[local] ignoring invalid tmux pane snapshot row on ${this.deviceId}: ${formatSnapshotRowForLog(line)}`);
|
|
97364
97461
|
continue;
|
|
97365
97462
|
}
|
|
97366
|
-
const index = Number.parseInt(indexRaw ?? "", 10);
|
|
97367
|
-
const width = Number.parseInt(widthRaw ?? "", 10);
|
|
97368
|
-
const height = Number.parseInt(heightRaw ?? "", 10);
|
|
97369
97463
|
const pane = {
|
|
97370
97464
|
id: paneId,
|
|
97371
97465
|
windowId,
|
|
97372
|
-
index
|
|
97466
|
+
index,
|
|
97373
97467
|
title: this.pendingPaneTitles.get(paneId) ?? (titleRaw?.trim() ? titleRaw : undefined),
|
|
97374
97468
|
currentCommand: currentCommandRaw?.trim() ? currentCommandRaw.trim() : undefined,
|
|
97375
97469
|
active: activeRaw === "1",
|
|
97376
|
-
width
|
|
97377
|
-
height
|
|
97470
|
+
width,
|
|
97471
|
+
height
|
|
97378
97472
|
};
|
|
97379
97473
|
if (pane.active && windowActiveRaw === "1") {
|
|
97380
97474
|
this.activePaneId = paneId;
|
|
@@ -97391,6 +97485,23 @@ ${panesRes.stderr}`;
|
|
|
97391
97485
|
window2.panes.sort((left, right) => left.index - right.index);
|
|
97392
97486
|
}
|
|
97393
97487
|
}
|
|
97488
|
+
isSnapshotFlag(value) {
|
|
97489
|
+
return value === "0" || value === "1";
|
|
97490
|
+
}
|
|
97491
|
+
discardInvalidSnapshot() {
|
|
97492
|
+
if (!this.snapshotSession) {
|
|
97493
|
+
this.snapshotWindows.clear();
|
|
97494
|
+
this.activeWindowId = null;
|
|
97495
|
+
this.activePaneId = null;
|
|
97496
|
+
return;
|
|
97497
|
+
}
|
|
97498
|
+
if (this.snapshotWindows.size === 0) {
|
|
97499
|
+
console.warn(`[local] ignoring tmux snapshot with no valid windows on ${this.deviceId}`);
|
|
97500
|
+
this.snapshotSession = null;
|
|
97501
|
+
this.activeWindowId = null;
|
|
97502
|
+
this.activePaneId = null;
|
|
97503
|
+
}
|
|
97504
|
+
}
|
|
97394
97505
|
emitSnapshot() {
|
|
97395
97506
|
const session = this.snapshotSession ? {
|
|
97396
97507
|
id: this.snapshotSession.id,
|
|
@@ -97451,6 +97562,7 @@ ${panesRes.stderr}`;
|
|
|
97451
97562
|
this.recoverFromTargetMissingError(message);
|
|
97452
97563
|
return result;
|
|
97453
97564
|
}
|
|
97565
|
+
console.warn(`[local] tmux command failed deviceId=${this.deviceId} sessionName=${this.sessionName} argv=${argv.join(" ")} exitCode=${result.exitCode}: ${message}`);
|
|
97454
97566
|
this.notifyRuntimeError(message);
|
|
97455
97567
|
if (this.connected && !this.manualDisconnect && this.isTmuxServerGoneMessage(message)) {
|
|
97456
97568
|
console.warn(`[local] tmux server gone on ${this.deviceId}: ${message}`);
|
|
@@ -97862,51 +97974,12 @@ function hasRenderableTerminalContent2(value) {
|
|
|
97862
97974
|
}
|
|
97863
97975
|
var BELL_DEDUP_WINDOW_MS2 = 200;
|
|
97864
97976
|
var COMMAND_SENTINEL = "\x1ETMEX_END ";
|
|
97865
|
-
var SNAPSHOT_FIELD_SEPARATOR = "|";
|
|
97866
97977
|
var CONTROL_MAX_RESTARTS2 = 3;
|
|
97867
97978
|
var CONTROL_RESTART_DELAY_MS2 = 500;
|
|
97868
97979
|
var CONTROL_STABLE_RESET_MS2 = 1e4;
|
|
97869
97980
|
var CONTROL_STDERR_TAIL_LIMIT2 = 2048;
|
|
97870
97981
|
var CONTROL_ATTACH_READY_TIMEOUT_MS2 = 3000;
|
|
97871
97982
|
var PARKING_WINDOW_NAME2 = "tmex-park";
|
|
97872
|
-
function splitSnapshotFields(line, fieldCount) {
|
|
97873
|
-
const parts = line.split(SNAPSHOT_FIELD_SEPARATOR);
|
|
97874
|
-
if (parts.length <= fieldCount) {
|
|
97875
|
-
return parts;
|
|
97876
|
-
}
|
|
97877
|
-
if (fieldCount === 2) {
|
|
97878
|
-
return [parts[0] ?? "", parts.slice(1).join(SNAPSHOT_FIELD_SEPARATOR)];
|
|
97879
|
-
}
|
|
97880
|
-
if (fieldCount === 4) {
|
|
97881
|
-
return [parts[0] ?? "", parts[1] ?? "", parts.slice(2, -1).join(SNAPSHOT_FIELD_SEPARATOR), parts.at(-1) ?? ""];
|
|
97882
|
-
}
|
|
97883
|
-
if (fieldCount === 8) {
|
|
97884
|
-
return [
|
|
97885
|
-
parts[0] ?? "",
|
|
97886
|
-
parts[1] ?? "",
|
|
97887
|
-
parts[2] ?? "",
|
|
97888
|
-
parts.slice(3, -4).join(SNAPSHOT_FIELD_SEPARATOR),
|
|
97889
|
-
parts.at(-4) ?? "",
|
|
97890
|
-
parts.at(-3) ?? "",
|
|
97891
|
-
parts.at(-2) ?? "",
|
|
97892
|
-
parts.at(-1) ?? ""
|
|
97893
|
-
];
|
|
97894
|
-
}
|
|
97895
|
-
if (fieldCount === 9) {
|
|
97896
|
-
return [
|
|
97897
|
-
parts[0] ?? "",
|
|
97898
|
-
parts[1] ?? "",
|
|
97899
|
-
parts[2] ?? "",
|
|
97900
|
-
parts.slice(3, -5).join(SNAPSHOT_FIELD_SEPARATOR),
|
|
97901
|
-
parts.at(-5) ?? "",
|
|
97902
|
-
parts.at(-4) ?? "",
|
|
97903
|
-
parts.at(-3) ?? "",
|
|
97904
|
-
parts.at(-2) ?? "",
|
|
97905
|
-
parts.at(-1) ?? ""
|
|
97906
|
-
];
|
|
97907
|
-
}
|
|
97908
|
-
return parts;
|
|
97909
|
-
}
|
|
97910
97983
|
|
|
97911
97984
|
class SshExternalTmuxConnection {
|
|
97912
97985
|
deviceId;
|
|
@@ -98018,7 +98091,7 @@ class SshExternalTmuxConnection {
|
|
|
98018
98091
|
if (!this.connected) {
|
|
98019
98092
|
return;
|
|
98020
98093
|
}
|
|
98021
|
-
this.runAndRefresh(["select-window", "-t", windowId]).catch((error51) => {
|
|
98094
|
+
this.runAndRefresh(["select-window", "-t", windowId], true).catch((error51) => {
|
|
98022
98095
|
this.callbacks.onError(error51);
|
|
98023
98096
|
});
|
|
98024
98097
|
}
|
|
@@ -98558,6 +98631,7 @@ ${panesRes.stderr}`;
|
|
|
98558
98631
|
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
98559
98632
|
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
98560
98633
|
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
98634
|
+
this.discardInvalidSnapshot();
|
|
98561
98635
|
this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
|
|
98562
98636
|
this.emitSnapshot();
|
|
98563
98637
|
}
|
|
@@ -98568,8 +98642,10 @@ ${panesRes.stderr}`;
|
|
|
98568
98642
|
continue;
|
|
98569
98643
|
}
|
|
98570
98644
|
const [id, name24] = splitSnapshotFields(line, 2);
|
|
98571
|
-
if (id) {
|
|
98645
|
+
if (isTmuxSessionId(id)) {
|
|
98572
98646
|
this.snapshotSession = { id, name: name24 ?? "" };
|
|
98647
|
+
} else {
|
|
98648
|
+
console.warn(`[ssh] ignoring invalid tmux session id on ${this.deviceId}: ${id ?? ""}`);
|
|
98573
98649
|
}
|
|
98574
98650
|
return;
|
|
98575
98651
|
}
|
|
@@ -98581,17 +98657,18 @@ ${panesRes.stderr}`;
|
|
|
98581
98657
|
continue;
|
|
98582
98658
|
}
|
|
98583
98659
|
const [id, indexRaw, name24, activeRaw] = splitSnapshotFields(line, 4);
|
|
98584
|
-
|
|
98660
|
+
const index = parseSnapshotInteger(indexRaw);
|
|
98661
|
+
if (!isTmuxWindowId(id) || index === null || !this.isSnapshotFlag(activeRaw)) {
|
|
98662
|
+
console.warn(`[ssh] ignoring invalid tmux window snapshot row on ${this.deviceId}: ${formatSnapshotRowForLog(line)}`);
|
|
98585
98663
|
continue;
|
|
98586
98664
|
}
|
|
98587
|
-
const index = Number.parseInt(indexRaw ?? "", 10);
|
|
98588
98665
|
const active = activeRaw === "1";
|
|
98589
98666
|
if (active) {
|
|
98590
98667
|
this.activeWindowId = id;
|
|
98591
98668
|
}
|
|
98592
98669
|
this.snapshotWindows.set(id, {
|
|
98593
98670
|
id,
|
|
98594
|
-
index
|
|
98671
|
+
index,
|
|
98595
98672
|
name: name24 ?? "",
|
|
98596
98673
|
active,
|
|
98597
98674
|
panes: []
|
|
@@ -98607,21 +98684,22 @@ ${panesRes.stderr}`;
|
|
|
98607
98684
|
continue;
|
|
98608
98685
|
}
|
|
98609
98686
|
const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw, windowActiveRaw, currentCommandRaw] = splitSnapshotFields(line, 9);
|
|
98610
|
-
|
|
98687
|
+
const index = parseSnapshotInteger(indexRaw);
|
|
98688
|
+
const width = parseSnapshotInteger(widthRaw);
|
|
98689
|
+
const height = parseSnapshotInteger(heightRaw);
|
|
98690
|
+
if (!isTmuxPaneId(paneId) || !isTmuxWindowId(windowId) || index === null || width === null || height === null || !this.isSnapshotFlag(activeRaw) || !this.isSnapshotFlag(windowActiveRaw)) {
|
|
98691
|
+
console.warn(`[ssh] ignoring invalid tmux pane snapshot row on ${this.deviceId}: ${formatSnapshotRowForLog(line)}`);
|
|
98611
98692
|
continue;
|
|
98612
98693
|
}
|
|
98613
|
-
const index = Number.parseInt(indexRaw ?? "", 10);
|
|
98614
|
-
const width = Number.parseInt(widthRaw ?? "", 10);
|
|
98615
|
-
const height = Number.parseInt(heightRaw ?? "", 10);
|
|
98616
98694
|
const pane = {
|
|
98617
98695
|
id: paneId,
|
|
98618
98696
|
windowId,
|
|
98619
|
-
index
|
|
98697
|
+
index,
|
|
98620
98698
|
title: this.pendingPaneTitles.get(paneId) ?? (titleRaw?.trim() ? titleRaw : undefined),
|
|
98621
98699
|
currentCommand: currentCommandRaw?.trim() ? currentCommandRaw.trim() : undefined,
|
|
98622
98700
|
active: activeRaw === "1",
|
|
98623
|
-
width
|
|
98624
|
-
height
|
|
98701
|
+
width,
|
|
98702
|
+
height
|
|
98625
98703
|
};
|
|
98626
98704
|
if (pane.active && windowActiveRaw === "1") {
|
|
98627
98705
|
this.activePaneId = paneId;
|
|
@@ -98638,6 +98716,23 @@ ${panesRes.stderr}`;
|
|
|
98638
98716
|
window2.panes.sort((left, right) => left.index - right.index);
|
|
98639
98717
|
}
|
|
98640
98718
|
}
|
|
98719
|
+
isSnapshotFlag(value) {
|
|
98720
|
+
return value === "0" || value === "1";
|
|
98721
|
+
}
|
|
98722
|
+
discardInvalidSnapshot() {
|
|
98723
|
+
if (!this.snapshotSession) {
|
|
98724
|
+
this.snapshotWindows.clear();
|
|
98725
|
+
this.activeWindowId = null;
|
|
98726
|
+
this.activePaneId = null;
|
|
98727
|
+
return;
|
|
98728
|
+
}
|
|
98729
|
+
if (this.snapshotWindows.size === 0) {
|
|
98730
|
+
console.warn(`[ssh] ignoring tmux snapshot with no valid windows on ${this.deviceId}`);
|
|
98731
|
+
this.snapshotSession = null;
|
|
98732
|
+
this.activeWindowId = null;
|
|
98733
|
+
this.activePaneId = null;
|
|
98734
|
+
}
|
|
98735
|
+
}
|
|
98641
98736
|
emitSnapshot() {
|
|
98642
98737
|
const session = this.snapshotSession ? {
|
|
98643
98738
|
id: this.snapshotSession.id,
|
|
@@ -98698,6 +98793,7 @@ ${panesRes.stderr}`;
|
|
|
98698
98793
|
this.recoverFromTargetMissingError(message);
|
|
98699
98794
|
return result;
|
|
98700
98795
|
}
|
|
98796
|
+
console.warn(`[ssh] tmux command failed deviceId=${this.deviceId} sessionName=${this.sessionName} argv=${argv.join(" ")} exitCode=${result.exitCode}: ${message}`);
|
|
98701
98797
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
98702
98798
|
lastSeenAt: new Date().toISOString(),
|
|
98703
98799
|
tmuxAvailable: false,
|
|
@@ -105067,6 +105163,7 @@ class SwitchBarrier {
|
|
|
105067
105163
|
console.warn(`[switch-barrier] Transaction timeout at stage: ${stage} for ${deviceId}`);
|
|
105068
105164
|
if (stage === "history") {
|
|
105069
105165
|
this.sendLiveResume(ws, deviceId, expectedToken);
|
|
105166
|
+
sessionStateStore.stopOutputBuffering(ws, deviceId);
|
|
105070
105167
|
pending.callbacks.onTimeout?.(stage);
|
|
105071
105168
|
return;
|
|
105072
105169
|
}
|
|
@@ -105566,17 +105663,15 @@ class WebSocketServer {
|
|
|
105566
105663
|
}
|
|
105567
105664
|
handleTmuxSelect(ws, data) {
|
|
105568
105665
|
const deviceId = data.deviceId;
|
|
105569
|
-
const paneId = data.paneId ?? undefined;
|
|
105570
|
-
if (paneId) {
|
|
105571
|
-
ws.data.borshState.selectedPanes[deviceId] = paneId;
|
|
105572
|
-
this.refreshSnapshotPolling(deviceId);
|
|
105573
|
-
}
|
|
105574
105666
|
const entry = this.connections.get(deviceId);
|
|
105575
105667
|
if (!entry)
|
|
105576
105668
|
return;
|
|
105577
105669
|
const windowId = data.windowId ?? undefined;
|
|
105670
|
+
const paneId = data.paneId ?? undefined;
|
|
105578
105671
|
if (!windowId || !paneId)
|
|
105579
105672
|
return;
|
|
105673
|
+
if (!this.canSelectPane(entry, deviceId, windowId, paneId))
|
|
105674
|
+
return;
|
|
105580
105675
|
const started = switchBarrier.startTransaction(ws, {
|
|
105581
105676
|
deviceId,
|
|
105582
105677
|
windowId,
|
|
@@ -105590,6 +105685,8 @@ class WebSocketServer {
|
|
|
105590
105685
|
this.sendError(ws, null, exports_ws_borsh.ERROR_SELECT_CONFLICT, "Failed to start select transaction", false);
|
|
105591
105686
|
return;
|
|
105592
105687
|
}
|
|
105688
|
+
ws.data.borshState.selectedPanes[deviceId] = paneId;
|
|
105689
|
+
this.refreshSnapshotPolling(deviceId);
|
|
105593
105690
|
switchBarrier.sendSwitchAck(ws, deviceId);
|
|
105594
105691
|
const cols = data.cols ?? null;
|
|
105595
105692
|
const rows = data.rows ?? null;
|
|
@@ -105603,8 +105700,41 @@ class WebSocketServer {
|
|
|
105603
105700
|
const entry = this.connections.get(deviceId);
|
|
105604
105701
|
if (!entry)
|
|
105605
105702
|
return;
|
|
105703
|
+
if (!this.canSelectWindow(entry, deviceId, windowId))
|
|
105704
|
+
return;
|
|
105606
105705
|
entry.runtime.selectWindow(windowId);
|
|
105607
105706
|
}
|
|
105707
|
+
canSelectWindow(entry, deviceId, windowId) {
|
|
105708
|
+
if (!isTmuxWindowId(windowId)) {
|
|
105709
|
+
console.warn(`[ws] rejecting invalid tmux window id on ${deviceId}: ${windowId ?? ""}`);
|
|
105710
|
+
entry.runtime.requestSnapshot();
|
|
105711
|
+
return false;
|
|
105712
|
+
}
|
|
105713
|
+
const windows = entry.lastSnapshot?.session?.windows;
|
|
105714
|
+
if (!windows?.some((window2) => window2.id === windowId)) {
|
|
105715
|
+
console.warn(`[ws] rejecting missing tmux window id on ${deviceId}: ${windowId}`);
|
|
105716
|
+
entry.runtime.requestSnapshot();
|
|
105717
|
+
return false;
|
|
105718
|
+
}
|
|
105719
|
+
return true;
|
|
105720
|
+
}
|
|
105721
|
+
canSelectPane(entry, deviceId, windowId, paneId) {
|
|
105722
|
+
if (!this.canSelectWindow(entry, deviceId, windowId)) {
|
|
105723
|
+
return false;
|
|
105724
|
+
}
|
|
105725
|
+
if (!isTmuxPaneId(paneId)) {
|
|
105726
|
+
console.warn(`[ws] rejecting invalid tmux pane id on ${deviceId}: ${paneId ?? ""}`);
|
|
105727
|
+
entry.runtime.requestSnapshot();
|
|
105728
|
+
return false;
|
|
105729
|
+
}
|
|
105730
|
+
const window2 = entry.lastSnapshot?.session?.windows.find((candidate) => candidate.id === windowId);
|
|
105731
|
+
if (!window2?.panes.some((pane) => pane.id === paneId)) {
|
|
105732
|
+
console.warn(`[ws] rejecting missing tmux pane id on ${deviceId}: ${paneId}`);
|
|
105733
|
+
entry.runtime.requestSnapshot();
|
|
105734
|
+
return false;
|
|
105735
|
+
}
|
|
105736
|
+
return true;
|
|
105737
|
+
}
|
|
105608
105738
|
handleTermInput(deviceId, paneId, data) {
|
|
105609
105739
|
const entry = this.connections.get(deviceId);
|
|
105610
105740
|
if (!entry)
|