tmex-cli 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/server.js +718 -186
- package/package.json +1 -1
- package/resources/fe-dist/assets/DevicePage-n4JoyDed.js +26 -0
- package/resources/fe-dist/assets/DevicePage-n4JoyDed.js.map +1 -0
- package/resources/fe-dist/assets/DevicesPage-BwLKaiUR.js +17 -0
- package/resources/fe-dist/assets/{DevicesPage-CtNzaW_c.js.map → DevicesPage-BwLKaiUR.js.map} +1 -1
- package/resources/fe-dist/assets/SettingsPage-hS99lHcp.js +17 -0
- package/resources/fe-dist/assets/SettingsPage-hS99lHcp.js.map +1 -0
- package/resources/fe-dist/assets/index-CJaX5rlK.css +1 -4527
- package/resources/fe-dist/assets/index-CJyFlAt8.js +449 -0
- package/resources/fe-dist/assets/index-CJyFlAt8.js.map +1 -0
- package/resources/fe-dist/assets/select-DGBwxGiK.js +17 -0
- package/resources/fe-dist/assets/{select-BNsiC9zT.js.map → select-DGBwxGiK.js.map} +1 -1
- package/resources/fe-dist/assets/switch-CWUBjs7N.js +12 -0
- package/resources/fe-dist/assets/{switch-CIU4AisU.js.map → switch-CWUBjs7N.js.map} +1 -1
- package/resources/fe-dist/assets/useValueChanged-DwJ_SDCu.js +7 -0
- package/resources/fe-dist/assets/{useValueChanged-V23H0VpC.js.map → useValueChanged-DwJ_SDCu.js.map} +1 -1
- package/resources/fe-dist/index.html +1 -1
- package/resources/gateway-drizzle/0002_broad_vengeance.sql +3 -0
- package/resources/gateway-drizzle/meta/0000_snapshot.json +17 -6
- package/resources/gateway-drizzle/meta/0001_snapshot.json +17 -6
- package/resources/gateway-drizzle/meta/0002_snapshot.json +535 -0
- package/resources/gateway-drizzle/meta/_journal.json +8 -1
- package/resources/fe-dist/assets/DevicePage-iSkEDEpS.js +0 -4570
- package/resources/fe-dist/assets/DevicePage-iSkEDEpS.js.map +0 -1
- package/resources/fe-dist/assets/DevicesPage-CtNzaW_c.js +0 -2143
- package/resources/fe-dist/assets/SettingsPage-D25_d6j9.js +0 -1144
- package/resources/fe-dist/assets/SettingsPage-D25_d6j9.js.map +0 -1
- package/resources/fe-dist/assets/index-dsVN7rgz.js +0 -200
- package/resources/fe-dist/assets/index-dsVN7rgz.js.map +0 -1
- package/resources/fe-dist/assets/select-BNsiC9zT.js +0 -2805
- package/resources/fe-dist/assets/switch-CIU4AisU.js +0 -234
- package/resources/fe-dist/assets/useValueChanged-V23H0VpC.js +0 -351
package/dist/runtime/server.js
CHANGED
|
@@ -20446,8 +20446,11 @@ var I18N_RESOURCES = {
|
|
|
20446
20446
|
siteUrl: "Site URL",
|
|
20447
20447
|
siteUrlPlaceholder: "http://localhost:3000",
|
|
20448
20448
|
bellThrottle: "Bell Throttle (seconds)",
|
|
20449
|
+
notificationThrottle: "Notification Throttle (seconds)",
|
|
20449
20450
|
enableBrowserBellToast: "Enable Browser Bell Toast",
|
|
20451
|
+
enableBrowserNotificationToast: "Enable Browser Notification Toast",
|
|
20450
20452
|
enableTelegramBellPush: "Enable Telegram Bell Push",
|
|
20453
|
+
enableTelegramNotificationPush: "Enable Telegram Notification Push",
|
|
20451
20454
|
sshReconnectRetries: "SSH Reconnect Retries",
|
|
20452
20455
|
sshReconnectDelay: "SSH Reconnect Delay (seconds)",
|
|
20453
20456
|
language: "Language",
|
|
@@ -20591,6 +20594,7 @@ Time: {{time}}`,
|
|
|
20591
20594
|
clickToJump: "Click to jump to corresponding pane",
|
|
20592
20595
|
eventType: {
|
|
20593
20596
|
terminal_bell: "\uD83D\uDD14 Terminal Bell",
|
|
20597
|
+
terminal_notification: "\uD83D\uDD14 Terminal Notification",
|
|
20594
20598
|
tmux_window_close: "\uD83E\uDE9F Window Closed",
|
|
20595
20599
|
tmux_pane_close: "\uD83D\uDCF1 Pane Closed",
|
|
20596
20600
|
device_tmux_missing: "\u26A0\uFE0F Tmux Missing",
|
|
@@ -20609,7 +20613,8 @@ Time: {{time}}`,
|
|
|
20609
20613
|
title: "\uD83D\uDD14 Bell from {{siteName}}: {{terminalTopbarLabel}}",
|
|
20610
20614
|
viewLink: "Click to view",
|
|
20611
20615
|
terminalTopbarLabel: "Window {{window}} \xB7 Pane {{pane}} @ {{device}}"
|
|
20612
|
-
}
|
|
20616
|
+
},
|
|
20617
|
+
telegramNotification: {}
|
|
20613
20618
|
},
|
|
20614
20619
|
sidebar: {
|
|
20615
20620
|
noWindows: "No windows",
|
|
@@ -20773,8 +20778,11 @@ Time: {{time}}`,
|
|
|
20773
20778
|
siteUrl: "\u7AD9\u70B9\u8BBF\u95EE URL",
|
|
20774
20779
|
siteUrlPlaceholder: "http://localhost:3000",
|
|
20775
20780
|
bellThrottle: "Bell \u9891\u63A7\uFF08\u79D2\uFF09",
|
|
20781
|
+
notificationThrottle: "\u901A\u77E5\u9891\u63A7\uFF08\u79D2\uFF09",
|
|
20776
20782
|
enableBrowserBellToast: "\u5F00\u542F\u6D4F\u89C8\u5668 Bell Toast",
|
|
20783
|
+
enableBrowserNotificationToast: "\u5F00\u542F\u6D4F\u89C8\u5668\u901A\u77E5 Toast",
|
|
20777
20784
|
enableTelegramBellPush: "\u5F00\u542F Telegram Bell \u63A8\u9001",
|
|
20785
|
+
enableTelegramNotificationPush: "\u5F00\u542F Telegram \u901A\u77E5\u63A8\u9001",
|
|
20778
20786
|
sshReconnectRetries: "SSH \u91CD\u8FDE\u6B21\u6570",
|
|
20779
20787
|
sshReconnectDelay: "SSH \u91CD\u8FDE\u7B49\u5F85\uFF08\u79D2\uFF09",
|
|
20780
20788
|
language: "\u8BED\u8A00",
|
|
@@ -20918,6 +20926,7 @@ Bot\uFF1A{{botName}}
|
|
|
20918
20926
|
clickToJump: "\u70B9\u51FB\u8DF3\u8F6C\u5230\u5BF9\u5E94 Pane",
|
|
20919
20927
|
eventType: {
|
|
20920
20928
|
terminal_bell: "\uD83D\uDD14 \u7EC8\u7AEF Bell",
|
|
20929
|
+
terminal_notification: "\uD83D\uDD14 \u7EC8\u7AEF\u901A\u77E5",
|
|
20921
20930
|
tmux_window_close: "\uD83E\uDE9F \u7A97\u53E3\u5173\u95ED",
|
|
20922
20931
|
tmux_pane_close: "\uD83D\uDCF1 Pane \u5173\u95ED",
|
|
20923
20932
|
device_tmux_missing: "\u26A0\uFE0F Tmux \u4E0D\u53EF\u7528",
|
|
@@ -20936,7 +20945,8 @@ Bot\uFF1A{{botName}}
|
|
|
20936
20945
|
title: "\uD83D\uDD14 \u6765\u81EA {{siteName}} \u7684 Bell\uFF1A{{terminalTopbarLabel}}",
|
|
20937
20946
|
viewLink: "\u70B9\u51FB\u67E5\u770B",
|
|
20938
20947
|
terminalTopbarLabel: "\u7A97\u53E3 {{window}} \xB7 Pane {{pane}} @ {{device}}"
|
|
20939
|
-
}
|
|
20948
|
+
},
|
|
20949
|
+
telegramNotification: {}
|
|
20940
20950
|
},
|
|
20941
20951
|
sidebar: {
|
|
20942
20952
|
noWindows: "\u6682\u65E0\u7A97\u53E3",
|
|
@@ -21100,8 +21110,11 @@ Bot\uFF1A{{botName}}
|
|
|
21100
21110
|
siteUrl: "\u30B5\u30A4\u30C8 URL",
|
|
21101
21111
|
siteUrlPlaceholder: "http://localhost:3000",
|
|
21102
21112
|
bellThrottle: "\u30D9\u30EB\u5236\u9650\uFF08\u79D2\uFF09",
|
|
21113
|
+
notificationThrottle: "\u901A\u77E5\u5236\u9650\uFF08\u79D2\uFF09",
|
|
21103
21114
|
enableBrowserBellToast: "\u30D6\u30E9\u30A6\u30B6\u30D9\u30EB Toast \u3092\u6709\u52B9\u306B\u3059\u308B",
|
|
21115
|
+
enableBrowserNotificationToast: "\u30D6\u30E9\u30A6\u30B6\u901A\u77E5 Toast \u3092\u6709\u52B9\u306B\u3059\u308B",
|
|
21104
21116
|
enableTelegramBellPush: "Telegram \u30D9\u30EB\u30D7\u30C3\u30B7\u30E5\u3092\u6709\u52B9\u306B\u3059\u308B",
|
|
21117
|
+
enableTelegramNotificationPush: "Telegram \u901A\u77E5\u30D7\u30C3\u30B7\u30E5\u3092\u6709\u52B9\u306B\u3059\u308B",
|
|
21105
21118
|
sshReconnectRetries: "SSH \u518D\u63A5\u7D9A\u8A66\u884C\u56DE\u6570",
|
|
21106
21119
|
sshReconnectDelay: "SSH \u518D\u63A5\u7D9A\u5F85\u6A5F\uFF08\u79D2\uFF09",
|
|
21107
21120
|
language: "\u8A00\u8A9E",
|
|
@@ -21245,6 +21258,7 @@ Bot\uFF1A{{botName}}
|
|
|
21245
21258
|
clickToJump: "\u5BFE\u5FDC\u3059\u308B\u30DA\u30A4\u30F3\u306B\u30B8\u30E3\u30F3\u30D7",
|
|
21246
21259
|
eventType: {
|
|
21247
21260
|
terminal_bell: "\uD83D\uDD14 \u30BF\u30FC\u30DF\u30CA\u30EB\u30D9\u30EB",
|
|
21261
|
+
terminal_notification: "\uD83D\uDD14 \u30BF\u30FC\u30DF\u30CA\u30EB\u901A\u77E5",
|
|
21248
21262
|
tmux_window_close: "\uD83E\uDE9F \u30A6\u30A3\u30F3\u30C9\u30A6\u9589\u3058\u308B",
|
|
21249
21263
|
tmux_pane_close: "\uD83D\uDCF1 \u30DA\u30A4\u30F3\u9589\u3058\u308B",
|
|
21250
21264
|
device_tmux_missing: "\u26A0\uFE0F Tmux \u304C\u3042\u308A\u307E\u305B\u3093",
|
|
@@ -21263,7 +21277,8 @@ Bot\uFF1A{{botName}}
|
|
|
21263
21277
|
title: "\uD83D\uDD14 {{siteName}} \u304B\u3089\u306E\u30D9\u30EB\uFF1A{{terminalTopbarLabel}}",
|
|
21264
21278
|
viewLink: "\u8868\u793A",
|
|
21265
21279
|
terminalTopbarLabel: "\u30A6\u30A3\u30F3\u30C9\u30A6 {{window}} \xB7 \u30DA\u30A4\u30F3 {{pane}} @ {{device}}"
|
|
21266
|
-
}
|
|
21280
|
+
},
|
|
21281
|
+
telegramNotification: {}
|
|
21267
21282
|
},
|
|
21268
21283
|
sidebar: {
|
|
21269
21284
|
noWindows: "\u30A6\u30A3\u30F3\u30C9\u30A6\u304C\u3042\u308A\u307E\u305B\u3093",
|
|
@@ -21550,6 +21565,7 @@ __export(exports_schema, {
|
|
|
21550
21565
|
OptionU32Schema: () => OptionU32Schema,
|
|
21551
21566
|
OptionU16Schema: () => OptionU16Schema,
|
|
21552
21567
|
OptionStringSchema: () => OptionStringSchema,
|
|
21568
|
+
NotificationEventSchema: () => NotificationEventSchema,
|
|
21553
21569
|
LiveResumeSchema: () => LiveResumeSchema,
|
|
21554
21570
|
LayoutChangeEventSchema: () => LayoutChangeEventSchema,
|
|
21555
21571
|
HelloS2CSchema: () => HelloS2CSchema,
|
|
@@ -21776,6 +21792,16 @@ var BellEventSchema = import_zorsh.b.struct({
|
|
|
21776
21792
|
paneIndex: OptionU16Schema,
|
|
21777
21793
|
paneUrl: OptionStringSchema
|
|
21778
21794
|
});
|
|
21795
|
+
var NotificationEventSchema = import_zorsh.b.struct({
|
|
21796
|
+
source: import_zorsh.b.u8(),
|
|
21797
|
+
title: OptionStringSchema,
|
|
21798
|
+
body: import_zorsh.b.string(),
|
|
21799
|
+
windowId: OptionStringSchema,
|
|
21800
|
+
paneId: OptionStringSchema,
|
|
21801
|
+
windowIndex: OptionU16Schema,
|
|
21802
|
+
paneIndex: OptionU16Schema,
|
|
21803
|
+
paneUrl: OptionStringSchema
|
|
21804
|
+
});
|
|
21779
21805
|
// ../shared/src/ws-borsh/codec.ts
|
|
21780
21806
|
var MAGIC = new Uint8Array([84, 88]);
|
|
21781
21807
|
var CURRENT_VERSION = 1;
|
|
@@ -21988,6 +22014,16 @@ function resetChunkStreamId() {
|
|
|
21988
22014
|
nextChunkStreamId = 1;
|
|
21989
22015
|
}
|
|
21990
22016
|
// ../shared/src/ws-borsh/convert.ts
|
|
22017
|
+
var notificationSourceToU8 = {
|
|
22018
|
+
osc9: 1,
|
|
22019
|
+
osc777: 2,
|
|
22020
|
+
osc1337: 3
|
|
22021
|
+
};
|
|
22022
|
+
var notificationSourceFromU8 = {
|
|
22023
|
+
1: "osc9",
|
|
22024
|
+
2: "osc777",
|
|
22025
|
+
3: "osc1337"
|
|
22026
|
+
};
|
|
21991
22027
|
function encodeDeviceEventPayload(payload) {
|
|
21992
22028
|
const eventTypeMap = {
|
|
21993
22029
|
"tmux-missing": 1,
|
|
@@ -22015,7 +22051,8 @@ function encodeTmuxEventPayload(payload) {
|
|
|
22015
22051
|
"pane-active": 7,
|
|
22016
22052
|
"layout-change": 8,
|
|
22017
22053
|
bell: 9,
|
|
22018
|
-
output: 10
|
|
22054
|
+
output: 10,
|
|
22055
|
+
notification: 11
|
|
22019
22056
|
};
|
|
22020
22057
|
const eventData = encodeEventData(payload.type, payload.data);
|
|
22021
22058
|
const wireData = {
|
|
@@ -22083,6 +22120,19 @@ function encodeEventData(type, data) {
|
|
|
22083
22120
|
}
|
|
22084
22121
|
case "output":
|
|
22085
22122
|
return new Uint8Array;
|
|
22123
|
+
case "notification": {
|
|
22124
|
+
const d = data;
|
|
22125
|
+
return NotificationEventSchema.serialize({
|
|
22126
|
+
source: notificationSourceToU8[d.source],
|
|
22127
|
+
title: d.title ?? null,
|
|
22128
|
+
body: d.body,
|
|
22129
|
+
windowId: d.windowId ?? null,
|
|
22130
|
+
paneId: d.paneId ?? null,
|
|
22131
|
+
windowIndex: d.windowIndex ?? null,
|
|
22132
|
+
paneIndex: d.paneIndex ?? null,
|
|
22133
|
+
paneUrl: d.paneUrl ?? null
|
|
22134
|
+
});
|
|
22135
|
+
}
|
|
22086
22136
|
default:
|
|
22087
22137
|
return new Uint8Array;
|
|
22088
22138
|
}
|
|
@@ -22149,9 +22199,13 @@ function decodeTmuxEventPayload(data) {
|
|
|
22149
22199
|
7: "pane-active",
|
|
22150
22200
|
8: "layout-change",
|
|
22151
22201
|
9: "bell",
|
|
22152
|
-
10: "output"
|
|
22202
|
+
10: "output",
|
|
22203
|
+
11: "notification"
|
|
22153
22204
|
};
|
|
22154
|
-
const type = eventTypeMap[wire.eventType]
|
|
22205
|
+
const type = eventTypeMap[wire.eventType];
|
|
22206
|
+
if (!type) {
|
|
22207
|
+
throw new Error(`Unknown tmux event type: ${wire.eventType}`);
|
|
22208
|
+
}
|
|
22155
22209
|
return {
|
|
22156
22210
|
deviceId: wire.deviceId,
|
|
22157
22211
|
type,
|
|
@@ -22189,6 +22243,19 @@ function decodeEventData(type, data) {
|
|
|
22189
22243
|
paneUrl: bell.paneUrl ?? undefined
|
|
22190
22244
|
};
|
|
22191
22245
|
}
|
|
22246
|
+
case "notification": {
|
|
22247
|
+
const notification = NotificationEventSchema.deserialize(data);
|
|
22248
|
+
return {
|
|
22249
|
+
source: notificationSourceFromU8[notification.source] ?? "osc9",
|
|
22250
|
+
title: notification.title ?? undefined,
|
|
22251
|
+
body: notification.body,
|
|
22252
|
+
windowId: notification.windowId ?? undefined,
|
|
22253
|
+
paneId: notification.paneId ?? undefined,
|
|
22254
|
+
windowIndex: notification.windowIndex ?? undefined,
|
|
22255
|
+
paneIndex: notification.paneIndex ?? undefined,
|
|
22256
|
+
paneUrl: notification.paneUrl ?? undefined
|
|
22257
|
+
};
|
|
22258
|
+
}
|
|
22192
22259
|
default:
|
|
22193
22260
|
return {};
|
|
22194
22261
|
}
|
|
@@ -22308,6 +22375,13 @@ var runtimeController = new RuntimeController;
|
|
|
22308
22375
|
function getEnv(key, defaultValue) {
|
|
22309
22376
|
return process.env[key] ?? defaultValue;
|
|
22310
22377
|
}
|
|
22378
|
+
function getBooleanEnv(key, defaultValue) {
|
|
22379
|
+
const value = process.env[key];
|
|
22380
|
+
if (value === undefined) {
|
|
22381
|
+
return defaultValue;
|
|
22382
|
+
}
|
|
22383
|
+
return value === "1" || value.toLowerCase() === "true" || value.toLowerCase() === "yes";
|
|
22384
|
+
}
|
|
22311
22385
|
var config = {
|
|
22312
22386
|
masterKey: process.env.TMEX_MASTER_KEY,
|
|
22313
22387
|
port: Number.parseInt(getEnv("GATEWAY_PORT", "9663"), 10),
|
|
@@ -22315,6 +22389,8 @@ var config = {
|
|
|
22315
22389
|
siteNameDefault: getEnv("TMEX_SITE_NAME", "tmex"),
|
|
22316
22390
|
databaseUrl: getEnv("DATABASE_URL", "./tmex.db"),
|
|
22317
22391
|
bellThrottleSecondsDefault: Number.parseInt(getEnv("TMEX_BELL_THROTTLE_SECONDS", "6"), 10),
|
|
22392
|
+
notificationThrottleSecondsDefault: Number.parseInt(getEnv("TMEX_NOTIFICATION_THROTTLE_SECONDS", "3"), 10),
|
|
22393
|
+
tmuxAllowPassthrough: getBooleanEnv("TMEX_TMUX_ALLOW_PASSTHROUGH", false),
|
|
22318
22394
|
sshReconnectMaxRetriesDefault: Number.parseInt(getEnv("TMEX_SSH_RECONNECT_MAX_RETRIES", "2"), 10),
|
|
22319
22395
|
sshReconnectDelaySecondsDefault: Number.parseInt(getEnv("TMEX_SSH_RECONNECT_DELAY_SECONDS", "10"), 10),
|
|
22320
22396
|
languageDefault: getEnv("TMEX_DEFAULT_LANGUAGE", "en_US"),
|
|
@@ -28753,8 +28829,13 @@ var siteSettings = sqliteTable("site_settings", {
|
|
|
28753
28829
|
siteName: text("site_name").notNull(),
|
|
28754
28830
|
siteUrl: text("site_url").notNull(),
|
|
28755
28831
|
bellThrottleSeconds: integer("bell_throttle_seconds").notNull(),
|
|
28832
|
+
notificationThrottleSeconds: integer("notification_throttle_seconds").notNull().default(3),
|
|
28756
28833
|
enableBrowserBellToast: integer("enable_browser_bell_toast", { mode: "boolean" }).notNull().default(true),
|
|
28834
|
+
enableBrowserNotificationToast: integer("enable_browser_notification_toast", { mode: "boolean" }).notNull().default(true),
|
|
28757
28835
|
enableTelegramBellPush: integer("enable_telegram_bell_push", { mode: "boolean" }).notNull().default(true),
|
|
28836
|
+
enableTelegramNotificationPush: integer("enable_telegram_notification_push", {
|
|
28837
|
+
mode: "boolean"
|
|
28838
|
+
}).notNull().default(true),
|
|
28758
28839
|
sshReconnectMaxRetries: integer("ssh_reconnect_max_retries").notNull(),
|
|
28759
28840
|
sshReconnectDelaySeconds: integer("ssh_reconnect_delay_seconds").notNull(),
|
|
28760
28841
|
language: text("language").notNull().default("en_US"),
|
|
@@ -28869,8 +28950,11 @@ function toSiteSettings(row) {
|
|
|
28869
28950
|
siteName: row.siteName,
|
|
28870
28951
|
siteUrl: row.siteUrl,
|
|
28871
28952
|
bellThrottleSeconds: row.bellThrottleSeconds,
|
|
28953
|
+
notificationThrottleSeconds: row.notificationThrottleSeconds,
|
|
28872
28954
|
enableBrowserBellToast: row.enableBrowserBellToast,
|
|
28955
|
+
enableBrowserNotificationToast: row.enableBrowserNotificationToast,
|
|
28873
28956
|
enableTelegramBellPush: row.enableTelegramBellPush,
|
|
28957
|
+
enableTelegramNotificationPush: row.enableTelegramNotificationPush,
|
|
28874
28958
|
sshReconnectMaxRetries: row.sshReconnectMaxRetries,
|
|
28875
28959
|
sshReconnectDelaySeconds: row.sshReconnectDelaySeconds,
|
|
28876
28960
|
language: normalizeLocale(row.language),
|
|
@@ -28921,8 +29005,11 @@ function ensureSiteSettingsInitialized() {
|
|
|
28921
29005
|
siteName: config.siteNameDefault,
|
|
28922
29006
|
siteUrl: config.baseUrl,
|
|
28923
29007
|
bellThrottleSeconds: config.bellThrottleSecondsDefault,
|
|
29008
|
+
notificationThrottleSeconds: config.notificationThrottleSecondsDefault,
|
|
28924
29009
|
enableBrowserBellToast: true,
|
|
29010
|
+
enableBrowserNotificationToast: true,
|
|
28925
29011
|
enableTelegramBellPush: true,
|
|
29012
|
+
enableTelegramNotificationPush: true,
|
|
28926
29013
|
sshReconnectMaxRetries: config.sshReconnectMaxRetriesDefault,
|
|
28927
29014
|
sshReconnectDelaySeconds: config.sshReconnectDelaySecondsDefault,
|
|
28928
29015
|
language: normalizeLocale(config.languageDefault),
|
|
@@ -29048,8 +29135,11 @@ function updateSiteSettings(updates) {
|
|
|
29048
29135
|
siteName: updates.siteName ?? current.siteName,
|
|
29049
29136
|
siteUrl: updates.siteUrl ?? current.siteUrl,
|
|
29050
29137
|
bellThrottleSeconds: updates.bellThrottleSeconds ?? current.bellThrottleSeconds,
|
|
29138
|
+
notificationThrottleSeconds: updates.notificationThrottleSeconds ?? current.notificationThrottleSeconds,
|
|
29051
29139
|
enableBrowserBellToast: updates.enableBrowserBellToast ?? current.enableBrowserBellToast,
|
|
29140
|
+
enableBrowserNotificationToast: updates.enableBrowserNotificationToast ?? current.enableBrowserNotificationToast,
|
|
29052
29141
|
enableTelegramBellPush: updates.enableTelegramBellPush ?? current.enableTelegramBellPush,
|
|
29142
|
+
enableTelegramNotificationPush: updates.enableTelegramNotificationPush ?? current.enableTelegramNotificationPush,
|
|
29053
29143
|
sshReconnectMaxRetries: updates.sshReconnectMaxRetries ?? current.sshReconnectMaxRetries,
|
|
29054
29144
|
sshReconnectDelaySeconds: updates.sshReconnectDelaySeconds ?? current.sshReconnectDelaySeconds,
|
|
29055
29145
|
language: updates.language ? normalizeLocale(updates.language) : current.language,
|
|
@@ -29060,8 +29150,11 @@ function updateSiteSettings(updates) {
|
|
|
29060
29150
|
siteName: next.siteName,
|
|
29061
29151
|
siteUrl: next.siteUrl,
|
|
29062
29152
|
bellThrottleSeconds: next.bellThrottleSeconds,
|
|
29153
|
+
notificationThrottleSeconds: next.notificationThrottleSeconds,
|
|
29063
29154
|
enableBrowserBellToast: next.enableBrowserBellToast,
|
|
29155
|
+
enableBrowserNotificationToast: next.enableBrowserNotificationToast,
|
|
29064
29156
|
enableTelegramBellPush: next.enableTelegramBellPush,
|
|
29157
|
+
enableTelegramNotificationPush: next.enableTelegramNotificationPush,
|
|
29065
29158
|
sshReconnectMaxRetries: next.sshReconnectMaxRetries,
|
|
29066
29159
|
sshReconnectDelaySeconds: next.sshReconnectDelaySeconds,
|
|
29067
29160
|
language: next.language,
|
|
@@ -51725,6 +51818,7 @@ class EventNotifier {
|
|
|
51725
51818
|
lastRefresh = 0;
|
|
51726
51819
|
REFRESH_INTERVAL = 60000;
|
|
51727
51820
|
bellThrottleMap = new Map;
|
|
51821
|
+
notificationThrottleMap = new Map;
|
|
51728
51822
|
refreshConfig() {
|
|
51729
51823
|
const now = Date.now();
|
|
51730
51824
|
if (now - this.lastRefresh < this.REFRESH_INTERVAL)
|
|
@@ -51740,8 +51834,14 @@ class EventNotifier {
|
|
|
51740
51834
|
eventType,
|
|
51741
51835
|
timestamp: new Date().toISOString()
|
|
51742
51836
|
};
|
|
51743
|
-
if (eventType === "terminal_bell"
|
|
51744
|
-
|
|
51837
|
+
if (eventType === "terminal_bell") {
|
|
51838
|
+
if (!this.shouldPassBellThrottle(fullEvent)) {
|
|
51839
|
+
return;
|
|
51840
|
+
}
|
|
51841
|
+
} else if (eventType === "terminal_notification") {
|
|
51842
|
+
if (!this.shouldPassNotificationThrottle(fullEvent)) {
|
|
51843
|
+
return;
|
|
51844
|
+
}
|
|
51745
51845
|
}
|
|
51746
51846
|
await Promise.all([
|
|
51747
51847
|
this.sendWebhooks(eventType, fullEvent),
|
|
@@ -51763,6 +51863,22 @@ class EventNotifier {
|
|
|
51763
51863
|
this.bellThrottleMap.set(key, now);
|
|
51764
51864
|
return true;
|
|
51765
51865
|
}
|
|
51866
|
+
shouldPassNotificationThrottle(event) {
|
|
51867
|
+
const settings = getSiteSettings();
|
|
51868
|
+
const throttleMs = Math.max(0, settings.notificationThrottleSeconds) * 1000;
|
|
51869
|
+
if (throttleMs === 0) {
|
|
51870
|
+
return true;
|
|
51871
|
+
}
|
|
51872
|
+
const source = typeof event.payload?.source === "string" ? event.payload.source : "unknown";
|
|
51873
|
+
const key = `${event.device.id}:${event.tmux?.paneId ?? "-"}:notification:${source}`;
|
|
51874
|
+
const now = Date.now();
|
|
51875
|
+
const previous = this.notificationThrottleMap.get(key) ?? 0;
|
|
51876
|
+
if (now - previous < throttleMs) {
|
|
51877
|
+
return false;
|
|
51878
|
+
}
|
|
51879
|
+
this.notificationThrottleMap.set(key, now);
|
|
51880
|
+
return true;
|
|
51881
|
+
}
|
|
51766
51882
|
async sendWebhooks(eventType, event) {
|
|
51767
51883
|
const targets = this.webhooks.filter((w) => w.eventMask.includes(eventType));
|
|
51768
51884
|
await Promise.all(targets.map(async (webhook) => {
|
|
@@ -51800,6 +51916,14 @@ class EventNotifier {
|
|
|
51800
51916
|
await telegramService.sendToAuthorizedChats({ text: bellMessage, parseMode: "HTML" });
|
|
51801
51917
|
return;
|
|
51802
51918
|
}
|
|
51919
|
+
if (eventType === "terminal_notification") {
|
|
51920
|
+
if (!settings.enableTelegramNotificationPush) {
|
|
51921
|
+
return;
|
|
51922
|
+
}
|
|
51923
|
+
const notificationMessage = this.formatTelegramNotificationMessage(event);
|
|
51924
|
+
await telegramService.sendToAuthorizedChats({ text: notificationMessage, parseMode: "HTML" });
|
|
51925
|
+
return;
|
|
51926
|
+
}
|
|
51803
51927
|
const message = this.formatTelegramMessage(event, settings);
|
|
51804
51928
|
await telegramService.sendToAuthorizedChats({ text: message });
|
|
51805
51929
|
}
|
|
@@ -51824,11 +51948,34 @@ class EventNotifier {
|
|
|
51824
51948
|
lines.push("", `<a href="${escapeTelegramHtmlAttribute(tgSafePaneUrl)}">${escapeTelegramHtmlText(t2("notification.telegramBell.viewLink"))}</a>`);
|
|
51825
51949
|
}
|
|
51826
51950
|
return lines.join(`
|
|
51951
|
+
`);
|
|
51952
|
+
}
|
|
51953
|
+
formatTelegramNotificationMessage(event) {
|
|
51954
|
+
const title = typeof event.payload?.title === "string" ? event.payload.title : "";
|
|
51955
|
+
const body = typeof event.payload?.message === "string" ? event.payload.message : "";
|
|
51956
|
+
const lines = [];
|
|
51957
|
+
if (title) {
|
|
51958
|
+
lines.push(escapeTelegramHtmlText(title));
|
|
51959
|
+
}
|
|
51960
|
+
if (body) {
|
|
51961
|
+
lines.push(escapeTelegramHtmlText(body));
|
|
51962
|
+
}
|
|
51963
|
+
const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
|
|
51964
|
+
const topbarLabel = this.buildTerminalTopbarLabel(event);
|
|
51965
|
+
const footer = `from ${event.site.name}: ${topbarLabel}`;
|
|
51966
|
+
if (paneUrl) {
|
|
51967
|
+
const tgSafePaneUrl = encodePercentForTelegramUrl(paneUrl);
|
|
51968
|
+
lines.push("", `<a href="${escapeTelegramHtmlAttribute(tgSafePaneUrl)}">${escapeTelegramHtmlText(footer)}</a>`);
|
|
51969
|
+
} else {
|
|
51970
|
+
lines.push("", escapeTelegramHtmlText(footer));
|
|
51971
|
+
}
|
|
51972
|
+
return lines.join(`
|
|
51827
51973
|
`);
|
|
51828
51974
|
}
|
|
51829
51975
|
formatTelegramMessage(event, settings) {
|
|
51830
51976
|
const emojiMap = {
|
|
51831
51977
|
terminal_bell: "\uD83D\uDD14",
|
|
51978
|
+
terminal_notification: "\uD83D\uDD14",
|
|
51832
51979
|
tmux_window_close: "\uD83E\uDE9F",
|
|
51833
51980
|
tmux_pane_close: "\uD83D\uDCF1",
|
|
51834
51981
|
device_tmux_missing: "\u26A0\uFE0F",
|
|
@@ -52075,12 +52222,80 @@ function encodeInputToHexChunks(input, chunkBytes = SEND_KEYS_HEX_CHUNK_BYTES) {
|
|
|
52075
52222
|
return chunks;
|
|
52076
52223
|
}
|
|
52077
52224
|
|
|
52078
|
-
// ../../apps/gateway/src/tmux-client/pane-
|
|
52225
|
+
// ../../apps/gateway/src/tmux-client/pane-stream-parser.ts
|
|
52079
52226
|
var decoder = new TextDecoder;
|
|
52080
|
-
|
|
52227
|
+
var MAX_OSC_KIND_BYTES = 16;
|
|
52228
|
+
var MAX_OSC_PAYLOAD_BYTES = 8 * 1024;
|
|
52229
|
+
function createPaneStreamParser(options) {
|
|
52081
52230
|
let phase = "normal";
|
|
52082
52231
|
let oscKind = "";
|
|
52232
|
+
let oscPayloadBytes = [];
|
|
52083
52233
|
let titleBytes = [];
|
|
52234
|
+
let warnedOscPayloadOverflow = false;
|
|
52235
|
+
function resetOscState() {
|
|
52236
|
+
oscKind = "";
|
|
52237
|
+
oscPayloadBytes = [];
|
|
52238
|
+
}
|
|
52239
|
+
function appendOscPayloadByte(byte) {
|
|
52240
|
+
if (oscPayloadBytes.length >= MAX_OSC_PAYLOAD_BYTES) {
|
|
52241
|
+
if (!warnedOscPayloadOverflow) {
|
|
52242
|
+
warnedOscPayloadOverflow = true;
|
|
52243
|
+
console.warn("[tmex] pane stream parser dropped oversized OSC payload");
|
|
52244
|
+
}
|
|
52245
|
+
oscPayloadBytes = [];
|
|
52246
|
+
phase = "osc-body-ignore";
|
|
52247
|
+
return false;
|
|
52248
|
+
}
|
|
52249
|
+
oscPayloadBytes.push(byte);
|
|
52250
|
+
return true;
|
|
52251
|
+
}
|
|
52252
|
+
function emitTitle(bytes) {
|
|
52253
|
+
const title = decoder.decode(new Uint8Array(bytes)).trim();
|
|
52254
|
+
if (!title) {
|
|
52255
|
+
return;
|
|
52256
|
+
}
|
|
52257
|
+
options.onTitle(title);
|
|
52258
|
+
}
|
|
52259
|
+
function emitOsc() {
|
|
52260
|
+
const payload = decoder.decode(new Uint8Array(oscPayloadBytes));
|
|
52261
|
+
switch (oscKind) {
|
|
52262
|
+
case "0":
|
|
52263
|
+
case "1":
|
|
52264
|
+
case "2":
|
|
52265
|
+
emitTitle(oscPayloadBytes);
|
|
52266
|
+
return;
|
|
52267
|
+
case "9":
|
|
52268
|
+
if (/^4(;|$)/.test(payload)) {
|
|
52269
|
+
return;
|
|
52270
|
+
}
|
|
52271
|
+
options.onNotification({ source: "osc9", body: payload });
|
|
52272
|
+
return;
|
|
52273
|
+
case "777": {
|
|
52274
|
+
const verbSeparatorIndex = payload.indexOf(";");
|
|
52275
|
+
const verb = verbSeparatorIndex >= 0 ? payload.slice(0, verbSeparatorIndex) : payload;
|
|
52276
|
+
if (verb !== "notify") {
|
|
52277
|
+
return;
|
|
52278
|
+
}
|
|
52279
|
+
const rest = verbSeparatorIndex >= 0 ? payload.slice(verbSeparatorIndex + 1) : "";
|
|
52280
|
+
const titleSeparatorIndex = rest.indexOf(";");
|
|
52281
|
+
const title = titleSeparatorIndex >= 0 ? rest.slice(0, titleSeparatorIndex) : rest;
|
|
52282
|
+
const body = titleSeparatorIndex >= 0 ? rest.slice(titleSeparatorIndex + 1) : "";
|
|
52283
|
+
options.onNotification({
|
|
52284
|
+
source: "osc777",
|
|
52285
|
+
title: title || undefined,
|
|
52286
|
+
body
|
|
52287
|
+
});
|
|
52288
|
+
return;
|
|
52289
|
+
}
|
|
52290
|
+
case "1337":
|
|
52291
|
+
if (/^RequestAttention=(yes|once|fireworks|true)$/i.test(payload)) {
|
|
52292
|
+
options.onNotification({ source: "osc1337", body: "RequestAttention" });
|
|
52293
|
+
}
|
|
52294
|
+
return;
|
|
52295
|
+
default:
|
|
52296
|
+
return;
|
|
52297
|
+
}
|
|
52298
|
+
}
|
|
52084
52299
|
return {
|
|
52085
52300
|
push(data) {
|
|
52086
52301
|
const output = [];
|
|
@@ -52088,36 +52303,57 @@ function createPaneTitleParser(options) {
|
|
|
52088
52303
|
if (phase === "normal") {
|
|
52089
52304
|
if (byte === 27) {
|
|
52090
52305
|
phase = "esc";
|
|
52091
|
-
|
|
52092
|
-
output.push(byte);
|
|
52306
|
+
continue;
|
|
52093
52307
|
}
|
|
52308
|
+
if (byte === 7) {
|
|
52309
|
+
options.onBell();
|
|
52310
|
+
continue;
|
|
52311
|
+
}
|
|
52312
|
+
output.push(byte);
|
|
52094
52313
|
continue;
|
|
52095
52314
|
}
|
|
52096
52315
|
if (phase === "esc") {
|
|
52097
52316
|
if (byte === 93) {
|
|
52098
|
-
|
|
52099
|
-
|
|
52317
|
+
resetOscState();
|
|
52318
|
+
phase = "osc-params";
|
|
52319
|
+
continue;
|
|
52320
|
+
}
|
|
52321
|
+
if (byte === 107) {
|
|
52100
52322
|
titleBytes = [];
|
|
52323
|
+
phase = "screen-title";
|
|
52101
52324
|
continue;
|
|
52102
52325
|
}
|
|
52103
52326
|
output.push(27, byte);
|
|
52104
52327
|
phase = "normal";
|
|
52105
52328
|
continue;
|
|
52106
52329
|
}
|
|
52107
|
-
if (phase === "osc") {
|
|
52330
|
+
if (phase === "osc-params") {
|
|
52108
52331
|
if (byte === 59) {
|
|
52109
|
-
phase = oscKind === "0" || oscKind === "2" ? "osc-
|
|
52110
|
-
|
|
52111
|
-
|
|
52112
|
-
|
|
52332
|
+
phase = oscKind === "0" || oscKind === "1" || oscKind === "2" || oscKind === "9" || oscKind === "777" || oscKind === "1337" ? "osc-body" : "osc-body-ignore";
|
|
52333
|
+
continue;
|
|
52334
|
+
}
|
|
52335
|
+
if (byte === 7) {
|
|
52336
|
+
emitOsc();
|
|
52337
|
+
resetOscState();
|
|
52338
|
+
phase = "normal";
|
|
52339
|
+
continue;
|
|
52340
|
+
}
|
|
52341
|
+
if (byte === 27) {
|
|
52342
|
+
phase = "osc-st";
|
|
52343
|
+
continue;
|
|
52344
|
+
}
|
|
52345
|
+
if (oscKind.length >= MAX_OSC_KIND_BYTES) {
|
|
52346
|
+
resetOscState();
|
|
52347
|
+
phase = "osc-body-ignore";
|
|
52113
52348
|
continue;
|
|
52114
52349
|
}
|
|
52115
52350
|
oscKind += String.fromCharCode(byte);
|
|
52116
52351
|
continue;
|
|
52117
52352
|
}
|
|
52118
|
-
if (phase === "osc-
|
|
52353
|
+
if (phase === "osc-body") {
|
|
52119
52354
|
if (byte === 7) {
|
|
52120
|
-
|
|
52355
|
+
emitOsc();
|
|
52356
|
+
resetOscState();
|
|
52121
52357
|
phase = "normal";
|
|
52122
52358
|
continue;
|
|
52123
52359
|
}
|
|
@@ -52125,31 +52361,69 @@ function createPaneTitleParser(options) {
|
|
|
52125
52361
|
phase = "osc-st";
|
|
52126
52362
|
continue;
|
|
52127
52363
|
}
|
|
52364
|
+
appendOscPayloadByte(byte);
|
|
52365
|
+
continue;
|
|
52366
|
+
}
|
|
52367
|
+
if (phase === "osc-body-ignore") {
|
|
52368
|
+
if (byte === 7) {
|
|
52369
|
+
resetOscState();
|
|
52370
|
+
phase = "normal";
|
|
52371
|
+
continue;
|
|
52372
|
+
}
|
|
52373
|
+
if (byte === 27) {
|
|
52374
|
+
phase = "osc-st-ignore";
|
|
52375
|
+
}
|
|
52376
|
+
continue;
|
|
52377
|
+
}
|
|
52378
|
+
if (phase === "osc-st") {
|
|
52379
|
+
if (byte === 92) {
|
|
52380
|
+
emitOsc();
|
|
52381
|
+
resetOscState();
|
|
52382
|
+
phase = "normal";
|
|
52383
|
+
continue;
|
|
52384
|
+
}
|
|
52385
|
+
if (!appendOscPayloadByte(27)) {
|
|
52386
|
+
continue;
|
|
52387
|
+
}
|
|
52388
|
+
appendOscPayloadByte(byte);
|
|
52389
|
+
continue;
|
|
52390
|
+
}
|
|
52391
|
+
if (phase === "osc-st-ignore") {
|
|
52392
|
+
if (byte === 92) {
|
|
52393
|
+
resetOscState();
|
|
52394
|
+
phase = "normal";
|
|
52395
|
+
continue;
|
|
52396
|
+
}
|
|
52397
|
+
phase = "osc-body-ignore";
|
|
52398
|
+
continue;
|
|
52399
|
+
}
|
|
52400
|
+
if (phase === "screen-title") {
|
|
52401
|
+
if (byte === 7) {
|
|
52402
|
+
emitTitle(titleBytes);
|
|
52403
|
+
titleBytes = [];
|
|
52404
|
+
phase = "normal";
|
|
52405
|
+
continue;
|
|
52406
|
+
}
|
|
52407
|
+
if (byte === 27) {
|
|
52408
|
+
phase = "screen-title-st";
|
|
52409
|
+
continue;
|
|
52410
|
+
}
|
|
52128
52411
|
titleBytes.push(byte);
|
|
52129
52412
|
continue;
|
|
52130
52413
|
}
|
|
52131
52414
|
if (byte === 92) {
|
|
52132
|
-
emitTitle(
|
|
52415
|
+
emitTitle(titleBytes);
|
|
52416
|
+
titleBytes = [];
|
|
52133
52417
|
phase = "normal";
|
|
52134
52418
|
continue;
|
|
52135
52419
|
}
|
|
52136
52420
|
titleBytes.push(27, byte);
|
|
52137
|
-
phase = "
|
|
52421
|
+
phase = "screen-title";
|
|
52138
52422
|
}
|
|
52139
52423
|
return new Uint8Array(output);
|
|
52140
52424
|
}
|
|
52141
52425
|
};
|
|
52142
52426
|
}
|
|
52143
|
-
function emitTitle(onTitle, titleBytes) {
|
|
52144
|
-
const title = decoder.decode(new Uint8Array(titleBytes)).trim();
|
|
52145
|
-
if (!title) {
|
|
52146
|
-
return;
|
|
52147
|
-
}
|
|
52148
|
-
onTitle(title);
|
|
52149
|
-
}
|
|
52150
|
-
function encoderFromString(value) {
|
|
52151
|
-
return new TextEncoder().encode(value);
|
|
52152
|
-
}
|
|
52153
52427
|
|
|
52154
52428
|
// ../../apps/gateway/src/tmux-client/local-external-connection.ts
|
|
52155
52429
|
function hasRenderableTerminalContent(value) {
|
|
@@ -52193,8 +52467,7 @@ class LocalExternalTmuxConnection {
|
|
|
52193
52467
|
pendingPaneTitles = new Map;
|
|
52194
52468
|
snapshotSession = null;
|
|
52195
52469
|
snapshotWindows = new Map;
|
|
52196
|
-
|
|
52197
|
-
pipeReadAbort = null;
|
|
52470
|
+
paneReaders = new Map;
|
|
52198
52471
|
pipeTransition = Promise.resolve();
|
|
52199
52472
|
inputTransition = Promise.resolve();
|
|
52200
52473
|
hookReadAbort = null;
|
|
@@ -52231,6 +52504,7 @@ class LocalExternalTmuxConnection {
|
|
|
52231
52504
|
});
|
|
52232
52505
|
this.ensureRuntimeDirs();
|
|
52233
52506
|
await this.ensureSession();
|
|
52507
|
+
await this.configureSessionOptions();
|
|
52234
52508
|
if (this.deps.enableHooks) {
|
|
52235
52509
|
await this.startHooks();
|
|
52236
52510
|
}
|
|
@@ -52248,7 +52522,7 @@ class LocalExternalTmuxConnection {
|
|
|
52248
52522
|
}
|
|
52249
52523
|
this.manualDisconnect = true;
|
|
52250
52524
|
this.connected = false;
|
|
52251
|
-
this.
|
|
52525
|
+
this.stopAllPipeReaders();
|
|
52252
52526
|
if (this.deps.enableHooks) {
|
|
52253
52527
|
this.stopHooks();
|
|
52254
52528
|
}
|
|
@@ -52346,6 +52620,26 @@ class LocalExternalTmuxConnection {
|
|
|
52346
52620
|
}
|
|
52347
52621
|
await this.runTmux(["new-session", "-d", "-c", homedir(), "-s", this.sessionName]);
|
|
52348
52622
|
}
|
|
52623
|
+
async configureSessionOptions() {
|
|
52624
|
+
await this.runTmuxAllowFailure([
|
|
52625
|
+
"set-option",
|
|
52626
|
+
"-t",
|
|
52627
|
+
this.sessionName,
|
|
52628
|
+
"-s",
|
|
52629
|
+
"allow-passthrough",
|
|
52630
|
+
config.tmuxAllowPassthrough ? "on" : "off"
|
|
52631
|
+
]);
|
|
52632
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "extended-keys", "on"]);
|
|
52633
|
+
await this.runTmuxAllowFailure([
|
|
52634
|
+
"set-option",
|
|
52635
|
+
"-t",
|
|
52636
|
+
this.sessionName,
|
|
52637
|
+
"-s",
|
|
52638
|
+
"extended-keys-format",
|
|
52639
|
+
"csi-u"
|
|
52640
|
+
]);
|
|
52641
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "on"]);
|
|
52642
|
+
}
|
|
52349
52643
|
ensureRuntimeDirs() {
|
|
52350
52644
|
mkdirSync(this.fsPaths.rootDir, { recursive: true, mode: 448 });
|
|
52351
52645
|
mkdirSync(this.fsPaths.panesDir, { recursive: true, mode: 448 });
|
|
@@ -52381,14 +52675,16 @@ class LocalExternalTmuxConnection {
|
|
|
52381
52675
|
readerProcess.kill();
|
|
52382
52676
|
rmSync(fifoPath, { force: true });
|
|
52383
52677
|
};
|
|
52384
|
-
await this.installHook("alert-bell", ["bell", "#{window_id}", "#{pane_id}"]);
|
|
52385
52678
|
await this.installHook("pane-exited", ["pane-exited", "#{window_id}", "#{pane_id}"]);
|
|
52386
52679
|
await this.installHook("pane-died", ["pane-died", "#{window_id}", "#{pane_id}"]);
|
|
52680
|
+
await this.installHook("after-new-window", ["refresh"]);
|
|
52681
|
+
await this.installHook("after-split-window", ["refresh"]);
|
|
52387
52682
|
}
|
|
52388
52683
|
async stopHooks() {
|
|
52389
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "alert-bell"]);
|
|
52390
52684
|
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-exited"]);
|
|
52391
52685
|
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-died"]);
|
|
52686
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-new-window"]);
|
|
52687
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-split-window"]);
|
|
52392
52688
|
this.hookReadAbort?.();
|
|
52393
52689
|
this.hookReadAbort = null;
|
|
52394
52690
|
this.hookBuffer = "";
|
|
@@ -52419,22 +52715,9 @@ class LocalExternalTmuxConnection {
|
|
|
52419
52715
|
}
|
|
52420
52716
|
const [type, windowId, paneId] = line.split("\t");
|
|
52421
52717
|
if (type === "bell") {
|
|
52422
|
-
const key = paneId || windowId || "-";
|
|
52423
|
-
const previous = this.bellDedup.get(key) ?? 0;
|
|
52424
|
-
const now = Date.now();
|
|
52425
|
-
if (now - previous >= BELL_DEDUP_WINDOW_MS) {
|
|
52426
|
-
this.bellDedup.set(key, now);
|
|
52427
|
-
this.callbacks.onEvent({
|
|
52428
|
-
type: "bell",
|
|
52429
|
-
data: {
|
|
52430
|
-
windowId: windowId || undefined,
|
|
52431
|
-
paneId: paneId || this.activePaneId || undefined
|
|
52432
|
-
}
|
|
52433
|
-
});
|
|
52434
|
-
}
|
|
52435
52718
|
continue;
|
|
52436
52719
|
}
|
|
52437
|
-
if (type === "pane-exited" || type === "pane-died") {
|
|
52720
|
+
if (type === "pane-exited" || type === "pane-died" || type === "refresh") {
|
|
52438
52721
|
this.requestSnapshot();
|
|
52439
52722
|
}
|
|
52440
52723
|
}
|
|
@@ -52465,7 +52748,6 @@ class LocalExternalTmuxConnection {
|
|
|
52465
52748
|
this.activePaneId = paneId;
|
|
52466
52749
|
await this.runTmux(["select-window", "-t", windowId], true);
|
|
52467
52750
|
await this.runTmux(["select-pane", "-t", paneId], true);
|
|
52468
|
-
await this.startPipeForPane(paneId);
|
|
52469
52751
|
if (size) {
|
|
52470
52752
|
await this.resizePaneInternal(paneId, size.cols, size.rows);
|
|
52471
52753
|
}
|
|
@@ -52520,6 +52802,7 @@ class LocalExternalTmuxConnection {
|
|
|
52520
52802
|
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
52521
52803
|
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
52522
52804
|
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
52805
|
+
await this.syncPipeReaders();
|
|
52523
52806
|
this.emitSnapshot();
|
|
52524
52807
|
}
|
|
52525
52808
|
parseSnapshotSession(lines) {
|
|
@@ -52617,69 +52900,122 @@ class LocalExternalTmuxConnection {
|
|
|
52617
52900
|
}
|
|
52618
52901
|
return null;
|
|
52619
52902
|
}
|
|
52620
|
-
|
|
52621
|
-
|
|
52622
|
-
|
|
52623
|
-
|
|
52903
|
+
recordBell(paneId, windowId) {
|
|
52904
|
+
const key = paneId || windowId || "-";
|
|
52905
|
+
const previous = this.bellDedup.get(key) ?? 0;
|
|
52906
|
+
const now = Date.now();
|
|
52907
|
+
if (now - previous < BELL_DEDUP_WINDOW_MS) {
|
|
52908
|
+
return;
|
|
52909
|
+
}
|
|
52910
|
+
this.bellDedup.set(key, now);
|
|
52911
|
+
this.callbacks.onEvent({
|
|
52912
|
+
type: "bell",
|
|
52913
|
+
data: {
|
|
52914
|
+
windowId,
|
|
52915
|
+
paneId: paneId || this.activePaneId || undefined
|
|
52916
|
+
}
|
|
52917
|
+
});
|
|
52918
|
+
}
|
|
52919
|
+
emitNotification(paneId, notification) {
|
|
52920
|
+
this.callbacks.onEvent({
|
|
52921
|
+
type: "notification",
|
|
52922
|
+
data: {
|
|
52923
|
+
paneId,
|
|
52924
|
+
...notification
|
|
52925
|
+
}
|
|
52926
|
+
});
|
|
52927
|
+
}
|
|
52928
|
+
getExpectedPaneIds() {
|
|
52929
|
+
return Array.from(this.snapshotWindows.values()).sort((left, right) => left.index - right.index).flatMap((window2) => window2.panes.map((pane) => pane.id));
|
|
52930
|
+
}
|
|
52931
|
+
async startPipeForPaneNow(paneId) {
|
|
52932
|
+
if (this.paneReaders.has(paneId)) {
|
|
52933
|
+
return;
|
|
52934
|
+
}
|
|
52935
|
+
const fifoPath = this.fsPaths.paneFifoPath(paneId);
|
|
52936
|
+
this.ensureRuntimeDirs();
|
|
52937
|
+
rmSync(fifoPath, { force: true });
|
|
52938
|
+
await this.runShell(`mkfifo ${quoteShellArg(fifoPath)}`);
|
|
52939
|
+
const parser = createPaneStreamParser({
|
|
52940
|
+
onTitle: (title) => {
|
|
52941
|
+
this.pendingPaneTitles.set(paneId, title);
|
|
52942
|
+
this.requestSnapshot();
|
|
52943
|
+
},
|
|
52944
|
+
onBell: () => {
|
|
52945
|
+
this.recordBell(paneId);
|
|
52946
|
+
},
|
|
52947
|
+
onNotification: (notification) => {
|
|
52948
|
+
this.emitNotification(paneId, notification);
|
|
52624
52949
|
}
|
|
52625
|
-
|
|
52626
|
-
|
|
52627
|
-
|
|
52950
|
+
});
|
|
52951
|
+
const readerProcess = Bun.spawn(["/bin/sh", "-lc", `cat ${quoteShellArg(fifoPath)}`], {
|
|
52952
|
+
stdout: "pipe",
|
|
52953
|
+
stderr: "pipe"
|
|
52954
|
+
});
|
|
52955
|
+
const reader = readerProcess.stdout.getReader();
|
|
52956
|
+
const stopReader = () => {
|
|
52957
|
+
reader.releaseLock();
|
|
52958
|
+
readerProcess.kill();
|
|
52628
52959
|
rmSync(fifoPath, { force: true });
|
|
52629
|
-
|
|
52630
|
-
|
|
52631
|
-
|
|
52632
|
-
|
|
52633
|
-
|
|
52634
|
-
|
|
52635
|
-
|
|
52636
|
-
|
|
52637
|
-
stdout: "pipe",
|
|
52638
|
-
stderr: "pipe"
|
|
52639
|
-
});
|
|
52640
|
-
const reader = readerProcess.stdout.getReader();
|
|
52641
|
-
(async () => {
|
|
52642
|
-
try {
|
|
52643
|
-
while (true) {
|
|
52644
|
-
const chunk2 = await reader.read();
|
|
52645
|
-
if (chunk2.done) {
|
|
52646
|
-
break;
|
|
52647
|
-
}
|
|
52648
|
-
const raw = chunk2.value;
|
|
52649
|
-
const output = parser.push(raw);
|
|
52650
|
-
if (Array.from(raw).includes(7)) {
|
|
52651
|
-
this.callbacks.onEvent({ type: "bell", data: { paneId } });
|
|
52652
|
-
}
|
|
52653
|
-
if (output.length > 0) {
|
|
52654
|
-
this.callbacks.onTerminalOutput(paneId, output);
|
|
52655
|
-
}
|
|
52960
|
+
};
|
|
52961
|
+
this.paneReaders.set(paneId, { paneId, fifoPath, stopReader });
|
|
52962
|
+
(async () => {
|
|
52963
|
+
try {
|
|
52964
|
+
while (true) {
|
|
52965
|
+
const chunk2 = await reader.read();
|
|
52966
|
+
if (chunk2.done) {
|
|
52967
|
+
break;
|
|
52656
52968
|
}
|
|
52657
|
-
|
|
52658
|
-
if (
|
|
52659
|
-
this.callbacks.
|
|
52969
|
+
const output = parser.push(chunk2.value);
|
|
52970
|
+
if (output.length > 0) {
|
|
52971
|
+
this.callbacks.onTerminalOutput(paneId, output);
|
|
52660
52972
|
}
|
|
52661
52973
|
}
|
|
52662
|
-
}
|
|
52663
|
-
|
|
52664
|
-
|
|
52665
|
-
|
|
52666
|
-
|
|
52667
|
-
|
|
52974
|
+
} catch (error) {
|
|
52975
|
+
if (!this.manualDisconnect && !shouldIgnoreReaderAbortError(error)) {
|
|
52976
|
+
this.callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
52977
|
+
}
|
|
52978
|
+
}
|
|
52979
|
+
})();
|
|
52980
|
+
try {
|
|
52668
52981
|
await this.runTmux(["pipe-pane", "-O", "-t", paneId, `cat >${fifoPath}`]);
|
|
52669
|
-
|
|
52670
|
-
|
|
52671
|
-
|
|
52672
|
-
|
|
52673
|
-
|
|
52982
|
+
} catch (error) {
|
|
52983
|
+
this.paneReaders.delete(paneId);
|
|
52984
|
+
stopReader();
|
|
52985
|
+
throw error;
|
|
52986
|
+
}
|
|
52674
52987
|
}
|
|
52675
|
-
async
|
|
52676
|
-
const
|
|
52677
|
-
|
|
52678
|
-
|
|
52679
|
-
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
52988
|
+
async stopPipeForPaneNow(paneId) {
|
|
52989
|
+
const handle = this.paneReaders.get(paneId);
|
|
52990
|
+
if (!handle) {
|
|
52991
|
+
return;
|
|
52680
52992
|
}
|
|
52681
|
-
this.
|
|
52682
|
-
this.
|
|
52993
|
+
this.paneReaders.delete(paneId);
|
|
52994
|
+
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
52995
|
+
handle.stopReader();
|
|
52996
|
+
}
|
|
52997
|
+
async syncPipeReaders() {
|
|
52998
|
+
const expectedPaneIds = this.getExpectedPaneIds();
|
|
52999
|
+
const expectedSet = new Set(expectedPaneIds);
|
|
53000
|
+
await this.queuePipeTransition(async () => {
|
|
53001
|
+
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
53002
|
+
if (!expectedSet.has(paneId)) {
|
|
53003
|
+
await this.stopPipeForPaneNow(paneId);
|
|
53004
|
+
}
|
|
53005
|
+
}
|
|
53006
|
+
for (const paneId of expectedPaneIds) {
|
|
53007
|
+
if (!this.paneReaders.has(paneId)) {
|
|
53008
|
+
await this.startPipeForPaneNow(paneId);
|
|
53009
|
+
}
|
|
53010
|
+
}
|
|
53011
|
+
});
|
|
53012
|
+
}
|
|
53013
|
+
async stopAllPipeReaders() {
|
|
53014
|
+
await this.queuePipeTransition(async () => {
|
|
53015
|
+
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
53016
|
+
await this.stopPipeForPaneNow(paneId);
|
|
53017
|
+
}
|
|
53018
|
+
});
|
|
52683
53019
|
}
|
|
52684
53020
|
queuePipeTransition(task) {
|
|
52685
53021
|
const next = this.pipeTransition.catch(() => {
|
|
@@ -53068,8 +53404,7 @@ class SshExternalTmuxConnection {
|
|
|
53068
53404
|
pendingPaneTitles = new Map;
|
|
53069
53405
|
snapshotSession = null;
|
|
53070
53406
|
snapshotWindows = new Map;
|
|
53071
|
-
|
|
53072
|
-
pipeReadAbort = null;
|
|
53407
|
+
paneReaders = new Map;
|
|
53073
53408
|
pipeTransition = Promise.resolve();
|
|
53074
53409
|
hookReadAbort = null;
|
|
53075
53410
|
hookBuffer = "";
|
|
@@ -53115,6 +53450,7 @@ class SshExternalTmuxConnection {
|
|
|
53115
53450
|
await this.openCommandChannel();
|
|
53116
53451
|
await this.ensureRemoteRuntimeDirs();
|
|
53117
53452
|
await this.ensureSession();
|
|
53453
|
+
await this.configureSessionOptions();
|
|
53118
53454
|
await this.startHooks();
|
|
53119
53455
|
this.connected = true;
|
|
53120
53456
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
@@ -53323,6 +53659,26 @@ class SshExternalTmuxConnection {
|
|
|
53323
53659
|
}
|
|
53324
53660
|
await this.runTmux(["new-session", "-d", "-c", this.remoteHomeDir, "-s", this.sessionName]);
|
|
53325
53661
|
}
|
|
53662
|
+
async configureSessionOptions() {
|
|
53663
|
+
await this.runTmuxAllowFailure([
|
|
53664
|
+
"set-option",
|
|
53665
|
+
"-t",
|
|
53666
|
+
this.sessionName,
|
|
53667
|
+
"-s",
|
|
53668
|
+
"allow-passthrough",
|
|
53669
|
+
config.tmuxAllowPassthrough ? "on" : "off"
|
|
53670
|
+
]);
|
|
53671
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "extended-keys", "on"]);
|
|
53672
|
+
await this.runTmuxAllowFailure([
|
|
53673
|
+
"set-option",
|
|
53674
|
+
"-t",
|
|
53675
|
+
this.sessionName,
|
|
53676
|
+
"-s",
|
|
53677
|
+
"extended-keys-format",
|
|
53678
|
+
"csi-u"
|
|
53679
|
+
]);
|
|
53680
|
+
await this.runTmuxAllowFailure(["set-option", "-t", this.sessionName, "-g", "focus-events", "on"]);
|
|
53681
|
+
}
|
|
53326
53682
|
async startHooks() {
|
|
53327
53683
|
await this.ensureRemoteRuntimeDirs();
|
|
53328
53684
|
const fifoPath = this.fsPaths.hookFifoPath;
|
|
@@ -53341,14 +53697,16 @@ class SshExternalTmuxConnection {
|
|
|
53341
53697
|
stopReader();
|
|
53342
53698
|
this.runShellAllowFailure(`rm -f ${quoteShellArg(fifoPath)}`);
|
|
53343
53699
|
};
|
|
53344
|
-
await this.installHook("alert-bell", ["bell", "#{window_id}", "#{pane_id}"]);
|
|
53345
53700
|
await this.installHook("pane-exited", ["pane-exited", "#{window_id}", "#{pane_id}"]);
|
|
53346
53701
|
await this.installHook("pane-died", ["pane-died", "#{window_id}", "#{pane_id}"]);
|
|
53702
|
+
await this.installHook("after-new-window", ["refresh"]);
|
|
53703
|
+
await this.installHook("after-split-window", ["refresh"]);
|
|
53347
53704
|
}
|
|
53348
53705
|
async stopHooks() {
|
|
53349
|
-
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "alert-bell"]);
|
|
53350
53706
|
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-exited"]);
|
|
53351
53707
|
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-died"]);
|
|
53708
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-new-window"]);
|
|
53709
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "after-split-window"]);
|
|
53352
53710
|
this.hookReadAbort?.();
|
|
53353
53711
|
this.hookReadAbort = null;
|
|
53354
53712
|
this.hookBuffer = "";
|
|
@@ -53379,22 +53737,9 @@ class SshExternalTmuxConnection {
|
|
|
53379
53737
|
}
|
|
53380
53738
|
const [type, windowId, paneId] = line.split("\t");
|
|
53381
53739
|
if (type === "bell") {
|
|
53382
|
-
const key = paneId || windowId || "-";
|
|
53383
|
-
const previous = this.bellDedup.get(key) ?? 0;
|
|
53384
|
-
const now = Date.now();
|
|
53385
|
-
if (now - previous >= BELL_DEDUP_WINDOW_MS2) {
|
|
53386
|
-
this.bellDedup.set(key, now);
|
|
53387
|
-
this.callbacks.onEvent({
|
|
53388
|
-
type: "bell",
|
|
53389
|
-
data: {
|
|
53390
|
-
windowId: windowId || undefined,
|
|
53391
|
-
paneId: paneId || this.activePaneId || undefined
|
|
53392
|
-
}
|
|
53393
|
-
});
|
|
53394
|
-
}
|
|
53395
53740
|
continue;
|
|
53396
53741
|
}
|
|
53397
|
-
if (type === "pane-exited" || type === "pane-died") {
|
|
53742
|
+
if (type === "pane-exited" || type === "pane-died" || type === "refresh") {
|
|
53398
53743
|
this.requestSnapshot();
|
|
53399
53744
|
}
|
|
53400
53745
|
}
|
|
@@ -53425,7 +53770,6 @@ class SshExternalTmuxConnection {
|
|
|
53425
53770
|
this.activePaneId = paneId;
|
|
53426
53771
|
await this.runTmux(["select-window", "-t", windowId], true);
|
|
53427
53772
|
await this.runTmux(["select-pane", "-t", paneId], true);
|
|
53428
|
-
await this.startPipeForPane(paneId);
|
|
53429
53773
|
if (size) {
|
|
53430
53774
|
await this.resizePaneInternal(paneId, size.cols, size.rows);
|
|
53431
53775
|
}
|
|
@@ -53480,6 +53824,7 @@ class SshExternalTmuxConnection {
|
|
|
53480
53824
|
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
53481
53825
|
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
53482
53826
|
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
53827
|
+
await this.syncPipeReaders();
|
|
53483
53828
|
this.emitSnapshot();
|
|
53484
53829
|
}
|
|
53485
53830
|
parseSnapshotSession(lines) {
|
|
@@ -53577,56 +53922,114 @@ class SshExternalTmuxConnection {
|
|
|
53577
53922
|
}
|
|
53578
53923
|
return null;
|
|
53579
53924
|
}
|
|
53580
|
-
|
|
53581
|
-
|
|
53582
|
-
|
|
53583
|
-
|
|
53925
|
+
recordBell(paneId, windowId) {
|
|
53926
|
+
const key = paneId || windowId || "-";
|
|
53927
|
+
const previous = this.bellDedup.get(key) ?? 0;
|
|
53928
|
+
const now = Date.now();
|
|
53929
|
+
if (now - previous < BELL_DEDUP_WINDOW_MS2) {
|
|
53930
|
+
return;
|
|
53931
|
+
}
|
|
53932
|
+
this.bellDedup.set(key, now);
|
|
53933
|
+
this.callbacks.onEvent({
|
|
53934
|
+
type: "bell",
|
|
53935
|
+
data: {
|
|
53936
|
+
windowId,
|
|
53937
|
+
paneId: paneId || this.activePaneId || undefined
|
|
53938
|
+
}
|
|
53939
|
+
});
|
|
53940
|
+
}
|
|
53941
|
+
emitNotification(paneId, notification) {
|
|
53942
|
+
this.callbacks.onEvent({
|
|
53943
|
+
type: "notification",
|
|
53944
|
+
data: {
|
|
53945
|
+
paneId,
|
|
53946
|
+
...notification
|
|
53584
53947
|
}
|
|
53585
|
-
|
|
53586
|
-
|
|
53587
|
-
|
|
53588
|
-
|
|
53589
|
-
|
|
53590
|
-
|
|
53591
|
-
|
|
53592
|
-
|
|
53948
|
+
});
|
|
53949
|
+
}
|
|
53950
|
+
getExpectedPaneIds() {
|
|
53951
|
+
return Array.from(this.snapshotWindows.values()).sort((left, right) => left.index - right.index).flatMap((window2) => window2.panes.map((pane) => pane.id));
|
|
53952
|
+
}
|
|
53953
|
+
async startPipeForPaneNow(paneId) {
|
|
53954
|
+
if (this.paneReaders.has(paneId)) {
|
|
53955
|
+
return;
|
|
53956
|
+
}
|
|
53957
|
+
const fifoPath = this.fsPaths.paneFifoPath(paneId);
|
|
53958
|
+
await this.ensureRemoteRuntimeDirs();
|
|
53959
|
+
await this.runShell(`rm -f ${quoteShellArg(fifoPath)} && mkfifo ${quoteShellArg(fifoPath)} && chmod 600 ${quoteShellArg(fifoPath)}`);
|
|
53960
|
+
const parser = createPaneStreamParser({
|
|
53961
|
+
onTitle: (title) => {
|
|
53962
|
+
this.pendingPaneTitles.set(paneId, title);
|
|
53963
|
+
this.requestSnapshot();
|
|
53964
|
+
},
|
|
53965
|
+
onBell: () => {
|
|
53966
|
+
this.recordBell(paneId);
|
|
53967
|
+
},
|
|
53968
|
+
onNotification: (notification) => {
|
|
53969
|
+
this.emitNotification(paneId, notification);
|
|
53970
|
+
}
|
|
53971
|
+
});
|
|
53972
|
+
const stopReader = await this.openReaderChannel(`exec cat ${quoteShellArg(fifoPath)}`, {
|
|
53973
|
+
onData: (raw) => {
|
|
53974
|
+
const output = parser.push(raw);
|
|
53975
|
+
if (output.length > 0) {
|
|
53976
|
+
this.callbacks.onTerminalOutput(paneId, output);
|
|
53593
53977
|
}
|
|
53594
|
-
}
|
|
53595
|
-
|
|
53596
|
-
|
|
53597
|
-
|
|
53598
|
-
if (Array.from(raw).includes(7)) {
|
|
53599
|
-
this.callbacks.onEvent({ type: "bell", data: { paneId } });
|
|
53600
|
-
}
|
|
53601
|
-
if (output.length > 0) {
|
|
53602
|
-
this.callbacks.onTerminalOutput(paneId, output);
|
|
53603
|
-
}
|
|
53604
|
-
},
|
|
53605
|
-
onClose: () => {
|
|
53606
|
-
if (!this.manualDisconnect && this.currentPipePaneId === paneId) {
|
|
53607
|
-
this.callbacks.onError(new Error(`SSH pane reader closed unexpectedly: ${paneId}`));
|
|
53608
|
-
}
|
|
53978
|
+
},
|
|
53979
|
+
onClose: () => {
|
|
53980
|
+
if (!this.manualDisconnect && this.paneReaders.has(paneId)) {
|
|
53981
|
+
this.callbacks.onError(new Error(`SSH pane reader closed unexpectedly: ${paneId}`));
|
|
53609
53982
|
}
|
|
53610
|
-
}
|
|
53611
|
-
|
|
53983
|
+
}
|
|
53984
|
+
});
|
|
53985
|
+
const handle = {
|
|
53986
|
+
paneId,
|
|
53987
|
+
fifoPath,
|
|
53988
|
+
stopReader: () => {
|
|
53612
53989
|
stopReader();
|
|
53613
53990
|
this.runShellAllowFailure(`rm -f ${quoteShellArg(fifoPath)}`);
|
|
53614
|
-
}
|
|
53991
|
+
}
|
|
53992
|
+
};
|
|
53993
|
+
this.paneReaders.set(paneId, handle);
|
|
53994
|
+
try {
|
|
53615
53995
|
await this.runTmux(["pipe-pane", "-O", "-t", paneId, `cat >${fifoPath}`]);
|
|
53616
|
-
|
|
53617
|
-
|
|
53618
|
-
|
|
53619
|
-
|
|
53620
|
-
|
|
53996
|
+
} catch (error) {
|
|
53997
|
+
this.paneReaders.delete(paneId);
|
|
53998
|
+
handle.stopReader();
|
|
53999
|
+
throw error;
|
|
54000
|
+
}
|
|
53621
54001
|
}
|
|
53622
|
-
async
|
|
53623
|
-
const
|
|
53624
|
-
|
|
53625
|
-
|
|
53626
|
-
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
54002
|
+
async stopPipeForPaneNow(paneId) {
|
|
54003
|
+
const handle = this.paneReaders.get(paneId);
|
|
54004
|
+
if (!handle) {
|
|
54005
|
+
return;
|
|
53627
54006
|
}
|
|
53628
|
-
this.
|
|
53629
|
-
this.
|
|
54007
|
+
this.paneReaders.delete(paneId);
|
|
54008
|
+
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
54009
|
+
handle.stopReader();
|
|
54010
|
+
}
|
|
54011
|
+
async syncPipeReaders() {
|
|
54012
|
+
const expectedPaneIds = this.getExpectedPaneIds();
|
|
54013
|
+
const expectedSet = new Set(expectedPaneIds);
|
|
54014
|
+
await this.queuePipeTransition(async () => {
|
|
54015
|
+
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
54016
|
+
if (!expectedSet.has(paneId)) {
|
|
54017
|
+
await this.stopPipeForPaneNow(paneId);
|
|
54018
|
+
}
|
|
54019
|
+
}
|
|
54020
|
+
for (const paneId of expectedPaneIds) {
|
|
54021
|
+
if (!this.paneReaders.has(paneId)) {
|
|
54022
|
+
await this.startPipeForPaneNow(paneId);
|
|
54023
|
+
}
|
|
54024
|
+
}
|
|
54025
|
+
});
|
|
54026
|
+
}
|
|
54027
|
+
async stopAllPipeReaders() {
|
|
54028
|
+
await this.queuePipeTransition(async () => {
|
|
54029
|
+
for (const paneId of Array.from(this.paneReaders.keys())) {
|
|
54030
|
+
await this.stopPipeForPaneNow(paneId);
|
|
54031
|
+
}
|
|
54032
|
+
});
|
|
53630
54033
|
}
|
|
53631
54034
|
queuePipeTransition(task) {
|
|
53632
54035
|
const next = this.pipeTransition.catch(() => {
|
|
@@ -53796,7 +54199,7 @@ printf '\\036TMEX_END %s %d\\036\\n' ${quoteShellArg(commandId)} $?
|
|
|
53796
54199
|
}
|
|
53797
54200
|
this.connected = false;
|
|
53798
54201
|
this.cleanupPromise = (async () => {
|
|
53799
|
-
await this.
|
|
54202
|
+
await this.stopAllPipeReaders().catch(() => {
|
|
53800
54203
|
return;
|
|
53801
54204
|
});
|
|
53802
54205
|
await this.stopHooks().catch(() => {
|
|
@@ -54025,7 +54428,7 @@ function pickPaneById(windows, paneId) {
|
|
|
54025
54428
|
}
|
|
54026
54429
|
return null;
|
|
54027
54430
|
}
|
|
54028
|
-
function
|
|
54431
|
+
function resolvePaneContext(options) {
|
|
54029
54432
|
const { deviceId, snapshot, rawData } = options;
|
|
54030
54433
|
const raw = rawData ?? {};
|
|
54031
54434
|
const bellWindowId = typeof raw.windowId === "string" && raw.windowId ? raw.windowId : undefined;
|
|
@@ -54100,6 +54503,34 @@ var defaultDeps = {
|
|
|
54100
54503
|
}
|
|
54101
54504
|
});
|
|
54102
54505
|
},
|
|
54506
|
+
async notifyNotification(context) {
|
|
54507
|
+
const { device, settings, notification } = context;
|
|
54508
|
+
await eventNotifier.notify("terminal_notification", {
|
|
54509
|
+
site: {
|
|
54510
|
+
name: settings.siteName,
|
|
54511
|
+
url: settings.siteUrl
|
|
54512
|
+
},
|
|
54513
|
+
device: {
|
|
54514
|
+
id: device.id,
|
|
54515
|
+
name: device.name,
|
|
54516
|
+
type: device.type,
|
|
54517
|
+
host: device.host
|
|
54518
|
+
},
|
|
54519
|
+
tmux: {
|
|
54520
|
+
sessionName: device.session,
|
|
54521
|
+
windowId: notification.windowId,
|
|
54522
|
+
paneId: notification.paneId,
|
|
54523
|
+
windowIndex: notification.windowIndex,
|
|
54524
|
+
paneIndex: notification.paneIndex,
|
|
54525
|
+
paneUrl: notification.paneUrl
|
|
54526
|
+
},
|
|
54527
|
+
payload: {
|
|
54528
|
+
source: notification.source,
|
|
54529
|
+
title: notification.title,
|
|
54530
|
+
message: notification.body
|
|
54531
|
+
}
|
|
54532
|
+
});
|
|
54533
|
+
},
|
|
54103
54534
|
fallbackReconnectDelayMs: 60000
|
|
54104
54535
|
};
|
|
54105
54536
|
|
|
@@ -54294,25 +54725,44 @@ class PushSupervisor {
|
|
|
54294
54725
|
if (!entry || entry.generation !== generation || entry.runtime !== runtime) {
|
|
54295
54726
|
return;
|
|
54296
54727
|
}
|
|
54297
|
-
if (event.type !== "bell") {
|
|
54298
|
-
return;
|
|
54299
|
-
}
|
|
54300
54728
|
const device = this.deps.getDevice(deviceId);
|
|
54301
54729
|
if (!device) {
|
|
54302
54730
|
return;
|
|
54303
54731
|
}
|
|
54304
54732
|
const settings = this.deps.getSettings();
|
|
54305
|
-
const
|
|
54733
|
+
const paneContext = resolvePaneContext({
|
|
54306
54734
|
deviceId,
|
|
54307
54735
|
siteUrl: settings.siteUrl,
|
|
54308
54736
|
snapshot: entry.lastSnapshot,
|
|
54309
54737
|
rawData: event.data
|
|
54310
54738
|
});
|
|
54311
|
-
|
|
54312
|
-
|
|
54313
|
-
|
|
54314
|
-
|
|
54315
|
-
|
|
54739
|
+
if (event.type === "bell") {
|
|
54740
|
+
await this.deps.notifyBell({
|
|
54741
|
+
device,
|
|
54742
|
+
settings,
|
|
54743
|
+
bell: paneContext
|
|
54744
|
+
});
|
|
54745
|
+
return;
|
|
54746
|
+
}
|
|
54747
|
+
if (event.type === "notification") {
|
|
54748
|
+
const raw = event.data ?? {};
|
|
54749
|
+
const title = typeof raw.title === "string" && raw.title ? raw.title : undefined;
|
|
54750
|
+
const body = typeof raw.body === "string" ? raw.body : "";
|
|
54751
|
+
if (!title && !body) {
|
|
54752
|
+
return;
|
|
54753
|
+
}
|
|
54754
|
+
const source = raw.source === "osc9" || raw.source === "osc777" || raw.source === "osc1337" ? raw.source : "osc9";
|
|
54755
|
+
await this.deps.notifyNotification({
|
|
54756
|
+
device,
|
|
54757
|
+
settings,
|
|
54758
|
+
notification: {
|
|
54759
|
+
...paneContext,
|
|
54760
|
+
source,
|
|
54761
|
+
title,
|
|
54762
|
+
body
|
|
54763
|
+
}
|
|
54764
|
+
});
|
|
54765
|
+
}
|
|
54316
54766
|
}
|
|
54317
54767
|
}
|
|
54318
54768
|
var pushSupervisor = new PushSupervisor;
|
|
@@ -54501,18 +54951,37 @@ function normalizeSiteSettingsInput(body) {
|
|
|
54501
54951
|
}
|
|
54502
54952
|
updates.bellThrottleSeconds = value;
|
|
54503
54953
|
}
|
|
54954
|
+
if (body.notificationThrottleSeconds !== undefined) {
|
|
54955
|
+
const value = Math.floor(Number(body.notificationThrottleSeconds));
|
|
54956
|
+
if (Number.isNaN(value) || value < 0 || value > 300) {
|
|
54957
|
+
throw new Error(t2("apiError.bellThrottleInvalid"));
|
|
54958
|
+
}
|
|
54959
|
+
updates.notificationThrottleSeconds = value;
|
|
54960
|
+
}
|
|
54504
54961
|
if (body.enableBrowserBellToast !== undefined) {
|
|
54505
54962
|
if (typeof body.enableBrowserBellToast !== "boolean") {
|
|
54506
54963
|
throw new Error(t2("apiError.invalidRequest"));
|
|
54507
54964
|
}
|
|
54508
54965
|
updates.enableBrowserBellToast = body.enableBrowserBellToast;
|
|
54509
54966
|
}
|
|
54967
|
+
if (body.enableBrowserNotificationToast !== undefined) {
|
|
54968
|
+
if (typeof body.enableBrowserNotificationToast !== "boolean") {
|
|
54969
|
+
throw new Error(t2("apiError.invalidRequest"));
|
|
54970
|
+
}
|
|
54971
|
+
updates.enableBrowserNotificationToast = body.enableBrowserNotificationToast;
|
|
54972
|
+
}
|
|
54510
54973
|
if (body.enableTelegramBellPush !== undefined) {
|
|
54511
54974
|
if (typeof body.enableTelegramBellPush !== "boolean") {
|
|
54512
54975
|
throw new Error(t2("apiError.invalidRequest"));
|
|
54513
54976
|
}
|
|
54514
54977
|
updates.enableTelegramBellPush = body.enableTelegramBellPush;
|
|
54515
54978
|
}
|
|
54979
|
+
if (body.enableTelegramNotificationPush !== undefined) {
|
|
54980
|
+
if (typeof body.enableTelegramNotificationPush !== "boolean") {
|
|
54981
|
+
throw new Error(t2("apiError.invalidRequest"));
|
|
54982
|
+
}
|
|
54983
|
+
updates.enableTelegramNotificationPush = body.enableTelegramNotificationPush;
|
|
54984
|
+
}
|
|
54516
54985
|
if (body.sshReconnectMaxRetries !== undefined) {
|
|
54517
54986
|
const value = Math.floor(Number(body.sshReconnectMaxRetries));
|
|
54518
54987
|
if (Number.isNaN(value) || value < 0 || value > 20) {
|
|
@@ -55018,7 +55487,8 @@ class SessionStateStore {
|
|
|
55018
55487
|
deviceConnections: new Map,
|
|
55019
55488
|
selectTransactions: new Map,
|
|
55020
55489
|
outputGates: new Map,
|
|
55021
|
-
bellThrottles: new Map
|
|
55490
|
+
bellThrottles: new Map,
|
|
55491
|
+
notificationThrottles: new Map
|
|
55022
55492
|
};
|
|
55023
55493
|
this.states.set(ws, state);
|
|
55024
55494
|
return state;
|
|
@@ -55248,6 +55718,28 @@ class SessionStateStore {
|
|
|
55248
55718
|
ctx.throttleSeconds = throttleSeconds;
|
|
55249
55719
|
return true;
|
|
55250
55720
|
}
|
|
55721
|
+
shouldAllowNotification(ws, deviceId, paneId, source, throttleSeconds) {
|
|
55722
|
+
const state = this.states.get(ws);
|
|
55723
|
+
if (!state)
|
|
55724
|
+
return false;
|
|
55725
|
+
const key = `${deviceId}:${paneId}:${source}`;
|
|
55726
|
+
const now = Date.now();
|
|
55727
|
+
let ctx = state.notificationThrottles.get(key);
|
|
55728
|
+
if (!ctx) {
|
|
55729
|
+
ctx = {
|
|
55730
|
+
lastBellAt: 0,
|
|
55731
|
+
throttleSeconds
|
|
55732
|
+
};
|
|
55733
|
+
state.notificationThrottles.set(key, ctx);
|
|
55734
|
+
}
|
|
55735
|
+
const throttleMs = throttleSeconds * 1000;
|
|
55736
|
+
if (now - ctx.lastBellAt < throttleMs) {
|
|
55737
|
+
return false;
|
|
55738
|
+
}
|
|
55739
|
+
ctx.lastBellAt = now;
|
|
55740
|
+
ctx.throttleSeconds = throttleSeconds;
|
|
55741
|
+
return true;
|
|
55742
|
+
}
|
|
55251
55743
|
cleanupDevice(ws, deviceId) {
|
|
55252
55744
|
const state = this.states.get(ws);
|
|
55253
55745
|
if (!state)
|
|
@@ -55260,6 +55752,11 @@ class SessionStateStore {
|
|
|
55260
55752
|
state.bellThrottles.delete(key);
|
|
55261
55753
|
}
|
|
55262
55754
|
}
|
|
55755
|
+
for (const key of state.notificationThrottles.keys()) {
|
|
55756
|
+
if (key.startsWith(`${deviceId}:`)) {
|
|
55757
|
+
state.notificationThrottles.delete(key);
|
|
55758
|
+
}
|
|
55759
|
+
}
|
|
55263
55760
|
}
|
|
55264
55761
|
cleanup(ws) {
|
|
55265
55762
|
this.states.delete(ws);
|
|
@@ -56028,13 +56525,21 @@ class WebSocketServer {
|
|
|
56028
56525
|
return;
|
|
56029
56526
|
this.scheduleSnapshot(deviceId);
|
|
56030
56527
|
const extendedEvent = await this.extendTmuxEvent(deviceId, event);
|
|
56528
|
+
const settings = getSiteSettings();
|
|
56529
|
+
if (extendedEvent.type === "notification") {
|
|
56530
|
+
const data = extendedEvent.data ?? {};
|
|
56531
|
+
const title = typeof data.title === "string" && data.title ? data.title : "";
|
|
56532
|
+
const body = typeof data.body === "string" ? data.body : "";
|
|
56533
|
+
if (!title && !body) {
|
|
56534
|
+
return;
|
|
56535
|
+
}
|
|
56536
|
+
}
|
|
56031
56537
|
const payloadBytes = exports_ws_borsh.encodeTmuxEventPayload({
|
|
56032
56538
|
deviceId,
|
|
56033
56539
|
type: extendedEvent.type,
|
|
56034
56540
|
data: extendedEvent.data
|
|
56035
56541
|
});
|
|
56036
56542
|
if (extendedEvent.type === "bell") {
|
|
56037
|
-
const settings = getSiteSettings();
|
|
56038
56543
|
const data = extendedEvent.data ?? {};
|
|
56039
56544
|
const paneId = typeof data.paneId === "string" && data.paneId ? data.paneId : "-";
|
|
56040
56545
|
for (const client of entry.clients) {
|
|
@@ -56045,25 +56550,52 @@ class WebSocketServer {
|
|
|
56045
56550
|
}
|
|
56046
56551
|
return;
|
|
56047
56552
|
}
|
|
56553
|
+
if (extendedEvent.type === "notification") {
|
|
56554
|
+
const data = extendedEvent.data ?? {};
|
|
56555
|
+
const paneId = typeof data.paneId === "string" && data.paneId ? data.paneId : "-";
|
|
56556
|
+
const source = typeof data.source === "string" && data.source ? data.source : "osc9";
|
|
56557
|
+
for (const client of entry.clients) {
|
|
56558
|
+
if (!sessionStateStore.shouldAllowNotification(client, deviceId, paneId, source, settings.notificationThrottleSeconds)) {
|
|
56559
|
+
continue;
|
|
56560
|
+
}
|
|
56561
|
+
this.sendEnvelope(client, exports_ws_borsh.KIND_TMUX_EVENT, payloadBytes);
|
|
56562
|
+
}
|
|
56563
|
+
return;
|
|
56564
|
+
}
|
|
56048
56565
|
for (const client of entry.clients) {
|
|
56049
56566
|
this.sendEnvelope(client, exports_ws_borsh.KIND_TMUX_EVENT, payloadBytes);
|
|
56050
56567
|
}
|
|
56051
56568
|
}
|
|
56052
56569
|
async extendTmuxEvent(deviceId, event) {
|
|
56053
|
-
if (event.type !== "bell") {
|
|
56570
|
+
if (event.type !== "bell" && event.type !== "notification") {
|
|
56054
56571
|
return event;
|
|
56055
56572
|
}
|
|
56056
56573
|
const settings = getSiteSettings();
|
|
56057
56574
|
const snapshot = this.connections.get(deviceId)?.lastSnapshot ?? null;
|
|
56058
|
-
const
|
|
56575
|
+
const paneContext = resolvePaneContext({
|
|
56059
56576
|
deviceId,
|
|
56060
56577
|
siteUrl: settings.siteUrl,
|
|
56061
56578
|
snapshot,
|
|
56062
56579
|
rawData: event.data
|
|
56063
56580
|
});
|
|
56581
|
+
if (event.type === "bell") {
|
|
56582
|
+
return {
|
|
56583
|
+
type: "bell",
|
|
56584
|
+
data: paneContext
|
|
56585
|
+
};
|
|
56586
|
+
}
|
|
56587
|
+
const raw = event.data ?? {};
|
|
56588
|
+
const source = raw.source === "osc9" || raw.source === "osc777" || raw.source === "osc1337" ? raw.source : "osc9";
|
|
56589
|
+
const title = typeof raw.title === "string" && raw.title ? raw.title : undefined;
|
|
56590
|
+
const body = typeof raw.body === "string" ? raw.body : "";
|
|
56064
56591
|
return {
|
|
56065
|
-
type: "
|
|
56066
|
-
data
|
|
56592
|
+
type: "notification",
|
|
56593
|
+
data: {
|
|
56594
|
+
...paneContext,
|
|
56595
|
+
source,
|
|
56596
|
+
title,
|
|
56597
|
+
body
|
|
56598
|
+
}
|
|
56067
56599
|
};
|
|
56068
56600
|
}
|
|
56069
56601
|
broadcastStateSnapshot(deviceId, payload) {
|