tmex-cli 0.9.0-test-1 → 0.9.2

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.
@@ -7721,7 +7721,7 @@ var require_bcrypt_pbkdf = __commonJS((exports, module) => {
7721
7721
 
7722
7722
  // ../../node_modules/.bun/cpu-features@0.0.10/node_modules/cpu-features/build/Release/cpufeatures.node
7723
7723
  var require_cpufeatures = __commonJS((exports, module) => {
7724
- module.exports = __require("./cpufeatures-j6gtp52k.node");
7724
+ module.exports = __require("./cpufeatures-dxrn1j88.node");
7725
7725
  });
7726
7726
 
7727
7727
  // ../../node_modules/.bun/cpu-features@0.0.10/node_modules/cpu-features/lib/index.js
@@ -8334,12 +8334,12 @@ var require_utils3 = __commonJS((exports, module) => {
8334
8334
 
8335
8335
  // ../../node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node
8336
8336
  var require_sshcrypto = __commonJS((exports, module) => {
8337
- module.exports = __require("./sshcrypto-k4w22zcv.node");
8337
+ module.exports = __require("./sshcrypto-fjcj736m.node");
8338
8338
  });
8339
8339
 
8340
8340
  // ../../node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib/protocol/crypto/poly1305.js
8341
8341
  var require_poly1305 = __commonJS((exports, module) => {
8342
- var __dirname = "/Users/krhougs/LocalCodes/tmex/.claude/worktrees/prod-crash-hardening/node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib/protocol/crypto", __filename = "/Users/krhougs/LocalCodes/tmex/.claude/worktrees/prod-crash-hardening/node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib/protocol/crypto/poly1305.js";
8342
+ var __dirname = "/Users/krhougs/LocalCodes/tmex/node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib/protocol/crypto", __filename = "/Users/krhougs/LocalCodes/tmex/node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib/protocol/crypto/poly1305.js";
8343
8343
  var createPoly1305 = function() {
8344
8344
  var _scriptDir = typeof document !== "undefined" && document.currentScript ? document.currentScript.src : undefined;
8345
8345
  if (typeof __filename !== "undefined")
@@ -11112,7 +11112,7 @@ ${formatted}-----END ${type} KEY-----`;
11112
11112
 
11113
11113
  // ../../node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib/agent.js
11114
11114
  var require_agent = __commonJS((exports, module) => {
11115
- var __dirname = "/Users/krhougs/LocalCodes/tmex/.claude/worktrees/prod-crash-hardening/node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib";
11115
+ var __dirname = "/Users/krhougs/LocalCodes/tmex/node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib";
11116
11116
  var { Socket } = __require("net");
11117
11117
  var { Duplex } = __require("stream");
11118
11118
  var { resolve: resolve3 } = __require("path");
@@ -95739,6 +95739,9 @@ var TMEX_INJECTED_ENV_EXACT = new Set(["NODE_ENV", "DATABASE_URL", "GATEWAY_PORT
95739
95739
  function isTmexInjectedEnvKey(key) {
95740
95740
  return key.startsWith("TMEX_") || TMEX_INJECTED_ENV_EXACT.has(key);
95741
95741
  }
95742
+ function isUtf8Locale(value) {
95743
+ return typeof value === "string" && /utf-?8/i.test(value);
95744
+ }
95742
95745
  function buildLocalTmuxEnv(resolvedPath, baseEnv = process.env) {
95743
95746
  const nextEnv = {};
95744
95747
  for (const [key, value] of Object.entries(baseEnv)) {
@@ -95750,8 +95753,8 @@ function buildLocalTmuxEnv(resolvedPath, baseEnv = process.env) {
95750
95753
  if (resolvedPath) {
95751
95754
  nextEnv.PATH = resolvedPath;
95752
95755
  }
95753
- if (!nextEnv.LC_CTYPE && !nextEnv.LC_ALL && !nextEnv.LANG) {
95754
- nextEnv.LC_CTYPE = "en_US.UTF-8";
95756
+ if (!isUtf8Locale(nextEnv.LC_ALL) && (nextEnv.LC_ALL || !isUtf8Locale(nextEnv.LC_CTYPE) && !isUtf8Locale(nextEnv.LANG))) {
95757
+ nextEnv.LC_ALL = "C.UTF-8";
95755
95758
  }
95756
95759
  return nextEnv;
95757
95760
  }
@@ -96631,6 +96634,76 @@ function encodeInputToHexChunks(input, chunkBytes = SEND_KEYS_HEX_CHUNK_BYTES) {
96631
96634
  return chunks;
96632
96635
  }
96633
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
+
96634
96707
  // ../../apps/gateway/src/tmux-client/target-missing.ts
96635
96708
  class TmuxTargetMissingError extends Error {
96636
96709
  constructor(message) {
@@ -96854,7 +96927,7 @@ class LocalExternalTmuxConnection {
96854
96927
  if (!this.connected) {
96855
96928
  return;
96856
96929
  }
96857
- this.runAndRefresh(["select-window", "-t", windowId]).catch((error51) => {
96930
+ this.runAndRefresh(["select-window", "-t", windowId], true).catch((error51) => {
96858
96931
  this.callbacks.onError(error51);
96859
96932
  });
96860
96933
  }
@@ -97278,14 +97351,14 @@ class LocalExternalTmuxConnection {
97278
97351
  "-p",
97279
97352
  "-t",
97280
97353
  this.sessionName,
97281
- "#{session_id}\t#{session_name}"
97354
+ ["#{session_id}", "#{session_name}"].join(SNAPSHOT_FIELD_SEPARATOR)
97282
97355
  ]),
97283
97356
  this.runTmuxAllowFailure([
97284
97357
  "list-windows",
97285
97358
  "-t",
97286
97359
  this.sessionName,
97287
97360
  "-F",
97288
- "#{window_id}\t#{window_index}\t#{window_name}\t#{window_active}"
97361
+ ["#{window_id}", "#{window_index}", "#{window_name}", "#{window_active}"].join(SNAPSHOT_FIELD_SEPARATOR)
97289
97362
  ]),
97290
97363
  this.runTmuxAllowFailure([
97291
97364
  "list-panes",
@@ -97293,7 +97366,17 @@ class LocalExternalTmuxConnection {
97293
97366
  "-t",
97294
97367
  this.sessionName,
97295
97368
  "-F",
97296
- "#{pane_id}\t#{window_id}\t#{pane_index}\t#{pane_title}\t#{pane_active}\t#{pane_width}\t#{pane_height}\t#{window_active}\t#{pane_current_command}"
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)
97297
97380
  ])
97298
97381
  ]);
97299
97382
  if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
@@ -97317,6 +97400,7 @@ ${panesRes.stderr}`;
97317
97400
  this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
97318
97401
  this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
97319
97402
  this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
97403
+ this.discardInvalidSnapshot();
97320
97404
  this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
97321
97405
  this.emitSnapshot();
97322
97406
  }
@@ -97326,9 +97410,11 @@ ${panesRes.stderr}`;
97326
97410
  if (!line.trim()) {
97327
97411
  continue;
97328
97412
  }
97329
- const [id, name24] = line.split("\t");
97330
- if (id) {
97413
+ const [id, name24] = splitSnapshotFields(line, 2);
97414
+ if (isTmuxSessionId(id)) {
97331
97415
  this.snapshotSession = { id, name: name24 ?? "" };
97416
+ } else {
97417
+ console.warn(`[local] ignoring invalid tmux session id on ${this.deviceId}: ${id ?? ""}`);
97332
97418
  }
97333
97419
  return;
97334
97420
  }
@@ -97339,18 +97425,19 @@ ${panesRes.stderr}`;
97339
97425
  if (!line.trim()) {
97340
97426
  continue;
97341
97427
  }
97342
- const [id, indexRaw, name24, activeRaw] = line.split("\t");
97343
- if (!id) {
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)}`);
97344
97432
  continue;
97345
97433
  }
97346
- const index = Number.parseInt(indexRaw ?? "", 10);
97347
97434
  const active = activeRaw === "1";
97348
97435
  if (active) {
97349
97436
  this.activeWindowId = id;
97350
97437
  }
97351
97438
  this.snapshotWindows.set(id, {
97352
97439
  id,
97353
- index: Number.isNaN(index) ? 0 : index,
97440
+ index,
97354
97441
  name: name24 ?? "",
97355
97442
  active,
97356
97443
  panes: []
@@ -97365,22 +97452,23 @@ ${panesRes.stderr}`;
97365
97452
  if (!line.trim()) {
97366
97453
  continue;
97367
97454
  }
97368
- const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw, windowActiveRaw, currentCommandRaw] = line.split("\t");
97369
- if (!paneId || !windowId) {
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)}`);
97370
97461
  continue;
97371
97462
  }
97372
- const index = Number.parseInt(indexRaw ?? "", 10);
97373
- const width = Number.parseInt(widthRaw ?? "", 10);
97374
- const height = Number.parseInt(heightRaw ?? "", 10);
97375
97463
  const pane = {
97376
97464
  id: paneId,
97377
97465
  windowId,
97378
- index: Number.isNaN(index) ? 0 : index,
97466
+ index,
97379
97467
  title: this.pendingPaneTitles.get(paneId) ?? (titleRaw?.trim() ? titleRaw : undefined),
97380
97468
  currentCommand: currentCommandRaw?.trim() ? currentCommandRaw.trim() : undefined,
97381
97469
  active: activeRaw === "1",
97382
- width: Number.isNaN(width) ? 0 : width,
97383
- height: Number.isNaN(height) ? 0 : height
97470
+ width,
97471
+ height
97384
97472
  };
97385
97473
  if (pane.active && windowActiveRaw === "1") {
97386
97474
  this.activePaneId = paneId;
@@ -97397,6 +97485,23 @@ ${panesRes.stderr}`;
97397
97485
  window2.panes.sort((left, right) => left.index - right.index);
97398
97486
  }
97399
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
+ }
97400
97505
  emitSnapshot() {
97401
97506
  const session = this.snapshotSession ? {
97402
97507
  id: this.snapshotSession.id,
@@ -97457,6 +97562,7 @@ ${panesRes.stderr}`;
97457
97562
  this.recoverFromTargetMissingError(message);
97458
97563
  return result;
97459
97564
  }
97565
+ console.warn(`[local] tmux command failed deviceId=${this.deviceId} sessionName=${this.sessionName} argv=${argv.join(" ")} exitCode=${result.exitCode}: ${message}`);
97460
97566
  this.notifyRuntimeError(message);
97461
97567
  if (this.connected && !this.manualDisconnect && this.isTmuxServerGoneMessage(message)) {
97462
97568
  console.warn(`[local] tmux server gone on ${this.deviceId}: ${message}`);
@@ -97868,51 +97974,12 @@ function hasRenderableTerminalContent2(value) {
97868
97974
  }
97869
97975
  var BELL_DEDUP_WINDOW_MS2 = 200;
97870
97976
  var COMMAND_SENTINEL = "\x1ETMEX_END ";
97871
- var SNAPSHOT_FIELD_SEPARATOR = "|";
97872
97977
  var CONTROL_MAX_RESTARTS2 = 3;
97873
97978
  var CONTROL_RESTART_DELAY_MS2 = 500;
97874
97979
  var CONTROL_STABLE_RESET_MS2 = 1e4;
97875
97980
  var CONTROL_STDERR_TAIL_LIMIT2 = 2048;
97876
97981
  var CONTROL_ATTACH_READY_TIMEOUT_MS2 = 3000;
97877
97982
  var PARKING_WINDOW_NAME2 = "tmex-park";
97878
- function splitSnapshotFields(line, fieldCount) {
97879
- const parts = line.split(SNAPSHOT_FIELD_SEPARATOR);
97880
- if (parts.length <= fieldCount) {
97881
- return parts;
97882
- }
97883
- if (fieldCount === 2) {
97884
- return [parts[0] ?? "", parts.slice(1).join(SNAPSHOT_FIELD_SEPARATOR)];
97885
- }
97886
- if (fieldCount === 4) {
97887
- return [parts[0] ?? "", parts[1] ?? "", parts.slice(2, -1).join(SNAPSHOT_FIELD_SEPARATOR), parts.at(-1) ?? ""];
97888
- }
97889
- if (fieldCount === 8) {
97890
- return [
97891
- parts[0] ?? "",
97892
- parts[1] ?? "",
97893
- parts[2] ?? "",
97894
- parts.slice(3, -4).join(SNAPSHOT_FIELD_SEPARATOR),
97895
- parts.at(-4) ?? "",
97896
- parts.at(-3) ?? "",
97897
- parts.at(-2) ?? "",
97898
- parts.at(-1) ?? ""
97899
- ];
97900
- }
97901
- if (fieldCount === 9) {
97902
- return [
97903
- parts[0] ?? "",
97904
- parts[1] ?? "",
97905
- parts[2] ?? "",
97906
- parts.slice(3, -5).join(SNAPSHOT_FIELD_SEPARATOR),
97907
- parts.at(-5) ?? "",
97908
- parts.at(-4) ?? "",
97909
- parts.at(-3) ?? "",
97910
- parts.at(-2) ?? "",
97911
- parts.at(-1) ?? ""
97912
- ];
97913
- }
97914
- return parts;
97915
- }
97916
97983
 
97917
97984
  class SshExternalTmuxConnection {
97918
97985
  deviceId;
@@ -98024,7 +98091,7 @@ class SshExternalTmuxConnection {
98024
98091
  if (!this.connected) {
98025
98092
  return;
98026
98093
  }
98027
- this.runAndRefresh(["select-window", "-t", windowId]).catch((error51) => {
98094
+ this.runAndRefresh(["select-window", "-t", windowId], true).catch((error51) => {
98028
98095
  this.callbacks.onError(error51);
98029
98096
  });
98030
98097
  }
@@ -98564,6 +98631,7 @@ ${panesRes.stderr}`;
98564
98631
  this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
98565
98632
  this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
98566
98633
  this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
98634
+ this.discardInvalidSnapshot();
98567
98635
  this.controlSubscription?.prunePanes(new Set(this.getExpectedPaneIds()));
98568
98636
  this.emitSnapshot();
98569
98637
  }
@@ -98574,8 +98642,10 @@ ${panesRes.stderr}`;
98574
98642
  continue;
98575
98643
  }
98576
98644
  const [id, name24] = splitSnapshotFields(line, 2);
98577
- if (id) {
98645
+ if (isTmuxSessionId(id)) {
98578
98646
  this.snapshotSession = { id, name: name24 ?? "" };
98647
+ } else {
98648
+ console.warn(`[ssh] ignoring invalid tmux session id on ${this.deviceId}: ${id ?? ""}`);
98579
98649
  }
98580
98650
  return;
98581
98651
  }
@@ -98587,17 +98657,18 @@ ${panesRes.stderr}`;
98587
98657
  continue;
98588
98658
  }
98589
98659
  const [id, indexRaw, name24, activeRaw] = splitSnapshotFields(line, 4);
98590
- if (!id) {
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)}`);
98591
98663
  continue;
98592
98664
  }
98593
- const index = Number.parseInt(indexRaw ?? "", 10);
98594
98665
  const active = activeRaw === "1";
98595
98666
  if (active) {
98596
98667
  this.activeWindowId = id;
98597
98668
  }
98598
98669
  this.snapshotWindows.set(id, {
98599
98670
  id,
98600
- index: Number.isNaN(index) ? 0 : index,
98671
+ index,
98601
98672
  name: name24 ?? "",
98602
98673
  active,
98603
98674
  panes: []
@@ -98613,21 +98684,22 @@ ${panesRes.stderr}`;
98613
98684
  continue;
98614
98685
  }
98615
98686
  const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw, windowActiveRaw, currentCommandRaw] = splitSnapshotFields(line, 9);
98616
- if (!paneId || !windowId) {
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)}`);
98617
98692
  continue;
98618
98693
  }
98619
- const index = Number.parseInt(indexRaw ?? "", 10);
98620
- const width = Number.parseInt(widthRaw ?? "", 10);
98621
- const height = Number.parseInt(heightRaw ?? "", 10);
98622
98694
  const pane = {
98623
98695
  id: paneId,
98624
98696
  windowId,
98625
- index: Number.isNaN(index) ? 0 : index,
98697
+ index,
98626
98698
  title: this.pendingPaneTitles.get(paneId) ?? (titleRaw?.trim() ? titleRaw : undefined),
98627
98699
  currentCommand: currentCommandRaw?.trim() ? currentCommandRaw.trim() : undefined,
98628
98700
  active: activeRaw === "1",
98629
- width: Number.isNaN(width) ? 0 : width,
98630
- height: Number.isNaN(height) ? 0 : height
98701
+ width,
98702
+ height
98631
98703
  };
98632
98704
  if (pane.active && windowActiveRaw === "1") {
98633
98705
  this.activePaneId = paneId;
@@ -98644,6 +98716,23 @@ ${panesRes.stderr}`;
98644
98716
  window2.panes.sort((left, right) => left.index - right.index);
98645
98717
  }
98646
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
+ }
98647
98736
  emitSnapshot() {
98648
98737
  const session = this.snapshotSession ? {
98649
98738
  id: this.snapshotSession.id,
@@ -98704,6 +98793,7 @@ ${panesRes.stderr}`;
98704
98793
  this.recoverFromTargetMissingError(message);
98705
98794
  return result;
98706
98795
  }
98796
+ console.warn(`[ssh] tmux command failed deviceId=${this.deviceId} sessionName=${this.sessionName} argv=${argv.join(" ")} exitCode=${result.exitCode}: ${message}`);
98707
98797
  updateDeviceRuntimeStatus(this.deviceId, {
98708
98798
  lastSeenAt: new Date().toISOString(),
98709
98799
  tmuxAvailable: false,
@@ -105573,17 +105663,15 @@ class WebSocketServer {
105573
105663
  }
105574
105664
  handleTmuxSelect(ws, data) {
105575
105665
  const deviceId = data.deviceId;
105576
- const paneId = data.paneId ?? undefined;
105577
- if (paneId) {
105578
- ws.data.borshState.selectedPanes[deviceId] = paneId;
105579
- this.refreshSnapshotPolling(deviceId);
105580
- }
105581
105666
  const entry = this.connections.get(deviceId);
105582
105667
  if (!entry)
105583
105668
  return;
105584
105669
  const windowId = data.windowId ?? undefined;
105670
+ const paneId = data.paneId ?? undefined;
105585
105671
  if (!windowId || !paneId)
105586
105672
  return;
105673
+ if (!this.canSelectPane(entry, deviceId, windowId, paneId))
105674
+ return;
105587
105675
  const started = switchBarrier.startTransaction(ws, {
105588
105676
  deviceId,
105589
105677
  windowId,
@@ -105597,6 +105685,8 @@ class WebSocketServer {
105597
105685
  this.sendError(ws, null, exports_ws_borsh.ERROR_SELECT_CONFLICT, "Failed to start select transaction", false);
105598
105686
  return;
105599
105687
  }
105688
+ ws.data.borshState.selectedPanes[deviceId] = paneId;
105689
+ this.refreshSnapshotPolling(deviceId);
105600
105690
  switchBarrier.sendSwitchAck(ws, deviceId);
105601
105691
  const cols = data.cols ?? null;
105602
105692
  const rows = data.rows ?? null;
@@ -105610,8 +105700,41 @@ class WebSocketServer {
105610
105700
  const entry = this.connections.get(deviceId);
105611
105701
  if (!entry)
105612
105702
  return;
105703
+ if (!this.canSelectWindow(entry, deviceId, windowId))
105704
+ return;
105613
105705
  entry.runtime.selectWindow(windowId);
105614
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
+ }
105615
105738
  handleTermInput(deviceId, paneId, data) {
105616
105739
  const entry = this.connections.get(deviceId);
105617
105740
  if (!entry)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmex-cli",
3
- "version": "0.9.0-test-1",
3
+ "version": "0.9.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tmex": "./bin/tmex.js",