tmex-cli 0.4.2 → 0.4.4

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 +491 -94
  2. package/package.json +1 -1
  3. package/resources/fe-dist/assets/DevicePage-Di11uzbS.js +26 -0
  4. package/resources/fe-dist/assets/{DevicePage-CKaPUo7L.js.map → DevicePage-Di11uzbS.js.map} +1 -1
  5. package/resources/fe-dist/assets/DevicesPage-dj7Pel6f.js +17 -0
  6. package/resources/fe-dist/assets/DevicesPage-dj7Pel6f.js.map +1 -0
  7. package/resources/fe-dist/assets/SettingsPage-DwpbDA_e.js +17 -0
  8. package/resources/fe-dist/assets/{SettingsPage-BfkOW0fc.js.map → SettingsPage-DwpbDA_e.js.map} +1 -1
  9. package/resources/fe-dist/assets/index-40zyi_9K.css +1 -0
  10. package/resources/fe-dist/assets/index-61hq_aNX.js +215 -0
  11. package/resources/fe-dist/assets/index-61hq_aNX.js.map +1 -0
  12. package/resources/fe-dist/assets/select-DI4HQZd4.js +17 -0
  13. package/resources/fe-dist/assets/{select-CNlE6MiW.js.map → select-DI4HQZd4.js.map} +1 -1
  14. package/resources/fe-dist/assets/switch-DsfDIUrU.js +12 -0
  15. package/resources/fe-dist/assets/{switch-CxkzOIL6.js.map → switch-DsfDIUrU.js.map} +1 -1
  16. package/resources/fe-dist/assets/useValueChanged-AVaPYvln.js +7 -0
  17. package/resources/fe-dist/assets/{useValueChanged-CO2U5MoL.js.map → useValueChanged-AVaPYvln.js.map} +1 -1
  18. package/resources/fe-dist/index.html +2 -2
  19. package/resources/gateway-drizzle/0003_glamorous_lizard.sql +1 -0
  20. package/resources/gateway-drizzle/meta/0003_snapshot.json +542 -0
  21. package/resources/gateway-drizzle/meta/_journal.json +7 -0
  22. package/resources/fe-dist/assets/DevicePage-CKaPUo7L.js +0 -26
  23. package/resources/fe-dist/assets/DevicesPage-FqU-Dxhu.js +0 -17
  24. package/resources/fe-dist/assets/DevicesPage-FqU-Dxhu.js.map +0 -1
  25. package/resources/fe-dist/assets/SettingsPage-BfkOW0fc.js +0 -17
  26. package/resources/fe-dist/assets/index-EgHfq93I.js +0 -449
  27. package/resources/fe-dist/assets/index-EgHfq93I.js.map +0 -1
  28. package/resources/fe-dist/assets/index-Ytlj3p_q.css +0 -1
  29. package/resources/fe-dist/assets/select-CNlE6MiW.js +0 -17
  30. package/resources/fe-dist/assets/switch-CxkzOIL6.js +0 -12
  31. package/resources/fe-dist/assets/useValueChanged-CO2U5MoL.js +0 -7
@@ -20524,6 +20524,8 @@ var I18N_RESOURCES = {
20524
20524
  chatId: "Chat ID",
20525
20525
  applyTime: "Application Time",
20526
20526
  gatewayOnline: "\uD83D\uDFE2 Gateway online @ {{siteName}}",
20527
+ deviceConnectionError: `\uD83D\uDD34 {{siteName}}: Connection error on device "{{deviceName}}" ({{host}}) [{{category}}]
20528
+ {{error}}`,
20527
20529
  authSuccess: "\u2705 Authorized. You will now receive notifications.",
20528
20530
  authPending: "\u23F3 Authorization request received. Please approve in tmex settings.",
20529
20531
  authFailed: "\u274C Authorization request failed. Please contact administrator.",
@@ -20564,11 +20566,30 @@ Time: {{time}}`,
20564
20566
  hostNotFound: "Host not found: Unable to resolve hostname. Please check DNS or hostname configuration.",
20565
20567
  handshakeFailed: "Handshake failed: Unable to establish secure connection. Possibly incompatible key exchange algorithm.",
20566
20568
  tmuxUnavailable: "Remote tmux unavailable or failed to start. Please ensure tmux is installed and available in the remote shell PATH.",
20569
+ connectionClosed: "Connection closed, attempting to reconnect",
20567
20570
  unknown: "Connection failed: {{message}}",
20568
20571
  reconnecting: "Connection interrupted, reconnecting in {{delay}} seconds ({{attempt}}/{{maxRetries}})",
20569
20572
  reconnectFailed: "Auto-reconnect failed, please retry manually",
20570
20573
  reconnected: "Device reconnected automatically"
20571
20574
  },
20575
+ deviceStatus: {
20576
+ reconnecting: "Reconnecting {{delay}}s",
20577
+ offline: "Offline",
20578
+ errorBadge: {
20579
+ authFailed: "Auth failed",
20580
+ agentUnavailable: "Agent unavailable",
20581
+ agentNoIdentity: "Agent has no keys",
20582
+ configRefNotSupported: "SSH Config unsupported",
20583
+ networkUnreachable: "Network unreachable",
20584
+ connectionRefused: "Refused",
20585
+ timeout: "Timeout",
20586
+ hostNotFound: "Host not found",
20587
+ handshakeFailed: "Handshake failed",
20588
+ tmuxUnavailable: "Tmux unavailable",
20589
+ connectionClosed: "Disconnected",
20590
+ unknown: "Connection error"
20591
+ }
20592
+ },
20572
20593
  websocket: {
20573
20594
  error: "WebSocket connection error",
20574
20595
  checkGateway: "Please check Gateway status",
@@ -20862,6 +20883,8 @@ Time: {{time}}`,
20862
20883
  chatId: "chatId",
20863
20884
  applyTime: "\u7533\u8BF7\u65F6\u95F4",
20864
20885
  gatewayOnline: "\uD83D\uDFE2 Gateway online @ {{siteName}}",
20886
+ deviceConnectionError: `\uD83D\uDD34 {{siteName}}\uFF1A\u8BBE\u5907\u300C{{deviceName}}\u300D({{host}}) \u8FDE\u63A5\u5F02\u5E38 [{{category}}]
20887
+ {{error}}`,
20865
20888
  authSuccess: "\u2705 \u5DF2\u6388\u6743\uFF0C\u53EF\u63A5\u6536\u901A\u77E5\u3002",
20866
20889
  authPending: "\u23F3 \u5DF2\u6536\u5230\u6388\u6743\u7533\u8BF7\uFF0C\u8BF7\u5728 tmex \u8BBE\u7F6E\u9875\u5BA1\u6279\u3002",
20867
20890
  authFailed: "\u274C \u6388\u6743\u7533\u8BF7\u5931\u8D25\uFF0C\u8BF7\u8054\u7CFB\u7BA1\u7406\u5458\u3002",
@@ -20902,11 +20925,30 @@ Bot\uFF1A{{botName}}
20902
20925
  hostNotFound: "\u4E3B\u673A\u672A\u627E\u5230\uFF1A\u65E0\u6CD5\u89E3\u6790\u4E3B\u673A\u5730\u5740\uFF0C\u8BF7\u68C0\u67E5 DNS \u6216\u4E3B\u673A\u540D\u662F\u5426\u6B63\u786E",
20903
20926
  handshakeFailed: "\u63E1\u624B\u5931\u8D25\uFF1A\u65E0\u6CD5\u5EFA\u7ACB\u5B89\u5168\u8FDE\u63A5\uFF0C\u53EF\u80FD\u662F\u5BC6\u94A5\u4EA4\u6362\u7B97\u6CD5\u4E0D\u517C\u5BB9",
20904
20927
  tmuxUnavailable: "\u8FDC\u7AEF tmux \u4E0D\u53EF\u7528\u6216\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8FDC\u7AEF\u662F\u5426\u5DF2\u5B89\u88C5 tmux\uFF0C\u4E14\u8FDC\u7AEF shell PATH \u53EF\u627E\u5230 tmux",
20928
+ connectionClosed: "\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u5C1D\u8BD5\u91CD\u8FDE\u4E2D",
20905
20929
  unknown: "\u8FDE\u63A5\u5931\u8D25\uFF1A{{message}}",
20906
20930
  reconnecting: "\u8FDE\u63A5\u4E2D\u65AD\uFF0C{{delay}} \u79D2\u540E\u81EA\u52A8\u91CD\u8FDE\uFF08{{attempt}}/{{maxRetries}}\uFF09",
20907
20931
  reconnectFailed: "\u81EA\u52A8\u91CD\u8FDE\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u91CD\u8BD5",
20908
20932
  reconnected: "\u8BBE\u5907\u5DF2\u81EA\u52A8\u91CD\u8FDE"
20909
20933
  },
20934
+ deviceStatus: {
20935
+ reconnecting: "\u91CD\u8FDE\u4E2D {{delay}}s",
20936
+ offline: "\u79BB\u7EBF",
20937
+ errorBadge: {
20938
+ authFailed: "\u8BA4\u8BC1\u5931\u8D25",
20939
+ agentUnavailable: "Agent \u4E0D\u53EF\u7528",
20940
+ agentNoIdentity: "Agent \u65E0\u5BC6\u94A5",
20941
+ configRefNotSupported: "\u4E0D\u652F\u6301 SSH Config",
20942
+ networkUnreachable: "\u7F51\u7EDC\u4E0D\u53EF\u8FBE",
20943
+ connectionRefused: "\u8FDE\u63A5\u88AB\u62D2",
20944
+ timeout: "\u8FDE\u63A5\u8D85\u65F6",
20945
+ hostNotFound: "\u4E3B\u673A\u672A\u627E\u5230",
20946
+ handshakeFailed: "\u63E1\u624B\u5931\u8D25",
20947
+ tmuxUnavailable: "tmux \u4E0D\u53EF\u7528",
20948
+ connectionClosed: "\u8FDE\u63A5\u5DF2\u65AD\u5F00",
20949
+ unknown: "\u8FDE\u63A5\u5F02\u5E38"
20950
+ }
20951
+ },
20910
20952
  websocket: {
20911
20953
  error: "WebSocket \u8FDE\u63A5\u9519\u8BEF",
20912
20954
  checkGateway: "\u8BF7\u68C0\u67E5 Gateway \u72B6\u6001",
@@ -21200,6 +21242,8 @@ Bot\uFF1A{{botName}}
21200
21242
  chatId: "Chat ID",
21201
21243
  applyTime: "\u7533\u8ACB\u6642\u9593",
21202
21244
  gatewayOnline: "\uD83D\uDFE2 Gateway online @ {{siteName}}",
21245
+ deviceConnectionError: `\uD83D\uDD34 {{siteName}}\uFF1A\u30C7\u30D0\u30A4\u30B9\u300C{{deviceName}}\u300D({{host}}) \u3067\u63A5\u7D9A\u30A8\u30E9\u30FC [{{category}}]
21246
+ {{error}}`,
21203
21247
  authSuccess: "\u2705 \u627F\u8A8D\u3055\u308C\u307E\u3057\u305F\u3002\u901A\u77E5\u3092\u53D7\u4FE1\u3067\u304D\u307E\u3059\u3002",
21204
21248
  authPending: "\u23F3 \u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u53D7\u4FE1\u3057\u307E\u3057\u305F\u3002tmex \u8A2D\u5B9A\u30DA\u30FC\u30B8\u3067\u627F\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
21205
21249
  authFailed: "\u274C \u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u7BA1\u7406\u8005\u306B\u9023\u7D61\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
@@ -21240,11 +21284,30 @@ Bot\uFF1A{{botName}}
21240
21284
  hostNotFound: "\u30DB\u30B9\u30C8\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093\uFF1A\u30DB\u30B9\u30C8\u540D\u3092\u89E3\u6C7A\u3067\u304D\u307E\u305B\u3093\u3002DNS \u307E\u305F\u306F\u30DB\u30B9\u30C8\u540D\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
21241
21285
  handshakeFailed: "\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A\u5B89\u5168\u306A\u63A5\u7D9A\u3092\u78BA\u7ACB\u3067\u304D\u307E\u305B\u3093\u3002\u30AD\u30FC\u4EA4\u63DB\u30A2\u30EB\u30B4\u30EA\u30BA\u30E0\u304C\u4E92\u63DB\u6027\u304C\u306A\u3044\u53EF\u80FD\u6027\u304C\u3042\u308A\u307E\u3059\u3002",
21242
21286
  tmuxUnavailable: "\u30EA\u30E2\u30FC\u30C8 tmux \u304C\u5229\u7528\u3067\u304D\u306A\u3044\u304B\u8D77\u52D5\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002tmux \u304C\u30A4\u30F3\u30B9\u30C8\u30FC\u30EB\u3055\u308C\u3001\u30EA\u30E2\u30FC\u30C8 shell \u306E PATH \u304B\u3089\u53C2\u7167\u3067\u304D\u308B\u3053\u3068\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
21287
+ connectionClosed: "\u63A5\u7D9A\u304C\u5207\u65AD\u3055\u308C\u307E\u3057\u305F\u3002\u518D\u63A5\u7D9A\u3092\u8A66\u307F\u3066\u3044\u307E\u3059",
21243
21288
  unknown: "\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A{{message}}",
21244
21289
  reconnecting: "\u63A5\u7D9A\u304C\u4E2D\u65AD\u3055\u308C\u307E\u3057\u305F\u3002{{delay}} \u79D2\u5F8C\u306B\u518D\u63A5\u7D9A\u3057\u307E\u3059\uFF08{{attempt}}/{{maxRetries}}\uFF09",
21245
21290
  reconnectFailed: "\u81EA\u52D5\u518D\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u624B\u52D5\u3067\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044",
21246
21291
  reconnected: "\u30C7\u30D0\u30A4\u30B9\u304C\u81EA\u52D5\u7684\u306B\u518D\u63A5\u7D9A\u3055\u308C\u307E\u3057\u305F"
21247
21292
  },
21293
+ deviceStatus: {
21294
+ reconnecting: "\u518D\u63A5\u7D9A\u4E2D {{delay}}s",
21295
+ offline: "\u30AA\u30D5\u30E9\u30A4\u30F3",
21296
+ errorBadge: {
21297
+ authFailed: "\u8A8D\u8A3C\u5931\u6557",
21298
+ agentUnavailable: "Agent \u5229\u7528\u4E0D\u53EF",
21299
+ agentNoIdentity: "Agent \u306B\u9375\u306A\u3057",
21300
+ configRefNotSupported: "SSH Config \u975E\u5BFE\u5FDC",
21301
+ networkUnreachable: "\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u4E0D\u53EF\u9054",
21302
+ connectionRefused: "\u63A5\u7D9A\u62D2\u5426",
21303
+ timeout: "\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8",
21304
+ hostNotFound: "\u30DB\u30B9\u30C8\u672A\u691C\u51FA",
21305
+ handshakeFailed: "\u30CF\u30F3\u30C9\u30B7\u30A7\u30A4\u30AF\u5931\u6557",
21306
+ tmuxUnavailable: "tmux \u5229\u7528\u4E0D\u53EF",
21307
+ connectionClosed: "\u5207\u65AD",
21308
+ unknown: "\u63A5\u7D9A\u30A8\u30E9\u30FC"
21309
+ }
21310
+ },
21248
21311
  websocket: {
21249
21312
  error: "WebSocket \u63A5\u7D9A\u30A8\u30E9\u30FC",
21250
21313
  checkGateway: "Gateway \u72B6\u614B\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044",
@@ -28884,7 +28947,8 @@ var deviceRuntimeStatus = sqliteTable("device_runtime_status", {
28884
28947
  deviceId: text("device_id").primaryKey().references(() => devices.id, { onDelete: "cascade" }),
28885
28948
  lastSeenAt: text("last_seen_at"),
28886
28949
  tmuxAvailable: integer("tmux_available", { mode: "boolean" }).notNull().default(false),
28887
- lastError: text("last_error")
28950
+ lastError: text("last_error"),
28951
+ lastErrorType: text("last_error_type")
28888
28952
  });
28889
28953
  var webhookEndpoints = sqliteTable("webhook_endpoints", {
28890
28954
  id: text("id").primaryKey(),
@@ -29057,7 +29121,8 @@ function createDevice(device) {
29057
29121
  deviceId: device.id,
29058
29122
  lastSeenAt: null,
29059
29123
  tmuxAvailable: false,
29060
- lastError: null
29124
+ lastError: null,
29125
+ lastErrorType: null
29061
29126
  }).onConflictDoNothing({ target: deviceRuntimeStatus.deviceId }).run();
29062
29127
  });
29063
29128
  }
@@ -29114,6 +29179,26 @@ function deleteDevice(id) {
29114
29179
  const orm = getDb();
29115
29180
  orm.delete(devices).where(eq(devices.id, id)).run();
29116
29181
  }
29182
+ function getDeviceRuntimeStatus(deviceId) {
29183
+ const orm = getDb();
29184
+ const row = orm.select().from(deviceRuntimeStatus).where(eq(deviceRuntimeStatus.deviceId, deviceId)).get();
29185
+ if (!row) {
29186
+ return {
29187
+ deviceId,
29188
+ lastSeenAt: null,
29189
+ tmuxAvailable: false,
29190
+ lastError: null,
29191
+ lastErrorType: null
29192
+ };
29193
+ }
29194
+ return {
29195
+ deviceId: row.deviceId,
29196
+ lastSeenAt: row.lastSeenAt,
29197
+ tmuxAvailable: row.tmuxAvailable,
29198
+ lastError: row.lastError,
29199
+ lastErrorType: row.lastErrorType
29200
+ };
29201
+ }
29117
29202
  function updateDeviceRuntimeStatus(deviceId, status) {
29118
29203
  const orm = getDb();
29119
29204
  const setValues = {};
@@ -29126,6 +29211,9 @@ function updateDeviceRuntimeStatus(deviceId, status) {
29126
29211
  if (status.lastError !== undefined) {
29127
29212
  setValues.lastError = status.lastError;
29128
29213
  }
29214
+ if (status.lastErrorType !== undefined) {
29215
+ setValues.lastErrorType = status.lastErrorType;
29216
+ }
29129
29217
  if (Object.keys(setValues).length === 0) {
29130
29218
  return;
29131
29219
  }
@@ -52033,6 +52121,241 @@ var eventNotifier = new EventNotifier;
52033
52121
  import { mkdirSync, rmSync } from "fs";
52034
52122
  import { homedir } from "os";
52035
52123
 
52124
+ // ../../apps/gateway/src/ws/error-classify.ts
52125
+ function classifySshError(error) {
52126
+ const msg = error.message.toLowerCase();
52127
+ if (msg.includes("ssh_config_ref_not_supported")) {
52128
+ return {
52129
+ type: "ssh_config_ref_not_supported",
52130
+ messageKey: "sshError.configRefNotSupported"
52131
+ };
52132
+ }
52133
+ if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
52134
+ return {
52135
+ type: "agent_unavailable",
52136
+ messageKey: "sshError.agentUnavailable"
52137
+ };
52138
+ }
52139
+ if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
52140
+ return {
52141
+ type: "agent_no_identity",
52142
+ messageKey: "sshError.agentNoIdentities"
52143
+ };
52144
+ }
52145
+ if (msg.includes("permission denied")) {
52146
+ return {
52147
+ type: "auth_failed",
52148
+ messageKey: "sshError.authFailed"
52149
+ };
52150
+ }
52151
+ if (msg.includes("all configured authentication methods failed")) {
52152
+ return {
52153
+ type: "auth_failed",
52154
+ messageKey: "sshError.authFailedGeneric"
52155
+ };
52156
+ }
52157
+ if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
52158
+ return {
52159
+ type: "network_unreachable",
52160
+ messageKey: "sshError.networkUnreachable"
52161
+ };
52162
+ }
52163
+ if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
52164
+ return {
52165
+ type: "connection_refused",
52166
+ messageKey: "sshError.connectionRefused"
52167
+ };
52168
+ }
52169
+ if (msg.includes("timeout") || msg.includes("etimedout")) {
52170
+ return {
52171
+ type: "timeout",
52172
+ messageKey: "sshError.connectionTimeout"
52173
+ };
52174
+ }
52175
+ if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
52176
+ return {
52177
+ type: "host_not_found",
52178
+ messageKey: "sshError.hostNotFound"
52179
+ };
52180
+ }
52181
+ if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
52182
+ return {
52183
+ type: "handshake_failed",
52184
+ messageKey: "sshError.handshakeFailed"
52185
+ };
52186
+ }
52187
+ 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")) {
52188
+ return {
52189
+ type: "tmux_unavailable",
52190
+ messageKey: "sshError.tmuxUnavailable"
52191
+ };
52192
+ }
52193
+ if (msg.includes("ssh_connection_closed") || msg.includes("connection closed") || msg.includes("ssh command channel not ready") || msg.includes("ssh connection not ready") || msg.includes("channel closed")) {
52194
+ return {
52195
+ type: "connection_closed",
52196
+ messageKey: "sshError.connectionClosed"
52197
+ };
52198
+ }
52199
+ return {
52200
+ type: "unknown",
52201
+ messageKey: "sshError.unknown",
52202
+ messageParams: { message: error.message }
52203
+ };
52204
+ }
52205
+
52206
+ // ../../apps/gateway/src/push/connection-alerts.ts
52207
+ var NOTIFY_THROTTLE_MS = 5 * 60 * 1000;
52208
+ function toErrorObject(err) {
52209
+ if (err instanceof Error) {
52210
+ return err;
52211
+ }
52212
+ if (typeof err === "string") {
52213
+ return new Error(err);
52214
+ }
52215
+ try {
52216
+ return new Error(JSON.stringify(err));
52217
+ } catch {
52218
+ return new Error(String(err));
52219
+ }
52220
+ }
52221
+
52222
+ class ConnectionAlertNotifier {
52223
+ throttleMap = new Map;
52224
+ broadcaster = null;
52225
+ settingsProvider = () => getSiteSettings();
52226
+ persister = (deviceId, friendlyMessage, errorType) => {
52227
+ updateDeviceRuntimeStatus(deviceId, {
52228
+ lastSeenAt: new Date().toISOString(),
52229
+ lastError: friendlyMessage,
52230
+ lastErrorType: errorType
52231
+ });
52232
+ };
52233
+ telegramSender = (text2) => telegramService.sendToAuthorizedChats({ text: text2 });
52234
+ setBroadcaster(broadcaster) {
52235
+ this.broadcaster = broadcaster;
52236
+ }
52237
+ setSettingsProvider(provider) {
52238
+ this.settingsProvider = provider;
52239
+ }
52240
+ setPersister(persister) {
52241
+ this.persister = persister;
52242
+ }
52243
+ setTelegramSender(sender) {
52244
+ this.telegramSender = sender;
52245
+ }
52246
+ async notify(alert) {
52247
+ const { device, error, source, silentTelegram = false, persist = true } = alert;
52248
+ const errObj = toErrorObject(error);
52249
+ const classified = classifySshError(errObj);
52250
+ const friendlyMessage = t2(classified.messageKey, { ...classified.messageParams });
52251
+ const rawMessage = errObj.message;
52252
+ console.error(`[conn-alert] device ${device.id} (${device.name}) source=${source} type=${classified.type}: ${rawMessage}`);
52253
+ if (persist) {
52254
+ try {
52255
+ this.persister(device.id, friendlyMessage, classified.type);
52256
+ } catch (dbErr) {
52257
+ console.error("[conn-alert] failed to persist runtime status:", dbErr);
52258
+ }
52259
+ }
52260
+ if (this.broadcaster) {
52261
+ try {
52262
+ this.broadcaster(device.id, {
52263
+ deviceId: device.id,
52264
+ type: "error",
52265
+ errorType: classified.type,
52266
+ message: friendlyMessage,
52267
+ rawMessage
52268
+ });
52269
+ } catch (broadcastErr) {
52270
+ console.error("[conn-alert] failed to broadcast:", broadcastErr);
52271
+ }
52272
+ }
52273
+ if (!silentTelegram && this.shouldSendTelegram(device.id, classified.type)) {
52274
+ await this.sendTelegram(device, classified.type, friendlyMessage, rawMessage);
52275
+ }
52276
+ return {
52277
+ errorType: classified.type,
52278
+ messageKey: classified.messageKey,
52279
+ message: friendlyMessage,
52280
+ rawMessage
52281
+ };
52282
+ }
52283
+ clear(deviceId) {
52284
+ for (const key of this.throttleMap.keys()) {
52285
+ if (key.startsWith(`${deviceId}:`)) {
52286
+ this.throttleMap.delete(key);
52287
+ }
52288
+ }
52289
+ }
52290
+ shouldSendTelegram(deviceId, errorType) {
52291
+ const key = `${deviceId}:${errorType}`;
52292
+ const now = Date.now();
52293
+ const last = this.throttleMap.get(key) ?? 0;
52294
+ if (now - last < NOTIFY_THROTTLE_MS) {
52295
+ return false;
52296
+ }
52297
+ this.throttleMap.set(key, now);
52298
+ for (const [otherKey, ts] of this.throttleMap) {
52299
+ if (otherKey !== key && otherKey.startsWith(`${deviceId}:`) && now - ts >= NOTIFY_THROTTLE_MS) {
52300
+ this.throttleMap.delete(otherKey);
52301
+ }
52302
+ }
52303
+ return true;
52304
+ }
52305
+ async sendTelegram(device, errorType, friendlyMessage, rawMessage) {
52306
+ let settings;
52307
+ try {
52308
+ settings = this.settingsProvider();
52309
+ } catch (err) {
52310
+ console.error("[conn-alert] failed to read site settings:", err);
52311
+ return;
52312
+ }
52313
+ const categoryKey = `deviceStatus.errorBadge.${toBadgeKey(errorType)}`;
52314
+ const translatedCategory = t2(categoryKey, { defaultValue: errorType });
52315
+ const text2 = t2("telegram.deviceConnectionError", {
52316
+ siteName: settings.siteName,
52317
+ deviceName: device.name,
52318
+ host: device.host ?? "-",
52319
+ category: translatedCategory,
52320
+ error: friendlyMessage || rawMessage
52321
+ });
52322
+ try {
52323
+ await this.telegramSender(text2);
52324
+ } catch (notifyErr) {
52325
+ console.error("[conn-alert] telegram send failed:", notifyErr);
52326
+ }
52327
+ }
52328
+ }
52329
+ function toBadgeKey(errorType) {
52330
+ switch (errorType) {
52331
+ case "auth_failed":
52332
+ return "authFailed";
52333
+ case "agent_unavailable":
52334
+ return "agentUnavailable";
52335
+ case "agent_no_identity":
52336
+ return "agentNoIdentity";
52337
+ case "ssh_config_ref_not_supported":
52338
+ return "configRefNotSupported";
52339
+ case "network_unreachable":
52340
+ return "networkUnreachable";
52341
+ case "connection_refused":
52342
+ return "connectionRefused";
52343
+ case "timeout":
52344
+ return "timeout";
52345
+ case "host_not_found":
52346
+ return "hostNotFound";
52347
+ case "handshake_failed":
52348
+ return "handshakeFailed";
52349
+ case "tmux_unavailable":
52350
+ return "tmuxUnavailable";
52351
+ case "connection_closed":
52352
+ return "connectionClosed";
52353
+ default:
52354
+ return "unknown";
52355
+ }
52356
+ }
52357
+ var connectionAlertNotifier = new ConnectionAlertNotifier;
52358
+
52036
52359
  // ../../apps/gateway/src/tmux/local-shell-path.ts
52037
52360
  import { existsSync } from "fs";
52038
52361
  import { delimiter, join as join2 } from "path";
@@ -52491,6 +52814,8 @@ class LocalExternalTmuxConnection {
52491
52814
  hookReadAbort = null;
52492
52815
  hookBuffer = "";
52493
52816
  bellDedup = new Map;
52817
+ closeNotified = false;
52818
+ cleanupPromise = null;
52494
52819
  fsPaths = createRuntimeFsPaths({
52495
52820
  deviceId: "pending",
52496
52821
  sessionName: "pending",
@@ -52507,6 +52832,7 @@ class LocalExternalTmuxConnection {
52507
52832
  }
52508
52833
  async connect() {
52509
52834
  this.manualDisconnect = false;
52835
+ this.closeNotified = false;
52510
52836
  this.device = this.deps.getDevice(this.deviceId);
52511
52837
  if (!this.device) {
52512
52838
  throw new Error(`Device not found: ${this.deviceId}`);
@@ -52530,7 +52856,8 @@ class LocalExternalTmuxConnection {
52530
52856
  updateDeviceRuntimeStatus(this.deviceId, {
52531
52857
  lastSeenAt: new Date().toISOString(),
52532
52858
  tmuxAvailable: true,
52533
- lastError: null
52859
+ lastError: null,
52860
+ lastErrorType: null
52534
52861
  });
52535
52862
  await this.requestSnapshotInternal();
52536
52863
  }
@@ -52814,6 +53141,20 @@ class LocalExternalTmuxConnection {
52814
53141
  ])
52815
53142
  ]);
52816
53143
  if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
53144
+ const stderrBlob = `${sessionRes.stderr}
53145
+ ${windowsRes.stderr}
53146
+ ${panesRes.stderr}`;
53147
+ if (this.connected && !this.manualDisconnect && this.isTmuxServerGoneMessage(stderrBlob)) {
53148
+ const message = stderrBlob.trim().split(/\r?\n/).find((line) => line.trim())?.trim() ?? "tmux server gone";
53149
+ console.warn(`[local] tmux server gone during snapshot on ${this.deviceId}: ${message}`);
53150
+ updateDeviceRuntimeStatus(this.deviceId, {
53151
+ lastSeenAt: new Date().toISOString(),
53152
+ tmuxAvailable: false,
53153
+ lastError: message
53154
+ });
53155
+ this.shutdownInternal(true);
53156
+ return;
53157
+ }
52817
53158
  this.callbacks.onSnapshot({ deviceId: this.deviceId, session: null });
52818
53159
  return;
52819
53160
  }
@@ -53058,13 +53399,30 @@ class LocalExternalTmuxConnection {
53058
53399
  this.recoverFromTargetMissingError(message);
53059
53400
  return result;
53060
53401
  }
53061
- updateDeviceRuntimeStatus(this.deviceId, {
53062
- lastSeenAt: new Date().toISOString(),
53063
- tmuxAvailable: false,
53064
- lastError: message
53065
- });
53402
+ this.notifyRuntimeError(message);
53403
+ if (this.connected && !this.manualDisconnect && this.isTmuxServerGoneMessage(message)) {
53404
+ console.warn(`[local] tmux server gone on ${this.deviceId}: ${message}`);
53405
+ this.shutdownInternal(true);
53406
+ }
53066
53407
  throw new Error(message);
53067
53408
  }
53409
+ async notifyRuntimeError(message) {
53410
+ const device = getDeviceById(this.deviceId);
53411
+ if (!device) {
53412
+ updateDeviceRuntimeStatus(this.deviceId, {
53413
+ lastSeenAt: new Date().toISOString(),
53414
+ tmuxAvailable: false,
53415
+ lastError: message
53416
+ });
53417
+ return;
53418
+ }
53419
+ await connectionAlertNotifier.notify({
53420
+ device,
53421
+ error: new Error(message),
53422
+ source: "runtime",
53423
+ silentTelegram: true
53424
+ });
53425
+ }
53068
53426
  async runTmuxAllowFailure(argv) {
53069
53427
  return this.deps.run(["tmux", ...argv]);
53070
53428
  }
@@ -53072,6 +53430,40 @@ class LocalExternalTmuxConnection {
53072
53430
  const normalized = message.toLowerCase();
53073
53431
  return normalized.includes("can't find window") || normalized.includes("can't find pane") || normalized.includes("no such window") || normalized.includes("no such pane");
53074
53432
  }
53433
+ isTmuxServerGoneMessage(message) {
53434
+ const normalized = message.toLowerCase();
53435
+ return normalized.includes("no server running on") || normalized.includes("no sessions") || normalized.includes("lost server") || normalized.includes("can't find session") || normalized.includes("session not found") || normalized.includes("no such session");
53436
+ }
53437
+ async shutdownInternal(notifyClose) {
53438
+ if (this.cleanupPromise) {
53439
+ await this.cleanupPromise;
53440
+ if (notifyClose && !this.closeNotified && !this.manualDisconnect) {
53441
+ this.closeNotified = true;
53442
+ this.callbacks.onClose();
53443
+ }
53444
+ return;
53445
+ }
53446
+ this.connected = false;
53447
+ this.cleanupPromise = (async () => {
53448
+ await this.stopAllPipeReaders().catch(() => {
53449
+ return;
53450
+ });
53451
+ if (this.deps.enableHooks) {
53452
+ await this.stopHooks().catch(() => {
53453
+ return;
53454
+ });
53455
+ }
53456
+ try {
53457
+ rmSync(this.fsPaths.rootDir, { recursive: true, force: true });
53458
+ } catch {}
53459
+ })();
53460
+ await this.cleanupPromise;
53461
+ this.cleanupPromise = null;
53462
+ if (notifyClose && !this.closeNotified && !this.manualDisconnect) {
53463
+ this.closeNotified = true;
53464
+ this.callbacks.onClose();
53465
+ }
53466
+ }
53075
53467
  recoverFromTargetMissingError(message) {
53076
53468
  const normalized = message.toLowerCase();
53077
53469
  if (normalized.includes("window")) {
@@ -53474,7 +53866,8 @@ class SshExternalTmuxConnection {
53474
53866
  updateDeviceRuntimeStatus(this.deviceId, {
53475
53867
  lastSeenAt: new Date().toISOString(),
53476
53868
  tmuxAvailable: true,
53477
- lastError: null
53869
+ lastError: null,
53870
+ lastErrorType: null
53478
53871
  });
53479
53872
  await this.requestSnapshotInternal();
53480
53873
  }
@@ -53706,9 +54099,11 @@ class SshExternalTmuxConnection {
53706
54099
  this.handleHookChunk(data.toString());
53707
54100
  },
53708
54101
  onClose: () => {
53709
- if (!this.manualDisconnect) {
53710
- this.callbacks.onError(new Error("SSH hook reader closed unexpectedly"));
54102
+ if (this.manualDisconnect) {
54103
+ return;
53711
54104
  }
54105
+ console.error("[ssh] hook reader channel closed unexpectedly, tearing down");
54106
+ this.shutdownInternal(true);
53712
54107
  }
53713
54108
  });
53714
54109
  this.hookReadAbort = () => {
@@ -53836,6 +54231,20 @@ class SshExternalTmuxConnection {
53836
54231
  ])
53837
54232
  ]);
53838
54233
  if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
54234
+ const stderrBlob = `${sessionRes.stderr}
54235
+ ${windowsRes.stderr}
54236
+ ${panesRes.stderr}`;
54237
+ if (this.connected && !this.manualDisconnect && this.isTmuxServerGoneMessage(stderrBlob)) {
54238
+ const message = stderrBlob.trim().split(/\r?\n/).find((line) => line.trim())?.trim() ?? "tmux server gone";
54239
+ console.warn(`[ssh] tmux server gone during snapshot on ${this.deviceId}: ${message}`);
54240
+ updateDeviceRuntimeStatus(this.deviceId, {
54241
+ lastSeenAt: new Date().toISOString(),
54242
+ tmuxAvailable: false,
54243
+ lastError: message
54244
+ });
54245
+ this.shutdownInternal(true);
54246
+ return;
54247
+ }
53839
54248
  this.callbacks.onSnapshot({ deviceId: this.deviceId, session: null });
53840
54249
  return;
53841
54250
  }
@@ -53995,9 +54404,17 @@ class SshExternalTmuxConnection {
53995
54404
  }
53996
54405
  },
53997
54406
  onClose: () => {
53998
- if (!this.manualDisconnect && this.paneReaders.has(paneId)) {
53999
- this.callbacks.onError(new Error(`SSH pane reader closed unexpectedly: ${paneId}`));
54407
+ if (this.manualDisconnect) {
54408
+ return;
54000
54409
  }
54410
+ const existing = this.paneReaders.get(paneId);
54411
+ if (!existing) {
54412
+ return;
54413
+ }
54414
+ console.warn(`[ssh] pane reader channel closed for ${paneId}, resync on next snapshot`);
54415
+ this.paneReaders.delete(paneId);
54416
+ this.runShellAllowFailure(`rm -f ${quoteShellArg(existing.fifoPath)}`);
54417
+ this.requestSnapshot();
54001
54418
  }
54002
54419
  });
54003
54420
  const handle = {
@@ -54053,7 +54470,9 @@ class SshExternalTmuxConnection {
54053
54470
  const next = this.pipeTransition.catch(() => {
54054
54471
  return;
54055
54472
  }).then(task);
54056
- this.pipeTransition = next;
54473
+ this.pipeTransition = next.catch(() => {
54474
+ return;
54475
+ });
54057
54476
  return next;
54058
54477
  }
54059
54478
  async runTmux(argv, allowTargetMissing = false, timeoutMs = 1e4) {
@@ -54071,6 +54490,10 @@ class SshExternalTmuxConnection {
54071
54490
  tmuxAvailable: false,
54072
54491
  lastError: message
54073
54492
  });
54493
+ if (this.connected && !this.manualDisconnect && this.isTmuxServerGoneMessage(message)) {
54494
+ console.warn(`[ssh] tmux server gone on ${this.deviceId}: ${message}`);
54495
+ this.shutdownInternal(true);
54496
+ }
54074
54497
  throw new Error(message);
54075
54498
  }
54076
54499
  async runTmuxAllowFailure(argv, timeoutMs = 1e4) {
@@ -54096,6 +54519,8 @@ class SshExternalTmuxConnection {
54096
54519
  }).then(() => this.executeShellCommand(command, timeoutMs));
54097
54520
  this.commandQueue = next.then(() => {
54098
54521
  return;
54522
+ }, () => {
54523
+ return;
54099
54524
  });
54100
54525
  return next;
54101
54526
  }
@@ -54196,6 +54621,10 @@ printf '\\036TMEX_END %s %d\\036\\n' ${quoteShellArg(commandId)} $?
54196
54621
  const normalized = message.toLowerCase();
54197
54622
  return normalized.includes("can't find window") || normalized.includes("can't find pane") || normalized.includes("no such window") || normalized.includes("no such pane");
54198
54623
  }
54624
+ isTmuxServerGoneMessage(message) {
54625
+ const normalized = message.toLowerCase();
54626
+ return normalized.includes("no server running on") || normalized.includes("no sessions") || normalized.includes("lost server") || normalized.includes("can't find session") || normalized.includes("session not found") || normalized.includes("no such session");
54627
+ }
54199
54628
  recoverFromTargetMissingError(message) {
54200
54629
  const normalized = message.toLowerCase();
54201
54630
  if (normalized.includes("window")) {
@@ -54657,9 +55086,14 @@ class PushSupervisor {
54657
55086
  },
54658
55087
  onError: (error) => {
54659
55088
  console.error(`[push] tmux error on device ${entry.deviceId}:`, error);
55089
+ connectionAlertNotifier.notify({
55090
+ device,
55091
+ error,
55092
+ source: "runtime"
55093
+ });
54660
55094
  },
54661
55095
  onClose: () => {
54662
- this.handleClose(entry.deviceId, generation, runtime);
55096
+ this.handleClose(entry.deviceId, generation, runtime, device);
54663
55097
  }
54664
55098
  });
54665
55099
  entry.runtime = runtime;
@@ -54683,6 +55117,11 @@ class PushSupervisor {
54683
55117
  return;
54684
55118
  }
54685
55119
  console.error(`[push] failed connecting device ${entry.deviceId}:`, err);
55120
+ await connectionAlertNotifier.notify({
55121
+ device,
55122
+ error: err,
55123
+ source: "connect"
55124
+ });
54686
55125
  detachRuntime();
54687
55126
  entry.detachRuntime = null;
54688
55127
  entry.runtime = null;
@@ -54720,11 +55159,16 @@ class PushSupervisor {
54720
55159
  this.connectEntry(entry);
54721
55160
  }, delayMs);
54722
55161
  }
54723
- async handleClose(deviceId, generation, runtime) {
55162
+ async handleClose(deviceId, generation, runtime, device) {
54724
55163
  const entry = this.entries.get(deviceId);
54725
55164
  if (!entry || entry.generation !== generation || entry.runtime !== runtime) {
54726
55165
  return;
54727
55166
  }
55167
+ await connectionAlertNotifier.notify({
55168
+ device,
55169
+ error: new Error("ssh_connection_closed"),
55170
+ source: "close"
55171
+ });
54728
55172
  entry.detachRuntime?.();
54729
55173
  entry.detachRuntime = null;
54730
55174
  entry.runtime = null;
@@ -54785,82 +55229,6 @@ class PushSupervisor {
54785
55229
  }
54786
55230
  var pushSupervisor = new PushSupervisor;
54787
55231
 
54788
- // ../../apps/gateway/src/ws/error-classify.ts
54789
- function classifySshError(error) {
54790
- const msg = error.message.toLowerCase();
54791
- if (msg.includes("ssh_config_ref_not_supported")) {
54792
- return {
54793
- type: "ssh_config_ref_not_supported",
54794
- messageKey: "sshError.configRefNotSupported"
54795
- };
54796
- }
54797
- if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
54798
- return {
54799
- type: "agent_unavailable",
54800
- messageKey: "sshError.agentUnavailable"
54801
- };
54802
- }
54803
- if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
54804
- return {
54805
- type: "agent_no_identity",
54806
- messageKey: "sshError.agentNoIdentities"
54807
- };
54808
- }
54809
- if (msg.includes("permission denied")) {
54810
- return {
54811
- type: "auth_failed",
54812
- messageKey: "sshError.authFailed"
54813
- };
54814
- }
54815
- if (msg.includes("all configured authentication methods failed")) {
54816
- return {
54817
- type: "auth_failed",
54818
- messageKey: "sshError.authFailedGeneric"
54819
- };
54820
- }
54821
- if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
54822
- return {
54823
- type: "network_unreachable",
54824
- messageKey: "sshError.networkUnreachable"
54825
- };
54826
- }
54827
- if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
54828
- return {
54829
- type: "connection_refused",
54830
- messageKey: "sshError.connectionRefused"
54831
- };
54832
- }
54833
- if (msg.includes("timeout") || msg.includes("etimedout")) {
54834
- return {
54835
- type: "timeout",
54836
- messageKey: "sshError.connectionTimeout"
54837
- };
54838
- }
54839
- if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
54840
- return {
54841
- type: "host_not_found",
54842
- messageKey: "sshError.hostNotFound"
54843
- };
54844
- }
54845
- if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
54846
- return {
54847
- type: "handshake_failed",
54848
- messageKey: "sshError.handshakeFailed"
54849
- };
54850
- }
54851
- 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")) {
54852
- return {
54853
- type: "tmux_unavailable",
54854
- messageKey: "sshError.tmuxUnavailable"
54855
- };
54856
- }
54857
- return {
54858
- type: "unknown",
54859
- messageKey: "sshError.unknown",
54860
- messageParams: { message: error.message }
54861
- };
54862
- }
54863
-
54864
55232
  // ../../apps/gateway/src/api/test-connection.ts
54865
55233
  function inferFailurePhase(errorType) {
54866
55234
  if (errorType === "tmux_unavailable") {
@@ -55094,8 +55462,18 @@ function handleApiRequest(req, _server) {
55094
55462
  }
55095
55463
  return json2({ error: t2("apiError.notFound") }, 404);
55096
55464
  }
55465
+ function enrichDeviceWithRuntime(device) {
55466
+ const status = getDeviceRuntimeStatus(device.id);
55467
+ return {
55468
+ ...device,
55469
+ lastSeenAt: status.lastSeenAt,
55470
+ lastError: status.lastError,
55471
+ lastErrorType: status.lastErrorType,
55472
+ tmuxAvailable: status.tmuxAvailable
55473
+ };
55474
+ }
55097
55475
  async function handleGetDevices() {
55098
- const devices2 = getAllDevices();
55476
+ const devices2 = getAllDevices().map(enrichDeviceWithRuntime);
55099
55477
  return json2({ devices: devices2 });
55100
55478
  }
55101
55479
  async function handleGetDevice(id) {
@@ -55103,7 +55481,7 @@ async function handleGetDevice(id) {
55103
55481
  if (!device) {
55104
55482
  return json2({ error: t2("apiError.deviceNotFound") }, 404);
55105
55483
  }
55106
- return json2({ device });
55484
+ return json2({ device: enrichDeviceWithRuntime(device) });
55107
55485
  }
55108
55486
  async function handleCreateDevice(req) {
55109
55487
  const body = await req.json();
@@ -56675,6 +57053,15 @@ class WebSocketServer {
56675
57053
  this.sendEnvelope(client, exports_ws_borsh.KIND_DEVICE_EVENT, payloadBytes);
56676
57054
  }
56677
57055
  }
57056
+ broadcastDeviceError(deviceId, payload) {
57057
+ const entry = this.connections.get(deviceId);
57058
+ if (!entry)
57059
+ return;
57060
+ const payloadBytes = exports_ws_borsh.encodeDeviceEventPayload(payload);
57061
+ for (const client of entry.clients) {
57062
+ this.sendEnvelope(client, exports_ws_borsh.KIND_DEVICE_EVENT, payloadBytes);
57063
+ }
57064
+ }
56678
57065
  broadcastDeviceEvent(entry, payload) {
56679
57066
  const payloadBytes = exports_ws_borsh.encodeDeviceEventPayload(payload);
56680
57067
  for (const client of entry.clients) {
@@ -56767,6 +57154,9 @@ async function createGatewayRuntime(options = {}) {
56767
57154
  runtimeController.reset();
56768
57155
  primeLocalShellPath();
56769
57156
  const wsServer = new WebSocketServer;
57157
+ connectionAlertNotifier.setBroadcaster((deviceId, payload) => {
57158
+ wsServer.broadcastDeviceError(deviceId, payload);
57159
+ });
56770
57160
  await telegramService.refresh();
56771
57161
  await pushSupervisor.start();
56772
57162
  try {
@@ -56809,6 +57199,7 @@ async function createGatewayRuntime(options = {}) {
56809
57199
  runtimeController.onRestart(listener);
56810
57200
  },
56811
57201
  async stop() {
57202
+ connectionAlertNotifier.setBroadcaster(null);
56812
57203
  wsServer.closeAll();
56813
57204
  await pushSupervisor.stopAll();
56814
57205
  await tmuxRuntimeRegistry.shutdownAll();
@@ -57115,6 +57506,12 @@ async function main() {
57115
57506
  });
57116
57507
  console.log(`[tmex] ${t3("runtime.started", { url: `http://${host}:${port}` })}`);
57117
57508
  }
57509
+ process.on("unhandledRejection", (reason) => {
57510
+ console.error("[tmex][unhandledRejection]", reason);
57511
+ });
57512
+ process.on("uncaughtException", (error) => {
57513
+ console.error("[tmex][uncaughtException]", error);
57514
+ });
57118
57515
  try {
57119
57516
  await main();
57120
57517
  } catch (error) {