tmex-cli 0.3.1 → 0.4.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.
Files changed (31) hide show
  1. package/dist/runtime/server.js +445 -219
  2. package/package.json +1 -1
  3. package/resources/fe-dist/assets/DevicePage-iSkEDEpS.js +4570 -0
  4. package/resources/fe-dist/assets/DevicePage-iSkEDEpS.js.map +1 -0
  5. package/resources/fe-dist/assets/DevicesPage-CtNzaW_c.js +2143 -0
  6. package/resources/fe-dist/assets/{DevicesPage-C76Xejy5.js.map → DevicesPage-CtNzaW_c.js.map} +1 -1
  7. package/resources/fe-dist/assets/SettingsPage-D25_d6j9.js +1144 -0
  8. package/resources/fe-dist/assets/{SettingsPage-DQ9W4fOo.js.map → SettingsPage-D25_d6j9.js.map} +1 -1
  9. package/resources/fe-dist/assets/index-CJaX5rlK.css +4527 -0
  10. package/resources/fe-dist/assets/index-dsVN7rgz.js +200 -0
  11. package/resources/fe-dist/assets/index-dsVN7rgz.js.map +1 -0
  12. package/resources/fe-dist/assets/select-BNsiC9zT.js +2805 -0
  13. package/resources/fe-dist/assets/{select-Wn7lKWHQ.js.map → select-BNsiC9zT.js.map} +1 -1
  14. package/resources/fe-dist/assets/switch-CIU4AisU.js +234 -0
  15. package/resources/fe-dist/assets/{switch-JVIhfemP.js.map → switch-CIU4AisU.js.map} +1 -1
  16. package/resources/fe-dist/assets/useValueChanged-V23H0VpC.js +351 -0
  17. package/resources/fe-dist/assets/{useValueChanged-DU---PIl.js.map → useValueChanged-V23H0VpC.js.map} +1 -1
  18. package/resources/fe-dist/index.html +2 -2
  19. package/resources/gateway-drizzle/meta/0000_snapshot.json +6 -17
  20. package/resources/gateway-drizzle/meta/0001_snapshot.json +6 -17
  21. package/resources/gateway-drizzle/meta/_journal.json +1 -1
  22. package/resources/fe-dist/assets/DevicePage-BTbDSWYN.js +0 -26
  23. package/resources/fe-dist/assets/DevicePage-BTbDSWYN.js.map +0 -1
  24. package/resources/fe-dist/assets/DevicesPage-C76Xejy5.js +0 -17
  25. package/resources/fe-dist/assets/SettingsPage-DQ9W4fOo.js +0 -17
  26. package/resources/fe-dist/assets/index-Bmahx5fj.js +0 -448
  27. package/resources/fe-dist/assets/index-Bmahx5fj.js.map +0 -1
  28. package/resources/fe-dist/assets/index-CyKyNcdz.css +0 -1
  29. package/resources/fe-dist/assets/select-Wn7lKWHQ.js +0 -17
  30. package/resources/fe-dist/assets/switch-JVIhfemP.js +0 -12
  31. package/resources/fe-dist/assets/useValueChanged-DU---PIl.js +0 -7
@@ -20281,8 +20281,8 @@ var require_lib3 = __commonJS((exports, module) => {
20281
20281
  });
20282
20282
 
20283
20283
  // src/runtime/server.ts
20284
- import { existsSync as existsSync3 } from "fs";
20285
- import { extname, join as join4, normalize, resolve as resolve2, sep } from "path";
20284
+ import { existsSync as existsSync4 } from "fs";
20285
+ import { extname, join as join5, normalize, resolve as resolve2, sep } from "path";
20286
20286
 
20287
20287
  // ../../apps/gateway/src/crypto/errors.ts
20288
20288
  function contextLabel(context) {
@@ -52310,7 +52310,7 @@ class LocalExternalTmuxConnection {
52310
52310
  if (!this.connected) {
52311
52311
  return;
52312
52312
  }
52313
- const argv = name ? ["new-window", "-n", name] : ["new-window"];
52313
+ const argv = name ? ["new-window", "-t", this.sessionName, "-n", name] : ["new-window", "-t", this.sessionName];
52314
52314
  this.runAndRefresh(argv).catch((error) => {
52315
52315
  this.callbacks.onError(error);
52316
52316
  });
@@ -52479,8 +52479,8 @@ class LocalExternalTmuxConnection {
52479
52479
  async capturePaneHistory(paneId) {
52480
52480
  const mode = (await this.runTmux(["display-message", "-p", "-t", paneId, "#{alternate_on}"], true)).stdout.trim();
52481
52481
  const alternateScreen = mode === "1";
52482
- const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-p"], true)).stdout;
52483
- const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-p", "-q"], true)).stdout;
52482
+ const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-N", "-p"], true)).stdout;
52483
+ const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-N", "-p", "-q"], true)).stdout;
52484
52484
  const history = alternateScreen ? hasRenderableTerminalContent(normal) ? normal : alternate : normal || alternate;
52485
52485
  if (history) {
52486
52486
  this.callbacks.onTerminalHistory(paneId, history, alternateScreen);
@@ -52733,6 +52733,10 @@ class LocalExternalTmuxConnection {
52733
52733
  // ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
52734
52734
  var import_ssh2 = __toESM(require_lib3(), 1);
52735
52735
 
52736
+ // ../../apps/gateway/src/tmux-client/ssh-connect-config.ts
52737
+ import { existsSync as existsSync2, readFileSync } from "fs";
52738
+ import { join as join4 } from "path";
52739
+
52736
52740
  // ../../apps/gateway/src/tmux/ssh-auth.ts
52737
52741
  function normalizeEnvValue(value) {
52738
52742
  const trimmed = value?.trim();
@@ -52765,6 +52769,219 @@ function resolveSshAgentSocket(authMode, env = process.env) {
52765
52769
  return;
52766
52770
  }
52767
52771
 
52772
+ // ../../apps/gateway/src/tmux-client/ssh-connect-config.ts
52773
+ function defaultRunSync2(cmd) {
52774
+ const result = Bun.spawnSync(cmd, {
52775
+ env: process.env,
52776
+ stdout: "pipe",
52777
+ stderr: "pipe"
52778
+ });
52779
+ return {
52780
+ exitCode: result.exitCode,
52781
+ stdout: Buffer.from(result.stdout).toString("utf8"),
52782
+ stderr: Buffer.from(result.stderr).toString("utf8")
52783
+ };
52784
+ }
52785
+ function expandHomePath(value, env) {
52786
+ const trimmed = value.trim();
52787
+ if (trimmed === "~") {
52788
+ return env.HOME?.trim() || trimmed;
52789
+ }
52790
+ if (trimmed.startsWith("~/") && env.HOME?.trim()) {
52791
+ return join4(env.HOME.trim(), trimmed.slice(2));
52792
+ }
52793
+ return trimmed;
52794
+ }
52795
+ function parseSshConfigOutput(stdout, env) {
52796
+ let host = "";
52797
+ let port;
52798
+ let username;
52799
+ let identityAgent;
52800
+ const identityFiles = [];
52801
+ for (const rawLine of stdout.split(/\r?\n/)) {
52802
+ const line = rawLine.trim();
52803
+ if (!line) {
52804
+ continue;
52805
+ }
52806
+ const firstSpace = line.indexOf(" ");
52807
+ if (firstSpace <= 0) {
52808
+ continue;
52809
+ }
52810
+ const key = line.slice(0, firstSpace).trim().toLowerCase();
52811
+ const value = line.slice(firstSpace + 1).trim();
52812
+ if (!value) {
52813
+ continue;
52814
+ }
52815
+ switch (key) {
52816
+ case "hostname":
52817
+ host = value;
52818
+ break;
52819
+ case "port": {
52820
+ const parsedPort = Number.parseInt(value, 10);
52821
+ port = Number.isNaN(parsedPort) ? undefined : parsedPort;
52822
+ break;
52823
+ }
52824
+ case "user":
52825
+ username = value;
52826
+ break;
52827
+ case "identityagent":
52828
+ identityAgent = value;
52829
+ break;
52830
+ case "identityfile":
52831
+ identityFiles.push(expandHomePath(value, env));
52832
+ break;
52833
+ }
52834
+ }
52835
+ if (!host) {
52836
+ throw new Error("ssh_config_ref_invalid: SSH Config \u5F15\u7528\u672A\u89E3\u6790\u5230 hostname");
52837
+ }
52838
+ return {
52839
+ host,
52840
+ port,
52841
+ username,
52842
+ identityAgent,
52843
+ identityFiles
52844
+ };
52845
+ }
52846
+ function toSshAuthEnv(env) {
52847
+ return {
52848
+ SSH_AUTH_SOCK: env.SSH_AUTH_SOCK,
52849
+ USER: env.USER,
52850
+ LOGNAME: env.LOGNAME
52851
+ };
52852
+ }
52853
+ function resolveAgentFromConfig(identityAgent, deps) {
52854
+ const trimmed = identityAgent?.trim();
52855
+ if (!trimmed || trimmed.toLowerCase() === "none") {
52856
+ return;
52857
+ }
52858
+ if (trimmed === "SSH_AUTH_SOCK" || trimmed === "$SSH_AUTH_SOCK") {
52859
+ return resolveSshAgentSocket("auto", toSshAuthEnv(deps.env));
52860
+ }
52861
+ const expanded = expandHomePath(trimmed, deps.env);
52862
+ return deps.fileExists(expanded) ? expanded : undefined;
52863
+ }
52864
+ function resolvePrivateKeyFromConfig(identityFiles, deps) {
52865
+ for (const identityFile of identityFiles) {
52866
+ if (!deps.fileExists(identityFile)) {
52867
+ continue;
52868
+ }
52869
+ return deps.readTextFile(identityFile);
52870
+ }
52871
+ return;
52872
+ }
52873
+ function resolveSshConfigRef(device, deps) {
52874
+ const ref = device.sshConfigRef?.trim();
52875
+ if (!ref) {
52876
+ return null;
52877
+ }
52878
+ const result = deps.runSync(["ssh", "-G", ref]);
52879
+ if (result.exitCode !== 0) {
52880
+ const detail = result.stderr.trim() || result.stdout.trim() || ref;
52881
+ throw new Error(`ssh_config_ref_resolve_failed: ${detail}`);
52882
+ }
52883
+ return parseSshConfigOutput(result.stdout, deps.env);
52884
+ }
52885
+ async function resolveSshConnectConfig(device, decrypt2, inputDeps = {}) {
52886
+ const deps = {
52887
+ env: inputDeps.env ?? process.env,
52888
+ runSync: inputDeps.runSync ?? defaultRunSync2,
52889
+ fileExists: inputDeps.fileExists ?? existsSync2,
52890
+ readTextFile: inputDeps.readTextFile ?? ((path) => readFileSync(path, "utf8"))
52891
+ };
52892
+ const sshEnv = toSshAuthEnv(deps.env);
52893
+ const resolvedConfig = resolveSshConfigRef(device, deps);
52894
+ const host = resolvedConfig?.host ?? device.host;
52895
+ const port = resolvedConfig?.port ?? device.port ?? 22;
52896
+ const username = resolvedConfig?.username ?? resolveSshUsername(device.username, device.authMode, sshEnv);
52897
+ if (!host) {
52898
+ throw new Error("SSH device missing host");
52899
+ }
52900
+ const authConfig = {
52901
+ host,
52902
+ port,
52903
+ username
52904
+ };
52905
+ const configAgent = resolveAgentFromConfig(resolvedConfig?.identityAgent, deps);
52906
+ const envAgent = resolveSshAgentSocket("auto", sshEnv);
52907
+ const configPrivateKey = resolvePrivateKeyFromConfig(resolvedConfig?.identityFiles ?? [], deps);
52908
+ switch (device.authMode) {
52909
+ case "password": {
52910
+ if (!device.passwordEnc) {
52911
+ throw new Error("auth_password_missing: \u5BC6\u7801\u8BA4\u8BC1\u672A\u63D0\u4F9B\u5BC6\u7801");
52912
+ }
52913
+ authConfig.password = await decrypt2(device.passwordEnc, {
52914
+ scope: "device",
52915
+ entityId: device.id,
52916
+ field: "password_enc"
52917
+ });
52918
+ break;
52919
+ }
52920
+ case "key": {
52921
+ if (!device.privateKeyEnc) {
52922
+ throw new Error("auth_key_missing: \u79C1\u94A5\u8BA4\u8BC1\u672A\u63D0\u4F9B\u79C1\u94A5");
52923
+ }
52924
+ authConfig.privateKey = await decrypt2(device.privateKeyEnc, {
52925
+ scope: "device",
52926
+ entityId: device.id,
52927
+ field: "private_key_enc"
52928
+ });
52929
+ if (device.privateKeyPassphraseEnc) {
52930
+ authConfig.passphrase = await decrypt2(device.privateKeyPassphraseEnc, {
52931
+ scope: "device",
52932
+ entityId: device.id,
52933
+ field: "private_key_passphrase_enc"
52934
+ });
52935
+ }
52936
+ break;
52937
+ }
52938
+ case "agent": {
52939
+ authConfig.agent = configAgent ?? resolveSshAgentSocket("agent", sshEnv);
52940
+ break;
52941
+ }
52942
+ case "configRef": {
52943
+ if (!resolvedConfig) {
52944
+ throw new Error("ssh_config_ref_missing: SSH Config \u5F15\u7528\u4E0D\u80FD\u4E3A\u7A7A");
52945
+ }
52946
+ if (configAgent ?? envAgent) {
52947
+ authConfig.agent = configAgent ?? envAgent;
52948
+ }
52949
+ if (configPrivateKey) {
52950
+ authConfig.privateKey = configPrivateKey;
52951
+ }
52952
+ if (!authConfig.agent && !authConfig.privateKey) {
52953
+ throw new Error("ssh_config_ref_auth_missing: SSH Config \u5F15\u7528\u672A\u89E3\u6790\u5230\u53EF\u7528\u8BA4\u8BC1\u65B9\u5F0F\uFF08IdentityAgent / IdentityFile / SSH_AUTH_SOCK\uFF09");
52954
+ }
52955
+ break;
52956
+ }
52957
+ case "auto": {
52958
+ if (configAgent ?? envAgent) {
52959
+ authConfig.agent = configAgent ?? envAgent;
52960
+ }
52961
+ if (device.privateKeyEnc) {
52962
+ authConfig.privateKey = await decrypt2(device.privateKeyEnc, {
52963
+ scope: "device",
52964
+ entityId: device.id,
52965
+ field: "private_key_enc"
52966
+ });
52967
+ } else if (configPrivateKey) {
52968
+ authConfig.privateKey = configPrivateKey;
52969
+ } else if (device.passwordEnc) {
52970
+ authConfig.password = await decrypt2(device.passwordEnc, {
52971
+ scope: "device",
52972
+ entityId: device.id,
52973
+ field: "password_enc"
52974
+ });
52975
+ }
52976
+ break;
52977
+ }
52978
+ }
52979
+ if (device.authMode === "auto" && !authConfig.agent && !authConfig.privateKey && !authConfig.password) {
52980
+ throw new Error("auth_auto_missing: auto \u6A21\u5F0F\u4E0B\u672A\u627E\u5230\u53EF\u7528\u8BA4\u8BC1\u65B9\u5F0F\uFF08SSH_AUTH_SOCK / \u79C1\u94A5 / \u5BC6\u7801\uFF09");
52981
+ }
52982
+ return authConfig;
52983
+ }
52984
+
52768
52985
  // ../../apps/gateway/src/tmux-client/ssh-bootstrap.ts
52769
52986
  function buildSshBootstrapScript() {
52770
52987
  return [
@@ -52810,6 +53027,31 @@ function hasRenderableTerminalContent2(value) {
52810
53027
  }
52811
53028
  var BELL_DEDUP_WINDOW_MS2 = 200;
52812
53029
  var COMMAND_SENTINEL = "\x1ETMEX_END ";
53030
+ var SNAPSHOT_FIELD_SEPARATOR = "|";
53031
+ function splitSnapshotFields(line, fieldCount) {
53032
+ const parts = line.split(SNAPSHOT_FIELD_SEPARATOR);
53033
+ if (parts.length <= fieldCount) {
53034
+ return parts;
53035
+ }
53036
+ if (fieldCount === 2) {
53037
+ return [parts[0] ?? "", parts.slice(1).join(SNAPSHOT_FIELD_SEPARATOR)];
53038
+ }
53039
+ if (fieldCount === 4) {
53040
+ return [parts[0] ?? "", parts[1] ?? "", parts.slice(2, -1).join(SNAPSHOT_FIELD_SEPARATOR), parts.at(-1) ?? ""];
53041
+ }
53042
+ if (fieldCount === 7) {
53043
+ return [
53044
+ parts[0] ?? "",
53045
+ parts[1] ?? "",
53046
+ parts[2] ?? "",
53047
+ parts.slice(3, -3).join(SNAPSHOT_FIELD_SEPARATOR),
53048
+ parts.at(-3) ?? "",
53049
+ parts.at(-2) ?? "",
53050
+ parts.at(-1) ?? ""
53051
+ ];
53052
+ }
53053
+ return parts;
53054
+ }
52813
53055
 
52814
53056
  class SshExternalTmuxConnection {
52815
53057
  deviceId;
@@ -52938,7 +53180,7 @@ class SshExternalTmuxConnection {
52938
53180
  if (!this.connected) {
52939
53181
  return;
52940
53182
  }
52941
- const argv = name ? ["new-window", "-n", name] : ["new-window"];
53183
+ const argv = name ? ["new-window", "-t", this.sessionName, "-n", name] : ["new-window", "-t", this.sessionName];
52942
53184
  this.runAndRefresh(argv).catch((error) => {
52943
53185
  this.callbacks.onError(error);
52944
53186
  });
@@ -52971,80 +53213,7 @@ class SshExternalTmuxConnection {
52971
53213
  if (!this.device) {
52972
53214
  throw new Error("SSH device not loaded");
52973
53215
  }
52974
- const host = this.device.host;
52975
- const port = this.device.port ?? 22;
52976
- const username = resolveSshUsername(this.device.username, this.device.authMode);
52977
- if (this.device.authMode === "configRef" || !host && this.device.sshConfigRef) {
52978
- throw new Error("ssh_config_ref_not_supported: \u5F53\u524D\u7248\u672C\u6682\u4E0D\u652F\u6301 SSH Config \u5F15\u7528\uFF0C\u8BF7\u6539\u4E3A\u586B\u5199 host + username\uFF0C\u5E76\u9009\u62E9 Agent/\u79C1\u94A5/\u5BC6\u7801\u8BA4\u8BC1");
52979
- }
52980
- if (!host) {
52981
- throw new Error("SSH device missing host");
52982
- }
52983
- const authConfig = {
52984
- host,
52985
- port,
52986
- username
52987
- };
52988
- switch (this.device.authMode) {
52989
- case "password": {
52990
- if (!this.device.passwordEnc) {
52991
- throw new Error("auth_password_missing: \u5BC6\u7801\u8BA4\u8BC1\u672A\u63D0\u4F9B\u5BC6\u7801");
52992
- }
52993
- authConfig.password = await this.deps.decrypt(this.device.passwordEnc, {
52994
- scope: "device",
52995
- entityId: this.device.id,
52996
- field: "password_enc"
52997
- });
52998
- break;
52999
- }
53000
- case "key": {
53001
- if (!this.device.privateKeyEnc) {
53002
- throw new Error("auth_key_missing: \u79C1\u94A5\u8BA4\u8BC1\u672A\u63D0\u4F9B\u79C1\u94A5");
53003
- }
53004
- authConfig.privateKey = await this.deps.decrypt(this.device.privateKeyEnc, {
53005
- scope: "device",
53006
- entityId: this.device.id,
53007
- field: "private_key_enc"
53008
- });
53009
- if (this.device.privateKeyPassphraseEnc) {
53010
- authConfig.passphrase = await this.deps.decrypt(this.device.privateKeyPassphraseEnc, {
53011
- scope: "device",
53012
- entityId: this.device.id,
53013
- field: "private_key_passphrase_enc"
53014
- });
53015
- }
53016
- break;
53017
- }
53018
- case "agent": {
53019
- authConfig.agent = resolveSshAgentSocket("agent");
53020
- break;
53021
- }
53022
- case "auto": {
53023
- const agentSocket = resolveSshAgentSocket("auto");
53024
- if (agentSocket) {
53025
- authConfig.agent = agentSocket;
53026
- }
53027
- if (this.device.privateKeyEnc) {
53028
- authConfig.privateKey = await this.deps.decrypt(this.device.privateKeyEnc, {
53029
- scope: "device",
53030
- entityId: this.device.id,
53031
- field: "private_key_enc"
53032
- });
53033
- } else if (this.device.passwordEnc) {
53034
- authConfig.password = await this.deps.decrypt(this.device.passwordEnc, {
53035
- scope: "device",
53036
- entityId: this.device.id,
53037
- field: "password_enc"
53038
- });
53039
- }
53040
- break;
53041
- }
53042
- case "configRef":
53043
- break;
53044
- }
53045
- if (this.device.authMode === "auto" && !authConfig.agent && !authConfig.privateKey && !authConfig.password) {
53046
- throw new Error("auth_auto_missing: auto \u6A21\u5F0F\u4E0B\u672A\u627E\u5230\u53EF\u7528\u8BA4\u8BC1\u65B9\u5F0F\uFF08SSH_AUTH_SOCK / \u79C1\u94A5 / \u5BC6\u7801\uFF09");
53047
- }
53216
+ const authConfig = await resolveSshConnectConfig(this.device, this.deps.decrypt);
53048
53217
  const client = this.deps.createClient();
53049
53218
  this.sshClient = client;
53050
53219
  await new Promise((resolve, reject) => {
@@ -53270,8 +53439,8 @@ class SshExternalTmuxConnection {
53270
53439
  async capturePaneHistory(paneId) {
53271
53440
  const mode = (await this.runTmux(["display-message", "-p", "-t", paneId, "#{alternate_on}"], true)).stdout.trim();
53272
53441
  const alternateScreen = mode === "1";
53273
- const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-p"], true, 30000)).stdout;
53274
- const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-p", "-q"], true, 30000)).stdout;
53442
+ const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-N", "-p"], true, 30000)).stdout;
53443
+ const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-N", "-p", "-q"], true, 30000)).stdout;
53275
53444
  const history = alternateScreen ? hasRenderableTerminalContent2(normal) ? normal : alternate : normal || alternate;
53276
53445
  if (history) {
53277
53446
  this.callbacks.onTerminalHistory(paneId, history, alternateScreen);
@@ -53287,21 +53456,21 @@ class SshExternalTmuxConnection {
53287
53456
  "-p",
53288
53457
  "-t",
53289
53458
  this.sessionName,
53290
- "#{session_id}\t#{session_name}"
53459
+ "#{session_id}|#{session_name}"
53291
53460
  ]),
53292
53461
  this.runTmuxAllowFailure([
53293
53462
  "list-windows",
53294
53463
  "-t",
53295
53464
  this.sessionName,
53296
53465
  "-F",
53297
- "#{window_id}\t#{window_index}\t#{window_name}\t#{window_active}"
53466
+ "#{window_id}|#{window_index}|#{window_name}|#{window_active}"
53298
53467
  ]),
53299
53468
  this.runTmuxAllowFailure([
53300
53469
  "list-panes",
53301
53470
  "-t",
53302
53471
  this.sessionName,
53303
53472
  "-F",
53304
- "#{pane_id}\t#{window_id}\t#{pane_index}\t#{pane_title}\t#{pane_active}\t#{pane_width}\t#{pane_height}"
53473
+ "#{pane_id}|#{window_id}|#{pane_index}|#{pane_title}|#{pane_active}|#{pane_width}|#{pane_height}"
53305
53474
  ])
53306
53475
  ]);
53307
53476
  if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
@@ -53319,7 +53488,7 @@ class SshExternalTmuxConnection {
53319
53488
  if (!line.trim()) {
53320
53489
  continue;
53321
53490
  }
53322
- const [id, name] = line.split("\t");
53491
+ const [id, name] = splitSnapshotFields(line, 2);
53323
53492
  if (id) {
53324
53493
  this.snapshotSession = { id, name: name ?? "" };
53325
53494
  }
@@ -53332,7 +53501,7 @@ class SshExternalTmuxConnection {
53332
53501
  if (!line.trim()) {
53333
53502
  continue;
53334
53503
  }
53335
- const [id, indexRaw, name, activeRaw] = line.split("\t");
53504
+ const [id, indexRaw, name, activeRaw] = splitSnapshotFields(line, 4);
53336
53505
  if (!id) {
53337
53506
  continue;
53338
53507
  }
@@ -53358,7 +53527,7 @@ class SshExternalTmuxConnection {
53358
53527
  if (!line.trim()) {
53359
53528
  continue;
53360
53529
  }
53361
- const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw] = line.split("\t");
53530
+ const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw] = splitSnapshotFields(line, 7);
53362
53531
  if (!paneId || !windowId) {
53363
53532
  continue;
53364
53533
  }
@@ -53702,6 +53871,7 @@ class DeviceSessionRuntime {
53702
53871
  }
53703
53872
  this.closeEmitted = true;
53704
53873
  this.terminated = true;
53874
+ this.connectPromise = null;
53705
53875
  this.broadcast((listener) => listener.onClose?.());
53706
53876
  }
53707
53877
  });
@@ -53713,7 +53883,7 @@ class DeviceSessionRuntime {
53713
53883
  };
53714
53884
  }
53715
53885
  async connect() {
53716
- if (this.terminated && !this.connectPromise) {
53886
+ if (this.terminated) {
53717
53887
  return Promise.reject(new Error(`Device session runtime already terminated: ${this.deviceId}`));
53718
53888
  }
53719
53889
  if (this.connectPromise) {
@@ -53721,6 +53891,7 @@ class DeviceSessionRuntime {
53721
53891
  }
53722
53892
  this.connectPromise = this.connection.connect().catch((error) => {
53723
53893
  this.terminated = true;
53894
+ this.connectPromise = null;
53724
53895
  throw error;
53725
53896
  });
53726
53897
  return this.connectPromise;
@@ -53731,6 +53902,7 @@ class DeviceSessionRuntime {
53731
53902
  }
53732
53903
  this.terminated = true;
53733
53904
  this.manualDisconnect = true;
53905
+ this.connectPromise = null;
53734
53906
  this.connection.disconnect();
53735
53907
  }
53736
53908
  async shutdown() {
@@ -54145,6 +54317,144 @@ class PushSupervisor {
54145
54317
  }
54146
54318
  var pushSupervisor = new PushSupervisor;
54147
54319
 
54320
+ // ../../apps/gateway/src/ws/error-classify.ts
54321
+ function classifySshError(error) {
54322
+ const msg = error.message.toLowerCase();
54323
+ if (msg.includes("ssh_config_ref_not_supported")) {
54324
+ return {
54325
+ type: "ssh_config_ref_not_supported",
54326
+ messageKey: "sshError.configRefNotSupported"
54327
+ };
54328
+ }
54329
+ if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
54330
+ return {
54331
+ type: "agent_unavailable",
54332
+ messageKey: "sshError.agentUnavailable"
54333
+ };
54334
+ }
54335
+ if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
54336
+ return {
54337
+ type: "agent_no_identity",
54338
+ messageKey: "sshError.agentNoIdentities"
54339
+ };
54340
+ }
54341
+ if (msg.includes("permission denied")) {
54342
+ return {
54343
+ type: "auth_failed",
54344
+ messageKey: "sshError.authFailed"
54345
+ };
54346
+ }
54347
+ if (msg.includes("all configured authentication methods failed")) {
54348
+ return {
54349
+ type: "auth_failed",
54350
+ messageKey: "sshError.authFailedGeneric"
54351
+ };
54352
+ }
54353
+ if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
54354
+ return {
54355
+ type: "network_unreachable",
54356
+ messageKey: "sshError.networkUnreachable"
54357
+ };
54358
+ }
54359
+ if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
54360
+ return {
54361
+ type: "connection_refused",
54362
+ messageKey: "sshError.connectionRefused"
54363
+ };
54364
+ }
54365
+ if (msg.includes("timeout") || msg.includes("etimedout")) {
54366
+ return {
54367
+ type: "timeout",
54368
+ messageKey: "sshError.connectionTimeout"
54369
+ };
54370
+ }
54371
+ if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
54372
+ return {
54373
+ type: "host_not_found",
54374
+ messageKey: "sshError.hostNotFound"
54375
+ };
54376
+ }
54377
+ if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
54378
+ return {
54379
+ type: "handshake_failed",
54380
+ messageKey: "sshError.handshakeFailed"
54381
+ };
54382
+ }
54383
+ if (msg.includes("remote tmux unavailable") || msg.includes("tmux_not_found") || msg.includes("tmux: command not found") || msg.includes("tmux control mode not ready") || msg.includes("tmux exited") || msg.includes("tmux_exec_failed")) {
54384
+ return {
54385
+ type: "tmux_unavailable",
54386
+ messageKey: "sshError.tmuxUnavailable"
54387
+ };
54388
+ }
54389
+ return {
54390
+ type: "unknown",
54391
+ messageKey: "sshError.unknown",
54392
+ messageParams: { message: error.message }
54393
+ };
54394
+ }
54395
+
54396
+ // ../../apps/gateway/src/api/test-connection.ts
54397
+ function inferFailurePhase(errorType) {
54398
+ if (errorType === "tmux_unavailable") {
54399
+ return "bootstrap";
54400
+ }
54401
+ return "connect";
54402
+ }
54403
+ function json(data, status = 200) {
54404
+ return new Response(JSON.stringify(data), {
54405
+ status,
54406
+ headers: {
54407
+ "Content-Type": "application/json"
54408
+ }
54409
+ });
54410
+ }
54411
+ async function handleDeviceTestConnection(deviceId, inputDeps = {}) {
54412
+ const deps = {
54413
+ getDevice: inputDeps.getDevice ?? ((currentDeviceId) => getDeviceById(currentDeviceId)),
54414
+ acquireRuntime: inputDeps.acquireRuntime ?? ((currentDeviceId) => tmuxRuntimeRegistry.acquire(currentDeviceId)),
54415
+ releaseRuntime: inputDeps.releaseRuntime ?? (async (currentDeviceId) => {
54416
+ await tmuxRuntimeRegistry.release(currentDeviceId);
54417
+ }),
54418
+ translate: inputDeps.translate ?? t2
54419
+ };
54420
+ const device = deps.getDevice(deviceId);
54421
+ if (!device) {
54422
+ return json({ error: deps.translate("apiError.deviceNotFound") }, 404);
54423
+ }
54424
+ const classifyErrorResponse = (error) => {
54425
+ const rawMessage = error instanceof Error ? error.message : String(error);
54426
+ const classified = classifySshError(new Error(rawMessage));
54427
+ const payload = {
54428
+ success: false,
54429
+ tmuxAvailable: false,
54430
+ phase: inferFailurePhase(classified.type),
54431
+ errorType: classified.type,
54432
+ message: deps.translate(classified.messageKey, classified.messageParams),
54433
+ rawMessage
54434
+ };
54435
+ return json(payload);
54436
+ };
54437
+ let runtime = null;
54438
+ try {
54439
+ runtime = await deps.acquireRuntime(deviceId);
54440
+ await runtime.connect();
54441
+ runtime.requestSnapshot();
54442
+ const payload = {
54443
+ success: true,
54444
+ tmuxAvailable: true,
54445
+ phase: "ready",
54446
+ message: deps.translate("common.success")
54447
+ };
54448
+ return json(payload);
54449
+ } catch (error) {
54450
+ return classifyErrorResponse(error);
54451
+ } finally {
54452
+ if (runtime) {
54453
+ await deps.releaseRuntime(deviceId, runtime);
54454
+ }
54455
+ }
54456
+ }
54457
+
54148
54458
  // ../../apps/gateway/src/api/index.ts
54149
54459
  function shouldReconnectPushSupervisor(existing, updates) {
54150
54460
  if (updates.type !== undefined && updates.type !== existing.type)
@@ -54293,28 +54603,28 @@ function handleApiRequest(req, _server) {
54293
54603
  return handleGetManifest(req.method);
54294
54604
  }
54295
54605
  if (path === "/healthz" && req.method === "GET") {
54296
- return json({ status: "ok", restarting: runtimeController.isRestarting() });
54606
+ return json2({ status: "ok", restarting: runtimeController.isRestarting() });
54297
54607
  }
54298
- return json({ error: t2("apiError.notFound") }, 404);
54608
+ return json2({ error: t2("apiError.notFound") }, 404);
54299
54609
  }
54300
54610
  async function handleGetDevices() {
54301
54611
  const devices2 = getAllDevices();
54302
- return json({ devices: devices2 });
54612
+ return json2({ devices: devices2 });
54303
54613
  }
54304
54614
  async function handleGetDevice(id) {
54305
54615
  const device = getDeviceById(id);
54306
54616
  if (!device) {
54307
- return json({ error: t2("apiError.deviceNotFound") }, 404);
54617
+ return json2({ error: t2("apiError.deviceNotFound") }, 404);
54308
54618
  }
54309
- return json({ device });
54619
+ return json2({ device });
54310
54620
  }
54311
54621
  async function handleCreateDevice(req) {
54312
54622
  const body = await req.json();
54313
54623
  if (!body.name || !body.type || !body.authMode) {
54314
- return json({ error: t2("apiError.missingFields") }, 400);
54624
+ return json2({ error: t2("apiError.missingFields") }, 400);
54315
54625
  }
54316
54626
  if (body.type === "ssh" && !body.host && !body.sshConfigRef) {
54317
- return json({ error: t2("apiError.sshRequiresHost") }, 400);
54627
+ return json2({ error: t2("apiError.sshRequiresHost") }, 400);
54318
54628
  }
54319
54629
  const now = new Date().toISOString();
54320
54630
  const device = {
@@ -54335,12 +54645,12 @@ async function handleCreateDevice(req) {
54335
54645
  };
54336
54646
  createDevice(device);
54337
54647
  await pushSupervisor.upsert(device.id);
54338
- return json({ device }, 201);
54648
+ return json2({ device }, 201);
54339
54649
  }
54340
54650
  async function handleUpdateDevice(req, id) {
54341
54651
  const existing = getDeviceById(id);
54342
54652
  if (!existing) {
54343
- return json({ error: t2("apiError.deviceNotFound") }, 404);
54653
+ return json2({ error: t2("apiError.deviceNotFound") }, 404);
54344
54654
  }
54345
54655
  const body = await req.json();
54346
54656
  const updates = {};
@@ -54370,61 +54680,53 @@ async function handleUpdateDevice(req, id) {
54370
54680
  await pushSupervisor.reconnect(id);
54371
54681
  }
54372
54682
  const device = getDeviceById(id);
54373
- return json({ device });
54683
+ return json2({ device });
54374
54684
  }
54375
54685
  async function handleDeleteDevice(id) {
54376
54686
  const existing = getDeviceById(id);
54377
54687
  if (!existing) {
54378
- return json({ error: t2("apiError.deviceNotFound") }, 404);
54688
+ return json2({ error: t2("apiError.deviceNotFound") }, 404);
54379
54689
  }
54380
54690
  deleteDevice(id);
54381
54691
  pushSupervisor.remove(id);
54382
- return json({ success: true });
54692
+ return json2({ success: true });
54383
54693
  }
54384
54694
  async function handleTestConnection(id) {
54385
- const device = getDeviceById(id);
54386
- if (!device) {
54387
- return json({ error: t2("apiError.deviceNotFound") }, 404);
54388
- }
54389
- return json({
54390
- success: true,
54391
- tmuxAvailable: false,
54392
- message: "Connection test not fully implemented yet"
54393
- });
54695
+ return handleDeviceTestConnection(id);
54394
54696
  }
54395
54697
  async function handleGetSiteSettings() {
54396
- return json({ settings: getSiteSettings() });
54698
+ return json2({ settings: getSiteSettings() });
54397
54699
  }
54398
54700
  async function handleUpdateSiteSettings(req) {
54399
54701
  try {
54400
54702
  const body = await req.json();
54401
54703
  const updates = normalizeSiteSettingsInput(body);
54402
54704
  const settings = updateSiteSettings(updates);
54403
- return json({ settings });
54705
+ return json2({ settings });
54404
54706
  } catch (err) {
54405
- return json({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
54707
+ return json2({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
54406
54708
  }
54407
54709
  }
54408
54710
  async function handleRestartGateway() {
54409
54711
  setTimeout(() => {
54410
54712
  runtimeController.requestRestart();
54411
54713
  }, 50);
54412
- return json({
54714
+ return json2({
54413
54715
  success: true,
54414
54716
  message: t2("settings.restartScheduled")
54415
54717
  });
54416
54718
  }
54417
54719
  async function handleGetTelegramBots() {
54418
54720
  const bots = getTelegramBotsWithStats();
54419
- return json({ bots });
54721
+ return json2({ bots });
54420
54722
  }
54421
54723
  async function handleCreateTelegramBot(req) {
54422
54724
  const body = await req.json();
54423
54725
  if (!body.name?.trim()) {
54424
- return json({ error: t2("apiError.botNameRequired") }, 400);
54726
+ return json2({ error: t2("apiError.botNameRequired") }, 400);
54425
54727
  }
54426
54728
  if (!body.token?.trim()) {
54427
- return json({ error: t2("apiError.botTokenRequired") }, 400);
54729
+ return json2({ error: t2("apiError.botTokenRequired") }, 400);
54428
54730
  }
54429
54731
  const now = new Date().toISOString();
54430
54732
  createTelegramBot({
@@ -54438,26 +54740,26 @@ async function handleCreateTelegramBot(req) {
54438
54740
  updatedAt: now
54439
54741
  });
54440
54742
  await telegramService.refresh();
54441
- return json({ success: true }, 201);
54743
+ return json2({ success: true }, 201);
54442
54744
  }
54443
54745
  async function handleUpdateTelegramBot(req, botId) {
54444
54746
  const existing = getTelegramBotById(botId);
54445
54747
  if (!existing) {
54446
- return json({ error: t2("apiError.botNotFound") }, 404);
54748
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54447
54749
  }
54448
54750
  const body = await req.json();
54449
54751
  const updates = {};
54450
54752
  if (body.name !== undefined) {
54451
54753
  const value = body.name.trim();
54452
54754
  if (!value) {
54453
- return json({ error: t2("apiError.botNameRequired") }, 400);
54755
+ return json2({ error: t2("apiError.botNameRequired") }, 400);
54454
54756
  }
54455
54757
  updates.name = value;
54456
54758
  }
54457
54759
  if (body.token !== undefined) {
54458
54760
  const token = body.token.trim();
54459
54761
  if (!token) {
54460
- return json({ error: t2("apiError.botTokenRequired") }, 400);
54762
+ return json2({ error: t2("apiError.botTokenRequired") }, 400);
54461
54763
  }
54462
54764
  updates.tokenEnc = await encrypt(token);
54463
54765
  }
@@ -54469,69 +54771,69 @@ async function handleUpdateTelegramBot(req, botId) {
54469
54771
  }
54470
54772
  updateTelegramBot(botId, updates);
54471
54773
  await telegramService.refresh();
54472
- return json({ success: true });
54774
+ return json2({ success: true });
54473
54775
  }
54474
54776
  async function handleDeleteTelegramBot(botId) {
54475
54777
  const existing = getTelegramBotById(botId);
54476
54778
  if (!existing) {
54477
- return json({ error: t2("apiError.botNotFound") }, 404);
54779
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54478
54780
  }
54479
54781
  deleteTelegramBot(botId);
54480
54782
  await telegramService.refresh();
54481
- return json({ success: true });
54783
+ return json2({ success: true });
54482
54784
  }
54483
54785
  async function handleListTelegramChats(botId) {
54484
54786
  const existing = getTelegramBotById(botId);
54485
54787
  if (!existing) {
54486
- return json({ error: t2("apiError.botNotFound") }, 404);
54788
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54487
54789
  }
54488
54790
  const chats = listTelegramChatsByBot(botId);
54489
- return json({ chats });
54791
+ return json2({ chats });
54490
54792
  }
54491
54793
  async function handleApproveTelegramChat(botId, chatId) {
54492
54794
  const existing = getTelegramBotById(botId);
54493
54795
  if (!existing) {
54494
- return json({ error: t2("apiError.botNotFound") }, 404);
54796
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54495
54797
  }
54496
54798
  const chat = approveTelegramChat(botId, chatId);
54497
54799
  if (!chat) {
54498
- return json({ error: t2("apiError.chatNotFound") }, 404);
54800
+ return json2({ error: t2("apiError.chatNotFound") }, 404);
54499
54801
  }
54500
54802
  const settings = getSiteSettings();
54501
54803
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.approveMessageTemplate", {
54502
54804
  botName: existing.name,
54503
54805
  time: new Date().toLocaleString(toBCP47(settings.language))
54504
54806
  }));
54505
- return json({ chat });
54807
+ return json2({ chat });
54506
54808
  }
54507
54809
  async function handleDeleteTelegramChat(botId, chatId) {
54508
54810
  const existing = getTelegramBotById(botId);
54509
54811
  if (!existing) {
54510
- return json({ error: t2("apiError.botNotFound") }, 404);
54812
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54511
54813
  }
54512
54814
  deleteTelegramChat(botId, chatId);
54513
- return json({ success: true });
54815
+ return json2({ success: true });
54514
54816
  }
54515
54817
  async function handleTestTelegramChat(botId, chatId) {
54516
54818
  const bot = getTelegramBotById(botId);
54517
54819
  if (!bot) {
54518
- return json({ error: t2("apiError.botNotFound") }, 404);
54820
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54519
54821
  }
54520
54822
  const settings = getSiteSettings();
54521
54823
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.testMessageTemplate", {
54522
54824
  siteName: settings.siteName,
54523
54825
  time: new Date().toLocaleString(toBCP47(settings.language))
54524
54826
  }));
54525
- return json({ success: true });
54827
+ return json2({ success: true });
54526
54828
  }
54527
54829
  async function handleGetWebhooks() {
54528
54830
  const webhooks = getAllWebhookEndpoints();
54529
- return json({ webhooks });
54831
+ return json2({ webhooks });
54530
54832
  }
54531
54833
  async function handleCreateWebhook(req) {
54532
54834
  const body = await req.json();
54533
54835
  if (!body.url || !body.secret) {
54534
- return json({ error: t2("apiError.urlAndSecretRequired") }, 400);
54836
+ return json2({ error: t2("apiError.urlAndSecretRequired") }, 400);
54535
54837
  }
54536
54838
  const now = new Date().toISOString();
54537
54839
  const endpoint = {
@@ -54544,11 +54846,11 @@ async function handleCreateWebhook(req) {
54544
54846
  updatedAt: now
54545
54847
  };
54546
54848
  createWebhookEndpoint(endpoint);
54547
- return json({ webhook: endpoint }, 201);
54849
+ return json2({ webhook: endpoint }, 201);
54548
54850
  }
54549
54851
  async function handleDeleteWebhook(id) {
54550
54852
  deleteWebhookEndpoint(id);
54551
- return json({ success: true });
54853
+ return json2({ success: true });
54552
54854
  }
54553
54855
  async function handleGetManifest(method) {
54554
54856
  const settings = getSiteSettings();
@@ -54581,7 +54883,7 @@ function manifestJson(data, method) {
54581
54883
  }
54582
54884
  });
54583
54885
  }
54584
- function json(data, status = 200, headers = {}) {
54886
+ function json2(data, status = 200, headers = {}) {
54585
54887
  return new Response(JSON.stringify(data), {
54586
54888
  status,
54587
54889
  headers: {
@@ -54592,7 +54894,7 @@ function json(data, status = 200, headers = {}) {
54592
54894
  }
54593
54895
 
54594
54896
  // ../../apps/gateway/src/db/migrate.ts
54595
- import { existsSync as existsSync2 } from "fs";
54897
+ import { existsSync as existsSync3 } from "fs";
54596
54898
  import { resolve } from "path";
54597
54899
 
54598
54900
  // ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/migrator.js
@@ -54639,7 +54941,7 @@ function resolveMigrationsFolder() {
54639
54941
  if (fromEnv)
54640
54942
  return fromEnv;
54641
54943
  const byCwd = resolve(process.cwd(), "drizzle");
54642
- if (existsSync2(byCwd))
54944
+ if (existsSync3(byCwd))
54643
54945
  return byCwd;
54644
54946
  return resolve(import.meta.dir, "../../drizzle");
54645
54947
  }
@@ -55208,82 +55510,6 @@ class SwitchBarrier {
55208
55510
  }
55209
55511
  var switchBarrier = new SwitchBarrier;
55210
55512
 
55211
- // ../../apps/gateway/src/ws/error-classify.ts
55212
- function classifySshError(error) {
55213
- const msg = error.message.toLowerCase();
55214
- if (msg.includes("ssh_config_ref_not_supported")) {
55215
- return {
55216
- type: "ssh_config_ref_not_supported",
55217
- messageKey: "sshError.configRefNotSupported"
55218
- };
55219
- }
55220
- if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
55221
- return {
55222
- type: "agent_unavailable",
55223
- messageKey: "sshError.agentUnavailable"
55224
- };
55225
- }
55226
- if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
55227
- return {
55228
- type: "agent_no_identity",
55229
- messageKey: "sshError.agentNoIdentities"
55230
- };
55231
- }
55232
- if (msg.includes("permission denied")) {
55233
- return {
55234
- type: "auth_failed",
55235
- messageKey: "sshError.authFailed"
55236
- };
55237
- }
55238
- if (msg.includes("all configured authentication methods failed")) {
55239
- return {
55240
- type: "auth_failed",
55241
- messageKey: "sshError.authFailedGeneric"
55242
- };
55243
- }
55244
- if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
55245
- return {
55246
- type: "network_unreachable",
55247
- messageKey: "sshError.networkUnreachable"
55248
- };
55249
- }
55250
- if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
55251
- return {
55252
- type: "connection_refused",
55253
- messageKey: "sshError.connectionRefused"
55254
- };
55255
- }
55256
- if (msg.includes("timeout") || msg.includes("etimedout")) {
55257
- return {
55258
- type: "timeout",
55259
- messageKey: "sshError.connectionTimeout"
55260
- };
55261
- }
55262
- if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
55263
- return {
55264
- type: "host_not_found",
55265
- messageKey: "sshError.hostNotFound"
55266
- };
55267
- }
55268
- if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
55269
- return {
55270
- type: "handshake_failed",
55271
- messageKey: "sshError.handshakeFailed"
55272
- };
55273
- }
55274
- if (msg.includes("remote tmux unavailable") || msg.includes("tmux_not_found") || msg.includes("tmux: command not found") || msg.includes("tmux control mode not ready") || msg.includes("tmux exited") || msg.includes("tmux_exec_failed")) {
55275
- return {
55276
- type: "tmux_unavailable",
55277
- messageKey: "sshError.tmuxUnavailable"
55278
- };
55279
- }
55280
- return {
55281
- type: "unknown",
55282
- messageKey: "sshError.unknown",
55283
- messageParams: { message: error.message }
55284
- };
55285
- }
55286
-
55287
55513
  // ../../apps/gateway/src/ws/index.ts
55288
55514
  var defaultDeps2 = {
55289
55515
  acquireRuntime: async (deviceId) => tmuxRuntimeRegistry.acquire(deviceId),
@@ -56302,9 +56528,9 @@ async function serveFrontend(req, staticRoot) {
56302
56528
  if (!requestedPath) {
56303
56529
  return new Response(t3("runtime.forbidden"), { status: 403 });
56304
56530
  }
56305
- const indexPath = join4(staticRoot, "index.html");
56306
- const targetPath = existsSync3(requestedPath) ? requestedPath : indexPath;
56307
- if (!existsSync3(targetPath)) {
56531
+ const indexPath = join5(staticRoot, "index.html");
56532
+ const targetPath = existsSync4(requestedPath) ? requestedPath : indexPath;
56533
+ if (!existsSync4(targetPath)) {
56308
56534
  return new Response(t3("runtime.frontendMissing"), { status: 500 });
56309
56535
  }
56310
56536
  const headers = new Headers;