tmex-cli 0.3.0 → 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 +466 -232
  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-BTB6mlBW.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-BDVq_Akt.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-7tfuDWw3.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-CNHRGb7-.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-Bi_NzPTr.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-BE5Hlzny.js +0 -26
  23. package/resources/fe-dist/assets/DevicePage-BE5Hlzny.js.map +0 -1
  24. package/resources/fe-dist/assets/DevicesPage-BTB6mlBW.js +0 -17
  25. package/resources/fe-dist/assets/SettingsPage-BDVq_Akt.js +0 -17
  26. package/resources/fe-dist/assets/index-Chps0Pui.js +0 -448
  27. package/resources/fe-dist/assets/index-Chps0Pui.js.map +0 -1
  28. package/resources/fe-dist/assets/index-CyKyNcdz.css +0 -1
  29. package/resources/fe-dist/assets/select-7tfuDWw3.js +0 -17
  30. package/resources/fe-dist/assets/switch-CNHRGb7-.js +0 -12
  31. package/resources/fe-dist/assets/useValueChanged-Bi_NzPTr.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) {
@@ -21687,6 +21687,7 @@ var TermHistorySchema = import_zorsh.b.struct({
21687
21687
  paneId: import_zorsh.b.string(),
21688
21688
  selectToken: import_zorsh.b.bytes(16),
21689
21689
  encoding: import_zorsh.b.u8(),
21690
+ alternateScreen: import_zorsh.b.bool(),
21690
21691
  data: import_zorsh.b.bytes()
21691
21692
  });
21692
21693
  var SwitchAckSchema = import_zorsh.b.struct({
@@ -52151,7 +52152,9 @@ function encoderFromString(value) {
52151
52152
  }
52152
52153
 
52153
52154
  // ../../apps/gateway/src/tmux-client/local-external-connection.ts
52154
- var DEFAULT_CHUNK_HISTORY_LINES = "-1000";
52155
+ function hasRenderableTerminalContent(value) {
52156
+ return value.trim().length > 0;
52157
+ }
52155
52158
  var BELL_DEDUP_WINDOW_MS = 200;
52156
52159
  function shouldIgnoreReaderAbortError(error) {
52157
52160
  if (!error || typeof error !== "object") {
@@ -52307,7 +52310,7 @@ class LocalExternalTmuxConnection {
52307
52310
  if (!this.connected) {
52308
52311
  return;
52309
52312
  }
52310
- 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];
52311
52314
  this.runAndRefresh(argv).catch((error) => {
52312
52315
  this.callbacks.onError(error);
52313
52316
  });
@@ -52475,11 +52478,12 @@ class LocalExternalTmuxConnection {
52475
52478
  }
52476
52479
  async capturePaneHistory(paneId) {
52477
52480
  const mode = (await this.runTmux(["display-message", "-p", "-t", paneId, "#{alternate_on}"], true)).stdout.trim();
52478
- const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", DEFAULT_CHUNK_HISTORY_LINES, "-e", "-p"], true)).stdout;
52479
- const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", DEFAULT_CHUNK_HISTORY_LINES, "-e", "-p", "-q"], true)).stdout;
52480
- const history = normal || alternate;
52481
+ const alternateScreen = mode === "1";
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
+ const history = alternateScreen ? hasRenderableTerminalContent(normal) ? normal : alternate : normal || alternate;
52481
52485
  if (history) {
52482
- this.callbacks.onTerminalHistory(paneId, history);
52486
+ this.callbacks.onTerminalHistory(paneId, history, alternateScreen);
52483
52487
  }
52484
52488
  }
52485
52489
  async requestSnapshotInternal() {
@@ -52729,6 +52733,10 @@ class LocalExternalTmuxConnection {
52729
52733
  // ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
52730
52734
  var import_ssh2 = __toESM(require_lib3(), 1);
52731
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
+
52732
52740
  // ../../apps/gateway/src/tmux/ssh-auth.ts
52733
52741
  function normalizeEnvValue(value) {
52734
52742
  const trimmed = value?.trim();
@@ -52761,6 +52769,219 @@ function resolveSshAgentSocket(authMode, env = process.env) {
52761
52769
  return;
52762
52770
  }
52763
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
+
52764
52985
  // ../../apps/gateway/src/tmux-client/ssh-bootstrap.ts
52765
52986
  function buildSshBootstrapScript() {
52766
52987
  return [
@@ -52801,9 +53022,36 @@ function parseSshBootstrapOutput(output) {
52801
53022
  }
52802
53023
 
52803
53024
  // ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
52804
- var DEFAULT_HISTORY_LINES = "-1000";
53025
+ function hasRenderableTerminalContent2(value) {
53026
+ return value.trim().length > 0;
53027
+ }
52805
53028
  var BELL_DEDUP_WINDOW_MS2 = 200;
52806
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
+ }
52807
53055
 
52808
53056
  class SshExternalTmuxConnection {
52809
53057
  deviceId;
@@ -52932,7 +53180,7 @@ class SshExternalTmuxConnection {
52932
53180
  if (!this.connected) {
52933
53181
  return;
52934
53182
  }
52935
- 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];
52936
53184
  this.runAndRefresh(argv).catch((error) => {
52937
53185
  this.callbacks.onError(error);
52938
53186
  });
@@ -52965,80 +53213,7 @@ class SshExternalTmuxConnection {
52965
53213
  if (!this.device) {
52966
53214
  throw new Error("SSH device not loaded");
52967
53215
  }
52968
- const host = this.device.host;
52969
- const port = this.device.port ?? 22;
52970
- const username = resolveSshUsername(this.device.username, this.device.authMode);
52971
- if (this.device.authMode === "configRef" || !host && this.device.sshConfigRef) {
52972
- 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");
52973
- }
52974
- if (!host) {
52975
- throw new Error("SSH device missing host");
52976
- }
52977
- const authConfig = {
52978
- host,
52979
- port,
52980
- username
52981
- };
52982
- switch (this.device.authMode) {
52983
- case "password": {
52984
- if (!this.device.passwordEnc) {
52985
- throw new Error("auth_password_missing: \u5BC6\u7801\u8BA4\u8BC1\u672A\u63D0\u4F9B\u5BC6\u7801");
52986
- }
52987
- authConfig.password = await this.deps.decrypt(this.device.passwordEnc, {
52988
- scope: "device",
52989
- entityId: this.device.id,
52990
- field: "password_enc"
52991
- });
52992
- break;
52993
- }
52994
- case "key": {
52995
- if (!this.device.privateKeyEnc) {
52996
- throw new Error("auth_key_missing: \u79C1\u94A5\u8BA4\u8BC1\u672A\u63D0\u4F9B\u79C1\u94A5");
52997
- }
52998
- authConfig.privateKey = await this.deps.decrypt(this.device.privateKeyEnc, {
52999
- scope: "device",
53000
- entityId: this.device.id,
53001
- field: "private_key_enc"
53002
- });
53003
- if (this.device.privateKeyPassphraseEnc) {
53004
- authConfig.passphrase = await this.deps.decrypt(this.device.privateKeyPassphraseEnc, {
53005
- scope: "device",
53006
- entityId: this.device.id,
53007
- field: "private_key_passphrase_enc"
53008
- });
53009
- }
53010
- break;
53011
- }
53012
- case "agent": {
53013
- authConfig.agent = resolveSshAgentSocket("agent");
53014
- break;
53015
- }
53016
- case "auto": {
53017
- const agentSocket = resolveSshAgentSocket("auto");
53018
- if (agentSocket) {
53019
- authConfig.agent = agentSocket;
53020
- }
53021
- if (this.device.privateKeyEnc) {
53022
- authConfig.privateKey = await this.deps.decrypt(this.device.privateKeyEnc, {
53023
- scope: "device",
53024
- entityId: this.device.id,
53025
- field: "private_key_enc"
53026
- });
53027
- } else if (this.device.passwordEnc) {
53028
- authConfig.password = await this.deps.decrypt(this.device.passwordEnc, {
53029
- scope: "device",
53030
- entityId: this.device.id,
53031
- field: "password_enc"
53032
- });
53033
- }
53034
- break;
53035
- }
53036
- case "configRef":
53037
- break;
53038
- }
53039
- if (this.device.authMode === "auto" && !authConfig.agent && !authConfig.privateKey && !authConfig.password) {
53040
- 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");
53041
- }
53216
+ const authConfig = await resolveSshConnectConfig(this.device, this.deps.decrypt);
53042
53217
  const client = this.deps.createClient();
53043
53218
  this.sshClient = client;
53044
53219
  await new Promise((resolve, reject) => {
@@ -53263,11 +53438,12 @@ class SshExternalTmuxConnection {
53263
53438
  }
53264
53439
  async capturePaneHistory(paneId) {
53265
53440
  const mode = (await this.runTmux(["display-message", "-p", "-t", paneId, "#{alternate_on}"], true)).stdout.trim();
53266
- const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", DEFAULT_HISTORY_LINES, "-e", "-p"], true, 30000)).stdout;
53267
- const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", DEFAULT_HISTORY_LINES, "-e", "-p", "-q"], true, 30000)).stdout;
53268
- const history = normal || alternate;
53441
+ const alternateScreen = mode === "1";
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;
53444
+ const history = alternateScreen ? hasRenderableTerminalContent2(normal) ? normal : alternate : normal || alternate;
53269
53445
  if (history) {
53270
- this.callbacks.onTerminalHistory(paneId, history);
53446
+ this.callbacks.onTerminalHistory(paneId, history, alternateScreen);
53271
53447
  }
53272
53448
  }
53273
53449
  async requestSnapshotInternal() {
@@ -53280,21 +53456,21 @@ class SshExternalTmuxConnection {
53280
53456
  "-p",
53281
53457
  "-t",
53282
53458
  this.sessionName,
53283
- "#{session_id}\t#{session_name}"
53459
+ "#{session_id}|#{session_name}"
53284
53460
  ]),
53285
53461
  this.runTmuxAllowFailure([
53286
53462
  "list-windows",
53287
53463
  "-t",
53288
53464
  this.sessionName,
53289
53465
  "-F",
53290
- "#{window_id}\t#{window_index}\t#{window_name}\t#{window_active}"
53466
+ "#{window_id}|#{window_index}|#{window_name}|#{window_active}"
53291
53467
  ]),
53292
53468
  this.runTmuxAllowFailure([
53293
53469
  "list-panes",
53294
53470
  "-t",
53295
53471
  this.sessionName,
53296
53472
  "-F",
53297
- "#{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}"
53298
53474
  ])
53299
53475
  ]);
53300
53476
  if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
@@ -53312,7 +53488,7 @@ class SshExternalTmuxConnection {
53312
53488
  if (!line.trim()) {
53313
53489
  continue;
53314
53490
  }
53315
- const [id, name] = line.split("\t");
53491
+ const [id, name] = splitSnapshotFields(line, 2);
53316
53492
  if (id) {
53317
53493
  this.snapshotSession = { id, name: name ?? "" };
53318
53494
  }
@@ -53325,7 +53501,7 @@ class SshExternalTmuxConnection {
53325
53501
  if (!line.trim()) {
53326
53502
  continue;
53327
53503
  }
53328
- const [id, indexRaw, name, activeRaw] = line.split("\t");
53504
+ const [id, indexRaw, name, activeRaw] = splitSnapshotFields(line, 4);
53329
53505
  if (!id) {
53330
53506
  continue;
53331
53507
  }
@@ -53351,7 +53527,7 @@ class SshExternalTmuxConnection {
53351
53527
  if (!line.trim()) {
53352
53528
  continue;
53353
53529
  }
53354
- const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw] = line.split("\t");
53530
+ const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw] = splitSnapshotFields(line, 7);
53355
53531
  if (!paneId || !windowId) {
53356
53532
  continue;
53357
53533
  }
@@ -53680,8 +53856,8 @@ class DeviceSessionRuntime {
53680
53856
  onTerminalOutput: (paneId, data) => {
53681
53857
  this.broadcast((listener) => listener.onTerminalOutput?.(paneId, data));
53682
53858
  },
53683
- onTerminalHistory: (paneId, data) => {
53684
- this.broadcast((listener) => listener.onTerminalHistory?.(paneId, data));
53859
+ onTerminalHistory: (paneId, data, alternateScreen) => {
53860
+ this.broadcast((listener) => listener.onTerminalHistory?.(paneId, data, alternateScreen));
53685
53861
  },
53686
53862
  onSnapshot: (payload) => {
53687
53863
  this.broadcast((listener) => listener.onSnapshot?.(payload));
@@ -53695,6 +53871,7 @@ class DeviceSessionRuntime {
53695
53871
  }
53696
53872
  this.closeEmitted = true;
53697
53873
  this.terminated = true;
53874
+ this.connectPromise = null;
53698
53875
  this.broadcast((listener) => listener.onClose?.());
53699
53876
  }
53700
53877
  });
@@ -53706,7 +53883,7 @@ class DeviceSessionRuntime {
53706
53883
  };
53707
53884
  }
53708
53885
  async connect() {
53709
- if (this.terminated && !this.connectPromise) {
53886
+ if (this.terminated) {
53710
53887
  return Promise.reject(new Error(`Device session runtime already terminated: ${this.deviceId}`));
53711
53888
  }
53712
53889
  if (this.connectPromise) {
@@ -53714,6 +53891,7 @@ class DeviceSessionRuntime {
53714
53891
  }
53715
53892
  this.connectPromise = this.connection.connect().catch((error) => {
53716
53893
  this.terminated = true;
53894
+ this.connectPromise = null;
53717
53895
  throw error;
53718
53896
  });
53719
53897
  return this.connectPromise;
@@ -53724,6 +53902,7 @@ class DeviceSessionRuntime {
53724
53902
  }
53725
53903
  this.terminated = true;
53726
53904
  this.manualDisconnect = true;
53905
+ this.connectPromise = null;
53727
53906
  this.connection.disconnect();
53728
53907
  }
53729
53908
  async shutdown() {
@@ -54138,6 +54317,144 @@ class PushSupervisor {
54138
54317
  }
54139
54318
  var pushSupervisor = new PushSupervisor;
54140
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
+
54141
54458
  // ../../apps/gateway/src/api/index.ts
54142
54459
  function shouldReconnectPushSupervisor(existing, updates) {
54143
54460
  if (updates.type !== undefined && updates.type !== existing.type)
@@ -54286,28 +54603,28 @@ function handleApiRequest(req, _server) {
54286
54603
  return handleGetManifest(req.method);
54287
54604
  }
54288
54605
  if (path === "/healthz" && req.method === "GET") {
54289
- return json({ status: "ok", restarting: runtimeController.isRestarting() });
54606
+ return json2({ status: "ok", restarting: runtimeController.isRestarting() });
54290
54607
  }
54291
- return json({ error: t2("apiError.notFound") }, 404);
54608
+ return json2({ error: t2("apiError.notFound") }, 404);
54292
54609
  }
54293
54610
  async function handleGetDevices() {
54294
54611
  const devices2 = getAllDevices();
54295
- return json({ devices: devices2 });
54612
+ return json2({ devices: devices2 });
54296
54613
  }
54297
54614
  async function handleGetDevice(id) {
54298
54615
  const device = getDeviceById(id);
54299
54616
  if (!device) {
54300
- return json({ error: t2("apiError.deviceNotFound") }, 404);
54617
+ return json2({ error: t2("apiError.deviceNotFound") }, 404);
54301
54618
  }
54302
- return json({ device });
54619
+ return json2({ device });
54303
54620
  }
54304
54621
  async function handleCreateDevice(req) {
54305
54622
  const body = await req.json();
54306
54623
  if (!body.name || !body.type || !body.authMode) {
54307
- return json({ error: t2("apiError.missingFields") }, 400);
54624
+ return json2({ error: t2("apiError.missingFields") }, 400);
54308
54625
  }
54309
54626
  if (body.type === "ssh" && !body.host && !body.sshConfigRef) {
54310
- return json({ error: t2("apiError.sshRequiresHost") }, 400);
54627
+ return json2({ error: t2("apiError.sshRequiresHost") }, 400);
54311
54628
  }
54312
54629
  const now = new Date().toISOString();
54313
54630
  const device = {
@@ -54328,12 +54645,12 @@ async function handleCreateDevice(req) {
54328
54645
  };
54329
54646
  createDevice(device);
54330
54647
  await pushSupervisor.upsert(device.id);
54331
- return json({ device }, 201);
54648
+ return json2({ device }, 201);
54332
54649
  }
54333
54650
  async function handleUpdateDevice(req, id) {
54334
54651
  const existing = getDeviceById(id);
54335
54652
  if (!existing) {
54336
- return json({ error: t2("apiError.deviceNotFound") }, 404);
54653
+ return json2({ error: t2("apiError.deviceNotFound") }, 404);
54337
54654
  }
54338
54655
  const body = await req.json();
54339
54656
  const updates = {};
@@ -54363,61 +54680,53 @@ async function handleUpdateDevice(req, id) {
54363
54680
  await pushSupervisor.reconnect(id);
54364
54681
  }
54365
54682
  const device = getDeviceById(id);
54366
- return json({ device });
54683
+ return json2({ device });
54367
54684
  }
54368
54685
  async function handleDeleteDevice(id) {
54369
54686
  const existing = getDeviceById(id);
54370
54687
  if (!existing) {
54371
- return json({ error: t2("apiError.deviceNotFound") }, 404);
54688
+ return json2({ error: t2("apiError.deviceNotFound") }, 404);
54372
54689
  }
54373
54690
  deleteDevice(id);
54374
54691
  pushSupervisor.remove(id);
54375
- return json({ success: true });
54692
+ return json2({ success: true });
54376
54693
  }
54377
54694
  async function handleTestConnection(id) {
54378
- const device = getDeviceById(id);
54379
- if (!device) {
54380
- return json({ error: t2("apiError.deviceNotFound") }, 404);
54381
- }
54382
- return json({
54383
- success: true,
54384
- tmuxAvailable: false,
54385
- message: "Connection test not fully implemented yet"
54386
- });
54695
+ return handleDeviceTestConnection(id);
54387
54696
  }
54388
54697
  async function handleGetSiteSettings() {
54389
- return json({ settings: getSiteSettings() });
54698
+ return json2({ settings: getSiteSettings() });
54390
54699
  }
54391
54700
  async function handleUpdateSiteSettings(req) {
54392
54701
  try {
54393
54702
  const body = await req.json();
54394
54703
  const updates = normalizeSiteSettingsInput(body);
54395
54704
  const settings = updateSiteSettings(updates);
54396
- return json({ settings });
54705
+ return json2({ settings });
54397
54706
  } catch (err) {
54398
- 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);
54399
54708
  }
54400
54709
  }
54401
54710
  async function handleRestartGateway() {
54402
54711
  setTimeout(() => {
54403
54712
  runtimeController.requestRestart();
54404
54713
  }, 50);
54405
- return json({
54714
+ return json2({
54406
54715
  success: true,
54407
54716
  message: t2("settings.restartScheduled")
54408
54717
  });
54409
54718
  }
54410
54719
  async function handleGetTelegramBots() {
54411
54720
  const bots = getTelegramBotsWithStats();
54412
- return json({ bots });
54721
+ return json2({ bots });
54413
54722
  }
54414
54723
  async function handleCreateTelegramBot(req) {
54415
54724
  const body = await req.json();
54416
54725
  if (!body.name?.trim()) {
54417
- return json({ error: t2("apiError.botNameRequired") }, 400);
54726
+ return json2({ error: t2("apiError.botNameRequired") }, 400);
54418
54727
  }
54419
54728
  if (!body.token?.trim()) {
54420
- return json({ error: t2("apiError.botTokenRequired") }, 400);
54729
+ return json2({ error: t2("apiError.botTokenRequired") }, 400);
54421
54730
  }
54422
54731
  const now = new Date().toISOString();
54423
54732
  createTelegramBot({
@@ -54431,26 +54740,26 @@ async function handleCreateTelegramBot(req) {
54431
54740
  updatedAt: now
54432
54741
  });
54433
54742
  await telegramService.refresh();
54434
- return json({ success: true }, 201);
54743
+ return json2({ success: true }, 201);
54435
54744
  }
54436
54745
  async function handleUpdateTelegramBot(req, botId) {
54437
54746
  const existing = getTelegramBotById(botId);
54438
54747
  if (!existing) {
54439
- return json({ error: t2("apiError.botNotFound") }, 404);
54748
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54440
54749
  }
54441
54750
  const body = await req.json();
54442
54751
  const updates = {};
54443
54752
  if (body.name !== undefined) {
54444
54753
  const value = body.name.trim();
54445
54754
  if (!value) {
54446
- return json({ error: t2("apiError.botNameRequired") }, 400);
54755
+ return json2({ error: t2("apiError.botNameRequired") }, 400);
54447
54756
  }
54448
54757
  updates.name = value;
54449
54758
  }
54450
54759
  if (body.token !== undefined) {
54451
54760
  const token = body.token.trim();
54452
54761
  if (!token) {
54453
- return json({ error: t2("apiError.botTokenRequired") }, 400);
54762
+ return json2({ error: t2("apiError.botTokenRequired") }, 400);
54454
54763
  }
54455
54764
  updates.tokenEnc = await encrypt(token);
54456
54765
  }
@@ -54462,69 +54771,69 @@ async function handleUpdateTelegramBot(req, botId) {
54462
54771
  }
54463
54772
  updateTelegramBot(botId, updates);
54464
54773
  await telegramService.refresh();
54465
- return json({ success: true });
54774
+ return json2({ success: true });
54466
54775
  }
54467
54776
  async function handleDeleteTelegramBot(botId) {
54468
54777
  const existing = getTelegramBotById(botId);
54469
54778
  if (!existing) {
54470
- return json({ error: t2("apiError.botNotFound") }, 404);
54779
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54471
54780
  }
54472
54781
  deleteTelegramBot(botId);
54473
54782
  await telegramService.refresh();
54474
- return json({ success: true });
54783
+ return json2({ success: true });
54475
54784
  }
54476
54785
  async function handleListTelegramChats(botId) {
54477
54786
  const existing = getTelegramBotById(botId);
54478
54787
  if (!existing) {
54479
- return json({ error: t2("apiError.botNotFound") }, 404);
54788
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54480
54789
  }
54481
54790
  const chats = listTelegramChatsByBot(botId);
54482
- return json({ chats });
54791
+ return json2({ chats });
54483
54792
  }
54484
54793
  async function handleApproveTelegramChat(botId, chatId) {
54485
54794
  const existing = getTelegramBotById(botId);
54486
54795
  if (!existing) {
54487
- return json({ error: t2("apiError.botNotFound") }, 404);
54796
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54488
54797
  }
54489
54798
  const chat = approveTelegramChat(botId, chatId);
54490
54799
  if (!chat) {
54491
- return json({ error: t2("apiError.chatNotFound") }, 404);
54800
+ return json2({ error: t2("apiError.chatNotFound") }, 404);
54492
54801
  }
54493
54802
  const settings = getSiteSettings();
54494
54803
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.approveMessageTemplate", {
54495
54804
  botName: existing.name,
54496
54805
  time: new Date().toLocaleString(toBCP47(settings.language))
54497
54806
  }));
54498
- return json({ chat });
54807
+ return json2({ chat });
54499
54808
  }
54500
54809
  async function handleDeleteTelegramChat(botId, chatId) {
54501
54810
  const existing = getTelegramBotById(botId);
54502
54811
  if (!existing) {
54503
- return json({ error: t2("apiError.botNotFound") }, 404);
54812
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54504
54813
  }
54505
54814
  deleteTelegramChat(botId, chatId);
54506
- return json({ success: true });
54815
+ return json2({ success: true });
54507
54816
  }
54508
54817
  async function handleTestTelegramChat(botId, chatId) {
54509
54818
  const bot = getTelegramBotById(botId);
54510
54819
  if (!bot) {
54511
- return json({ error: t2("apiError.botNotFound") }, 404);
54820
+ return json2({ error: t2("apiError.botNotFound") }, 404);
54512
54821
  }
54513
54822
  const settings = getSiteSettings();
54514
54823
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.testMessageTemplate", {
54515
54824
  siteName: settings.siteName,
54516
54825
  time: new Date().toLocaleString(toBCP47(settings.language))
54517
54826
  }));
54518
- return json({ success: true });
54827
+ return json2({ success: true });
54519
54828
  }
54520
54829
  async function handleGetWebhooks() {
54521
54830
  const webhooks = getAllWebhookEndpoints();
54522
- return json({ webhooks });
54831
+ return json2({ webhooks });
54523
54832
  }
54524
54833
  async function handleCreateWebhook(req) {
54525
54834
  const body = await req.json();
54526
54835
  if (!body.url || !body.secret) {
54527
- return json({ error: t2("apiError.urlAndSecretRequired") }, 400);
54836
+ return json2({ error: t2("apiError.urlAndSecretRequired") }, 400);
54528
54837
  }
54529
54838
  const now = new Date().toISOString();
54530
54839
  const endpoint = {
@@ -54537,11 +54846,11 @@ async function handleCreateWebhook(req) {
54537
54846
  updatedAt: now
54538
54847
  };
54539
54848
  createWebhookEndpoint(endpoint);
54540
- return json({ webhook: endpoint }, 201);
54849
+ return json2({ webhook: endpoint }, 201);
54541
54850
  }
54542
54851
  async function handleDeleteWebhook(id) {
54543
54852
  deleteWebhookEndpoint(id);
54544
- return json({ success: true });
54853
+ return json2({ success: true });
54545
54854
  }
54546
54855
  async function handleGetManifest(method) {
54547
54856
  const settings = getSiteSettings();
@@ -54574,7 +54883,7 @@ function manifestJson(data, method) {
54574
54883
  }
54575
54884
  });
54576
54885
  }
54577
- function json(data, status = 200, headers = {}) {
54886
+ function json2(data, status = 200, headers = {}) {
54578
54887
  return new Response(JSON.stringify(data), {
54579
54888
  status,
54580
54889
  headers: {
@@ -54585,7 +54894,7 @@ function json(data, status = 200, headers = {}) {
54585
54894
  }
54586
54895
 
54587
54896
  // ../../apps/gateway/src/db/migrate.ts
54588
- import { existsSync as existsSync2 } from "fs";
54897
+ import { existsSync as existsSync3 } from "fs";
54589
54898
  import { resolve } from "path";
54590
54899
 
54591
54900
  // ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/migrator.js
@@ -54632,7 +54941,7 @@ function resolveMigrationsFolder() {
54632
54941
  if (fromEnv)
54633
54942
  return fromEnv;
54634
54943
  const byCwd = resolve(process.cwd(), "drizzle");
54635
- if (existsSync2(byCwd))
54944
+ if (existsSync3(byCwd))
54636
54945
  return byCwd;
54637
54946
  return resolve(import.meta.dir, "../../drizzle");
54638
54947
  }
@@ -55053,7 +55362,7 @@ class SwitchBarrier {
55053
55362
  }
55054
55363
  pending.callbacks.onAckSent?.();
55055
55364
  }
55056
- sendTermHistory(ws, deviceId, paneId, historyData) {
55365
+ sendTermHistory(ws, deviceId, paneId, historyData, alternateScreen) {
55057
55366
  const pending = this.getPending(ws, deviceId);
55058
55367
  if (!pending)
55059
55368
  return;
@@ -55079,6 +55388,7 @@ class SwitchBarrier {
55079
55388
  paneId: context.paneId,
55080
55389
  selectToken: context.selectToken,
55081
55390
  encoding: 2,
55391
+ alternateScreen,
55082
55392
  data: historyData
55083
55393
  }, borshState.seqGen, borshState.maxFrameBytes);
55084
55394
  sendToClient(ws, historyMessages);
@@ -55200,82 +55510,6 @@ class SwitchBarrier {
55200
55510
  }
55201
55511
  var switchBarrier = new SwitchBarrier;
55202
55512
 
55203
- // ../../apps/gateway/src/ws/error-classify.ts
55204
- function classifySshError(error) {
55205
- const msg = error.message.toLowerCase();
55206
- if (msg.includes("ssh_config_ref_not_supported")) {
55207
- return {
55208
- type: "ssh_config_ref_not_supported",
55209
- messageKey: "sshError.configRefNotSupported"
55210
- };
55211
- }
55212
- if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
55213
- return {
55214
- type: "agent_unavailable",
55215
- messageKey: "sshError.agentUnavailable"
55216
- };
55217
- }
55218
- if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
55219
- return {
55220
- type: "agent_no_identity",
55221
- messageKey: "sshError.agentNoIdentities"
55222
- };
55223
- }
55224
- if (msg.includes("permission denied")) {
55225
- return {
55226
- type: "auth_failed",
55227
- messageKey: "sshError.authFailed"
55228
- };
55229
- }
55230
- if (msg.includes("all configured authentication methods failed")) {
55231
- return {
55232
- type: "auth_failed",
55233
- messageKey: "sshError.authFailedGeneric"
55234
- };
55235
- }
55236
- if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
55237
- return {
55238
- type: "network_unreachable",
55239
- messageKey: "sshError.networkUnreachable"
55240
- };
55241
- }
55242
- if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
55243
- return {
55244
- type: "connection_refused",
55245
- messageKey: "sshError.connectionRefused"
55246
- };
55247
- }
55248
- if (msg.includes("timeout") || msg.includes("etimedout")) {
55249
- return {
55250
- type: "timeout",
55251
- messageKey: "sshError.connectionTimeout"
55252
- };
55253
- }
55254
- if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
55255
- return {
55256
- type: "host_not_found",
55257
- messageKey: "sshError.hostNotFound"
55258
- };
55259
- }
55260
- if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
55261
- return {
55262
- type: "handshake_failed",
55263
- messageKey: "sshError.handshakeFailed"
55264
- };
55265
- }
55266
- 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")) {
55267
- return {
55268
- type: "tmux_unavailable",
55269
- messageKey: "sshError.tmuxUnavailable"
55270
- };
55271
- }
55272
- return {
55273
- type: "unknown",
55274
- messageKey: "sshError.unknown",
55275
- messageParams: { message: error.message }
55276
- };
55277
- }
55278
-
55279
55513
  // ../../apps/gateway/src/ws/index.ts
55280
55514
  var defaultDeps2 = {
55281
55515
  acquireRuntime: async (deviceId) => tmuxRuntimeRegistry.acquire(deviceId),
@@ -55328,8 +55562,8 @@ class WebSocketServer {
55328
55562
  onTerminalOutput: (paneId, data) => {
55329
55563
  this.broadcastTerminalOutput(deviceId, paneId, data);
55330
55564
  },
55331
- onTerminalHistory: (paneId, data) => {
55332
- this.broadcastTerminalHistory(deviceId, paneId, data);
55565
+ onTerminalHistory: (paneId, data, alternateScreen) => {
55566
+ this.broadcastTerminalHistory(deviceId, paneId, data, alternateScreen);
55333
55567
  },
55334
55568
  onSnapshot: (payload) => {
55335
55569
  this.broadcastStateSnapshot(deviceId, payload);
@@ -55863,7 +56097,7 @@ class WebSocketServer {
55863
56097
  this.sendChunked(client, exports_ws_borsh.KIND_TERM_OUTPUT, payloadBytes);
55864
56098
  }
55865
56099
  }
55866
- broadcastTerminalHistory(deviceId, paneId, data) {
56100
+ broadcastTerminalHistory(deviceId, paneId, data, alternateScreen) {
55867
56101
  const entry = this.connections.get(deviceId);
55868
56102
  if (!entry)
55869
56103
  return;
@@ -55872,7 +56106,7 @@ class WebSocketServer {
55872
56106
  if (client.data.borshState.selectedPanes[deviceId] !== paneId) {
55873
56107
  continue;
55874
56108
  }
55875
- switchBarrier.sendTermHistory(client, deviceId, paneId, historyBytes);
56109
+ switchBarrier.sendTermHistory(client, deviceId, paneId, historyBytes, alternateScreen);
55876
56110
  }
55877
56111
  }
55878
56112
  broadcastError(deviceId, err) {
@@ -56294,9 +56528,9 @@ async function serveFrontend(req, staticRoot) {
56294
56528
  if (!requestedPath) {
56295
56529
  return new Response(t3("runtime.forbidden"), { status: 403 });
56296
56530
  }
56297
- const indexPath = join4(staticRoot, "index.html");
56298
- const targetPath = existsSync3(requestedPath) ? requestedPath : indexPath;
56299
- if (!existsSync3(targetPath)) {
56531
+ const indexPath = join5(staticRoot, "index.html");
56532
+ const targetPath = existsSync4(requestedPath) ? requestedPath : indexPath;
56533
+ if (!existsSync4(targetPath)) {
56300
56534
  return new Response(t3("runtime.frontendMissing"), { status: 500 });
56301
56535
  }
56302
56536
  const headers = new Headers;