tmex-cli 0.2.6 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/runtime/{cpufeatures-jxwx4v5j.node → cpufeatures-9rk1k79p.node} +0 -0
- package/dist/runtime/server.js +1813 -1298
- package/dist/runtime/{sshcrypto-sx6755rw.node → sshcrypto-z1hq11fy.node} +0 -0
- package/package.json +1 -1
- package/resources/fe-dist/assets/DevicePage-BE5Hlzny.js +26 -0
- package/resources/fe-dist/assets/DevicePage-BE5Hlzny.js.map +1 -0
- package/resources/fe-dist/assets/DevicesPage-BTB6mlBW.js +17 -0
- package/resources/fe-dist/assets/{DevicesPage-BrvmKNIQ.js.map → DevicesPage-BTB6mlBW.js.map} +1 -1
- package/resources/fe-dist/assets/SettingsPage-BDVq_Akt.js +17 -0
- package/resources/fe-dist/assets/{SettingsPage-XFRzVSzI.js.map → SettingsPage-BDVq_Akt.js.map} +1 -1
- package/resources/fe-dist/assets/ghostty-vt-BKQMf-5X.wasm +0 -0
- package/resources/fe-dist/assets/index-Chps0Pui.js +448 -0
- package/resources/fe-dist/assets/index-Chps0Pui.js.map +1 -0
- package/resources/fe-dist/assets/index-CyKyNcdz.css +1 -0
- package/resources/fe-dist/assets/select-7tfuDWw3.js +17 -0
- package/resources/fe-dist/assets/{select-CRYPgkuV.js.map → select-7tfuDWw3.js.map} +1 -1
- package/resources/fe-dist/assets/switch-CNHRGb7-.js +12 -0
- package/resources/fe-dist/assets/{switch-D216fzW5.js.map → switch-CNHRGb7-.js.map} +1 -1
- package/resources/fe-dist/assets/useValueChanged-Bi_NzPTr.js +7 -0
- package/resources/fe-dist/assets/{useValueChanged-CJav2OS1.js.map → useValueChanged-Bi_NzPTr.js.map} +1 -1
- package/resources/fe-dist/index.html +2 -2
- package/resources/fe-dist/assets/DevicePage-6GBZ9nXN.css +0 -32
- package/resources/fe-dist/assets/DevicePage-BOtxQsop.js +0 -267
- package/resources/fe-dist/assets/DevicePage-BOtxQsop.js.map +0 -1
- package/resources/fe-dist/assets/DevicesPage-BrvmKNIQ.js +0 -17
- package/resources/fe-dist/assets/SettingsPage-XFRzVSzI.js +0 -17
- package/resources/fe-dist/assets/index-CxzyWqMn.css +0 -1
- package/resources/fe-dist/assets/index-Vweko0vT.js +0 -200
- package/resources/fe-dist/assets/index-Vweko0vT.js.map +0 -1
- package/resources/fe-dist/assets/select-CRYPgkuV.js +0 -17
- package/resources/fe-dist/assets/switch-D216fzW5.js +0 -12
- package/resources/fe-dist/assets/useValueChanged-CJav2OS1.js +0 -7
package/dist/runtime/server.js
CHANGED
|
@@ -5579,7 +5579,7 @@ var require_bcrypt_pbkdf = __commonJS((exports, module) => {
|
|
|
5579
5579
|
|
|
5580
5580
|
// ../../node_modules/.bun/cpu-features@0.0.10/node_modules/cpu-features/build/Release/cpufeatures.node
|
|
5581
5581
|
var require_cpufeatures = __commonJS((exports, module) => {
|
|
5582
|
-
module.exports = __require("./cpufeatures-
|
|
5582
|
+
module.exports = __require("./cpufeatures-9rk1k79p.node");
|
|
5583
5583
|
});
|
|
5584
5584
|
|
|
5585
5585
|
// ../../node_modules/.bun/cpu-features@0.0.10/node_modules/cpu-features/lib/index.js
|
|
@@ -6192,7 +6192,7 @@ var require_utils = __commonJS((exports, module) => {
|
|
|
6192
6192
|
|
|
6193
6193
|
// ../../node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib/protocol/crypto/build/Release/sshcrypto.node
|
|
6194
6194
|
var require_sshcrypto = __commonJS((exports, module) => {
|
|
6195
|
-
module.exports = __require("./sshcrypto-
|
|
6195
|
+
module.exports = __require("./sshcrypto-z1hq11fy.node");
|
|
6196
6196
|
});
|
|
6197
6197
|
|
|
6198
6198
|
// ../../node_modules/.bun/ssh2@1.17.0/node_modules/ssh2/lib/protocol/crypto/poly1305.js
|
|
@@ -20282,7 +20282,7 @@ var require_lib3 = __commonJS((exports, module) => {
|
|
|
20282
20282
|
|
|
20283
20283
|
// src/runtime/server.ts
|
|
20284
20284
|
import { existsSync as existsSync3 } from "fs";
|
|
20285
|
-
import { extname, join as
|
|
20285
|
+
import { extname, join as join4, normalize, resolve as resolve2, sep } from "path";
|
|
20286
20286
|
|
|
20287
20287
|
// ../../apps/gateway/src/crypto/errors.ts
|
|
20288
20288
|
function contextLabel(context) {
|
|
@@ -20554,7 +20554,7 @@ Time: {{time}}`,
|
|
|
20554
20554
|
connectionTimeout: "Connection timeout: Unable to connect to device. Please check network or firewall settings.",
|
|
20555
20555
|
hostNotFound: "Host not found: Unable to resolve hostname. Please check DNS or hostname configuration.",
|
|
20556
20556
|
handshakeFailed: "Handshake failed: Unable to establish secure connection. Possibly incompatible key exchange algorithm.",
|
|
20557
|
-
tmuxUnavailable: "Remote tmux unavailable or failed to start. Please ensure tmux is installed and
|
|
20557
|
+
tmuxUnavailable: "Remote tmux unavailable or failed to start. Please ensure tmux is installed and available in the remote shell PATH.",
|
|
20558
20558
|
unknown: "Connection failed: {{message}}",
|
|
20559
20559
|
reconnecting: "Connection interrupted, reconnecting in {{delay}} seconds ({{attempt}}/{{maxRetries}})",
|
|
20560
20560
|
reconnectFailed: "Auto-reconnect failed, please retry manually",
|
|
@@ -20881,7 +20881,7 @@ Bot\uFF1A{{botName}}
|
|
|
20881
20881
|
connectionTimeout: "\u8FDE\u63A5\u8D85\u65F6\uFF1A\u65E0\u6CD5\u8FDE\u63A5\u5230\u8BBE\u5907\uFF0C\u8BF7\u68C0\u67E5\u7F51\u7EDC\u6216\u9632\u706B\u5899\u8BBE\u7F6E",
|
|
20882
20882
|
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",
|
|
20883
20883
|
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",
|
|
20884
|
-
tmuxUnavailable: "\u8FDC\u7AEF tmux \u4E0D\u53EF\u7528\u6216\u542F\u52A8\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8FDC\u7AEF\u662F\u5426\u5DF2\u5B89\u88C5 tmux
|
|
20884
|
+
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",
|
|
20885
20885
|
unknown: "\u8FDE\u63A5\u5931\u8D25\uFF1A{{message}}",
|
|
20886
20886
|
reconnecting: "\u8FDE\u63A5\u4E2D\u65AD\uFF0C{{delay}} \u79D2\u540E\u81EA\u52A8\u91CD\u8FDE\uFF08{{attempt}}/{{maxRetries}}\uFF09",
|
|
20887
20887
|
reconnectFailed: "\u81EA\u52A8\u91CD\u8FDE\u5931\u8D25\uFF0C\u8BF7\u624B\u52A8\u91CD\u8BD5",
|
|
@@ -21208,7 +21208,7 @@ Bot\uFF1A{{botName}}
|
|
|
21208
21208
|
connectionTimeout: "\u63A5\u7D9A\u30BF\u30A4\u30E0\u30A2\u30A6\u30C8\uFF1A\u30C7\u30D0\u30A4\u30B9\u306B\u63A5\u7D9A\u3067\u304D\u307E\u305B\u3093\u3002\u30CD\u30C3\u30C8\u30EF\u30FC\u30AF\u307E\u305F\u306F\u30D5\u30A1\u30A4\u30A2\u30A6\u30A9\u30FC\u30EB\u8A2D\u5B9A\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
|
|
21209
21209
|
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",
|
|
21210
21210
|
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",
|
|
21211
|
-
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
|
|
21211
|
+
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",
|
|
21212
21212
|
unknown: "\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F\uFF1A{{message}}",
|
|
21213
21213
|
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",
|
|
21214
21214
|
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",
|
|
@@ -22367,7 +22367,7 @@ async function decryptWithContext(ciphertext, context) {
|
|
|
22367
22367
|
}
|
|
22368
22368
|
}
|
|
22369
22369
|
|
|
22370
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22370
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/entity.js
|
|
22371
22371
|
var entityKind = Symbol.for("drizzle:entityKind");
|
|
22372
22372
|
var hasOwnEntityKind = Symbol.for("drizzle:hasOwnEntityKind");
|
|
22373
22373
|
function is(value, type) {
|
|
@@ -22392,7 +22392,7 @@ function is(value, type) {
|
|
|
22392
22392
|
return false;
|
|
22393
22393
|
}
|
|
22394
22394
|
|
|
22395
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22395
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/column.js
|
|
22396
22396
|
class Column {
|
|
22397
22397
|
constructor(table, config2) {
|
|
22398
22398
|
this.table = table;
|
|
@@ -22442,7 +22442,7 @@ class Column {
|
|
|
22442
22442
|
}
|
|
22443
22443
|
}
|
|
22444
22444
|
|
|
22445
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22445
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/column-builder.js
|
|
22446
22446
|
class ColumnBuilder {
|
|
22447
22447
|
static [entityKind] = "ColumnBuilder";
|
|
22448
22448
|
config;
|
|
@@ -22498,20 +22498,20 @@ class ColumnBuilder {
|
|
|
22498
22498
|
}
|
|
22499
22499
|
}
|
|
22500
22500
|
|
|
22501
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22501
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/table.utils.js
|
|
22502
22502
|
var TableName = Symbol.for("drizzle:Name");
|
|
22503
22503
|
|
|
22504
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22504
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/tracing-utils.js
|
|
22505
22505
|
function iife(fn, ...args) {
|
|
22506
22506
|
return fn(...args);
|
|
22507
22507
|
}
|
|
22508
22508
|
|
|
22509
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22509
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/pg-core/unique-constraint.js
|
|
22510
22510
|
function uniqueKeyName(table, columns) {
|
|
22511
22511
|
return `${table[TableName]}_${columns.join("_")}_unique`;
|
|
22512
22512
|
}
|
|
22513
22513
|
|
|
22514
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22514
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/pg-core/columns/common.js
|
|
22515
22515
|
class PgColumn extends Column {
|
|
22516
22516
|
constructor(table, config2) {
|
|
22517
22517
|
if (!config2.uniqueName) {
|
|
@@ -22560,7 +22560,7 @@ class ExtraConfigColumn extends PgColumn {
|
|
|
22560
22560
|
}
|
|
22561
22561
|
}
|
|
22562
22562
|
|
|
22563
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22563
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/pg-core/columns/enum.js
|
|
22564
22564
|
class PgEnumObjectColumn extends PgColumn {
|
|
22565
22565
|
static [entityKind] = "PgEnumObjectColumn";
|
|
22566
22566
|
enum;
|
|
@@ -22590,7 +22590,7 @@ class PgEnumColumn extends PgColumn {
|
|
|
22590
22590
|
}
|
|
22591
22591
|
}
|
|
22592
22592
|
|
|
22593
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22593
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/subquery.js
|
|
22594
22594
|
class Subquery {
|
|
22595
22595
|
static [entityKind] = "Subquery";
|
|
22596
22596
|
constructor(sql, fields, alias, isWith = false, usedTables = []) {
|
|
@@ -22609,10 +22609,10 @@ class WithSubquery extends Subquery {
|
|
|
22609
22609
|
static [entityKind] = "WithSubquery";
|
|
22610
22610
|
}
|
|
22611
22611
|
|
|
22612
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22612
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/version.js
|
|
22613
22613
|
var version = "0.45.1";
|
|
22614
22614
|
|
|
22615
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22615
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/tracing.js
|
|
22616
22616
|
var otel;
|
|
22617
22617
|
var rawTracer;
|
|
22618
22618
|
var tracer = {
|
|
@@ -22639,10 +22639,10 @@ var tracer = {
|
|
|
22639
22639
|
}
|
|
22640
22640
|
};
|
|
22641
22641
|
|
|
22642
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22642
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/view-common.js
|
|
22643
22643
|
var ViewBaseConfig = Symbol.for("drizzle:ViewBaseConfig");
|
|
22644
22644
|
|
|
22645
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22645
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/table.js
|
|
22646
22646
|
var Schema = Symbol.for("drizzle:Schema");
|
|
22647
22647
|
var Columns = Symbol.for("drizzle:Columns");
|
|
22648
22648
|
var ExtraConfigColumns = Symbol.for("drizzle:ExtraConfigColumns");
|
|
@@ -22686,7 +22686,7 @@ function getTableUniqueName(table) {
|
|
|
22686
22686
|
return `${table[Schema] ?? "public"}.${table[TableName]}`;
|
|
22687
22687
|
}
|
|
22688
22688
|
|
|
22689
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
22689
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sql/sql.js
|
|
22690
22690
|
function isSQLWrapper(value) {
|
|
22691
22691
|
return value !== null && value !== undefined && typeof value.getSQL === "function";
|
|
22692
22692
|
}
|
|
@@ -23066,7 +23066,7 @@ Subquery.prototype.getSQL = function() {
|
|
|
23066
23066
|
return new SQL([this]);
|
|
23067
23067
|
};
|
|
23068
23068
|
|
|
23069
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23069
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/alias.js
|
|
23070
23070
|
class ColumnAliasProxyHandler {
|
|
23071
23071
|
constructor(table) {
|
|
23072
23072
|
this.table = table;
|
|
@@ -23145,7 +23145,7 @@ function mapColumnsInSQLToAlias(query, alias) {
|
|
|
23145
23145
|
}));
|
|
23146
23146
|
}
|
|
23147
23147
|
|
|
23148
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23148
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/errors.js
|
|
23149
23149
|
class DrizzleError extends Error {
|
|
23150
23150
|
static [entityKind] = "DrizzleError";
|
|
23151
23151
|
constructor({ message, cause }) {
|
|
@@ -23175,7 +23175,7 @@ class TransactionRollbackError extends DrizzleError {
|
|
|
23175
23175
|
}
|
|
23176
23176
|
}
|
|
23177
23177
|
|
|
23178
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23178
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/logger.js
|
|
23179
23179
|
class ConsoleLogWriter {
|
|
23180
23180
|
static [entityKind] = "ConsoleLogWriter";
|
|
23181
23181
|
write(message) {
|
|
@@ -23207,7 +23207,7 @@ class NoopLogger {
|
|
|
23207
23207
|
logQuery() {}
|
|
23208
23208
|
}
|
|
23209
23209
|
|
|
23210
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23210
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/query-promise.js
|
|
23211
23211
|
class QueryPromise {
|
|
23212
23212
|
static [entityKind] = "QueryPromise";
|
|
23213
23213
|
[Symbol.toStringTag] = "QueryPromise";
|
|
@@ -23228,7 +23228,7 @@ class QueryPromise {
|
|
|
23228
23228
|
}
|
|
23229
23229
|
}
|
|
23230
23230
|
|
|
23231
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23231
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/utils.js
|
|
23232
23232
|
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
23233
23233
|
const nullifyMap = {};
|
|
23234
23234
|
const result = columns.reduce((result2, { path, field }, columnIndex) => {
|
|
@@ -23382,7 +23382,7 @@ function isConfig(data) {
|
|
|
23382
23382
|
}
|
|
23383
23383
|
var textDecoder = typeof TextDecoder === "undefined" ? null : new TextDecoder;
|
|
23384
23384
|
|
|
23385
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23385
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/pg-core/table.js
|
|
23386
23386
|
var InlineForeignKeys = Symbol.for("drizzle:PgInlineForeignKeys");
|
|
23387
23387
|
var EnableRLS = Symbol.for("drizzle:EnableRLS");
|
|
23388
23388
|
|
|
@@ -23398,7 +23398,7 @@ class PgTable extends Table {
|
|
|
23398
23398
|
[Table.Symbol.ExtraConfigColumns] = {};
|
|
23399
23399
|
}
|
|
23400
23400
|
|
|
23401
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23401
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/pg-core/primary-keys.js
|
|
23402
23402
|
class PrimaryKeyBuilder {
|
|
23403
23403
|
static [entityKind] = "PgPrimaryKeyBuilder";
|
|
23404
23404
|
columns;
|
|
@@ -23426,7 +23426,7 @@ class PrimaryKey {
|
|
|
23426
23426
|
}
|
|
23427
23427
|
}
|
|
23428
23428
|
|
|
23429
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23429
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sql/expressions/conditions.js
|
|
23430
23430
|
function bindIfParam(value, column) {
|
|
23431
23431
|
if (isDriverValueEncoder(column) && !isSQLWrapper(value) && !is(value, Param) && !is(value, Placeholder) && !is(value, Column) && !is(value, Table) && !is(value, View)) {
|
|
23432
23432
|
return new Param(value, column);
|
|
@@ -23531,7 +23531,7 @@ function notIlike(column, value) {
|
|
|
23531
23531
|
return sql`${column} not ilike ${value}`;
|
|
23532
23532
|
}
|
|
23533
23533
|
|
|
23534
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23534
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sql/expressions/select.js
|
|
23535
23535
|
function asc(column) {
|
|
23536
23536
|
return sql`${column} asc`;
|
|
23537
23537
|
}
|
|
@@ -23539,7 +23539,7 @@ function desc(column) {
|
|
|
23539
23539
|
return sql`${column} desc`;
|
|
23540
23540
|
}
|
|
23541
23541
|
|
|
23542
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23542
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/relations.js
|
|
23543
23543
|
class Relation {
|
|
23544
23544
|
constructor(sourceTable, referencedTable, relationName) {
|
|
23545
23545
|
this.sourceTable = sourceTable;
|
|
@@ -23759,7 +23759,7 @@ function mapRelationalRow(tablesConfig, tableConfig, row, buildQueryResultSelect
|
|
|
23759
23759
|
return result;
|
|
23760
23760
|
}
|
|
23761
23761
|
|
|
23762
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
23762
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sql/functions/aggregate.js
|
|
23763
23763
|
function count(expression) {
|
|
23764
23764
|
return sql`count(${expression || sql.raw("*")})`.mapWith(Number);
|
|
23765
23765
|
}
|
|
@@ -26183,10 +26183,10 @@ var t2 = instance.t.bind(instance);
|
|
|
26183
26183
|
// ../../apps/gateway/src/db/client.ts
|
|
26184
26184
|
import { Database as Database2 } from "bun:sqlite";
|
|
26185
26185
|
|
|
26186
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26186
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/bun-sqlite/driver.js
|
|
26187
26187
|
import { Database } from "bun:sqlite";
|
|
26188
26188
|
|
|
26189
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26189
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/selection-proxy.js
|
|
26190
26190
|
class SelectionProxyHandler {
|
|
26191
26191
|
static [entityKind] = "SelectionProxyHandler";
|
|
26192
26192
|
config;
|
|
@@ -26238,7 +26238,7 @@ class SelectionProxyHandler {
|
|
|
26238
26238
|
}
|
|
26239
26239
|
}
|
|
26240
26240
|
|
|
26241
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26241
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/foreign-keys.js
|
|
26242
26242
|
class ForeignKeyBuilder {
|
|
26243
26243
|
static [entityKind] = "SQLiteForeignKeyBuilder";
|
|
26244
26244
|
reference;
|
|
@@ -26292,7 +26292,7 @@ class ForeignKey {
|
|
|
26292
26292
|
}
|
|
26293
26293
|
}
|
|
26294
26294
|
|
|
26295
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26295
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/unique-constraint.js
|
|
26296
26296
|
function uniqueKeyName2(table, columns) {
|
|
26297
26297
|
return `${table[TableName]}_${columns.join("_")}_unique`;
|
|
26298
26298
|
}
|
|
@@ -26337,7 +26337,7 @@ class UniqueConstraint {
|
|
|
26337
26337
|
}
|
|
26338
26338
|
}
|
|
26339
26339
|
|
|
26340
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26340
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/columns/common.js
|
|
26341
26341
|
class SQLiteColumnBuilder extends ColumnBuilder {
|
|
26342
26342
|
static [entityKind] = "SQLiteColumnBuilder";
|
|
26343
26343
|
foreignKeyConfigs = [];
|
|
@@ -26388,7 +26388,7 @@ class SQLiteColumn extends Column {
|
|
|
26388
26388
|
static [entityKind] = "SQLiteColumn";
|
|
26389
26389
|
}
|
|
26390
26390
|
|
|
26391
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26391
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/columns/blob.js
|
|
26392
26392
|
class SQLiteBigIntBuilder extends SQLiteColumnBuilder {
|
|
26393
26393
|
static [entityKind] = "SQLiteBigIntBuilder";
|
|
26394
26394
|
constructor(name) {
|
|
@@ -26476,7 +26476,7 @@ function blob(a, b3) {
|
|
|
26476
26476
|
return new SQLiteBlobBufferBuilder(name);
|
|
26477
26477
|
}
|
|
26478
26478
|
|
|
26479
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26479
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/columns/custom.js
|
|
26480
26480
|
class SQLiteCustomColumnBuilder extends SQLiteColumnBuilder {
|
|
26481
26481
|
static [entityKind] = "SQLiteCustomColumnBuilder";
|
|
26482
26482
|
constructor(name, fieldConfig, customTypeParams) {
|
|
@@ -26517,7 +26517,7 @@ function customType(customTypeParams) {
|
|
|
26517
26517
|
};
|
|
26518
26518
|
}
|
|
26519
26519
|
|
|
26520
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26520
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/columns/integer.js
|
|
26521
26521
|
class SQLiteBaseIntegerBuilder extends SQLiteColumnBuilder {
|
|
26522
26522
|
static [entityKind] = "SQLiteBaseIntegerBuilder";
|
|
26523
26523
|
constructor(name, dataType, columnType) {
|
|
@@ -26619,7 +26619,7 @@ function integer(a, b3) {
|
|
|
26619
26619
|
return new SQLiteIntegerBuilder(name);
|
|
26620
26620
|
}
|
|
26621
26621
|
|
|
26622
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26622
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/columns/numeric.js
|
|
26623
26623
|
class SQLiteNumericBuilder extends SQLiteColumnBuilder {
|
|
26624
26624
|
static [entityKind] = "SQLiteNumericBuilder";
|
|
26625
26625
|
constructor(name) {
|
|
@@ -26689,7 +26689,7 @@ function numeric(a, b3) {
|
|
|
26689
26689
|
return mode === "number" ? new SQLiteNumericNumberBuilder(name) : mode === "bigint" ? new SQLiteNumericBigIntBuilder(name) : new SQLiteNumericBuilder(name);
|
|
26690
26690
|
}
|
|
26691
26691
|
|
|
26692
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26692
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/columns/real.js
|
|
26693
26693
|
class SQLiteRealBuilder extends SQLiteColumnBuilder {
|
|
26694
26694
|
static [entityKind] = "SQLiteRealBuilder";
|
|
26695
26695
|
constructor(name) {
|
|
@@ -26710,7 +26710,7 @@ function real(name) {
|
|
|
26710
26710
|
return new SQLiteRealBuilder(name ?? "");
|
|
26711
26711
|
}
|
|
26712
26712
|
|
|
26713
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26713
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/columns/text.js
|
|
26714
26714
|
class SQLiteTextBuilder extends SQLiteColumnBuilder {
|
|
26715
26715
|
static [entityKind] = "SQLiteTextBuilder";
|
|
26716
26716
|
constructor(name, config2) {
|
|
@@ -26765,7 +26765,7 @@ function text(a, b3 = {}) {
|
|
|
26765
26765
|
return new SQLiteTextBuilder(name, config2);
|
|
26766
26766
|
}
|
|
26767
26767
|
|
|
26768
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26768
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/columns/all.js
|
|
26769
26769
|
function getSQLiteColumnBuilders() {
|
|
26770
26770
|
return {
|
|
26771
26771
|
blob,
|
|
@@ -26777,7 +26777,7 @@ function getSQLiteColumnBuilders() {
|
|
|
26777
26777
|
};
|
|
26778
26778
|
}
|
|
26779
26779
|
|
|
26780
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26780
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/table.js
|
|
26781
26781
|
var InlineForeignKeys2 = Symbol.for("drizzle:SQLiteInlineForeignKeys");
|
|
26782
26782
|
|
|
26783
26783
|
class SQLiteTable extends Table {
|
|
@@ -26811,7 +26811,7 @@ var sqliteTable = (name, columns, extraConfig) => {
|
|
|
26811
26811
|
return sqliteTableBase(name, columns, extraConfig);
|
|
26812
26812
|
};
|
|
26813
26813
|
|
|
26814
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26814
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/checks.js
|
|
26815
26815
|
class CheckBuilder {
|
|
26816
26816
|
constructor(name, value) {
|
|
26817
26817
|
this.name = name;
|
|
@@ -26838,7 +26838,7 @@ function check(name, value) {
|
|
|
26838
26838
|
return new CheckBuilder(name, value);
|
|
26839
26839
|
}
|
|
26840
26840
|
|
|
26841
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26841
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/utils.js
|
|
26842
26842
|
function extractUsedTable(table) {
|
|
26843
26843
|
if (is(table, SQLiteTable)) {
|
|
26844
26844
|
return [`${table[Table.Symbol.BaseName]}`];
|
|
@@ -26852,7 +26852,7 @@ function extractUsedTable(table) {
|
|
|
26852
26852
|
return [];
|
|
26853
26853
|
}
|
|
26854
26854
|
|
|
26855
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26855
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/query-builders/delete.js
|
|
26856
26856
|
class SQLiteDeleteBase extends QueryPromise {
|
|
26857
26857
|
constructor(table, session, dialect, withList) {
|
|
26858
26858
|
super();
|
|
@@ -26922,7 +26922,7 @@ class SQLiteDeleteBase extends QueryPromise {
|
|
|
26922
26922
|
}
|
|
26923
26923
|
}
|
|
26924
26924
|
|
|
26925
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26925
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/casing.js
|
|
26926
26926
|
function toSnakeCase(input) {
|
|
26927
26927
|
const words = input.replace(/['\u2019]/g, "").match(/[\da-z]+|[A-Z]+(?![a-z])|[A-Z][\da-z]+/g) ?? [];
|
|
26928
26928
|
return words.map((word) => word.toLowerCase()).join("_");
|
|
@@ -26975,12 +26975,12 @@ class CasingCache {
|
|
|
26975
26975
|
}
|
|
26976
26976
|
}
|
|
26977
26977
|
|
|
26978
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26978
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/view-base.js
|
|
26979
26979
|
class SQLiteViewBase extends View {
|
|
26980
26980
|
static [entityKind] = "SQLiteViewBase";
|
|
26981
26981
|
}
|
|
26982
26982
|
|
|
26983
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
26983
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/dialect.js
|
|
26984
26984
|
class SQLiteDialect {
|
|
26985
26985
|
static [entityKind] = "SQLiteDialect";
|
|
26986
26986
|
casing;
|
|
@@ -27530,7 +27530,7 @@ class SQLiteSyncDialect extends SQLiteDialect {
|
|
|
27530
27530
|
}
|
|
27531
27531
|
}
|
|
27532
27532
|
|
|
27533
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
27533
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/query-builders/query-builder.js
|
|
27534
27534
|
class TypedQueryBuilder {
|
|
27535
27535
|
static [entityKind] = "TypedQueryBuilder";
|
|
27536
27536
|
getSelectedFields() {
|
|
@@ -27538,7 +27538,7 @@ class TypedQueryBuilder {
|
|
|
27538
27538
|
}
|
|
27539
27539
|
}
|
|
27540
27540
|
|
|
27541
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
27541
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/query-builders/select.js
|
|
27542
27542
|
class SQLiteSelectBuilder {
|
|
27543
27543
|
static [entityKind] = "SQLiteSelectBuilder";
|
|
27544
27544
|
fields;
|
|
@@ -27836,7 +27836,7 @@ var unionAll = createSetOperator("union", true);
|
|
|
27836
27836
|
var intersect = createSetOperator("intersect", false);
|
|
27837
27837
|
var except = createSetOperator("except", false);
|
|
27838
27838
|
|
|
27839
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
27839
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/query-builders/query-builder.js
|
|
27840
27840
|
class QueryBuilder {
|
|
27841
27841
|
static [entityKind] = "SQLiteQueryBuilder";
|
|
27842
27842
|
dialect;
|
|
@@ -27895,7 +27895,7 @@ class QueryBuilder {
|
|
|
27895
27895
|
}
|
|
27896
27896
|
}
|
|
27897
27897
|
|
|
27898
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
27898
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/query-builders/insert.js
|
|
27899
27899
|
class SQLiteInsertBuilder {
|
|
27900
27900
|
constructor(table, session, dialect, withList) {
|
|
27901
27901
|
this.table = table;
|
|
@@ -28004,7 +28004,7 @@ class SQLiteInsertBase extends QueryPromise {
|
|
|
28004
28004
|
}
|
|
28005
28005
|
}
|
|
28006
28006
|
|
|
28007
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
28007
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/query-builders/update.js
|
|
28008
28008
|
class SQLiteUpdateBuilder {
|
|
28009
28009
|
constructor(table, session, dialect, withList) {
|
|
28010
28010
|
this.table = table;
|
|
@@ -28108,7 +28108,7 @@ class SQLiteUpdateBase extends QueryPromise {
|
|
|
28108
28108
|
}
|
|
28109
28109
|
}
|
|
28110
28110
|
|
|
28111
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
28111
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/query-builders/count.js
|
|
28112
28112
|
class SQLiteCountBuilder extends SQL {
|
|
28113
28113
|
constructor(params) {
|
|
28114
28114
|
super(SQLiteCountBuilder.buildEmbeddedCount(params.source, params.filters).queryChunks);
|
|
@@ -28143,7 +28143,7 @@ class SQLiteCountBuilder extends SQL {
|
|
|
28143
28143
|
}
|
|
28144
28144
|
}
|
|
28145
28145
|
|
|
28146
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
28146
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/query-builders/query.js
|
|
28147
28147
|
class RelationalQueryBuilder {
|
|
28148
28148
|
constructor(mode, fullSchema, schema, tableNamesMap, table, tableConfig, dialect, session) {
|
|
28149
28149
|
this.mode = mode;
|
|
@@ -28237,7 +28237,7 @@ class SQLiteSyncRelationalQuery extends SQLiteRelationalQuery {
|
|
|
28237
28237
|
}
|
|
28238
28238
|
}
|
|
28239
28239
|
|
|
28240
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
28240
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/query-builders/raw.js
|
|
28241
28241
|
class SQLiteRaw extends QueryPromise {
|
|
28242
28242
|
constructor(execute, getSQL, action, dialect, mapBatchResult) {
|
|
28243
28243
|
super();
|
|
@@ -28263,7 +28263,7 @@ class SQLiteRaw extends QueryPromise {
|
|
|
28263
28263
|
}
|
|
28264
28264
|
}
|
|
28265
28265
|
|
|
28266
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
28266
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/db.js
|
|
28267
28267
|
class BaseSQLiteDatabase {
|
|
28268
28268
|
constructor(resultKind, dialect, session, schema) {
|
|
28269
28269
|
this.resultKind = resultKind;
|
|
@@ -28386,7 +28386,7 @@ class BaseSQLiteDatabase {
|
|
|
28386
28386
|
}
|
|
28387
28387
|
}
|
|
28388
28388
|
|
|
28389
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
28389
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/cache/core/cache.js
|
|
28390
28390
|
class Cache {
|
|
28391
28391
|
static [entityKind] = "Cache";
|
|
28392
28392
|
}
|
|
@@ -28412,7 +28412,7 @@ async function hashQuery(sql2, params) {
|
|
|
28412
28412
|
return hashHex;
|
|
28413
28413
|
}
|
|
28414
28414
|
|
|
28415
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
28415
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/sqlite-core/session.js
|
|
28416
28416
|
class ExecuteResultSync extends QueryPromise {
|
|
28417
28417
|
constructor(resultCb) {
|
|
28418
28418
|
super();
|
|
@@ -28585,7 +28585,7 @@ class SQLiteTransaction extends BaseSQLiteDatabase {
|
|
|
28585
28585
|
}
|
|
28586
28586
|
}
|
|
28587
28587
|
|
|
28588
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
28588
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/bun-sqlite/session.js
|
|
28589
28589
|
class SQLiteBunSession extends SQLiteSession {
|
|
28590
28590
|
constructor(client, dialect, schema, options = {}) {
|
|
28591
28591
|
super(dialect);
|
|
@@ -28684,7 +28684,7 @@ class PreparedQuery extends SQLitePreparedQuery {
|
|
|
28684
28684
|
}
|
|
28685
28685
|
}
|
|
28686
28686
|
|
|
28687
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
28687
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/bun-sqlite/driver.js
|
|
28688
28688
|
class BunSQLiteDatabase extends BaseSQLiteDatabase {
|
|
28689
28689
|
static [entityKind] = "BunSQLiteDatabase";
|
|
28690
28690
|
}
|
|
@@ -51863,8 +51863,8 @@ class EventNotifier {
|
|
|
51863
51863
|
}
|
|
51864
51864
|
var eventNotifier = new EventNotifier;
|
|
51865
51865
|
|
|
51866
|
-
// ../../apps/gateway/src/tmux/connection.ts
|
|
51867
|
-
|
|
51866
|
+
// ../../apps/gateway/src/tmux-client/local-external-connection.ts
|
|
51867
|
+
import { mkdirSync, rmSync } from "fs";
|
|
51868
51868
|
import { homedir } from "os";
|
|
51869
51869
|
|
|
51870
51870
|
// ../../apps/gateway/src/tmux/local-shell-path.ts
|
|
@@ -52021,669 +52021,953 @@ function buildLocalTmuxEnv(resolvedPath, baseEnv = process.env) {
|
|
|
52021
52021
|
return nextEnv;
|
|
52022
52022
|
}
|
|
52023
52023
|
|
|
52024
|
-
// ../../apps/gateway/src/tmux/
|
|
52025
|
-
function
|
|
52026
|
-
|
|
52027
|
-
return trimmed ? trimmed : undefined;
|
|
52024
|
+
// ../../apps/gateway/src/tmux-client/command-builder.ts
|
|
52025
|
+
function quoteShellArg(value) {
|
|
52026
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
52028
52027
|
}
|
|
52029
|
-
function
|
|
52030
|
-
|
|
52031
|
-
|
|
52032
|
-
|
|
52033
|
-
|
|
52034
|
-
|
|
52035
|
-
|
|
52036
|
-
|
|
52037
|
-
|
|
52028
|
+
function joinShellArgs(argv) {
|
|
52029
|
+
return argv.map((arg) => quoteShellArg(arg)).join(" ");
|
|
52030
|
+
}
|
|
52031
|
+
|
|
52032
|
+
// ../../apps/gateway/src/tmux-client/fs-paths.ts
|
|
52033
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
52034
|
+
import { join as join3 } from "path";
|
|
52035
|
+
var DEFAULT_ROOT_DIR = "/tmp/tmex";
|
|
52036
|
+
var DEFAULT_GATEWAY_RUNTIME_ID = randomUUID2();
|
|
52037
|
+
function toSafePathSegment(value) {
|
|
52038
|
+
return Array.from(value).map((char) => {
|
|
52039
|
+
if (/^[A-Za-z0-9._-]$/.test(char)) {
|
|
52040
|
+
return char;
|
|
52041
|
+
}
|
|
52042
|
+
return `_${char.codePointAt(0)?.toString(16) ?? "00"}_`;
|
|
52043
|
+
}).join("");
|
|
52044
|
+
}
|
|
52045
|
+
function createRuntimeFsPaths(options) {
|
|
52046
|
+
const baseRootDir = options.rootDir ?? DEFAULT_ROOT_DIR;
|
|
52047
|
+
const safeSessionName = toSafePathSegment(options.sessionName?.trim() || "tmex");
|
|
52048
|
+
const safeGatewayRuntimeId = toSafePathSegment(options.gatewayRuntimeId?.trim() || DEFAULT_GATEWAY_RUNTIME_ID);
|
|
52049
|
+
const runtimeDirName = `${toSafePathSegment(options.deviceId)}-${safeGatewayRuntimeId}-${options.gatewayPid}`;
|
|
52050
|
+
const runtimeRootDir = join3(baseRootDir, runtimeDirName);
|
|
52051
|
+
const panesDir = join3(runtimeRootDir, "panes");
|
|
52052
|
+
const hooksDir = join3(runtimeRootDir, "hooks");
|
|
52053
|
+
return {
|
|
52054
|
+
rootDir: runtimeRootDir,
|
|
52055
|
+
panesDir,
|
|
52056
|
+
hooksDir,
|
|
52057
|
+
hookFifoPath: join3(hooksDir, "events.fifo"),
|
|
52058
|
+
paneFifoPath(paneId) {
|
|
52059
|
+
return join3(panesDir, `${safeSessionName}-${toSafePathSegment(paneId)}.fifo`);
|
|
52038
52060
|
}
|
|
52061
|
+
};
|
|
52062
|
+
}
|
|
52063
|
+
|
|
52064
|
+
// ../../apps/gateway/src/tmux-client/input-encoder.ts
|
|
52065
|
+
var encoder = new TextEncoder;
|
|
52066
|
+
var SEND_KEYS_HEX_CHUNK_BYTES = 256;
|
|
52067
|
+
function encodeInputToHexChunks(input, chunkBytes = SEND_KEYS_HEX_CHUNK_BYTES) {
|
|
52068
|
+
const bytes = encoder.encode(input);
|
|
52069
|
+
const chunks = [];
|
|
52070
|
+
for (let offset = 0;offset < bytes.length; offset += chunkBytes) {
|
|
52071
|
+
const chunk2 = bytes.slice(offset, offset + chunkBytes);
|
|
52072
|
+
chunks.push(Array.from(chunk2, (byte) => byte.toString(16).padStart(2, "0")));
|
|
52039
52073
|
}
|
|
52040
|
-
return
|
|
52074
|
+
return chunks;
|
|
52041
52075
|
}
|
|
52042
|
-
|
|
52043
|
-
|
|
52076
|
+
|
|
52077
|
+
// ../../apps/gateway/src/tmux-client/pane-title-parser.ts
|
|
52078
|
+
var decoder = new TextDecoder;
|
|
52079
|
+
function createPaneTitleParser(options) {
|
|
52080
|
+
let phase = "normal";
|
|
52081
|
+
let oscKind = "";
|
|
52082
|
+
let titleBytes = [];
|
|
52083
|
+
return {
|
|
52084
|
+
push(data) {
|
|
52085
|
+
const output = [];
|
|
52086
|
+
for (const byte of data) {
|
|
52087
|
+
if (phase === "normal") {
|
|
52088
|
+
if (byte === 27) {
|
|
52089
|
+
phase = "esc";
|
|
52090
|
+
} else {
|
|
52091
|
+
output.push(byte);
|
|
52092
|
+
}
|
|
52093
|
+
continue;
|
|
52094
|
+
}
|
|
52095
|
+
if (phase === "esc") {
|
|
52096
|
+
if (byte === 93) {
|
|
52097
|
+
phase = "osc";
|
|
52098
|
+
oscKind = "";
|
|
52099
|
+
titleBytes = [];
|
|
52100
|
+
continue;
|
|
52101
|
+
}
|
|
52102
|
+
output.push(27, byte);
|
|
52103
|
+
phase = "normal";
|
|
52104
|
+
continue;
|
|
52105
|
+
}
|
|
52106
|
+
if (phase === "osc") {
|
|
52107
|
+
if (byte === 59) {
|
|
52108
|
+
phase = oscKind === "0" || oscKind === "2" ? "osc-data" : "normal";
|
|
52109
|
+
if (phase === "normal") {
|
|
52110
|
+
output.push(27, 93, ...encoderFromString(oscKind), 59);
|
|
52111
|
+
}
|
|
52112
|
+
continue;
|
|
52113
|
+
}
|
|
52114
|
+
oscKind += String.fromCharCode(byte);
|
|
52115
|
+
continue;
|
|
52116
|
+
}
|
|
52117
|
+
if (phase === "osc-data") {
|
|
52118
|
+
if (byte === 7) {
|
|
52119
|
+
emitTitle(options.onTitle, titleBytes);
|
|
52120
|
+
phase = "normal";
|
|
52121
|
+
continue;
|
|
52122
|
+
}
|
|
52123
|
+
if (byte === 27) {
|
|
52124
|
+
phase = "osc-st";
|
|
52125
|
+
continue;
|
|
52126
|
+
}
|
|
52127
|
+
titleBytes.push(byte);
|
|
52128
|
+
continue;
|
|
52129
|
+
}
|
|
52130
|
+
if (byte === 92) {
|
|
52131
|
+
emitTitle(options.onTitle, titleBytes);
|
|
52132
|
+
phase = "normal";
|
|
52133
|
+
continue;
|
|
52134
|
+
}
|
|
52135
|
+
titleBytes.push(27, byte);
|
|
52136
|
+
phase = "osc-data";
|
|
52137
|
+
}
|
|
52138
|
+
return new Uint8Array(output);
|
|
52139
|
+
}
|
|
52140
|
+
};
|
|
52141
|
+
}
|
|
52142
|
+
function emitTitle(onTitle, titleBytes) {
|
|
52143
|
+
const title = decoder.decode(new Uint8Array(titleBytes)).trim();
|
|
52144
|
+
if (!title) {
|
|
52044
52145
|
return;
|
|
52045
52146
|
}
|
|
52046
|
-
|
|
52047
|
-
|
|
52048
|
-
|
|
52049
|
-
|
|
52050
|
-
if (authMode === "agent") {
|
|
52051
|
-
throw new Error("SSH_AUTH_SOCK \u672A\u8BBE\u7F6E\uFF0C\u65E0\u6CD5\u4F7F\u7528 SSH Agent \u8BA4\u8BC1");
|
|
52052
|
-
}
|
|
52053
|
-
return;
|
|
52147
|
+
onTitle(title);
|
|
52148
|
+
}
|
|
52149
|
+
function encoderFromString(value) {
|
|
52150
|
+
return new TextEncoder().encode(value);
|
|
52054
52151
|
}
|
|
52055
52152
|
|
|
52056
|
-
// ../../apps/gateway/src/tmux/
|
|
52057
|
-
|
|
52058
|
-
|
|
52153
|
+
// ../../apps/gateway/src/tmux-client/local-external-connection.ts
|
|
52154
|
+
var DEFAULT_CHUNK_HISTORY_LINES = "-1000";
|
|
52155
|
+
var BELL_DEDUP_WINDOW_MS = 200;
|
|
52156
|
+
function shouldIgnoreReaderAbortError(error) {
|
|
52157
|
+
if (!error || typeof error !== "object") {
|
|
52059
52158
|
return false;
|
|
52060
52159
|
}
|
|
52061
|
-
|
|
52062
|
-
|
|
52063
|
-
|
|
52160
|
+
const maybeError = error;
|
|
52161
|
+
return maybeError.name === "AbortError" && maybeError.code === "ERR_STREAM_RELEASE_LOCK" && typeof maybeError.message === "string" && maybeError.message.includes("releaseLock");
|
|
52162
|
+
}
|
|
52163
|
+
function defaultRun(argv) {
|
|
52164
|
+
return new Promise((resolve, reject) => {
|
|
52165
|
+
const subprocess = Bun.spawn(argv, {
|
|
52166
|
+
env: buildLocalTmuxEnv(getLocalShellPath()),
|
|
52167
|
+
stdout: "pipe",
|
|
52168
|
+
stderr: "pipe"
|
|
52169
|
+
});
|
|
52170
|
+
Promise.all([
|
|
52171
|
+
new Response(subprocess.stdout).text(),
|
|
52172
|
+
new Response(subprocess.stderr).text(),
|
|
52173
|
+
subprocess.exited
|
|
52174
|
+
]).then(([stdout, stderr, exitCode]) => {
|
|
52175
|
+
resolve({ stdout, stderr, exitCode });
|
|
52176
|
+
}).catch(reject);
|
|
52177
|
+
});
|
|
52178
|
+
}
|
|
52179
|
+
|
|
52180
|
+
class LocalExternalTmuxConnection {
|
|
52181
|
+
deviceId;
|
|
52182
|
+
deps;
|
|
52183
|
+
callbacks;
|
|
52184
|
+
device = null;
|
|
52185
|
+
sessionName = "tmex";
|
|
52186
|
+
connected = false;
|
|
52187
|
+
manualDisconnect = false;
|
|
52188
|
+
activeWindowId = null;
|
|
52189
|
+
activePaneId = null;
|
|
52190
|
+
pendingPaneTitles = new Map;
|
|
52191
|
+
snapshotSession = null;
|
|
52192
|
+
snapshotWindows = new Map;
|
|
52193
|
+
currentPipePaneId = null;
|
|
52194
|
+
pipeReadAbort = null;
|
|
52195
|
+
pipeTransition = Promise.resolve();
|
|
52196
|
+
inputTransition = Promise.resolve();
|
|
52197
|
+
hookReadAbort = null;
|
|
52198
|
+
hookBuffer = "";
|
|
52199
|
+
bellDedup = new Map;
|
|
52200
|
+
fsPaths = createRuntimeFsPaths({
|
|
52201
|
+
deviceId: "pending",
|
|
52202
|
+
sessionName: "pending",
|
|
52203
|
+
gatewayPid: process.pid
|
|
52204
|
+
});
|
|
52205
|
+
constructor(options, inputDeps = {}) {
|
|
52206
|
+
this.deviceId = options.deviceId;
|
|
52207
|
+
this.callbacks = options;
|
|
52208
|
+
this.deps = {
|
|
52209
|
+
enableHooks: inputDeps.enableHooks ?? true,
|
|
52210
|
+
getDevice: inputDeps.getDevice ?? ((deviceId) => getDeviceById(deviceId)),
|
|
52211
|
+
run: inputDeps.run ?? defaultRun
|
|
52212
|
+
};
|
|
52213
|
+
}
|
|
52214
|
+
async connect() {
|
|
52215
|
+
this.manualDisconnect = false;
|
|
52216
|
+
this.device = this.deps.getDevice(this.deviceId);
|
|
52217
|
+
if (!this.device) {
|
|
52218
|
+
throw new Error(`Device not found: ${this.deviceId}`);
|
|
52219
|
+
}
|
|
52220
|
+
if (this.device.type !== "local") {
|
|
52221
|
+
throw new Error(`LocalExternalTmuxConnection only supports local device: ${this.deviceId}`);
|
|
52222
|
+
}
|
|
52223
|
+
this.sessionName = this.device.session?.trim() || "tmex";
|
|
52224
|
+
this.fsPaths = createRuntimeFsPaths({
|
|
52225
|
+
deviceId: this.deviceId,
|
|
52226
|
+
sessionName: this.sessionName,
|
|
52227
|
+
gatewayPid: process.pid
|
|
52228
|
+
});
|
|
52229
|
+
this.ensureRuntimeDirs();
|
|
52230
|
+
await this.ensureSession();
|
|
52231
|
+
if (this.deps.enableHooks) {
|
|
52232
|
+
await this.startHooks();
|
|
52064
52233
|
}
|
|
52234
|
+
this.connected = true;
|
|
52235
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
52236
|
+
lastSeenAt: new Date().toISOString(),
|
|
52237
|
+
tmuxAvailable: true,
|
|
52238
|
+
lastError: null
|
|
52239
|
+
});
|
|
52240
|
+
await this.requestSnapshotInternal();
|
|
52065
52241
|
}
|
|
52066
|
-
|
|
52067
|
-
|
|
52068
|
-
|
|
52069
|
-
const bytes = [];
|
|
52070
|
-
const encoder = new TextEncoder;
|
|
52071
|
-
let cursor = 0;
|
|
52072
|
-
for (let i = 0;i < value.length; i++) {
|
|
52073
|
-
if (value[i] !== "\\") {
|
|
52074
|
-
continue;
|
|
52242
|
+
disconnect() {
|
|
52243
|
+
if (!this.connected && this.manualDisconnect) {
|
|
52244
|
+
return;
|
|
52075
52245
|
}
|
|
52076
|
-
|
|
52077
|
-
|
|
52078
|
-
|
|
52246
|
+
this.manualDisconnect = true;
|
|
52247
|
+
this.connected = false;
|
|
52248
|
+
this.stopPipe();
|
|
52249
|
+
if (this.deps.enableHooks) {
|
|
52250
|
+
this.stopHooks();
|
|
52079
52251
|
}
|
|
52080
|
-
|
|
52081
|
-
|
|
52082
|
-
|
|
52083
|
-
|
|
52084
|
-
|
|
52085
|
-
|
|
52086
|
-
|
|
52087
|
-
|
|
52088
|
-
|
|
52089
|
-
|
|
52090
|
-
|
|
52091
|
-
|
|
52092
|
-
function stripTmuxDcsWrapper(line) {
|
|
52093
|
-
let cleanLine = line;
|
|
52094
|
-
cleanLine = cleanLine.replace(/^\u001bP\d+p/, "");
|
|
52095
|
-
if (cleanLine.endsWith("\x1B\\")) {
|
|
52096
|
-
cleanLine = cleanLine.slice(0, -2);
|
|
52097
|
-
} else if (cleanLine.endsWith("\x9C")) {
|
|
52098
|
-
cleanLine = cleanLine.slice(0, -1);
|
|
52099
|
-
}
|
|
52100
|
-
return cleanLine;
|
|
52101
|
-
}
|
|
52102
|
-
|
|
52103
|
-
class TmuxControlParser {
|
|
52104
|
-
buffer = "";
|
|
52105
|
-
onEvent;
|
|
52106
|
-
onTerminalOutput;
|
|
52107
|
-
onPaneTitle;
|
|
52108
|
-
onOutputBlockBegin;
|
|
52109
|
-
onOutputBlock;
|
|
52110
|
-
onNonControlOutput;
|
|
52111
|
-
onExit;
|
|
52112
|
-
onReady;
|
|
52113
|
-
inOutputBlock = false;
|
|
52114
|
-
outputBlockMeta = null;
|
|
52115
|
-
outputBlockLines = [];
|
|
52116
|
-
readyNotified = false;
|
|
52117
|
-
lastOutputEndedWithCR = false;
|
|
52118
|
-
lastOutputFrame = null;
|
|
52119
|
-
outputTitleStates = new Map;
|
|
52120
|
-
constructor(options) {
|
|
52121
|
-
this.onEvent = options.onEvent;
|
|
52122
|
-
this.onTerminalOutput = options.onTerminalOutput;
|
|
52123
|
-
this.onPaneTitle = options.onPaneTitle;
|
|
52124
|
-
this.onOutputBlockBegin = options.onOutputBlockBegin;
|
|
52125
|
-
this.onOutputBlock = options.onOutputBlock;
|
|
52126
|
-
this.onNonControlOutput = options.onNonControlOutput;
|
|
52127
|
-
this.onExit = options.onExit;
|
|
52128
|
-
this.onReady = options.onReady;
|
|
52129
|
-
}
|
|
52130
|
-
processData(data) {
|
|
52131
|
-
const text2 = typeof data === "string" ? data : new TextDecoder().decode(data);
|
|
52132
|
-
this.buffer += text2;
|
|
52133
|
-
this.parseBuffer();
|
|
52134
|
-
}
|
|
52135
|
-
parseBuffer() {
|
|
52136
|
-
while (true) {
|
|
52137
|
-
const nlIndex = this.buffer.indexOf(`
|
|
52138
|
-
`);
|
|
52139
|
-
if (nlIndex === -1)
|
|
52140
|
-
break;
|
|
52141
|
-
let line = this.buffer.slice(0, nlIndex);
|
|
52142
|
-
this.buffer = this.buffer.slice(nlIndex + 1);
|
|
52143
|
-
if (line.endsWith("\r")) {
|
|
52144
|
-
line = line.slice(0, -1);
|
|
52145
|
-
}
|
|
52146
|
-
if (line) {
|
|
52147
|
-
this.parseLine(line);
|
|
52252
|
+
rmSync(this.fsPaths.rootDir, { recursive: true, force: true });
|
|
52253
|
+
}
|
|
52254
|
+
requestSnapshot() {
|
|
52255
|
+
this.requestSnapshotInternal();
|
|
52256
|
+
}
|
|
52257
|
+
sendInput(paneId, data) {
|
|
52258
|
+
if (!this.connected) {
|
|
52259
|
+
return;
|
|
52260
|
+
}
|
|
52261
|
+
const task = async () => {
|
|
52262
|
+
for (const chunk2 of encodeInputToHexChunks(data)) {
|
|
52263
|
+
await this.runTmux(["send-keys", "-H", "-t", paneId, ...chunk2]);
|
|
52148
52264
|
}
|
|
52265
|
+
};
|
|
52266
|
+
const next = this.inputTransition.catch(() => {
|
|
52267
|
+
return;
|
|
52268
|
+
}).then(task);
|
|
52269
|
+
this.inputTransition = next;
|
|
52270
|
+
next.catch((error) => {
|
|
52271
|
+
this.callbacks.onError(error);
|
|
52272
|
+
});
|
|
52273
|
+
}
|
|
52274
|
+
resizePane(paneId, cols, rows) {
|
|
52275
|
+
if (!this.connected) {
|
|
52276
|
+
return;
|
|
52149
52277
|
}
|
|
52278
|
+
this.resizePaneInternal(paneId, cols, rows).catch((error) => {
|
|
52279
|
+
this.callbacks.onError(error);
|
|
52280
|
+
});
|
|
52150
52281
|
}
|
|
52151
|
-
|
|
52152
|
-
|
|
52153
|
-
if (this.inOutputBlock) {
|
|
52154
|
-
if (cleanLine.startsWith("%end") || cleanLine.startsWith("%error")) {
|
|
52155
|
-
this.finishOutputBlock(cleanLine);
|
|
52156
|
-
return;
|
|
52157
|
-
}
|
|
52158
|
-
this.outputBlockLines.push(cleanLine);
|
|
52282
|
+
selectPane(windowId, paneId) {
|
|
52283
|
+
if (!this.connected) {
|
|
52159
52284
|
return;
|
|
52160
52285
|
}
|
|
52161
|
-
|
|
52286
|
+
this.selectPaneInternal(windowId, paneId, null).catch((error) => {
|
|
52287
|
+
this.callbacks.onError(error);
|
|
52288
|
+
});
|
|
52289
|
+
}
|
|
52290
|
+
selectPaneWithSize(windowId, paneId, cols, rows) {
|
|
52291
|
+
if (!this.connected) {
|
|
52292
|
+
return;
|
|
52293
|
+
}
|
|
52294
|
+
this.selectPaneInternal(windowId, paneId, { cols, rows }).catch((error) => {
|
|
52295
|
+
this.callbacks.onError(error);
|
|
52296
|
+
});
|
|
52297
|
+
}
|
|
52298
|
+
selectWindow(windowId) {
|
|
52299
|
+
if (!this.connected) {
|
|
52162
52300
|
return;
|
|
52163
|
-
|
|
52164
|
-
|
|
52301
|
+
}
|
|
52302
|
+
this.runAndRefresh(["select-window", "-t", windowId]).catch((error) => {
|
|
52303
|
+
this.callbacks.onError(error);
|
|
52304
|
+
});
|
|
52305
|
+
}
|
|
52306
|
+
createWindow(name) {
|
|
52307
|
+
if (!this.connected) {
|
|
52165
52308
|
return;
|
|
52166
52309
|
}
|
|
52167
|
-
|
|
52168
|
-
|
|
52169
|
-
this.
|
|
52310
|
+
const argv = name ? ["new-window", "-n", name] : ["new-window"];
|
|
52311
|
+
this.runAndRefresh(argv).catch((error) => {
|
|
52312
|
+
this.callbacks.onError(error);
|
|
52313
|
+
});
|
|
52314
|
+
}
|
|
52315
|
+
closeWindow(windowId) {
|
|
52316
|
+
if (!this.connected) {
|
|
52170
52317
|
return;
|
|
52171
52318
|
}
|
|
52172
|
-
this.
|
|
52173
|
-
|
|
52319
|
+
this.closeWindowInternal(windowId).catch((error) => {
|
|
52320
|
+
this.callbacks.onError(error);
|
|
52321
|
+
});
|
|
52174
52322
|
}
|
|
52175
|
-
|
|
52176
|
-
if (this.
|
|
52323
|
+
closePane(paneId) {
|
|
52324
|
+
if (!this.connected) {
|
|
52177
52325
|
return;
|
|
52178
|
-
|
|
52179
|
-
this.
|
|
52326
|
+
}
|
|
52327
|
+
this.runAndRefresh(["kill-pane", "-t", paneId], true).catch((error) => {
|
|
52328
|
+
this.callbacks.onError(error);
|
|
52329
|
+
});
|
|
52180
52330
|
}
|
|
52181
|
-
|
|
52182
|
-
|
|
52183
|
-
if (!meta) {
|
|
52331
|
+
renameWindow(windowId, name) {
|
|
52332
|
+
if (!this.connected) {
|
|
52184
52333
|
return;
|
|
52185
52334
|
}
|
|
52186
|
-
this.
|
|
52187
|
-
|
|
52188
|
-
|
|
52189
|
-
this.onOutputBlockBegin?.(meta);
|
|
52335
|
+
this.runAndRefresh(["rename-window", "-t", windowId, name]).catch((error) => {
|
|
52336
|
+
this.callbacks.onError(error);
|
|
52337
|
+
});
|
|
52190
52338
|
}
|
|
52191
|
-
|
|
52192
|
-
const
|
|
52193
|
-
|
|
52194
|
-
|
|
52195
|
-
this.outputBlockMeta = null;
|
|
52196
|
-
if (currentMeta && meta) {
|
|
52197
|
-
this.onOutputBlock?.({
|
|
52198
|
-
time: currentMeta.time,
|
|
52199
|
-
commandNo: currentMeta.commandNo,
|
|
52200
|
-
flags: currentMeta.flags,
|
|
52201
|
-
lines: this.outputBlockLines,
|
|
52202
|
-
isError: line.startsWith("%error")
|
|
52203
|
-
});
|
|
52339
|
+
async ensureSession() {
|
|
52340
|
+
const exists3 = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
|
|
52341
|
+
if (exists3.exitCode === 0) {
|
|
52342
|
+
return;
|
|
52204
52343
|
}
|
|
52205
|
-
this.
|
|
52206
|
-
this.notifyReady();
|
|
52344
|
+
await this.runTmux(["new-session", "-d", "-c", homedir(), "-s", this.sessionName]);
|
|
52207
52345
|
}
|
|
52208
|
-
|
|
52209
|
-
|
|
52210
|
-
|
|
52211
|
-
|
|
52212
|
-
|
|
52213
|
-
|
|
52214
|
-
|
|
52215
|
-
|
|
52216
|
-
|
|
52217
|
-
|
|
52218
|
-
const
|
|
52219
|
-
|
|
52220
|
-
|
|
52221
|
-
|
|
52346
|
+
ensureRuntimeDirs() {
|
|
52347
|
+
mkdirSync(this.fsPaths.rootDir, { recursive: true, mode: 448 });
|
|
52348
|
+
mkdirSync(this.fsPaths.panesDir, { recursive: true, mode: 448 });
|
|
52349
|
+
mkdirSync(this.fsPaths.hooksDir, { recursive: true, mode: 448 });
|
|
52350
|
+
}
|
|
52351
|
+
async startHooks() {
|
|
52352
|
+
this.ensureRuntimeDirs();
|
|
52353
|
+
const fifoPath = this.fsPaths.hookFifoPath;
|
|
52354
|
+
rmSync(fifoPath, { force: true });
|
|
52355
|
+
await this.runShell(`mkfifo ${quoteShellArg(fifoPath)}`);
|
|
52356
|
+
const readerProcess = Bun.spawn(["/bin/sh", "-lc", `tail -n +1 -f ${quoteShellArg(fifoPath)}`], {
|
|
52357
|
+
stdout: "pipe",
|
|
52358
|
+
stderr: "pipe"
|
|
52359
|
+
});
|
|
52360
|
+
const reader = readerProcess.stdout.getReader();
|
|
52361
|
+
(async () => {
|
|
52362
|
+
try {
|
|
52363
|
+
while (true) {
|
|
52364
|
+
const chunk2 = await reader.read();
|
|
52365
|
+
if (chunk2.done) {
|
|
52366
|
+
break;
|
|
52367
|
+
}
|
|
52368
|
+
this.handleHookChunk(new TextDecoder().decode(chunk2.value));
|
|
52369
|
+
}
|
|
52370
|
+
} catch (error) {
|
|
52371
|
+
if (!this.manualDisconnect && !shouldIgnoreReaderAbortError(error)) {
|
|
52372
|
+
this.callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
52373
|
+
}
|
|
52374
|
+
}
|
|
52375
|
+
})();
|
|
52376
|
+
this.hookReadAbort = () => {
|
|
52377
|
+
reader.releaseLock();
|
|
52378
|
+
readerProcess.kill();
|
|
52379
|
+
rmSync(fifoPath, { force: true });
|
|
52380
|
+
};
|
|
52381
|
+
await this.installHook("alert-bell", ["bell", "#{window_id}", "#{pane_id}"]);
|
|
52382
|
+
await this.installHook("pane-exited", ["pane-exited", "#{window_id}", "#{pane_id}"]);
|
|
52383
|
+
await this.installHook("pane-died", ["pane-died", "#{window_id}", "#{pane_id}"]);
|
|
52384
|
+
}
|
|
52385
|
+
async stopHooks() {
|
|
52386
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "alert-bell"]);
|
|
52387
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-exited"]);
|
|
52388
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-died"]);
|
|
52389
|
+
this.hookReadAbort?.();
|
|
52390
|
+
this.hookReadAbort = null;
|
|
52391
|
+
this.hookBuffer = "";
|
|
52392
|
+
}
|
|
52393
|
+
async installHook(hookName, fields) {
|
|
52394
|
+
const fifoPath = this.fsPaths.hookFifoPath;
|
|
52395
|
+
const innerScript = `printf '%s\\t%s\\t%s\\n' ${fields.map((field) => quoteShellArg(field)).join(" ")} >> ${quoteShellArg(fifoPath)}`;
|
|
52396
|
+
await this.runTmux([
|
|
52397
|
+
"set-hook",
|
|
52398
|
+
"-t",
|
|
52399
|
+
this.sessionName,
|
|
52400
|
+
hookName,
|
|
52401
|
+
`run-shell -b ${quoteShellArg(innerScript)}`
|
|
52402
|
+
]);
|
|
52222
52403
|
}
|
|
52223
|
-
|
|
52224
|
-
|
|
52225
|
-
|
|
52226
|
-
|
|
52227
|
-
|
|
52228
|
-
if (
|
|
52229
|
-
|
|
52404
|
+
handleHookChunk(text2) {
|
|
52405
|
+
this.hookBuffer += text2;
|
|
52406
|
+
while (true) {
|
|
52407
|
+
const newlineIndex = this.hookBuffer.indexOf(`
|
|
52408
|
+
`);
|
|
52409
|
+
if (newlineIndex < 0) {
|
|
52410
|
+
return;
|
|
52411
|
+
}
|
|
52412
|
+
const line = this.hookBuffer.slice(0, newlineIndex).trim();
|
|
52413
|
+
this.hookBuffer = this.hookBuffer.slice(newlineIndex + 1);
|
|
52414
|
+
if (!line) {
|
|
52415
|
+
continue;
|
|
52416
|
+
}
|
|
52417
|
+
const [type, windowId, paneId] = line.split("\t");
|
|
52418
|
+
if (type === "bell") {
|
|
52419
|
+
const key = paneId || windowId || "-";
|
|
52420
|
+
const previous = this.bellDedup.get(key) ?? 0;
|
|
52421
|
+
const now = Date.now();
|
|
52422
|
+
if (now - previous >= BELL_DEDUP_WINDOW_MS) {
|
|
52423
|
+
this.bellDedup.set(key, now);
|
|
52424
|
+
this.callbacks.onEvent({
|
|
52425
|
+
type: "bell",
|
|
52426
|
+
data: {
|
|
52427
|
+
windowId: windowId || undefined,
|
|
52428
|
+
paneId: paneId || this.activePaneId || undefined
|
|
52429
|
+
}
|
|
52430
|
+
});
|
|
52431
|
+
}
|
|
52432
|
+
continue;
|
|
52433
|
+
}
|
|
52434
|
+
if (type === "pane-exited" || type === "pane-died") {
|
|
52435
|
+
this.requestSnapshot();
|
|
52230
52436
|
}
|
|
52231
|
-
previousWasCR = byte === 13;
|
|
52232
52437
|
}
|
|
52233
|
-
|
|
52234
|
-
|
|
52235
|
-
|
|
52438
|
+
}
|
|
52439
|
+
async runAndRefresh(argv, allowTargetMissing = false) {
|
|
52440
|
+
await this.runTmux(argv, allowTargetMissing);
|
|
52441
|
+
await this.requestSnapshotInternal();
|
|
52442
|
+
}
|
|
52443
|
+
async closeWindowInternal(windowId) {
|
|
52444
|
+
const count2 = Number.parseInt((await this.runTmux(["display-message", "-p", "-t", this.sessionName, "#{session_windows}"])).stdout.trim() || "0", 10);
|
|
52445
|
+
if (count2 <= 1) {
|
|
52446
|
+
await this.runTmux(["new-window", "-d", "-t", this.sessionName]);
|
|
52236
52447
|
}
|
|
52237
|
-
|
|
52238
|
-
|
|
52239
|
-
|
|
52240
|
-
|
|
52241
|
-
|
|
52242
|
-
|
|
52243
|
-
|
|
52244
|
-
|
|
52245
|
-
normalized[writeIndex] = byte;
|
|
52246
|
-
writeIndex += 1;
|
|
52247
|
-
previousWasCR = byte === 13;
|
|
52448
|
+
await this.runAndRefresh(["kill-window", "-t", windowId], true);
|
|
52449
|
+
}
|
|
52450
|
+
async resizePaneInternal(paneId, cols, rows) {
|
|
52451
|
+
const safeCols = Math.max(2, Math.floor(cols));
|
|
52452
|
+
const safeRows = Math.max(2, Math.floor(rows));
|
|
52453
|
+
const windowId = this.findPaneWindowId(paneId) ?? (await this.runTmux(["display-message", "-p", "-t", paneId, "#{window_id}"], true)).stdout.trim();
|
|
52454
|
+
if (!windowId) {
|
|
52455
|
+
return;
|
|
52248
52456
|
}
|
|
52249
|
-
|
|
52457
|
+
await this.runTmux(["resize-window", "-t", windowId, "-x", String(safeCols), "-y", String(safeRows)], true);
|
|
52458
|
+
await this.requestSnapshotInternal();
|
|
52250
52459
|
}
|
|
52251
|
-
|
|
52252
|
-
|
|
52253
|
-
|
|
52254
|
-
|
|
52460
|
+
async selectPaneInternal(windowId, paneId, size) {
|
|
52461
|
+
this.activeWindowId = windowId;
|
|
52462
|
+
this.activePaneId = paneId;
|
|
52463
|
+
await this.runTmux(["select-window", "-t", windowId], true);
|
|
52464
|
+
await this.runTmux(["select-pane", "-t", paneId], true);
|
|
52465
|
+
await this.startPipeForPane(paneId);
|
|
52466
|
+
if (size) {
|
|
52467
|
+
await this.resizePaneInternal(paneId, size.cols, size.rows);
|
|
52468
|
+
}
|
|
52469
|
+
this.callbacks.onEvent({
|
|
52470
|
+
type: "pane-active",
|
|
52471
|
+
data: { windowId, paneId }
|
|
52472
|
+
});
|
|
52473
|
+
await this.capturePaneHistory(paneId);
|
|
52474
|
+
await this.requestSnapshotInternal();
|
|
52475
|
+
}
|
|
52476
|
+
async capturePaneHistory(paneId) {
|
|
52477
|
+
const mode = (await this.runTmux(["display-message", "-p", "-t", paneId, "#{alternate_on}"], true)).stdout.trim();
|
|
52478
|
+
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", DEFAULT_CHUNK_HISTORY_LINES, "-e", "-p"], true)).stdout;
|
|
52479
|
+
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", DEFAULT_CHUNK_HISTORY_LINES, "-e", "-p", "-q"], true)).stdout;
|
|
52480
|
+
const history = normal || alternate;
|
|
52481
|
+
if (history) {
|
|
52482
|
+
this.callbacks.onTerminalHistory(paneId, history);
|
|
52255
52483
|
}
|
|
52256
|
-
const created = {
|
|
52257
|
-
phase: "normal",
|
|
52258
|
-
titleBytes: []
|
|
52259
|
-
};
|
|
52260
|
-
this.outputTitleStates.set(paneId, created);
|
|
52261
|
-
return created;
|
|
52262
52484
|
}
|
|
52263
|
-
|
|
52264
|
-
if (
|
|
52485
|
+
async requestSnapshotInternal() {
|
|
52486
|
+
if (!this.connected) {
|
|
52265
52487
|
return;
|
|
52266
52488
|
}
|
|
52267
|
-
const
|
|
52268
|
-
|
|
52489
|
+
const [sessionRes, windowsRes, panesRes] = await Promise.all([
|
|
52490
|
+
this.runTmuxAllowFailure([
|
|
52491
|
+
"display-message",
|
|
52492
|
+
"-p",
|
|
52493
|
+
"-t",
|
|
52494
|
+
this.sessionName,
|
|
52495
|
+
"#{session_id}\t#{session_name}"
|
|
52496
|
+
]),
|
|
52497
|
+
this.runTmuxAllowFailure([
|
|
52498
|
+
"list-windows",
|
|
52499
|
+
"-t",
|
|
52500
|
+
this.sessionName,
|
|
52501
|
+
"-F",
|
|
52502
|
+
"#{window_id}\t#{window_index}\t#{window_name}\t#{window_active}"
|
|
52503
|
+
]),
|
|
52504
|
+
this.runTmuxAllowFailure([
|
|
52505
|
+
"list-panes",
|
|
52506
|
+
"-t",
|
|
52507
|
+
this.sessionName,
|
|
52508
|
+
"-F",
|
|
52509
|
+
"#{pane_id}\t#{window_id}\t#{pane_index}\t#{pane_title}\t#{pane_active}\t#{pane_width}\t#{pane_height}"
|
|
52510
|
+
])
|
|
52511
|
+
]);
|
|
52512
|
+
if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
|
|
52513
|
+
this.callbacks.onSnapshot({ deviceId: this.deviceId, session: null });
|
|
52269
52514
|
return;
|
|
52270
52515
|
}
|
|
52271
|
-
this.
|
|
52516
|
+
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
52517
|
+
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
52518
|
+
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
52519
|
+
this.emitSnapshot();
|
|
52272
52520
|
}
|
|
52273
|
-
|
|
52274
|
-
|
|
52275
|
-
|
|
52276
|
-
|
|
52277
|
-
const parseState = this.getTitleParseState(paneId);
|
|
52278
|
-
const output = [];
|
|
52279
|
-
let phase = parseState.phase;
|
|
52280
|
-
const titleBytes = parseState.titleBytes;
|
|
52281
|
-
for (const byte of data) {
|
|
52282
|
-
if (phase === "normal") {
|
|
52283
|
-
if (byte === 27) {
|
|
52284
|
-
phase = "esc";
|
|
52285
|
-
} else {
|
|
52286
|
-
output.push(byte);
|
|
52287
|
-
}
|
|
52521
|
+
parseSnapshotSession(lines) {
|
|
52522
|
+
this.snapshotSession = null;
|
|
52523
|
+
for (const line of lines) {
|
|
52524
|
+
if (!line.trim()) {
|
|
52288
52525
|
continue;
|
|
52289
52526
|
}
|
|
52290
|
-
|
|
52291
|
-
|
|
52292
|
-
|
|
52293
|
-
|
|
52294
|
-
|
|
52295
|
-
|
|
52296
|
-
|
|
52297
|
-
|
|
52298
|
-
|
|
52299
|
-
|
|
52300
|
-
|
|
52301
|
-
phase = "normal";
|
|
52302
|
-
}
|
|
52527
|
+
const [id, name] = line.split("\t");
|
|
52528
|
+
if (id) {
|
|
52529
|
+
this.snapshotSession = { id, name: name ?? "" };
|
|
52530
|
+
}
|
|
52531
|
+
return;
|
|
52532
|
+
}
|
|
52533
|
+
}
|
|
52534
|
+
parseSnapshotWindows(lines) {
|
|
52535
|
+
this.snapshotWindows.clear();
|
|
52536
|
+
for (const line of lines) {
|
|
52537
|
+
if (!line.trim()) {
|
|
52303
52538
|
continue;
|
|
52304
52539
|
}
|
|
52305
|
-
|
|
52306
|
-
|
|
52307
|
-
phase = "titleEsc";
|
|
52308
|
-
} else {
|
|
52309
|
-
titleBytes.push(byte);
|
|
52310
|
-
}
|
|
52540
|
+
const [id, indexRaw, name, activeRaw] = line.split("\t");
|
|
52541
|
+
if (!id) {
|
|
52311
52542
|
continue;
|
|
52312
52543
|
}
|
|
52313
|
-
|
|
52314
|
-
|
|
52315
|
-
|
|
52316
|
-
|
|
52317
|
-
} else if (byte === 27) {
|
|
52318
|
-
phase = "titleEsc";
|
|
52319
|
-
} else {
|
|
52320
|
-
titleBytes.push(27, byte);
|
|
52321
|
-
phase = "title";
|
|
52544
|
+
const index = Number.parseInt(indexRaw ?? "", 10);
|
|
52545
|
+
const active = activeRaw === "1";
|
|
52546
|
+
if (active) {
|
|
52547
|
+
this.activeWindowId = id;
|
|
52322
52548
|
}
|
|
52549
|
+
this.snapshotWindows.set(id, {
|
|
52550
|
+
id,
|
|
52551
|
+
index: Number.isNaN(index) ? 0 : index,
|
|
52552
|
+
name: name ?? "",
|
|
52553
|
+
active,
|
|
52554
|
+
panes: []
|
|
52555
|
+
});
|
|
52323
52556
|
}
|
|
52324
|
-
parseState.phase = phase;
|
|
52325
|
-
return new Uint8Array(output);
|
|
52326
52557
|
}
|
|
52327
|
-
|
|
52328
|
-
const
|
|
52329
|
-
|
|
52330
|
-
if (!isCrossModeDuplicate) {
|
|
52331
|
-
this.onTerminalOutput(paneId, data);
|
|
52558
|
+
parseSnapshotPanes(lines) {
|
|
52559
|
+
for (const window2 of this.snapshotWindows.values()) {
|
|
52560
|
+
window2.panes = [];
|
|
52332
52561
|
}
|
|
52333
|
-
|
|
52334
|
-
|
|
52335
|
-
|
|
52336
|
-
data: data.slice()
|
|
52337
|
-
};
|
|
52338
|
-
}
|
|
52339
|
-
parseControlLine(line) {
|
|
52340
|
-
const spaceIndex = line.indexOf(" ");
|
|
52341
|
-
const command = spaceIndex === -1 ? line : line.slice(0, spaceIndex);
|
|
52342
|
-
const args = spaceIndex === -1 ? "" : line.slice(spaceIndex + 1);
|
|
52343
|
-
switch (command) {
|
|
52344
|
-
case "%window-add":
|
|
52345
|
-
case "%unlinked-window-add":
|
|
52346
|
-
this.onEvent({ type: "window-add", data: { windowId: args } });
|
|
52347
|
-
break;
|
|
52348
|
-
case "%window-close":
|
|
52349
|
-
case "%unlinked-window-close":
|
|
52350
|
-
this.onEvent({ type: "window-close", data: { windowId: args } });
|
|
52351
|
-
break;
|
|
52352
|
-
case "%window-renamed":
|
|
52353
|
-
case "%unlinked-window-renamed": {
|
|
52354
|
-
const parts = this.parseArgs(args);
|
|
52355
|
-
this.onEvent({
|
|
52356
|
-
type: "window-renamed",
|
|
52357
|
-
data: { windowId: parts[0], name: parts[1] }
|
|
52358
|
-
});
|
|
52359
|
-
break;
|
|
52562
|
+
for (const line of lines) {
|
|
52563
|
+
if (!line.trim()) {
|
|
52564
|
+
continue;
|
|
52360
52565
|
}
|
|
52361
|
-
|
|
52362
|
-
|
|
52363
|
-
|
|
52364
|
-
type: "pane-active",
|
|
52365
|
-
data: { windowId: parts[0], paneId: parts[1] }
|
|
52366
|
-
});
|
|
52367
|
-
break;
|
|
52566
|
+
const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw] = line.split("\t");
|
|
52567
|
+
if (!paneId || !windowId) {
|
|
52568
|
+
continue;
|
|
52368
52569
|
}
|
|
52369
|
-
|
|
52370
|
-
|
|
52371
|
-
|
|
52372
|
-
|
|
52373
|
-
|
|
52374
|
-
|
|
52375
|
-
|
|
52376
|
-
|
|
52377
|
-
|
|
52378
|
-
|
|
52379
|
-
|
|
52380
|
-
|
|
52381
|
-
|
|
52570
|
+
const index = Number.parseInt(indexRaw ?? "", 10);
|
|
52571
|
+
const width = Number.parseInt(widthRaw ?? "", 10);
|
|
52572
|
+
const height = Number.parseInt(heightRaw ?? "", 10);
|
|
52573
|
+
const pane = {
|
|
52574
|
+
id: paneId,
|
|
52575
|
+
windowId,
|
|
52576
|
+
index: Number.isNaN(index) ? 0 : index,
|
|
52577
|
+
title: this.pendingPaneTitles.get(paneId) ?? (titleRaw?.trim() ? titleRaw : undefined),
|
|
52578
|
+
active: activeRaw === "1",
|
|
52579
|
+
width: Number.isNaN(width) ? 0 : width,
|
|
52580
|
+
height: Number.isNaN(height) ? 0 : height
|
|
52581
|
+
};
|
|
52582
|
+
if (pane.active) {
|
|
52583
|
+
this.activePaneId = paneId;
|
|
52584
|
+
this.activeWindowId = windowId;
|
|
52382
52585
|
}
|
|
52383
|
-
|
|
52384
|
-
|
|
52385
|
-
|
|
52386
|
-
const parts = this.parseArgs(args);
|
|
52387
|
-
this.onEvent({
|
|
52388
|
-
type: "window-add",
|
|
52389
|
-
data: { sessionId: parts[0], name: parts[1] }
|
|
52390
|
-
});
|
|
52391
|
-
break;
|
|
52586
|
+
const window2 = this.snapshotWindows.get(windowId);
|
|
52587
|
+
if (!window2) {
|
|
52588
|
+
continue;
|
|
52392
52589
|
}
|
|
52393
|
-
|
|
52394
|
-
|
|
52395
|
-
|
|
52396
|
-
|
|
52397
|
-
|
|
52398
|
-
|
|
52399
|
-
|
|
52400
|
-
|
|
52401
|
-
|
|
52590
|
+
window2.panes.push(pane);
|
|
52591
|
+
this.pendingPaneTitles.delete(paneId);
|
|
52592
|
+
}
|
|
52593
|
+
for (const window2 of this.snapshotWindows.values()) {
|
|
52594
|
+
window2.panes.sort((left, right) => left.index - right.index);
|
|
52595
|
+
}
|
|
52596
|
+
}
|
|
52597
|
+
emitSnapshot() {
|
|
52598
|
+
const session = this.snapshotSession ? {
|
|
52599
|
+
id: this.snapshotSession.id,
|
|
52600
|
+
name: this.snapshotSession.name,
|
|
52601
|
+
windows: Array.from(this.snapshotWindows.values()).sort((left, right) => left.index - right.index)
|
|
52602
|
+
} : null;
|
|
52603
|
+
this.callbacks.onSnapshot({
|
|
52604
|
+
deviceId: this.deviceId,
|
|
52605
|
+
session
|
|
52606
|
+
});
|
|
52607
|
+
}
|
|
52608
|
+
findPaneWindowId(paneId) {
|
|
52609
|
+
for (const window2 of this.snapshotWindows.values()) {
|
|
52610
|
+
if (window2.panes.some((pane) => pane.id === paneId)) {
|
|
52611
|
+
return window2.id;
|
|
52402
52612
|
}
|
|
52403
|
-
|
|
52404
|
-
|
|
52405
|
-
|
|
52406
|
-
|
|
52407
|
-
|
|
52408
|
-
|
|
52409
|
-
|
|
52613
|
+
}
|
|
52614
|
+
return null;
|
|
52615
|
+
}
|
|
52616
|
+
async startPipeForPane(paneId) {
|
|
52617
|
+
await this.queuePipeTransition(async () => {
|
|
52618
|
+
if (this.currentPipePaneId === paneId) {
|
|
52619
|
+
return;
|
|
52410
52620
|
}
|
|
52411
|
-
|
|
52412
|
-
|
|
52413
|
-
|
|
52414
|
-
|
|
52415
|
-
|
|
52416
|
-
|
|
52417
|
-
|
|
52418
|
-
|
|
52419
|
-
this.
|
|
52621
|
+
await this.stopPipeNow();
|
|
52622
|
+
const fifoPath = this.fsPaths.paneFifoPath(paneId);
|
|
52623
|
+
this.ensureRuntimeDirs();
|
|
52624
|
+
rmSync(fifoPath, { force: true });
|
|
52625
|
+
await this.runShell(`mkfifo ${quoteShellArg(fifoPath)}`);
|
|
52626
|
+
const parser = createPaneTitleParser({
|
|
52627
|
+
onTitle: (title) => {
|
|
52628
|
+
this.pendingPaneTitles.set(paneId, title);
|
|
52629
|
+
this.requestSnapshot();
|
|
52420
52630
|
}
|
|
52421
|
-
|
|
52422
|
-
}
|
|
52423
|
-
|
|
52424
|
-
|
|
52425
|
-
|
|
52426
|
-
|
|
52427
|
-
|
|
52428
|
-
|
|
52429
|
-
|
|
52430
|
-
|
|
52431
|
-
|
|
52432
|
-
|
|
52433
|
-
|
|
52434
|
-
|
|
52435
|
-
|
|
52436
|
-
|
|
52437
|
-
|
|
52438
|
-
|
|
52439
|
-
|
|
52440
|
-
|
|
52441
|
-
|
|
52442
|
-
|
|
52443
|
-
|
|
52444
|
-
|
|
52445
|
-
|
|
52446
|
-
|
|
52447
|
-
if (windowId)
|
|
52448
|
-
data.windowId = windowId;
|
|
52449
|
-
if (paneId)
|
|
52450
|
-
data.paneId = paneId;
|
|
52451
|
-
this.onEvent({ type: "bell", data });
|
|
52631
|
+
});
|
|
52632
|
+
const readerProcess = Bun.spawn(["/bin/sh", "-lc", `cat ${quoteShellArg(fifoPath)}`], {
|
|
52633
|
+
stdout: "pipe",
|
|
52634
|
+
stderr: "pipe"
|
|
52635
|
+
});
|
|
52636
|
+
const reader = readerProcess.stdout.getReader();
|
|
52637
|
+
(async () => {
|
|
52638
|
+
try {
|
|
52639
|
+
while (true) {
|
|
52640
|
+
const chunk2 = await reader.read();
|
|
52641
|
+
if (chunk2.done) {
|
|
52642
|
+
break;
|
|
52643
|
+
}
|
|
52644
|
+
const raw = chunk2.value;
|
|
52645
|
+
const output = parser.push(raw);
|
|
52646
|
+
if (Array.from(raw).includes(7)) {
|
|
52647
|
+
this.callbacks.onEvent({ type: "bell", data: { paneId } });
|
|
52648
|
+
}
|
|
52649
|
+
if (output.length > 0) {
|
|
52650
|
+
this.callbacks.onTerminalOutput(paneId, output);
|
|
52651
|
+
}
|
|
52652
|
+
}
|
|
52653
|
+
} catch (error) {
|
|
52654
|
+
if (!this.manualDisconnect && !shouldIgnoreReaderAbortError(error)) {
|
|
52655
|
+
this.callbacks.onError(error instanceof Error ? error : new Error(String(error)));
|
|
52656
|
+
}
|
|
52452
52657
|
}
|
|
52453
|
-
|
|
52454
|
-
|
|
52455
|
-
|
|
52456
|
-
|
|
52457
|
-
|
|
52458
|
-
|
|
52459
|
-
|
|
52460
|
-
|
|
52461
|
-
|
|
52462
|
-
|
|
52463
|
-
|
|
52658
|
+
})();
|
|
52659
|
+
this.pipeReadAbort = () => {
|
|
52660
|
+
reader.releaseLock();
|
|
52661
|
+
readerProcess.kill();
|
|
52662
|
+
rmSync(fifoPath, { force: true });
|
|
52663
|
+
};
|
|
52664
|
+
await this.runTmux(["pipe-pane", "-O", "-t", paneId, `cat >${fifoPath}`]);
|
|
52665
|
+
this.currentPipePaneId = paneId;
|
|
52666
|
+
});
|
|
52667
|
+
}
|
|
52668
|
+
async stopPipe() {
|
|
52669
|
+
await this.queuePipeTransition(() => this.stopPipeNow());
|
|
52670
|
+
}
|
|
52671
|
+
async stopPipeNow() {
|
|
52672
|
+
const paneId = this.currentPipePaneId;
|
|
52673
|
+
this.currentPipePaneId = null;
|
|
52674
|
+
if (paneId) {
|
|
52675
|
+
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
52464
52676
|
}
|
|
52677
|
+
this.pipeReadAbort?.();
|
|
52678
|
+
this.pipeReadAbort = null;
|
|
52465
52679
|
}
|
|
52466
|
-
|
|
52467
|
-
const
|
|
52468
|
-
|
|
52469
|
-
|
|
52470
|
-
|
|
52471
|
-
|
|
52472
|
-
|
|
52473
|
-
|
|
52474
|
-
|
|
52475
|
-
|
|
52476
|
-
|
|
52477
|
-
|
|
52478
|
-
|
|
52479
|
-
|
|
52480
|
-
|
|
52481
|
-
|
|
52680
|
+
queuePipeTransition(task) {
|
|
52681
|
+
const next = this.pipeTransition.catch(() => {
|
|
52682
|
+
return;
|
|
52683
|
+
}).then(task);
|
|
52684
|
+
this.pipeTransition = next;
|
|
52685
|
+
return next;
|
|
52686
|
+
}
|
|
52687
|
+
async runShell(command) {
|
|
52688
|
+
const result = await this.deps.run(["/bin/sh", "-lc", command]);
|
|
52689
|
+
if (result.exitCode !== 0) {
|
|
52690
|
+
throw new Error(result.stderr.trim() || `shell command failed: ${command}`);
|
|
52691
|
+
}
|
|
52692
|
+
}
|
|
52693
|
+
async runTmux(argv, allowTargetMissing = false) {
|
|
52694
|
+
const result = await this.runTmuxAllowFailure(argv);
|
|
52695
|
+
if (result.exitCode === 0) {
|
|
52696
|
+
return result;
|
|
52482
52697
|
}
|
|
52483
|
-
|
|
52484
|
-
|
|
52698
|
+
const message = (result.stderr.trim() || result.stdout.trim() || `tmux command failed: ${argv.join(" ")}`).trim();
|
|
52699
|
+
if (allowTargetMissing && this.isRecoverableTargetMissingError(message)) {
|
|
52700
|
+
this.recoverFromTargetMissingError(message);
|
|
52701
|
+
return result;
|
|
52485
52702
|
}
|
|
52486
|
-
|
|
52703
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
52704
|
+
lastSeenAt: new Date().toISOString(),
|
|
52705
|
+
tmuxAvailable: false,
|
|
52706
|
+
lastError: message
|
|
52707
|
+
});
|
|
52708
|
+
throw new Error(message);
|
|
52709
|
+
}
|
|
52710
|
+
async runTmuxAllowFailure(argv) {
|
|
52711
|
+
return this.deps.run(["tmux", ...argv]);
|
|
52712
|
+
}
|
|
52713
|
+
isRecoverableTargetMissingError(message) {
|
|
52714
|
+
const normalized = message.toLowerCase();
|
|
52715
|
+
return normalized.includes("can't find window") || normalized.includes("can't find pane") || normalized.includes("no such window") || normalized.includes("no such pane");
|
|
52716
|
+
}
|
|
52717
|
+
recoverFromTargetMissingError(message) {
|
|
52718
|
+
const normalized = message.toLowerCase();
|
|
52719
|
+
if (normalized.includes("window")) {
|
|
52720
|
+
this.activeWindowId = null;
|
|
52721
|
+
}
|
|
52722
|
+
if (normalized.includes("pane")) {
|
|
52723
|
+
this.activePaneId = null;
|
|
52724
|
+
}
|
|
52725
|
+
this.requestSnapshot();
|
|
52726
|
+
}
|
|
52727
|
+
}
|
|
52728
|
+
|
|
52729
|
+
// ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
|
|
52730
|
+
var import_ssh2 = __toESM(require_lib3(), 1);
|
|
52731
|
+
|
|
52732
|
+
// ../../apps/gateway/src/tmux/ssh-auth.ts
|
|
52733
|
+
function normalizeEnvValue(value) {
|
|
52734
|
+
const trimmed = value?.trim();
|
|
52735
|
+
return trimmed ? trimmed : undefined;
|
|
52736
|
+
}
|
|
52737
|
+
function resolveSshUsername(configuredUsername, authMode, env = process.env) {
|
|
52738
|
+
const explicitUsername = normalizeEnvValue(configuredUsername);
|
|
52739
|
+
if (explicitUsername) {
|
|
52740
|
+
return explicitUsername;
|
|
52741
|
+
}
|
|
52742
|
+
if (authMode === "agent" || authMode === "auto") {
|
|
52743
|
+
const currentUser = normalizeEnvValue(env.USER) ?? normalizeEnvValue(env.LOGNAME);
|
|
52744
|
+
if (currentUser) {
|
|
52745
|
+
return currentUser;
|
|
52746
|
+
}
|
|
52747
|
+
}
|
|
52748
|
+
return "root";
|
|
52749
|
+
}
|
|
52750
|
+
function resolveSshAgentSocket(authMode, env = process.env) {
|
|
52751
|
+
if (authMode !== "agent" && authMode !== "auto") {
|
|
52752
|
+
return;
|
|
52487
52753
|
}
|
|
52488
|
-
|
|
52489
|
-
|
|
52490
|
-
|
|
52491
|
-
|
|
52492
|
-
|
|
52754
|
+
const socket = normalizeEnvValue(env.SSH_AUTH_SOCK);
|
|
52755
|
+
if (socket) {
|
|
52756
|
+
return socket;
|
|
52757
|
+
}
|
|
52758
|
+
if (authMode === "agent") {
|
|
52759
|
+
throw new Error("SSH_AUTH_SOCK \u672A\u8BBE\u7F6E\uFF0C\u65E0\u6CD5\u4F7F\u7528 SSH Agent \u8BA4\u8BC1");
|
|
52493
52760
|
}
|
|
52761
|
+
return;
|
|
52494
52762
|
}
|
|
52495
52763
|
|
|
52496
|
-
// ../../apps/gateway/src/tmux/
|
|
52497
|
-
|
|
52498
|
-
|
|
52499
|
-
|
|
52764
|
+
// ../../apps/gateway/src/tmux-client/ssh-bootstrap.ts
|
|
52765
|
+
function buildSshBootstrapScript() {
|
|
52766
|
+
return [
|
|
52767
|
+
". /etc/profile 2>/dev/null || true",
|
|
52768
|
+
'[ -f "$HOME/.profile" ] && . "$HOME/.profile" 2>/dev/null || true',
|
|
52769
|
+
'[ -f "$HOME/.bash_profile" ] && . "$HOME/.bash_profile" 2>/dev/null || true',
|
|
52770
|
+
'TMUX_BIN="$(command -v tmux 2>/dev/null || true)"',
|
|
52771
|
+
'if [ -z "$TMUX_BIN" ]; then',
|
|
52772
|
+
" for p in /usr/local/bin/tmux /opt/homebrew/bin/tmux /usr/bin/tmux /bin/tmux; do",
|
|
52773
|
+
' [ -x "$p" ] && TMUX_BIN="$p" && break',
|
|
52774
|
+
" done",
|
|
52775
|
+
"fi",
|
|
52776
|
+
'HOME_DIR="${HOME:-$(pwd)}"',
|
|
52777
|
+
'if [ -z "$TMUX_BIN" ]; then',
|
|
52778
|
+
" printf 'TMEX_BOOT_FAIL\\ttmux_not_found\\n'",
|
|
52779
|
+
"else",
|
|
52780
|
+
` printf 'TMEX_BOOT_OK\\t%s\\t%s\\t%s\\n' "$TMUX_BIN" "$("$TMUX_BIN" -V 2>/dev/null)" "$HOME_DIR"`,
|
|
52781
|
+
"fi"
|
|
52782
|
+
].join(`
|
|
52783
|
+
`);
|
|
52784
|
+
}
|
|
52785
|
+
function parseSshBootstrapOutput(output) {
|
|
52786
|
+
const lines = output.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
|
|
52787
|
+
for (const line of lines) {
|
|
52788
|
+
if (line.startsWith("TMEX_BOOT_OK\t")) {
|
|
52789
|
+
const [, tmuxBin = "", tmuxVersion = "", homeDir = ""] = line.split("\t");
|
|
52790
|
+
if (!tmuxBin || !homeDir) {
|
|
52791
|
+
return { ok: false, reason: "invalid_bootstrap_payload" };
|
|
52792
|
+
}
|
|
52793
|
+
return { ok: true, tmuxBin, tmuxVersion, homeDir };
|
|
52794
|
+
}
|
|
52795
|
+
if (line.startsWith("TMEX_BOOT_FAIL\t")) {
|
|
52796
|
+
const [, reason = "tmux_bootstrap_failed"] = line.split("\t");
|
|
52797
|
+
return { ok: false, reason };
|
|
52798
|
+
}
|
|
52799
|
+
}
|
|
52800
|
+
return { ok: false, reason: "missing_bootstrap_marker" };
|
|
52500
52801
|
}
|
|
52501
52802
|
|
|
52502
|
-
|
|
52803
|
+
// ../../apps/gateway/src/tmux-client/ssh-external-connection.ts
|
|
52804
|
+
var DEFAULT_HISTORY_LINES = "-1000";
|
|
52805
|
+
var BELL_DEDUP_WINDOW_MS2 = 200;
|
|
52806
|
+
var COMMAND_SENTINEL = "\x1ETMEX_END ";
|
|
52807
|
+
|
|
52808
|
+
class SshExternalTmuxConnection {
|
|
52503
52809
|
deviceId;
|
|
52810
|
+
callbacks;
|
|
52811
|
+
deps;
|
|
52504
52812
|
device = null;
|
|
52505
|
-
|
|
52506
|
-
terminal = null;
|
|
52507
|
-
sshClient = null;
|
|
52508
|
-
sshStream = null;
|
|
52509
|
-
parser;
|
|
52510
|
-
onEvent;
|
|
52511
|
-
onTerminalOutput;
|
|
52512
|
-
onTerminalHistory;
|
|
52513
|
-
onSnapshot;
|
|
52514
|
-
onError;
|
|
52515
|
-
onClose;
|
|
52516
|
-
activePaneId = null;
|
|
52517
|
-
activeWindowId = null;
|
|
52813
|
+
sessionName = "tmex";
|
|
52518
52814
|
connected = false;
|
|
52519
52815
|
manualDisconnect = false;
|
|
52520
|
-
|
|
52521
|
-
|
|
52522
|
-
|
|
52523
|
-
|
|
52524
|
-
|
|
52525
|
-
startupNonControlOutput = [];
|
|
52526
|
-
lastExitReason = null;
|
|
52527
|
-
pendingCommandKinds = [];
|
|
52528
|
-
commandKindsByNo = new Map;
|
|
52529
|
-
pendingCapturePaneRequests = [];
|
|
52530
|
-
pendingCapturePaneModeRequests = [];
|
|
52816
|
+
closeNotified = false;
|
|
52817
|
+
cleanupPromise = null;
|
|
52818
|
+
activeWindowId = null;
|
|
52819
|
+
activePaneId = null;
|
|
52820
|
+
pendingPaneTitles = new Map;
|
|
52531
52821
|
snapshotSession = null;
|
|
52532
52822
|
snapshotWindows = new Map;
|
|
52533
|
-
|
|
52534
|
-
|
|
52535
|
-
|
|
52536
|
-
|
|
52537
|
-
|
|
52823
|
+
currentPipePaneId = null;
|
|
52824
|
+
pipeReadAbort = null;
|
|
52825
|
+
pipeTransition = Promise.resolve();
|
|
52826
|
+
hookReadAbort = null;
|
|
52827
|
+
hookBuffer = "";
|
|
52538
52828
|
bellDedup = new Map;
|
|
52539
|
-
|
|
52829
|
+
fsPaths = createRuntimeFsPaths({
|
|
52830
|
+
deviceId: "pending",
|
|
52831
|
+
sessionName: "pending",
|
|
52832
|
+
gatewayPid: process.pid
|
|
52833
|
+
});
|
|
52834
|
+
sshClient = null;
|
|
52835
|
+
commandStream = null;
|
|
52836
|
+
commandStdoutBuffer = "";
|
|
52837
|
+
pendingCommand = null;
|
|
52838
|
+
tmuxBin = "tmux";
|
|
52839
|
+
remoteHomeDir = ".";
|
|
52840
|
+
commandQueue = Promise.resolve();
|
|
52841
|
+
constructor(options, inputDeps = {}) {
|
|
52540
52842
|
this.deviceId = options.deviceId;
|
|
52541
|
-
this.
|
|
52542
|
-
this.
|
|
52543
|
-
|
|
52544
|
-
|
|
52545
|
-
|
|
52546
|
-
|
|
52547
|
-
this.readyPromise = new Promise((resolve, reject) => {
|
|
52548
|
-
this.resolveReady = resolve;
|
|
52549
|
-
this.rejectReady = reject;
|
|
52550
|
-
});
|
|
52551
|
-
this.parser = new TmuxControlParser({
|
|
52552
|
-
onEvent: (event) => this.handleTmuxEvent(event),
|
|
52553
|
-
onTerminalOutput: (paneId, data) => this.emitTerminalOutput(paneId, data),
|
|
52554
|
-
onPaneTitle: (paneId, title) => this.handlePaneTitleUpdate(paneId, title),
|
|
52555
|
-
onOutputBlockBegin: (meta) => this.handleOutputBlockBegin(meta),
|
|
52556
|
-
onOutputBlock: (block) => this.handleOutputBlock(block),
|
|
52557
|
-
onNonControlOutput: (line) => this.handleNonControlOutput(line),
|
|
52558
|
-
onReady: () => this.markReady(),
|
|
52559
|
-
onExit: (reason) => {
|
|
52560
|
-
this.lastExitReason = reason;
|
|
52561
|
-
if (!this.ready) {
|
|
52562
|
-
this.failReady(new Error(reason ? `tmux exited: ${reason}` : "tmux exited"));
|
|
52563
|
-
}
|
|
52564
|
-
}
|
|
52565
|
-
});
|
|
52566
|
-
}
|
|
52567
|
-
markReady() {
|
|
52568
|
-
if (this.ready || this.readyFailed)
|
|
52569
|
-
return;
|
|
52570
|
-
this.ready = true;
|
|
52571
|
-
this.resolveReady?.();
|
|
52572
|
-
this.resolveReady = null;
|
|
52573
|
-
this.rejectReady = null;
|
|
52574
|
-
this.startupNonControlOutput = [];
|
|
52575
|
-
}
|
|
52576
|
-
failReady(error) {
|
|
52577
|
-
if (this.ready || this.readyFailed)
|
|
52578
|
-
return;
|
|
52579
|
-
this.readyFailed = true;
|
|
52580
|
-
const detail = this.startupNonControlOutput.filter(Boolean).join(`
|
|
52581
|
-
`);
|
|
52582
|
-
const nextError = detail ? new Error(`${error.message}
|
|
52583
|
-
${detail}`) : error;
|
|
52584
|
-
this.rejectReady?.(nextError);
|
|
52585
|
-
this.resolveReady = null;
|
|
52586
|
-
this.rejectReady = null;
|
|
52587
|
-
}
|
|
52588
|
-
waitForReady(timeoutMs = 5000) {
|
|
52589
|
-
if (this.ready) {
|
|
52590
|
-
return Promise.resolve();
|
|
52591
|
-
}
|
|
52592
|
-
return new Promise((resolve, reject) => {
|
|
52593
|
-
const timer = setTimeout(() => {
|
|
52594
|
-
const detail = this.startupNonControlOutput.filter(Boolean).join(`
|
|
52595
|
-
`);
|
|
52596
|
-
reject(new Error(detail ? `tmux control mode not ready: ${detail}` : "tmux control mode not ready"));
|
|
52597
|
-
}, timeoutMs);
|
|
52598
|
-
this.readyPromise.then(() => {
|
|
52599
|
-
clearTimeout(timer);
|
|
52600
|
-
resolve();
|
|
52601
|
-
}).catch((err) => {
|
|
52602
|
-
clearTimeout(timer);
|
|
52603
|
-
reject(err);
|
|
52604
|
-
});
|
|
52605
|
-
});
|
|
52843
|
+
this.callbacks = options;
|
|
52844
|
+
this.deps = {
|
|
52845
|
+
getDevice: inputDeps.getDevice ?? ((deviceId) => getDeviceById(deviceId)),
|
|
52846
|
+
decrypt: inputDeps.decrypt ?? decryptWithContext,
|
|
52847
|
+
createClient: inputDeps.createClient ?? (() => new import_ssh2.Client)
|
|
52848
|
+
};
|
|
52606
52849
|
}
|
|
52607
52850
|
async connect() {
|
|
52608
52851
|
this.manualDisconnect = false;
|
|
52609
|
-
this.
|
|
52852
|
+
this.closeNotified = false;
|
|
52853
|
+
this.device = this.deps.getDevice(this.deviceId);
|
|
52610
52854
|
if (!this.device) {
|
|
52611
52855
|
throw new Error(`Device not found: ${this.deviceId}`);
|
|
52612
52856
|
}
|
|
52613
|
-
if (this.device.type
|
|
52614
|
-
|
|
52615
|
-
} else {
|
|
52616
|
-
await this.connectSSH();
|
|
52857
|
+
if (this.device.type !== "ssh") {
|
|
52858
|
+
throw new Error(`SshExternalTmuxConnection only supports ssh device: ${this.deviceId}`);
|
|
52617
52859
|
}
|
|
52618
|
-
|
|
52619
|
-
|
|
52620
|
-
|
|
52621
|
-
|
|
52622
|
-
|
|
52623
|
-
|
|
52624
|
-
|
|
52625
|
-
|
|
52626
|
-
|
|
52627
|
-
|
|
52628
|
-
|
|
52629
|
-
data: (_term, data) => {
|
|
52630
|
-
this.parser.processData(data);
|
|
52631
|
-
},
|
|
52632
|
-
exit: () => {
|
|
52633
|
-
if (!this.ready) {
|
|
52634
|
-
this.failReady(new Error("tmux terminal closed before ready"));
|
|
52635
|
-
}
|
|
52636
|
-
if (this.lastExitReason) {
|
|
52637
|
-
this.onError(new Error(`tmux exited: ${this.lastExitReason}`));
|
|
52638
|
-
}
|
|
52639
|
-
this.cleanup();
|
|
52640
|
-
if (!this.manualDisconnect) {
|
|
52641
|
-
this.onClose();
|
|
52642
|
-
}
|
|
52643
|
-
}
|
|
52644
|
-
}
|
|
52645
|
-
});
|
|
52646
|
-
this.terminal = this.subprocess.terminal ?? null;
|
|
52860
|
+
this.sessionName = this.device.session?.trim() || "tmex";
|
|
52861
|
+
this.fsPaths = createRuntimeFsPaths({
|
|
52862
|
+
deviceId: this.deviceId,
|
|
52863
|
+
sessionName: this.sessionName,
|
|
52864
|
+
gatewayPid: process.pid
|
|
52865
|
+
});
|
|
52866
|
+
await this.connectSshClient();
|
|
52867
|
+
await this.openCommandChannel();
|
|
52868
|
+
await this.ensureRemoteRuntimeDirs();
|
|
52869
|
+
await this.ensureSession();
|
|
52870
|
+
await this.startHooks();
|
|
52647
52871
|
this.connected = true;
|
|
52648
52872
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
52649
52873
|
lastSeenAt: new Date().toISOString(),
|
|
52650
52874
|
tmuxAvailable: true,
|
|
52651
52875
|
lastError: null
|
|
52652
52876
|
});
|
|
52653
|
-
|
|
52654
|
-
|
|
52655
|
-
|
|
52656
|
-
|
|
52657
|
-
|
|
52658
|
-
throw err;
|
|
52877
|
+
await this.requestSnapshotInternal();
|
|
52878
|
+
}
|
|
52879
|
+
disconnect() {
|
|
52880
|
+
if (this.manualDisconnect) {
|
|
52881
|
+
return;
|
|
52659
52882
|
}
|
|
52883
|
+
this.manualDisconnect = true;
|
|
52884
|
+
this.shutdownInternal(false);
|
|
52885
|
+
}
|
|
52886
|
+
requestSnapshot() {
|
|
52887
|
+
this.requestSnapshotInternal();
|
|
52660
52888
|
}
|
|
52661
|
-
|
|
52662
|
-
if (!this.
|
|
52889
|
+
sendInput(paneId, data) {
|
|
52890
|
+
if (!this.connected) {
|
|
52663
52891
|
return;
|
|
52664
|
-
|
|
52665
|
-
|
|
52666
|
-
|
|
52667
|
-
|
|
52668
|
-
const username = this.device.username;
|
|
52669
|
-
const sessionName = this.device.session ?? "tmex";
|
|
52670
|
-
const resolvedUsername = resolveSshUsername(username, this.device.authMode);
|
|
52671
|
-
const logStage = (stage, extra = {}) => {
|
|
52672
|
-
console.log("[ssh]", {
|
|
52673
|
-
stage,
|
|
52674
|
-
deviceId: this.deviceId,
|
|
52675
|
-
host,
|
|
52676
|
-
port,
|
|
52677
|
-
username: resolvedUsername,
|
|
52678
|
-
authMode: this.device?.authMode,
|
|
52679
|
-
...extra
|
|
52892
|
+
}
|
|
52893
|
+
for (const chunk2 of encodeInputToHexChunks(data)) {
|
|
52894
|
+
this.runTmux(["send-keys", "-H", "-t", paneId, ...chunk2]).catch((error) => {
|
|
52895
|
+
this.callbacks.onError(error);
|
|
52680
52896
|
});
|
|
52681
|
-
}
|
|
52682
|
-
|
|
52683
|
-
|
|
52684
|
-
|
|
52685
|
-
|
|
52897
|
+
}
|
|
52898
|
+
}
|
|
52899
|
+
resizePane(paneId, cols, rows) {
|
|
52900
|
+
if (!this.connected) {
|
|
52901
|
+
return;
|
|
52902
|
+
}
|
|
52903
|
+
this.resizePaneInternal(paneId, cols, rows).catch((error) => {
|
|
52904
|
+
this.callbacks.onError(error);
|
|
52905
|
+
});
|
|
52906
|
+
}
|
|
52907
|
+
selectPane(windowId, paneId) {
|
|
52908
|
+
if (!this.connected) {
|
|
52909
|
+
return;
|
|
52910
|
+
}
|
|
52911
|
+
this.selectPaneInternal(windowId, paneId, null).catch((error) => {
|
|
52912
|
+
this.callbacks.onError(error);
|
|
52686
52913
|
});
|
|
52914
|
+
}
|
|
52915
|
+
selectPaneWithSize(windowId, paneId, cols, rows) {
|
|
52916
|
+
if (!this.connected) {
|
|
52917
|
+
return;
|
|
52918
|
+
}
|
|
52919
|
+
this.selectPaneInternal(windowId, paneId, { cols, rows }).catch((error) => {
|
|
52920
|
+
this.callbacks.onError(error);
|
|
52921
|
+
});
|
|
52922
|
+
}
|
|
52923
|
+
selectWindow(windowId) {
|
|
52924
|
+
if (!this.connected) {
|
|
52925
|
+
return;
|
|
52926
|
+
}
|
|
52927
|
+
this.runAndRefresh(["select-window", "-t", windowId]).catch((error) => {
|
|
52928
|
+
this.callbacks.onError(error);
|
|
52929
|
+
});
|
|
52930
|
+
}
|
|
52931
|
+
createWindow(name) {
|
|
52932
|
+
if (!this.connected) {
|
|
52933
|
+
return;
|
|
52934
|
+
}
|
|
52935
|
+
const argv = name ? ["new-window", "-n", name] : ["new-window"];
|
|
52936
|
+
this.runAndRefresh(argv).catch((error) => {
|
|
52937
|
+
this.callbacks.onError(error);
|
|
52938
|
+
});
|
|
52939
|
+
}
|
|
52940
|
+
closeWindow(windowId) {
|
|
52941
|
+
if (!this.connected) {
|
|
52942
|
+
return;
|
|
52943
|
+
}
|
|
52944
|
+
this.closeWindowInternal(windowId).catch((error) => {
|
|
52945
|
+
this.callbacks.onError(error);
|
|
52946
|
+
});
|
|
52947
|
+
}
|
|
52948
|
+
closePane(paneId) {
|
|
52949
|
+
if (!this.connected) {
|
|
52950
|
+
return;
|
|
52951
|
+
}
|
|
52952
|
+
this.runAndRefresh(["kill-pane", "-t", paneId], true).catch((error) => {
|
|
52953
|
+
this.callbacks.onError(error);
|
|
52954
|
+
});
|
|
52955
|
+
}
|
|
52956
|
+
renameWindow(windowId, name) {
|
|
52957
|
+
if (!this.connected) {
|
|
52958
|
+
return;
|
|
52959
|
+
}
|
|
52960
|
+
this.runAndRefresh(["rename-window", "-t", windowId, name]).catch((error) => {
|
|
52961
|
+
this.callbacks.onError(error);
|
|
52962
|
+
});
|
|
52963
|
+
}
|
|
52964
|
+
async connectSshClient() {
|
|
52965
|
+
if (!this.device) {
|
|
52966
|
+
throw new Error("SSH device not loaded");
|
|
52967
|
+
}
|
|
52968
|
+
const host = this.device.host;
|
|
52969
|
+
const port = this.device.port ?? 22;
|
|
52970
|
+
const username = resolveSshUsername(this.device.username, this.device.authMode);
|
|
52687
52971
|
if (this.device.authMode === "configRef" || !host && this.device.sshConfigRef) {
|
|
52688
52972
|
throw new Error("ssh_config_ref_not_supported: \u5F53\u524D\u7248\u672C\u6682\u4E0D\u652F\u6301 SSH Config \u5F15\u7528\uFF0C\u8BF7\u6539\u4E3A\u586B\u5199 host + username\uFF0C\u5E76\u9009\u62E9 Agent/\u79C1\u94A5/\u5BC6\u7801\u8BA4\u8BC1");
|
|
52689
52973
|
}
|
|
@@ -52693,14 +52977,14 @@ ${detail}`) : error;
|
|
|
52693
52977
|
const authConfig = {
|
|
52694
52978
|
host,
|
|
52695
52979
|
port,
|
|
52696
|
-
username
|
|
52980
|
+
username
|
|
52697
52981
|
};
|
|
52698
52982
|
switch (this.device.authMode) {
|
|
52699
52983
|
case "password": {
|
|
52700
52984
|
if (!this.device.passwordEnc) {
|
|
52701
52985
|
throw new Error("auth_password_missing: \u5BC6\u7801\u8BA4\u8BC1\u672A\u63D0\u4F9B\u5BC6\u7801");
|
|
52702
52986
|
}
|
|
52703
|
-
authConfig.password = await
|
|
52987
|
+
authConfig.password = await this.deps.decrypt(this.device.passwordEnc, {
|
|
52704
52988
|
scope: "device",
|
|
52705
52989
|
entityId: this.device.id,
|
|
52706
52990
|
field: "password_enc"
|
|
@@ -52711,13 +52995,13 @@ ${detail}`) : error;
|
|
|
52711
52995
|
if (!this.device.privateKeyEnc) {
|
|
52712
52996
|
throw new Error("auth_key_missing: \u79C1\u94A5\u8BA4\u8BC1\u672A\u63D0\u4F9B\u79C1\u94A5");
|
|
52713
52997
|
}
|
|
52714
|
-
authConfig.privateKey = await
|
|
52998
|
+
authConfig.privateKey = await this.deps.decrypt(this.device.privateKeyEnc, {
|
|
52715
52999
|
scope: "device",
|
|
52716
53000
|
entityId: this.device.id,
|
|
52717
53001
|
field: "private_key_enc"
|
|
52718
53002
|
});
|
|
52719
53003
|
if (this.device.privateKeyPassphraseEnc) {
|
|
52720
|
-
authConfig.passphrase = await
|
|
53004
|
+
authConfig.passphrase = await this.deps.decrypt(this.device.privateKeyPassphraseEnc, {
|
|
52721
53005
|
scope: "device",
|
|
52722
53006
|
entityId: this.device.id,
|
|
52723
53007
|
field: "private_key_passphrase_enc"
|
|
@@ -52729,22 +53013,19 @@ ${detail}`) : error;
|
|
|
52729
53013
|
authConfig.agent = resolveSshAgentSocket("agent");
|
|
52730
53014
|
break;
|
|
52731
53015
|
}
|
|
52732
|
-
case "configRef": {
|
|
52733
|
-
break;
|
|
52734
|
-
}
|
|
52735
53016
|
case "auto": {
|
|
52736
53017
|
const agentSocket = resolveSshAgentSocket("auto");
|
|
52737
53018
|
if (agentSocket) {
|
|
52738
53019
|
authConfig.agent = agentSocket;
|
|
52739
53020
|
}
|
|
52740
53021
|
if (this.device.privateKeyEnc) {
|
|
52741
|
-
authConfig.privateKey = await
|
|
53022
|
+
authConfig.privateKey = await this.deps.decrypt(this.device.privateKeyEnc, {
|
|
52742
53023
|
scope: "device",
|
|
52743
53024
|
entityId: this.device.id,
|
|
52744
53025
|
field: "private_key_enc"
|
|
52745
53026
|
});
|
|
52746
53027
|
} else if (this.device.passwordEnc) {
|
|
52747
|
-
authConfig.password = await
|
|
53028
|
+
authConfig.password = await this.deps.decrypt(this.device.passwordEnc, {
|
|
52748
53029
|
scope: "device",
|
|
52749
53030
|
entityId: this.device.id,
|
|
52750
53031
|
field: "password_enc"
|
|
@@ -52752,17 +53033,15 @@ ${detail}`) : error;
|
|
|
52752
53033
|
}
|
|
52753
53034
|
break;
|
|
52754
53035
|
}
|
|
53036
|
+
case "configRef":
|
|
53037
|
+
break;
|
|
52755
53038
|
}
|
|
52756
53039
|
if (this.device.authMode === "auto" && !authConfig.agent && !authConfig.privateKey && !authConfig.password) {
|
|
52757
53040
|
throw new Error("auth_auto_missing: auto \u6A21\u5F0F\u4E0B\u672A\u627E\u5230\u53EF\u7528\u8BA4\u8BC1\u65B9\u5F0F\uFF08SSH_AUTH_SOCK / \u79C1\u94A5 / \u5BC6\u7801\uFF09");
|
|
52758
53041
|
}
|
|
52759
|
-
|
|
52760
|
-
|
|
52761
|
-
|
|
52762
|
-
hasPassphrase: Boolean(authConfig.passphrase),
|
|
52763
|
-
hasPassword: Boolean(authConfig.password)
|
|
52764
|
-
});
|
|
52765
|
-
return new Promise((resolve, reject) => {
|
|
53042
|
+
const client = this.deps.createClient();
|
|
53043
|
+
this.sshClient = client;
|
|
53044
|
+
await new Promise((resolve, reject) => {
|
|
52766
53045
|
let settled = false;
|
|
52767
53046
|
const resolveOnce = () => {
|
|
52768
53047
|
if (settled) {
|
|
@@ -52776,429 +53055,289 @@ ${detail}`) : error;
|
|
|
52776
53055
|
return;
|
|
52777
53056
|
}
|
|
52778
53057
|
settled = true;
|
|
52779
|
-
reject(error
|
|
53058
|
+
reject(error);
|
|
52780
53059
|
};
|
|
52781
|
-
|
|
52782
|
-
|
|
52783
|
-
logStage("ssh_ready");
|
|
52784
|
-
const tmuxCommand = `tmux -CC new-session -A -s ${sessionName}`;
|
|
52785
|
-
logStage("tmux_exec_start", { command: tmuxCommand });
|
|
52786
|
-
conn.exec(tmuxCommand, {
|
|
52787
|
-
pty: {
|
|
52788
|
-
term: "xterm-256color",
|
|
52789
|
-
cols: 80,
|
|
52790
|
-
rows: 30
|
|
52791
|
-
}
|
|
52792
|
-
}, (err, stream) => {
|
|
52793
|
-
if (err) {
|
|
52794
|
-
logStage("tmux_exec_failed", { error: err.message });
|
|
52795
|
-
rejectOnce(new Error(`tmux_exec_failed: \u542F\u52A8\u8FDC\u7AEF tmux \u5931\u8D25\uFF1A${err.message}`));
|
|
52796
|
-
return;
|
|
52797
|
-
}
|
|
52798
|
-
this.sshStream = stream;
|
|
52799
|
-
stream.on("close", () => {
|
|
52800
|
-
logStage("ssh_stream_closed");
|
|
52801
|
-
if (!this.ready) {
|
|
52802
|
-
this.failReady(new Error("SSH stream closed before tmux became ready"));
|
|
52803
|
-
}
|
|
52804
|
-
if (this.lastExitReason) {
|
|
52805
|
-
this.onError(new Error(`tmux exited: ${this.lastExitReason}`));
|
|
52806
|
-
}
|
|
52807
|
-
this.cleanup();
|
|
52808
|
-
if (!this.manualDisconnect) {
|
|
52809
|
-
this.onClose();
|
|
52810
|
-
}
|
|
52811
|
-
});
|
|
52812
|
-
stream.on("data", (data) => {
|
|
52813
|
-
this.parser.processData(data);
|
|
52814
|
-
});
|
|
52815
|
-
stream.stderr.on("data", (data) => {
|
|
52816
|
-
const chunk2 = data.toString();
|
|
52817
|
-
stderrTail = `${stderrTail}${chunk2}`.slice(-2000);
|
|
52818
|
-
console.error("[ssh] stderr:", chunk2);
|
|
52819
|
-
});
|
|
52820
|
-
this.connected = true;
|
|
52821
|
-
updateDeviceRuntimeStatus(this.deviceId, {
|
|
52822
|
-
lastSeenAt: new Date().toISOString(),
|
|
52823
|
-
tmuxAvailable: true,
|
|
52824
|
-
lastError: null
|
|
52825
|
-
});
|
|
52826
|
-
this.waitForReady().then(() => {
|
|
52827
|
-
this.configureWindowSizePolicy();
|
|
52828
|
-
logStage("tmux_ready");
|
|
52829
|
-
resolveOnce();
|
|
52830
|
-
}).catch((err2) => {
|
|
52831
|
-
const stderrText = stderrTail.trim();
|
|
52832
|
-
const nextError = stderrText.length > 0 ? new Error(`${err2 instanceof Error ? err2.message : String(err2)}
|
|
52833
|
-
ssh stderr: ${stderrText}`) : err2;
|
|
52834
|
-
logStage("tmux_ready_failed", {
|
|
52835
|
-
error: nextError instanceof Error ? nextError.message : String(nextError)
|
|
52836
|
-
});
|
|
52837
|
-
this.cleanup();
|
|
52838
|
-
rejectOnce(nextError);
|
|
52839
|
-
});
|
|
52840
|
-
});
|
|
53060
|
+
client.on("ready", () => {
|
|
53061
|
+
resolveOnce();
|
|
52841
53062
|
});
|
|
52842
|
-
|
|
52843
|
-
logStage("connect_error", { error: err.message });
|
|
53063
|
+
client.on("error", (error) => {
|
|
52844
53064
|
updateDeviceRuntimeStatus(this.deviceId, {
|
|
52845
53065
|
lastSeenAt: new Date().toISOString(),
|
|
52846
53066
|
tmuxAvailable: false,
|
|
52847
|
-
lastError:
|
|
53067
|
+
lastError: error.message
|
|
52848
53068
|
});
|
|
52849
|
-
|
|
53069
|
+
if (!settled) {
|
|
53070
|
+
rejectOnce(error);
|
|
53071
|
+
return;
|
|
53072
|
+
}
|
|
53073
|
+
if (!this.manualDisconnect) {
|
|
53074
|
+
this.callbacks.onError(error);
|
|
53075
|
+
this.shutdownInternal(true);
|
|
53076
|
+
}
|
|
52850
53077
|
});
|
|
52851
|
-
|
|
52852
|
-
|
|
52853
|
-
|
|
52854
|
-
|
|
53078
|
+
client.on("close", () => {
|
|
53079
|
+
if (!settled) {
|
|
53080
|
+
rejectOnce(new Error("SSH connection closed before ready"));
|
|
53081
|
+
return;
|
|
52855
53082
|
}
|
|
52856
|
-
this.cleanup();
|
|
52857
53083
|
if (!this.manualDisconnect) {
|
|
52858
|
-
this.
|
|
53084
|
+
this.shutdownInternal(true);
|
|
52859
53085
|
}
|
|
52860
53086
|
});
|
|
52861
|
-
|
|
52862
|
-
conn.connect(authConfig);
|
|
53087
|
+
client.connect(authConfig);
|
|
52863
53088
|
});
|
|
52864
53089
|
}
|
|
52865
|
-
|
|
52866
|
-
const
|
|
52867
|
-
const
|
|
52868
|
-
|
|
52869
|
-
|
|
52870
|
-
|
|
52871
|
-
|
|
52872
|
-
|
|
52873
|
-
|
|
52874
|
-
|
|
52875
|
-
|
|
52876
|
-
|
|
52877
|
-
|
|
52878
|
-
|
|
52879
|
-
|
|
52880
|
-
|
|
52881
|
-
|
|
52882
|
-
|
|
52883
|
-
|
|
52884
|
-
|
|
52885
|
-
|
|
52886
|
-
const data = event.data ?? {};
|
|
52887
|
-
const resolvedPaneId = (typeof data.paneId === "string" && data.paneId ? data.paneId : null) ?? this.activePaneId ?? undefined;
|
|
52888
|
-
const windowId = typeof data.windowId === "string" && data.windowId ? data.windowId : undefined;
|
|
52889
|
-
const key = resolvedPaneId ?? windowId ?? "-";
|
|
52890
|
-
if (!this.shouldPassBellDedup(key)) {
|
|
52891
|
-
return;
|
|
53090
|
+
async openCommandChannel() {
|
|
53091
|
+
const sshClient = this.requireSshClient();
|
|
53092
|
+
const stream = await new Promise((resolve, reject) => {
|
|
53093
|
+
sshClient.exec("/bin/sh -s", { pty: false }, (error, channel) => {
|
|
53094
|
+
if (error) {
|
|
53095
|
+
reject(error);
|
|
53096
|
+
return;
|
|
53097
|
+
}
|
|
53098
|
+
resolve(channel);
|
|
53099
|
+
});
|
|
53100
|
+
});
|
|
53101
|
+
this.commandStdoutBuffer = "";
|
|
53102
|
+
this.pendingCommand = null;
|
|
53103
|
+
this.commandStream = stream;
|
|
53104
|
+
stream.on("data", (data) => {
|
|
53105
|
+
this.commandStdoutBuffer += data.toString();
|
|
53106
|
+
this.flushCommandBuffer();
|
|
53107
|
+
});
|
|
53108
|
+
stream.stderr.on("data", (data) => {
|
|
53109
|
+
if (this.pendingCommand) {
|
|
53110
|
+
this.pendingCommand.stderr += data.toString();
|
|
52892
53111
|
}
|
|
52893
|
-
|
|
52894
|
-
|
|
52895
|
-
|
|
52896
|
-
|
|
52897
|
-
|
|
52898
|
-
|
|
52899
|
-
}
|
|
52900
|
-
});
|
|
52901
|
-
return;
|
|
53112
|
+
});
|
|
53113
|
+
stream.on("close", () => {
|
|
53114
|
+
this.rejectPendingCommand(new Error("SSH command channel closed"));
|
|
53115
|
+
this.commandStream = null;
|
|
53116
|
+
if (!this.manualDisconnect) {
|
|
53117
|
+
this.shutdownInternal(true);
|
|
52902
53118
|
}
|
|
52903
|
-
}
|
|
52904
|
-
this.
|
|
53119
|
+
});
|
|
53120
|
+
const bootstrap = await this.runShell(buildSshBootstrapScript());
|
|
53121
|
+
const parsed = parseSshBootstrapOutput(bootstrap.stdout);
|
|
53122
|
+
if (!parsed.ok) {
|
|
53123
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
53124
|
+
lastSeenAt: new Date().toISOString(),
|
|
53125
|
+
tmuxAvailable: false,
|
|
53126
|
+
lastError: parsed.reason
|
|
53127
|
+
});
|
|
53128
|
+
throw new Error(`remote tmux unavailable: ${parsed.reason}`);
|
|
53129
|
+
}
|
|
53130
|
+
this.tmuxBin = parsed.tmuxBin;
|
|
53131
|
+
this.remoteHomeDir = parsed.homeDir;
|
|
53132
|
+
}
|
|
53133
|
+
async ensureRemoteRuntimeDirs() {
|
|
53134
|
+
await this.runShell([
|
|
53135
|
+
`mkdir -p ${quoteShellArg(this.fsPaths.rootDir)}`,
|
|
53136
|
+
`mkdir -p ${quoteShellArg(this.fsPaths.panesDir)}`,
|
|
53137
|
+
`mkdir -p ${quoteShellArg(this.fsPaths.hooksDir)}`,
|
|
53138
|
+
`chmod 700 ${quoteShellArg(this.fsPaths.rootDir)}`,
|
|
53139
|
+
`chmod 700 ${quoteShellArg(this.fsPaths.panesDir)}`,
|
|
53140
|
+
`chmod 700 ${quoteShellArg(this.fsPaths.hooksDir)}`
|
|
53141
|
+
].join(`
|
|
53142
|
+
`));
|
|
53143
|
+
}
|
|
53144
|
+
async ensureSession() {
|
|
53145
|
+
const exists3 = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
|
|
53146
|
+
if (exists3.exitCode === 0) {
|
|
53147
|
+
return;
|
|
53148
|
+
}
|
|
53149
|
+
await this.runTmux(["new-session", "-d", "-c", this.remoteHomeDir, "-s", this.sessionName]);
|
|
53150
|
+
}
|
|
53151
|
+
async startHooks() {
|
|
53152
|
+
await this.ensureRemoteRuntimeDirs();
|
|
53153
|
+
const fifoPath = this.fsPaths.hookFifoPath;
|
|
53154
|
+
await this.runShell(`rm -f ${quoteShellArg(fifoPath)} && mkfifo ${quoteShellArg(fifoPath)} && chmod 600 ${quoteShellArg(fifoPath)}`);
|
|
53155
|
+
const stopReader = await this.openReaderChannel(`exec tail -n +1 -f ${quoteShellArg(fifoPath)}`, {
|
|
53156
|
+
onData: (data) => {
|
|
53157
|
+
this.handleHookChunk(data.toString());
|
|
53158
|
+
},
|
|
53159
|
+
onClose: () => {
|
|
53160
|
+
if (!this.manualDisconnect) {
|
|
53161
|
+
this.callbacks.onError(new Error("SSH hook reader closed unexpectedly"));
|
|
53162
|
+
}
|
|
53163
|
+
}
|
|
53164
|
+
});
|
|
53165
|
+
this.hookReadAbort = () => {
|
|
53166
|
+
stopReader();
|
|
53167
|
+
this.runShellAllowFailure(`rm -f ${quoteShellArg(fifoPath)}`);
|
|
53168
|
+
};
|
|
53169
|
+
await this.installHook("alert-bell", ["bell", "#{window_id}", "#{pane_id}"]);
|
|
53170
|
+
await this.installHook("pane-exited", ["pane-exited", "#{window_id}", "#{pane_id}"]);
|
|
53171
|
+
await this.installHook("pane-died", ["pane-died", "#{window_id}", "#{pane_id}"]);
|
|
53172
|
+
}
|
|
53173
|
+
async stopHooks() {
|
|
53174
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "alert-bell"]);
|
|
53175
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-exited"]);
|
|
53176
|
+
await this.runTmuxAllowFailure(["set-hook", "-u", "-t", this.sessionName, "pane-died"]);
|
|
53177
|
+
this.hookReadAbort?.();
|
|
53178
|
+
this.hookReadAbort = null;
|
|
53179
|
+
this.hookBuffer = "";
|
|
53180
|
+
}
|
|
53181
|
+
async installHook(hookName, fields) {
|
|
53182
|
+
const fifoPath = this.fsPaths.hookFifoPath;
|
|
53183
|
+
const innerScript = `printf '%s\\t%s\\t%s\\n' ${fields.map((field) => quoteShellArg(field)).join(" ")} >> ${quoteShellArg(fifoPath)}`;
|
|
53184
|
+
await this.runTmux([
|
|
53185
|
+
"set-hook",
|
|
53186
|
+
"-t",
|
|
53187
|
+
this.sessionName,
|
|
53188
|
+
hookName,
|
|
53189
|
+
`run-shell -b ${quoteShellArg(innerScript)}`
|
|
53190
|
+
]);
|
|
52905
53191
|
}
|
|
52906
|
-
|
|
52907
|
-
|
|
52908
|
-
|
|
52909
|
-
|
|
52910
|
-
|
|
52911
|
-
if (
|
|
52912
|
-
|
|
53192
|
+
handleHookChunk(text2) {
|
|
53193
|
+
this.hookBuffer += text2;
|
|
53194
|
+
while (true) {
|
|
53195
|
+
const newlineIndex = this.hookBuffer.indexOf(`
|
|
53196
|
+
`);
|
|
53197
|
+
if (newlineIndex < 0) {
|
|
53198
|
+
return;
|
|
52913
53199
|
}
|
|
52914
|
-
|
|
52915
|
-
|
|
53200
|
+
const line = this.hookBuffer.slice(0, newlineIndex).trim();
|
|
53201
|
+
this.hookBuffer = this.hookBuffer.slice(newlineIndex + 1);
|
|
53202
|
+
if (!line) {
|
|
53203
|
+
continue;
|
|
52916
53204
|
}
|
|
52917
|
-
|
|
52918
|
-
|
|
52919
|
-
|
|
52920
|
-
|
|
52921
|
-
|
|
52922
|
-
|
|
52923
|
-
|
|
52924
|
-
|
|
52925
|
-
|
|
52926
|
-
|
|
52927
|
-
|
|
52928
|
-
|
|
52929
|
-
|
|
52930
|
-
|
|
52931
|
-
|
|
52932
|
-
let updated = false;
|
|
52933
|
-
for (const window2 of this.snapshotWindows.values()) {
|
|
52934
|
-
const pane = window2.panes.find((item) => item.id === paneId);
|
|
52935
|
-
if (!pane) {
|
|
53205
|
+
const [type, windowId, paneId] = line.split("\t");
|
|
53206
|
+
if (type === "bell") {
|
|
53207
|
+
const key = paneId || windowId || "-";
|
|
53208
|
+
const previous = this.bellDedup.get(key) ?? 0;
|
|
53209
|
+
const now = Date.now();
|
|
53210
|
+
if (now - previous >= BELL_DEDUP_WINDOW_MS2) {
|
|
53211
|
+
this.bellDedup.set(key, now);
|
|
53212
|
+
this.callbacks.onEvent({
|
|
53213
|
+
type: "bell",
|
|
53214
|
+
data: {
|
|
53215
|
+
windowId: windowId || undefined,
|
|
53216
|
+
paneId: paneId || this.activePaneId || undefined
|
|
53217
|
+
}
|
|
53218
|
+
});
|
|
53219
|
+
}
|
|
52936
53220
|
continue;
|
|
52937
53221
|
}
|
|
52938
|
-
|
|
52939
|
-
|
|
52940
|
-
pane.title = nextTitle;
|
|
52941
|
-
updated = true;
|
|
53222
|
+
if (type === "pane-exited" || type === "pane-died") {
|
|
53223
|
+
this.requestSnapshot();
|
|
52942
53224
|
}
|
|
52943
|
-
this.pendingPaneTitles.delete(paneId);
|
|
52944
|
-
break;
|
|
52945
|
-
}
|
|
52946
|
-
if (found && !updated) {
|
|
52947
|
-
return;
|
|
52948
|
-
}
|
|
52949
|
-
if (!updated) {
|
|
52950
|
-
this.pendingPaneTitles.set(paneId, nextTitle);
|
|
52951
|
-
return;
|
|
52952
53225
|
}
|
|
52953
|
-
this.emitSnapshotIfReady();
|
|
52954
53226
|
}
|
|
52955
|
-
|
|
52956
|
-
|
|
52957
|
-
|
|
52958
|
-
this.sendCommand(`set-option -g -w window-size latest
|
|
52959
|
-
`);
|
|
52960
|
-
this.sendCommand(`set-option -g -w aggressive-resize off
|
|
52961
|
-
`);
|
|
53227
|
+
async runAndRefresh(argv, allowTargetMissing = false) {
|
|
53228
|
+
await this.runTmux(argv, allowTargetMissing);
|
|
53229
|
+
await this.requestSnapshotInternal();
|
|
52962
53230
|
}
|
|
52963
|
-
|
|
52964
|
-
|
|
52965
|
-
|
|
53231
|
+
async closeWindowInternal(windowId) {
|
|
53232
|
+
const count2 = Number.parseInt((await this.runTmux(["display-message", "-p", "-t", this.sessionName, "#{session_windows}"])).stdout.trim() || "0", 10);
|
|
53233
|
+
if (count2 <= 1) {
|
|
53234
|
+
await this.runTmux(["new-window", "-d", "-t", this.sessionName]);
|
|
52966
53235
|
}
|
|
52967
|
-
this.
|
|
52968
|
-
this.resizeSnapshotTimer = null;
|
|
52969
|
-
this.requestSnapshot();
|
|
52970
|
-
}, delayMs);
|
|
53236
|
+
await this.runAndRefresh(["kill-window", "-t", windowId], true);
|
|
52971
53237
|
}
|
|
52972
|
-
|
|
52973
|
-
|
|
52974
|
-
|
|
52975
|
-
this.
|
|
52976
|
-
|
|
52977
|
-
sendUtf8Bytes(paneId, data) {
|
|
52978
|
-
if (data.length === 0) {
|
|
53238
|
+
async resizePaneInternal(paneId, cols, rows) {
|
|
53239
|
+
const safeCols = Math.max(2, Math.floor(cols));
|
|
53240
|
+
const safeRows = Math.max(2, Math.floor(rows));
|
|
53241
|
+
const windowId = this.findPaneWindowId(paneId) ?? (await this.runTmux(["display-message", "-p", "-t", paneId, "#{window_id}"], true)).stdout.trim();
|
|
53242
|
+
if (!windowId) {
|
|
52979
53243
|
return;
|
|
52980
53244
|
}
|
|
52981
|
-
|
|
52982
|
-
|
|
52983
|
-
const chunk2 = data.slice(offset, offset + chunkSize);
|
|
52984
|
-
const hex = [];
|
|
52985
|
-
for (const byte of chunk2) {
|
|
52986
|
-
hex.push(byte.toString(16).padStart(2, "0"));
|
|
52987
|
-
}
|
|
52988
|
-
this.sendCommand(`send-keys -H -t ${paneId} ${hex.join(" ")}
|
|
52989
|
-
`);
|
|
52990
|
-
}
|
|
53245
|
+
await this.runTmux(["resize-window", "-t", windowId, "-x", String(safeCols), "-y", String(safeRows)], true);
|
|
53246
|
+
await this.requestSnapshotInternal();
|
|
52991
53247
|
}
|
|
52992
|
-
|
|
52993
|
-
if (!this.connected)
|
|
52994
|
-
return;
|
|
52995
|
-
const cmd = `send-keys -t ${paneId} ${key}
|
|
52996
|
-
`;
|
|
52997
|
-
this.sendCommand(cmd);
|
|
52998
|
-
}
|
|
52999
|
-
selectWindow(windowId) {
|
|
53000
|
-
if (!this.connected) {
|
|
53001
|
-
console.log("[tmux] cannot select window: not connected");
|
|
53002
|
-
return;
|
|
53003
|
-
}
|
|
53248
|
+
async selectPaneInternal(windowId, paneId, size) {
|
|
53004
53249
|
this.activeWindowId = windowId;
|
|
53005
|
-
this.sendCommand(`select-window -t ${windowId}
|
|
53006
|
-
`);
|
|
53007
|
-
this.requestSnapshot();
|
|
53008
|
-
}
|
|
53009
|
-
selectPane(windowId, paneId) {
|
|
53010
|
-
if (!this.connected) {
|
|
53011
|
-
console.log("[tmux] cannot select pane: not connected");
|
|
53012
|
-
return;
|
|
53013
|
-
}
|
|
53014
|
-
console.log("[tmux] selecting pane", paneId, "in window", windowId);
|
|
53015
53250
|
this.activePaneId = paneId;
|
|
53016
|
-
this.
|
|
53017
|
-
this.
|
|
53018
|
-
|
|
53019
|
-
|
|
53020
|
-
|
|
53021
|
-
this.capturePaneHistory(paneId);
|
|
53022
|
-
}
|
|
53023
|
-
capturePaneHistory(paneId) {
|
|
53024
|
-
if (!this.connected) {
|
|
53025
|
-
console.log("[tmux] cannot capture history: not connected");
|
|
53026
|
-
return;
|
|
53027
|
-
}
|
|
53028
|
-
console.log("[tmux] capturing history for pane", paneId);
|
|
53029
|
-
const existing = this.historyCaptureStates.get(paneId);
|
|
53030
|
-
if (existing?.timeout) {
|
|
53031
|
-
clearTimeout(existing.timeout);
|
|
53032
|
-
}
|
|
53033
|
-
const timeout = setTimeout(() => {
|
|
53034
|
-
this.emitCapturedHistory(paneId);
|
|
53035
|
-
}, 220);
|
|
53036
|
-
this.historyCaptureStates.set(paneId, {
|
|
53037
|
-
normal: null,
|
|
53038
|
-
alternate: null,
|
|
53039
|
-
preferAlternate: null,
|
|
53040
|
-
timeout
|
|
53041
|
-
});
|
|
53042
|
-
this.pendingCapturePaneModeRequests.push(paneId);
|
|
53043
|
-
this.sendCommand(`display-message -p -t ${paneId} "#{alternate_on}"
|
|
53044
|
-
`, "capture-pane-mode");
|
|
53045
|
-
this.pendingCapturePaneRequests.push({ paneId, mode: "normal" });
|
|
53046
|
-
this.sendCommand(`capture-pane -t ${paneId} -S -1000 -e -p
|
|
53047
|
-
`, "capture-pane");
|
|
53048
|
-
this.pendingCapturePaneRequests.push({ paneId, mode: "alternate" });
|
|
53049
|
-
this.sendCommand(`capture-pane -t ${paneId} -a -S -1000 -e -p -q
|
|
53050
|
-
`, "capture-pane");
|
|
53051
|
-
}
|
|
53052
|
-
resizePane(_paneId, cols, rows) {
|
|
53053
|
-
if (!this.connected)
|
|
53054
|
-
return;
|
|
53055
|
-
const safeCols = Math.max(2, Math.floor(cols));
|
|
53056
|
-
const safeRows = Math.max(2, Math.floor(rows));
|
|
53057
|
-
this.sendCommand(`refresh-client -C ${safeCols}x${safeRows}
|
|
53058
|
-
`);
|
|
53059
|
-
const windowId = this.activeWindowId;
|
|
53060
|
-
if (windowId) {
|
|
53061
|
-
this.sendCommand(`resize-window -x ${safeCols} -y ${safeRows} -t ${windowId}
|
|
53062
|
-
`);
|
|
53063
|
-
this.sendCommand(`set-window-option -t ${windowId} window-size latest
|
|
53064
|
-
`);
|
|
53251
|
+
await this.runTmux(["select-window", "-t", windowId], true);
|
|
53252
|
+
await this.runTmux(["select-pane", "-t", paneId], true);
|
|
53253
|
+
await this.startPipeForPane(paneId);
|
|
53254
|
+
if (size) {
|
|
53255
|
+
await this.resizePaneInternal(paneId, size.cols, size.rows);
|
|
53065
53256
|
}
|
|
53066
|
-
this.
|
|
53257
|
+
this.callbacks.onEvent({
|
|
53258
|
+
type: "pane-active",
|
|
53259
|
+
data: { windowId, paneId }
|
|
53260
|
+
});
|
|
53261
|
+
await this.capturePaneHistory(paneId);
|
|
53262
|
+
await this.requestSnapshotInternal();
|
|
53067
53263
|
}
|
|
53068
|
-
|
|
53069
|
-
|
|
53070
|
-
|
|
53071
|
-
|
|
53072
|
-
|
|
53073
|
-
|
|
53074
|
-
|
|
53075
|
-
this.sendCommand(`new-window
|
|
53076
|
-
`);
|
|
53264
|
+
async capturePaneHistory(paneId) {
|
|
53265
|
+
const mode = (await this.runTmux(["display-message", "-p", "-t", paneId, "#{alternate_on}"], true)).stdout.trim();
|
|
53266
|
+
const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", DEFAULT_HISTORY_LINES, "-e", "-p"], true, 30000)).stdout;
|
|
53267
|
+
const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", DEFAULT_HISTORY_LINES, "-e", "-p", "-q"], true, 30000)).stdout;
|
|
53268
|
+
const history = normal || alternate;
|
|
53269
|
+
if (history) {
|
|
53270
|
+
this.callbacks.onTerminalHistory(paneId, history);
|
|
53077
53271
|
}
|
|
53078
53272
|
}
|
|
53079
|
-
|
|
53080
|
-
if (!this.connected)
|
|
53081
|
-
return;
|
|
53082
|
-
this.sendCommand(`if-shell -F '#{==:#{session_windows},1}' 'new-window -d' ''
|
|
53083
|
-
`);
|
|
53084
|
-
this.sendCommand(`kill-window -t ${windowId}
|
|
53085
|
-
`);
|
|
53086
|
-
}
|
|
53087
|
-
closePane(paneId) {
|
|
53088
|
-
if (!this.connected)
|
|
53089
|
-
return;
|
|
53090
|
-
this.sendCommand(`kill-pane -t ${paneId}
|
|
53091
|
-
`);
|
|
53092
|
-
}
|
|
53093
|
-
renameWindow(windowId, name) {
|
|
53094
|
-
if (!this.connected)
|
|
53095
|
-
return;
|
|
53096
|
-
this.sendCommand(`rename-window -t ${windowId} "${name}"
|
|
53097
|
-
`);
|
|
53098
|
-
}
|
|
53099
|
-
requestSnapshot() {
|
|
53100
|
-
if (!this.connected)
|
|
53101
|
-
return;
|
|
53102
|
-
this.sendCommand(`display-message -p "#{session_id} #{session_name}"
|
|
53103
|
-
`, "snapshot-session");
|
|
53104
|
-
this.sendCommand(`list-windows -F "#{window_id} #{window_index} #{window_name} #{window_active}"
|
|
53105
|
-
`, "snapshot-windows");
|
|
53106
|
-
this.sendCommand(`list-panes -F "#{pane_id} #{window_id} #{pane_index} #{pane_title} #{pane_active} #{pane_width} #{pane_height}"
|
|
53107
|
-
`, "snapshot-panes");
|
|
53108
|
-
}
|
|
53109
|
-
handleOutputBlockBegin(meta) {
|
|
53110
|
-
const kind = this.pendingCommandKinds.shift() ?? "noop";
|
|
53111
|
-
this.commandKindsByNo.set(meta.commandNo, kind);
|
|
53112
|
-
}
|
|
53113
|
-
handleOutputBlock(block) {
|
|
53114
|
-
this.markReady();
|
|
53115
|
-
const kind = this.commandKindsByNo.get(block.commandNo);
|
|
53116
|
-
this.commandKindsByNo.delete(block.commandNo);
|
|
53117
|
-
console.log("[tmux] handleOutputBlock commandNo:", block.commandNo, "kind:", kind, "lines:", block.lines.length);
|
|
53118
|
-
const resolvedKind = kind ?? "noop";
|
|
53119
|
-
if (block.isError) {
|
|
53120
|
-
const message = block.lines.join(`
|
|
53121
|
-
`).trim();
|
|
53122
|
-
this.handleCaptureErrorFallback(resolvedKind);
|
|
53123
|
-
if (message) {
|
|
53124
|
-
if (this.isRecoverableTargetMissingError(message)) {
|
|
53125
|
-
this.recoverFromTargetMissingError(message);
|
|
53126
|
-
return;
|
|
53127
|
-
}
|
|
53128
|
-
this.onError(new Error(message));
|
|
53129
|
-
}
|
|
53273
|
+
async requestSnapshotInternal() {
|
|
53274
|
+
if (!this.connected) {
|
|
53130
53275
|
return;
|
|
53131
53276
|
}
|
|
53132
|
-
|
|
53133
|
-
|
|
53134
|
-
|
|
53135
|
-
|
|
53136
|
-
|
|
53137
|
-
|
|
53138
|
-
|
|
53139
|
-
|
|
53140
|
-
|
|
53141
|
-
|
|
53142
|
-
|
|
53143
|
-
|
|
53144
|
-
|
|
53145
|
-
|
|
53146
|
-
|
|
53147
|
-
|
|
53148
|
-
|
|
53149
|
-
|
|
53150
|
-
|
|
53151
|
-
|
|
53152
|
-
|
|
53153
|
-
|
|
53154
|
-
|
|
53155
|
-
|
|
53277
|
+
const [sessionRes, windowsRes, panesRes] = await Promise.all([
|
|
53278
|
+
this.runTmuxAllowFailure([
|
|
53279
|
+
"display-message",
|
|
53280
|
+
"-p",
|
|
53281
|
+
"-t",
|
|
53282
|
+
this.sessionName,
|
|
53283
|
+
"#{session_id}\t#{session_name}"
|
|
53284
|
+
]),
|
|
53285
|
+
this.runTmuxAllowFailure([
|
|
53286
|
+
"list-windows",
|
|
53287
|
+
"-t",
|
|
53288
|
+
this.sessionName,
|
|
53289
|
+
"-F",
|
|
53290
|
+
"#{window_id}\t#{window_index}\t#{window_name}\t#{window_active}"
|
|
53291
|
+
]),
|
|
53292
|
+
this.runTmuxAllowFailure([
|
|
53293
|
+
"list-panes",
|
|
53294
|
+
"-t",
|
|
53295
|
+
this.sessionName,
|
|
53296
|
+
"-F",
|
|
53297
|
+
"#{pane_id}\t#{window_id}\t#{pane_index}\t#{pane_title}\t#{pane_active}\t#{pane_width}\t#{pane_height}"
|
|
53298
|
+
])
|
|
53299
|
+
]);
|
|
53300
|
+
if (sessionRes.exitCode !== 0 || windowsRes.exitCode !== 0 || panesRes.exitCode !== 0) {
|
|
53301
|
+
this.callbacks.onSnapshot({ deviceId: this.deviceId, session: null });
|
|
53156
53302
|
return;
|
|
53157
53303
|
}
|
|
53158
|
-
|
|
53159
|
-
|
|
53160
|
-
|
|
53161
|
-
|
|
53162
|
-
isRecoverableTargetMissingError(message) {
|
|
53163
|
-
const normalized = message.toLowerCase();
|
|
53164
|
-
return normalized.includes("can't find window") || normalized.includes("can't find pane") || normalized.includes("no such window") || normalized.includes("no such pane");
|
|
53165
|
-
}
|
|
53166
|
-
recoverFromTargetMissingError(message) {
|
|
53167
|
-
const normalized = message.toLowerCase();
|
|
53168
|
-
if (normalized.includes("window")) {
|
|
53169
|
-
this.activeWindowId = null;
|
|
53170
|
-
}
|
|
53171
|
-
if (normalized.includes("pane")) {
|
|
53172
|
-
this.activePaneId = null;
|
|
53173
|
-
}
|
|
53174
|
-
this.requestSnapshot();
|
|
53304
|
+
this.parseSnapshotSession(sessionRes.stdout.split(/\r?\n/));
|
|
53305
|
+
this.parseSnapshotWindows(windowsRes.stdout.split(/\r?\n/));
|
|
53306
|
+
this.parseSnapshotPanes(panesRes.stdout.split(/\r?\n/));
|
|
53307
|
+
this.emitSnapshot();
|
|
53175
53308
|
}
|
|
53176
53309
|
parseSnapshotSession(lines) {
|
|
53310
|
+
this.snapshotSession = null;
|
|
53177
53311
|
for (const line of lines) {
|
|
53178
|
-
if (!line.trim())
|
|
53312
|
+
if (!line.trim()) {
|
|
53179
53313
|
continue;
|
|
53314
|
+
}
|
|
53180
53315
|
const [id, name] = line.split("\t");
|
|
53181
|
-
if (
|
|
53182
|
-
|
|
53183
|
-
|
|
53316
|
+
if (id) {
|
|
53317
|
+
this.snapshotSession = { id, name: name ?? "" };
|
|
53318
|
+
}
|
|
53184
53319
|
return;
|
|
53185
53320
|
}
|
|
53186
53321
|
}
|
|
53187
53322
|
parseSnapshotWindows(lines) {
|
|
53188
53323
|
this.snapshotWindows.clear();
|
|
53189
|
-
this.snapshotPanesReady = false;
|
|
53190
53324
|
for (const line of lines) {
|
|
53191
|
-
if (!line.trim())
|
|
53325
|
+
if (!line.trim()) {
|
|
53192
53326
|
continue;
|
|
53327
|
+
}
|
|
53193
53328
|
const [id, indexRaw, name, activeRaw] = line.split("\t");
|
|
53194
|
-
if (!id)
|
|
53329
|
+
if (!id) {
|
|
53195
53330
|
continue;
|
|
53331
|
+
}
|
|
53196
53332
|
const index = Number.parseInt(indexRaw ?? "", 10);
|
|
53197
53333
|
const active = activeRaw === "1";
|
|
53334
|
+
if (active) {
|
|
53335
|
+
this.activeWindowId = id;
|
|
53336
|
+
}
|
|
53198
53337
|
this.snapshotWindows.set(id, {
|
|
53199
53338
|
id,
|
|
53200
|
-
name: name ?? "",
|
|
53201
53339
|
index: Number.isNaN(index) ? 0 : index,
|
|
53340
|
+
name: name ?? "",
|
|
53202
53341
|
active,
|
|
53203
53342
|
panes: []
|
|
53204
53343
|
});
|
|
@@ -53209,195 +53348,493 @@ ssh stderr: ${stderrText}`) : err2;
|
|
|
53209
53348
|
window2.panes = [];
|
|
53210
53349
|
}
|
|
53211
53350
|
for (const line of lines) {
|
|
53212
|
-
if (!line.trim())
|
|
53351
|
+
if (!line.trim()) {
|
|
53213
53352
|
continue;
|
|
53353
|
+
}
|
|
53214
53354
|
const [paneId, windowId, indexRaw, titleRaw, activeRaw, widthRaw, heightRaw] = line.split("\t");
|
|
53215
|
-
if (!paneId || !windowId)
|
|
53355
|
+
if (!paneId || !windowId) {
|
|
53216
53356
|
continue;
|
|
53357
|
+
}
|
|
53217
53358
|
const index = Number.parseInt(indexRaw ?? "", 10);
|
|
53218
53359
|
const width = Number.parseInt(widthRaw ?? "", 10);
|
|
53219
53360
|
const height = Number.parseInt(heightRaw ?? "", 10);
|
|
53220
|
-
const title = titleRaw?.trim() ? titleRaw : undefined;
|
|
53221
53361
|
const pane = {
|
|
53222
53362
|
id: paneId,
|
|
53223
53363
|
windowId,
|
|
53224
53364
|
index: Number.isNaN(index) ? 0 : index,
|
|
53225
|
-
title: this.pendingPaneTitles.get(paneId) ??
|
|
53365
|
+
title: this.pendingPaneTitles.get(paneId) ?? (titleRaw?.trim() ? titleRaw : undefined),
|
|
53226
53366
|
active: activeRaw === "1",
|
|
53227
53367
|
width: Number.isNaN(width) ? 0 : width,
|
|
53228
53368
|
height: Number.isNaN(height) ? 0 : height
|
|
53229
53369
|
};
|
|
53230
|
-
|
|
53231
|
-
|
|
53370
|
+
if (pane.active) {
|
|
53371
|
+
this.activePaneId = paneId;
|
|
53372
|
+
this.activeWindowId = windowId;
|
|
53373
|
+
}
|
|
53374
|
+
const window2 = this.snapshotWindows.get(windowId);
|
|
53375
|
+
if (!window2) {
|
|
53232
53376
|
continue;
|
|
53233
|
-
|
|
53377
|
+
}
|
|
53378
|
+
window2.panes.push(pane);
|
|
53234
53379
|
this.pendingPaneTitles.delete(paneId);
|
|
53235
53380
|
}
|
|
53236
|
-
for (const
|
|
53237
|
-
|
|
53381
|
+
for (const window2 of this.snapshotWindows.values()) {
|
|
53382
|
+
window2.panes.sort((left, right) => left.index - right.index);
|
|
53238
53383
|
}
|
|
53239
|
-
this.snapshotPanesReady = true;
|
|
53240
53384
|
}
|
|
53241
|
-
|
|
53242
|
-
|
|
53243
|
-
return;
|
|
53244
|
-
if (this.snapshotWindows.size === 0)
|
|
53245
|
-
return;
|
|
53246
|
-
if (!this.snapshotPanesReady)
|
|
53247
|
-
return;
|
|
53248
|
-
const windows = Array.from(this.snapshotWindows.values()).sort((a, b3) => a.index - b3.index);
|
|
53249
|
-
const session = {
|
|
53385
|
+
emitSnapshot() {
|
|
53386
|
+
const session = this.snapshotSession ? {
|
|
53250
53387
|
id: this.snapshotSession.id,
|
|
53251
53388
|
name: this.snapshotSession.name,
|
|
53252
|
-
windows
|
|
53253
|
-
};
|
|
53254
|
-
this.onSnapshot({
|
|
53389
|
+
windows: Array.from(this.snapshotWindows.values()).sort((left, right) => left.index - right.index)
|
|
53390
|
+
} : null;
|
|
53391
|
+
this.callbacks.onSnapshot({
|
|
53255
53392
|
deviceId: this.deviceId,
|
|
53256
53393
|
session
|
|
53257
53394
|
});
|
|
53258
53395
|
}
|
|
53259
|
-
|
|
53260
|
-
this.
|
|
53261
|
-
|
|
53262
|
-
|
|
53263
|
-
|
|
53264
|
-
}
|
|
53265
|
-
if (this.sshStream) {
|
|
53266
|
-
this.sshStream.write(cmd);
|
|
53396
|
+
findPaneWindowId(paneId) {
|
|
53397
|
+
for (const window2 of this.snapshotWindows.values()) {
|
|
53398
|
+
if (window2.panes.some((pane) => pane.id === paneId)) {
|
|
53399
|
+
return window2.id;
|
|
53400
|
+
}
|
|
53267
53401
|
}
|
|
53402
|
+
return null;
|
|
53403
|
+
}
|
|
53404
|
+
async startPipeForPane(paneId) {
|
|
53405
|
+
await this.queuePipeTransition(async () => {
|
|
53406
|
+
if (this.currentPipePaneId === paneId) {
|
|
53407
|
+
return;
|
|
53408
|
+
}
|
|
53409
|
+
await this.stopPipeNow();
|
|
53410
|
+
const fifoPath = this.fsPaths.paneFifoPath(paneId);
|
|
53411
|
+
await this.ensureRemoteRuntimeDirs();
|
|
53412
|
+
await this.runShell(`rm -f ${quoteShellArg(fifoPath)} && mkfifo ${quoteShellArg(fifoPath)} && chmod 600 ${quoteShellArg(fifoPath)}`);
|
|
53413
|
+
const parser = createPaneTitleParser({
|
|
53414
|
+
onTitle: (title) => {
|
|
53415
|
+
this.pendingPaneTitles.set(paneId, title);
|
|
53416
|
+
this.requestSnapshot();
|
|
53417
|
+
}
|
|
53418
|
+
});
|
|
53419
|
+
const stopReader = await this.openReaderChannel(`exec cat ${quoteShellArg(fifoPath)}`, {
|
|
53420
|
+
onData: (raw) => {
|
|
53421
|
+
const output = parser.push(raw);
|
|
53422
|
+
if (Array.from(raw).includes(7)) {
|
|
53423
|
+
this.callbacks.onEvent({ type: "bell", data: { paneId } });
|
|
53424
|
+
}
|
|
53425
|
+
if (output.length > 0) {
|
|
53426
|
+
this.callbacks.onTerminalOutput(paneId, output);
|
|
53427
|
+
}
|
|
53428
|
+
},
|
|
53429
|
+
onClose: () => {
|
|
53430
|
+
if (!this.manualDisconnect && this.currentPipePaneId === paneId) {
|
|
53431
|
+
this.callbacks.onError(new Error(`SSH pane reader closed unexpectedly: ${paneId}`));
|
|
53432
|
+
}
|
|
53433
|
+
}
|
|
53434
|
+
});
|
|
53435
|
+
this.pipeReadAbort = () => {
|
|
53436
|
+
stopReader();
|
|
53437
|
+
this.runShellAllowFailure(`rm -f ${quoteShellArg(fifoPath)}`);
|
|
53438
|
+
};
|
|
53439
|
+
await this.runTmux(["pipe-pane", "-O", "-t", paneId, `cat >${fifoPath}`]);
|
|
53440
|
+
this.currentPipePaneId = paneId;
|
|
53441
|
+
});
|
|
53268
53442
|
}
|
|
53269
|
-
|
|
53270
|
-
this.
|
|
53271
|
-
this.onTerminalOutput(paneId, data);
|
|
53443
|
+
async stopPipe() {
|
|
53444
|
+
await this.queuePipeTransition(() => this.stopPipeNow());
|
|
53272
53445
|
}
|
|
53273
|
-
|
|
53274
|
-
|
|
53275
|
-
|
|
53446
|
+
async stopPipeNow() {
|
|
53447
|
+
const paneId = this.currentPipePaneId;
|
|
53448
|
+
this.currentPipePaneId = null;
|
|
53449
|
+
if (paneId) {
|
|
53450
|
+
await this.runTmuxAllowFailure(["pipe-pane", "-t", paneId]);
|
|
53276
53451
|
}
|
|
53277
|
-
|
|
53452
|
+
this.pipeReadAbort?.();
|
|
53453
|
+
this.pipeReadAbort = null;
|
|
53454
|
+
}
|
|
53455
|
+
queuePipeTransition(task) {
|
|
53456
|
+
const next = this.pipeTransition.catch(() => {
|
|
53278
53457
|
return;
|
|
53458
|
+
}).then(task);
|
|
53459
|
+
this.pipeTransition = next;
|
|
53460
|
+
return next;
|
|
53461
|
+
}
|
|
53462
|
+
async runTmux(argv, allowTargetMissing = false, timeoutMs = 1e4) {
|
|
53463
|
+
const result = await this.runTmuxAllowFailure(argv, timeoutMs);
|
|
53464
|
+
if (result.exitCode === 0) {
|
|
53465
|
+
return result;
|
|
53466
|
+
}
|
|
53467
|
+
const message = (result.stderr.trim() || result.stdout.trim() || `tmux command failed: ${argv.join(" ")}`).trim();
|
|
53468
|
+
if (allowTargetMissing && this.isRecoverableTargetMissingError(message)) {
|
|
53469
|
+
this.recoverFromTargetMissingError(message);
|
|
53470
|
+
return result;
|
|
53279
53471
|
}
|
|
53280
|
-
this.
|
|
53472
|
+
updateDeviceRuntimeStatus(this.deviceId, {
|
|
53473
|
+
lastSeenAt: new Date().toISOString(),
|
|
53474
|
+
tmuxAvailable: false,
|
|
53475
|
+
lastError: message
|
|
53476
|
+
});
|
|
53477
|
+
throw new Error(message);
|
|
53281
53478
|
}
|
|
53282
|
-
|
|
53283
|
-
this.
|
|
53284
|
-
this.cleanup();
|
|
53479
|
+
async runTmuxAllowFailure(argv, timeoutMs = 1e4) {
|
|
53480
|
+
return this.runShell(`${quoteShellArg(this.tmuxBin)} ${joinShellArgs(argv)}`, timeoutMs);
|
|
53285
53481
|
}
|
|
53286
|
-
|
|
53287
|
-
return this.
|
|
53482
|
+
async runShell(command, timeoutMs = 1e4) {
|
|
53483
|
+
return this.enqueueShellCommand(command, timeoutMs);
|
|
53288
53484
|
}
|
|
53289
|
-
|
|
53290
|
-
|
|
53291
|
-
|
|
53292
|
-
|
|
53293
|
-
|
|
53294
|
-
|
|
53485
|
+
async runShellAllowFailure(command, timeoutMs = 1e4) {
|
|
53486
|
+
try {
|
|
53487
|
+
return await this.enqueueShellCommand(command, timeoutMs);
|
|
53488
|
+
} catch (error) {
|
|
53489
|
+
return {
|
|
53490
|
+
exitCode: 1,
|
|
53491
|
+
stdout: "",
|
|
53492
|
+
stderr: error instanceof Error ? error.message : String(error)
|
|
53493
|
+
};
|
|
53295
53494
|
}
|
|
53296
|
-
|
|
53297
|
-
|
|
53298
|
-
const
|
|
53299
|
-
if (!state) {
|
|
53495
|
+
}
|
|
53496
|
+
enqueueShellCommand(command, timeoutMs) {
|
|
53497
|
+
const next = this.commandQueue.catch(() => {
|
|
53300
53498
|
return;
|
|
53499
|
+
}).then(() => this.executeShellCommand(command, timeoutMs));
|
|
53500
|
+
this.commandQueue = next.then(() => {
|
|
53501
|
+
return;
|
|
53502
|
+
});
|
|
53503
|
+
return next;
|
|
53504
|
+
}
|
|
53505
|
+
executeShellCommand(command, timeoutMs) {
|
|
53506
|
+
const stream = this.commandStream;
|
|
53507
|
+
if (!stream) {
|
|
53508
|
+
return Promise.reject(new Error("SSH command channel not ready"));
|
|
53301
53509
|
}
|
|
53302
|
-
|
|
53303
|
-
|
|
53304
|
-
|
|
53305
|
-
|
|
53306
|
-
|
|
53307
|
-
|
|
53308
|
-
|
|
53510
|
+
const commandId = crypto.randomUUID();
|
|
53511
|
+
const wrappedCommand = `{ ${command}; } 2>&1
|
|
53512
|
+
printf '\\036TMEX_END %s %d\\036\\n' ${quoteShellArg(commandId)} $?
|
|
53513
|
+
`;
|
|
53514
|
+
return new Promise((resolve, reject) => {
|
|
53515
|
+
const timer = setTimeout(() => {
|
|
53516
|
+
if (!this.pendingCommand || this.pendingCommand.id !== commandId) {
|
|
53517
|
+
return;
|
|
53518
|
+
}
|
|
53519
|
+
this.pendingCommand = null;
|
|
53520
|
+
reject(new Error(`remote command timed out: ${command}`));
|
|
53521
|
+
}, timeoutMs);
|
|
53522
|
+
this.pendingCommand = {
|
|
53523
|
+
id: commandId,
|
|
53524
|
+
stderr: "",
|
|
53525
|
+
resolve,
|
|
53526
|
+
reject,
|
|
53527
|
+
timer
|
|
53528
|
+
};
|
|
53529
|
+
stream.write(wrappedCommand);
|
|
53530
|
+
});
|
|
53531
|
+
}
|
|
53532
|
+
flushCommandBuffer() {
|
|
53533
|
+
while (true) {
|
|
53534
|
+
const sentinelIndex = this.commandStdoutBuffer.indexOf(COMMAND_SENTINEL);
|
|
53535
|
+
if (sentinelIndex < 0) {
|
|
53536
|
+
return;
|
|
53537
|
+
}
|
|
53538
|
+
const sentinelEnd = this.commandStdoutBuffer.indexOf("\x1E", sentinelIndex + COMMAND_SENTINEL.length);
|
|
53539
|
+
if (sentinelEnd < 0) {
|
|
53540
|
+
return;
|
|
53541
|
+
}
|
|
53542
|
+
const payload = this.commandStdoutBuffer.slice(sentinelIndex + COMMAND_SENTINEL.length, sentinelEnd).trim();
|
|
53543
|
+
const [commandId = "", exitCodeRaw = "1"] = payload.split(/\s+/);
|
|
53544
|
+
const stdout = this.commandStdoutBuffer.slice(0, sentinelIndex);
|
|
53545
|
+
this.commandStdoutBuffer = this.commandStdoutBuffer.slice(sentinelEnd + 1).replace(/^\r?\n/, "");
|
|
53546
|
+
const pending = this.pendingCommand;
|
|
53547
|
+
if (!pending || pending.id !== commandId) {
|
|
53548
|
+
continue;
|
|
53549
|
+
}
|
|
53550
|
+
this.pendingCommand = null;
|
|
53551
|
+
clearTimeout(pending.timer);
|
|
53552
|
+
pending.resolve({
|
|
53553
|
+
exitCode: Number.parseInt(exitCodeRaw, 10) || 0,
|
|
53554
|
+
stdout,
|
|
53555
|
+
stderr: pending.stderr
|
|
53556
|
+
});
|
|
53309
53557
|
}
|
|
53310
53558
|
}
|
|
53311
|
-
|
|
53312
|
-
const
|
|
53313
|
-
if (!
|
|
53559
|
+
rejectPendingCommand(error) {
|
|
53560
|
+
const pending = this.pendingCommand;
|
|
53561
|
+
if (!pending) {
|
|
53314
53562
|
return;
|
|
53315
53563
|
}
|
|
53316
|
-
|
|
53317
|
-
|
|
53564
|
+
this.pendingCommand = null;
|
|
53565
|
+
clearTimeout(pending.timer);
|
|
53566
|
+
pending.reject(error);
|
|
53567
|
+
}
|
|
53568
|
+
async openReaderChannel(command, options) {
|
|
53569
|
+
const sshClient = this.requireSshClient();
|
|
53570
|
+
const stream = await new Promise((resolve, reject) => {
|
|
53571
|
+
sshClient.exec("/bin/sh -s", { pty: false }, (error, channel) => {
|
|
53572
|
+
if (error) {
|
|
53573
|
+
reject(error);
|
|
53574
|
+
return;
|
|
53575
|
+
}
|
|
53576
|
+
resolve(channel);
|
|
53577
|
+
});
|
|
53578
|
+
});
|
|
53579
|
+
stream.on("data", (data) => {
|
|
53580
|
+
options.onData(data);
|
|
53581
|
+
});
|
|
53582
|
+
stream.stderr.on("data", (data) => {
|
|
53583
|
+
if (!this.manualDisconnect) {
|
|
53584
|
+
this.callbacks.onError(new Error(data.toString().trim() || "SSH reader stderr output"));
|
|
53585
|
+
}
|
|
53586
|
+
});
|
|
53587
|
+
stream.on("close", () => {
|
|
53588
|
+
options.onClose?.();
|
|
53589
|
+
});
|
|
53590
|
+
stream.write(`${command}
|
|
53591
|
+
`);
|
|
53592
|
+
return () => {
|
|
53593
|
+
stream.end();
|
|
53594
|
+
stream.close();
|
|
53595
|
+
stream.destroy();
|
|
53596
|
+
};
|
|
53597
|
+
}
|
|
53598
|
+
isRecoverableTargetMissingError(message) {
|
|
53599
|
+
const normalized = message.toLowerCase();
|
|
53600
|
+
return normalized.includes("can't find window") || normalized.includes("can't find pane") || normalized.includes("no such window") || normalized.includes("no such pane");
|
|
53601
|
+
}
|
|
53602
|
+
recoverFromTargetMissingError(message) {
|
|
53603
|
+
const normalized = message.toLowerCase();
|
|
53604
|
+
if (normalized.includes("window")) {
|
|
53605
|
+
this.activeWindowId = null;
|
|
53606
|
+
}
|
|
53607
|
+
if (normalized.includes("pane")) {
|
|
53608
|
+
this.activePaneId = null;
|
|
53609
|
+
}
|
|
53610
|
+
this.requestSnapshot();
|
|
53611
|
+
}
|
|
53612
|
+
async shutdownInternal(notifyClose) {
|
|
53613
|
+
if (this.cleanupPromise) {
|
|
53614
|
+
await this.cleanupPromise;
|
|
53615
|
+
if (notifyClose && !this.closeNotified && !this.manualDisconnect) {
|
|
53616
|
+
this.closeNotified = true;
|
|
53617
|
+
this.callbacks.onClose();
|
|
53618
|
+
}
|
|
53318
53619
|
return;
|
|
53319
53620
|
}
|
|
53320
|
-
|
|
53321
|
-
|
|
53322
|
-
|
|
53323
|
-
|
|
53324
|
-
|
|
53621
|
+
this.connected = false;
|
|
53622
|
+
this.cleanupPromise = (async () => {
|
|
53623
|
+
await this.stopPipe().catch(() => {
|
|
53624
|
+
return;
|
|
53625
|
+
});
|
|
53626
|
+
await this.stopHooks().catch(() => {
|
|
53627
|
+
return;
|
|
53628
|
+
});
|
|
53629
|
+
await this.runShellAllowFailure(`rm -rf ${quoteShellArg(this.fsPaths.rootDir)}`).catch(() => {
|
|
53630
|
+
return;
|
|
53631
|
+
});
|
|
53632
|
+
this.rejectPendingCommand(new Error("SSH command channel closed"));
|
|
53633
|
+
this.commandStream?.end();
|
|
53634
|
+
this.commandStream?.close();
|
|
53635
|
+
this.commandStream?.destroy();
|
|
53636
|
+
this.commandStream = null;
|
|
53637
|
+
this.sshClient?.end();
|
|
53638
|
+
this.sshClient = null;
|
|
53639
|
+
})();
|
|
53640
|
+
await this.cleanupPromise;
|
|
53641
|
+
this.cleanupPromise = null;
|
|
53642
|
+
if (notifyClose && !this.closeNotified && !this.manualDisconnect) {
|
|
53643
|
+
this.closeNotified = true;
|
|
53644
|
+
this.callbacks.onClose();
|
|
53325
53645
|
}
|
|
53326
|
-
|
|
53327
|
-
|
|
53646
|
+
}
|
|
53647
|
+
requireSshClient() {
|
|
53648
|
+
if (!this.sshClient) {
|
|
53649
|
+
throw new Error("SSH client not connected");
|
|
53328
53650
|
}
|
|
53651
|
+
return this.sshClient;
|
|
53329
53652
|
}
|
|
53330
|
-
|
|
53331
|
-
|
|
53332
|
-
|
|
53333
|
-
|
|
53334
|
-
|
|
53335
|
-
|
|
53336
|
-
|
|
53653
|
+
}
|
|
53654
|
+
|
|
53655
|
+
// ../../apps/gateway/src/tmux-client/device-session-runtime.ts
|
|
53656
|
+
function createDefaultConnection(options) {
|
|
53657
|
+
const device = getDeviceById(options.deviceId);
|
|
53658
|
+
if (device?.type === "local") {
|
|
53659
|
+
return new LocalExternalTmuxConnection(options);
|
|
53660
|
+
}
|
|
53661
|
+
return new SshExternalTmuxConnection(options);
|
|
53662
|
+
}
|
|
53663
|
+
|
|
53664
|
+
class DeviceSessionRuntime {
|
|
53665
|
+
deviceId;
|
|
53666
|
+
connection;
|
|
53667
|
+
listeners = new Set;
|
|
53668
|
+
connectPromise = null;
|
|
53669
|
+
terminated = false;
|
|
53670
|
+
closeEmitted = false;
|
|
53671
|
+
manualDisconnect = false;
|
|
53672
|
+
constructor(options) {
|
|
53673
|
+
this.deviceId = options.deviceId;
|
|
53674
|
+
const createConnection = options.createConnection ?? createDefaultConnection;
|
|
53675
|
+
this.connection = createConnection({
|
|
53676
|
+
deviceId: this.deviceId,
|
|
53677
|
+
onEvent: (event) => {
|
|
53678
|
+
this.broadcast((listener) => listener.onEvent?.(event));
|
|
53679
|
+
},
|
|
53680
|
+
onTerminalOutput: (paneId, data) => {
|
|
53681
|
+
this.broadcast((listener) => listener.onTerminalOutput?.(paneId, data));
|
|
53682
|
+
},
|
|
53683
|
+
onTerminalHistory: (paneId, data) => {
|
|
53684
|
+
this.broadcast((listener) => listener.onTerminalHistory?.(paneId, data));
|
|
53685
|
+
},
|
|
53686
|
+
onSnapshot: (payload) => {
|
|
53687
|
+
this.broadcast((listener) => listener.onSnapshot?.(payload));
|
|
53688
|
+
},
|
|
53689
|
+
onError: (error) => {
|
|
53690
|
+
this.broadcast((listener) => listener.onError?.(error));
|
|
53691
|
+
},
|
|
53692
|
+
onClose: () => {
|
|
53693
|
+
if (this.manualDisconnect || this.closeEmitted) {
|
|
53694
|
+
return;
|
|
53695
|
+
}
|
|
53696
|
+
this.closeEmitted = true;
|
|
53697
|
+
this.terminated = true;
|
|
53698
|
+
this.broadcast((listener) => listener.onClose?.());
|
|
53699
|
+
}
|
|
53700
|
+
});
|
|
53701
|
+
}
|
|
53702
|
+
subscribe(listener) {
|
|
53703
|
+
this.listeners.add(listener);
|
|
53704
|
+
return () => {
|
|
53705
|
+
this.listeners.delete(listener);
|
|
53706
|
+
};
|
|
53707
|
+
}
|
|
53708
|
+
async connect() {
|
|
53709
|
+
if (this.terminated && !this.connectPromise) {
|
|
53710
|
+
return Promise.reject(new Error(`Device session runtime already terminated: ${this.deviceId}`));
|
|
53337
53711
|
}
|
|
53338
|
-
|
|
53339
|
-
|
|
53340
|
-
let selected = normal;
|
|
53341
|
-
if (state.preferAlternate === true) {
|
|
53342
|
-
selected = alternate || normal;
|
|
53343
|
-
} else if (state.preferAlternate === false) {
|
|
53344
|
-
selected = normal || alternate;
|
|
53345
|
-
} else if (alternate.length > normal.length) {
|
|
53346
|
-
selected = alternate;
|
|
53712
|
+
if (this.connectPromise) {
|
|
53713
|
+
return this.connectPromise;
|
|
53347
53714
|
}
|
|
53348
|
-
|
|
53349
|
-
|
|
53350
|
-
|
|
53715
|
+
this.connectPromise = this.connection.connect().catch((error) => {
|
|
53716
|
+
this.terminated = true;
|
|
53717
|
+
throw error;
|
|
53718
|
+
});
|
|
53719
|
+
return this.connectPromise;
|
|
53720
|
+
}
|
|
53721
|
+
disconnect() {
|
|
53722
|
+
if (this.terminated) {
|
|
53723
|
+
return;
|
|
53351
53724
|
}
|
|
53352
|
-
this.
|
|
53725
|
+
this.terminated = true;
|
|
53726
|
+
this.manualDisconnect = true;
|
|
53727
|
+
this.connection.disconnect();
|
|
53353
53728
|
}
|
|
53354
|
-
|
|
53355
|
-
this.
|
|
53356
|
-
|
|
53357
|
-
|
|
53358
|
-
this.
|
|
53359
|
-
|
|
53360
|
-
|
|
53361
|
-
|
|
53362
|
-
|
|
53363
|
-
|
|
53729
|
+
async shutdown() {
|
|
53730
|
+
this.disconnect();
|
|
53731
|
+
}
|
|
53732
|
+
requestSnapshot() {
|
|
53733
|
+
this.connection.requestSnapshot();
|
|
53734
|
+
}
|
|
53735
|
+
sendInput(paneId, data) {
|
|
53736
|
+
this.connection.sendInput(paneId, data);
|
|
53737
|
+
}
|
|
53738
|
+
resizePane(paneId, cols, rows) {
|
|
53739
|
+
this.connection.resizePane(paneId, cols, rows);
|
|
53740
|
+
}
|
|
53741
|
+
selectPane(windowId, paneId) {
|
|
53742
|
+
this.connection.selectPane(windowId, paneId);
|
|
53743
|
+
}
|
|
53744
|
+
selectPaneWithSize(windowId, paneId, cols, rows) {
|
|
53745
|
+
this.connection.selectPaneWithSize(windowId, paneId, cols, rows);
|
|
53746
|
+
}
|
|
53747
|
+
selectWindow(windowId) {
|
|
53748
|
+
this.connection.selectWindow(windowId);
|
|
53749
|
+
}
|
|
53750
|
+
createWindow(name) {
|
|
53751
|
+
this.connection.createWindow(name);
|
|
53752
|
+
}
|
|
53753
|
+
closeWindow(windowId) {
|
|
53754
|
+
this.connection.closeWindow(windowId);
|
|
53755
|
+
}
|
|
53756
|
+
closePane(paneId) {
|
|
53757
|
+
this.connection.closePane(paneId);
|
|
53758
|
+
}
|
|
53759
|
+
renameWindow(windowId, name) {
|
|
53760
|
+
this.connection.renameWindow(windowId, name);
|
|
53761
|
+
}
|
|
53762
|
+
broadcast(action) {
|
|
53763
|
+
for (const listener of this.listeners) {
|
|
53764
|
+
try {
|
|
53765
|
+
action(listener);
|
|
53766
|
+
} catch (error) {
|
|
53767
|
+
console.error("[tmux-client] listener callback failed:", error);
|
|
53364
53768
|
}
|
|
53365
53769
|
}
|
|
53366
|
-
|
|
53367
|
-
|
|
53368
|
-
|
|
53369
|
-
|
|
53370
|
-
|
|
53371
|
-
|
|
53372
|
-
|
|
53373
|
-
|
|
53374
|
-
|
|
53375
|
-
|
|
53376
|
-
|
|
53377
|
-
|
|
53378
|
-
|
|
53379
|
-
|
|
53380
|
-
|
|
53381
|
-
|
|
53382
|
-
|
|
53383
|
-
|
|
53384
|
-
this.subprocess = null;
|
|
53385
|
-
if (this.sshStream) {
|
|
53386
|
-
this.sshStream.close();
|
|
53387
|
-
this.sshStream = null;
|
|
53388
|
-
}
|
|
53389
|
-
if (this.sshClient) {
|
|
53390
|
-
this.sshClient.end();
|
|
53391
|
-
this.sshClient = null;
|
|
53770
|
+
}
|
|
53771
|
+
}
|
|
53772
|
+
function createDeviceSessionRuntime(options) {
|
|
53773
|
+
return new DeviceSessionRuntime(options);
|
|
53774
|
+
}
|
|
53775
|
+
|
|
53776
|
+
// ../../apps/gateway/src/tmux-client/runtime-registry.ts
|
|
53777
|
+
class TmuxRuntimeRegistry {
|
|
53778
|
+
options;
|
|
53779
|
+
entries = new Map;
|
|
53780
|
+
constructor(options) {
|
|
53781
|
+
this.options = options;
|
|
53782
|
+
}
|
|
53783
|
+
acquire(deviceId) {
|
|
53784
|
+
const existing = this.entries.get(deviceId);
|
|
53785
|
+
if (existing) {
|
|
53786
|
+
existing.refCount += 1;
|
|
53787
|
+
return existing.promise;
|
|
53392
53788
|
}
|
|
53393
|
-
|
|
53394
|
-
|
|
53789
|
+
const entry = {
|
|
53790
|
+
refCount: 1,
|
|
53791
|
+
runtime: null,
|
|
53792
|
+
promise: this.options.createRuntime(deviceId).then((runtime) => {
|
|
53793
|
+
entry.runtime = runtime;
|
|
53794
|
+
return runtime;
|
|
53795
|
+
})
|
|
53796
|
+
};
|
|
53797
|
+
entry.promise = entry.promise.catch((error) => {
|
|
53798
|
+
if (this.entries.get(deviceId) === entry) {
|
|
53799
|
+
this.entries.delete(deviceId);
|
|
53800
|
+
}
|
|
53801
|
+
throw error;
|
|
53395
53802
|
});
|
|
53803
|
+
this.entries.set(deviceId, entry);
|
|
53804
|
+
return entry.promise;
|
|
53396
53805
|
}
|
|
53397
|
-
|
|
53398
|
-
|
|
53806
|
+
async release(deviceId) {
|
|
53807
|
+
const entry = this.entries.get(deviceId);
|
|
53808
|
+
if (!entry) {
|
|
53809
|
+
return;
|
|
53810
|
+
}
|
|
53811
|
+
entry.refCount -= 1;
|
|
53812
|
+
if (entry.refCount > 0) {
|
|
53813
|
+
return;
|
|
53814
|
+
}
|
|
53815
|
+
this.entries.delete(deviceId);
|
|
53816
|
+
const runtime = await entry.promise;
|
|
53817
|
+
await runtime.shutdown();
|
|
53818
|
+
}
|
|
53819
|
+
async shutdownAll() {
|
|
53820
|
+
const entries = Array.from(this.entries.values());
|
|
53821
|
+
this.entries.clear();
|
|
53822
|
+
await Promise.all(entries.map(async (entry) => {
|
|
53823
|
+
const runtime = entry.runtime ?? await entry.promise;
|
|
53824
|
+
await runtime.shutdown();
|
|
53825
|
+
}));
|
|
53399
53826
|
}
|
|
53400
53827
|
}
|
|
53828
|
+
function createTmuxRuntimeRegistry(options) {
|
|
53829
|
+
return new TmuxRuntimeRegistry(options);
|
|
53830
|
+
}
|
|
53831
|
+
|
|
53832
|
+
// ../../apps/gateway/src/tmux-client/registry.ts
|
|
53833
|
+
var tmuxRuntimeRegistry = createTmuxRuntimeRegistry({
|
|
53834
|
+
async createRuntime(deviceId) {
|
|
53835
|
+
return createDeviceSessionRuntime({ deviceId });
|
|
53836
|
+
}
|
|
53837
|
+
});
|
|
53401
53838
|
|
|
53402
53839
|
// ../../apps/gateway/src/tmux/bell-context.ts
|
|
53403
53840
|
function pickPaneById(windows, paneId) {
|
|
@@ -53454,7 +53891,10 @@ var defaultDeps = {
|
|
|
53454
53891
|
listDevices: () => getAllDevices(),
|
|
53455
53892
|
getDevice: (deviceId) => getDeviceById(deviceId),
|
|
53456
53893
|
getSettings: () => getSiteSettings(),
|
|
53457
|
-
|
|
53894
|
+
acquireRuntime: async (deviceId) => tmuxRuntimeRegistry.acquire(deviceId),
|
|
53895
|
+
releaseRuntime: async (deviceId, _runtime) => {
|
|
53896
|
+
await tmuxRuntimeRegistry.release(deviceId);
|
|
53897
|
+
},
|
|
53458
53898
|
async notifyBell(context) {
|
|
53459
53899
|
const { device, settings, bell } = context;
|
|
53460
53900
|
await eventNotifier.notify("terminal_bell", {
|
|
@@ -53506,7 +53946,7 @@ class PushSupervisor {
|
|
|
53506
53946
|
this.running = false;
|
|
53507
53947
|
for (const [deviceId, entry] of this.entries) {
|
|
53508
53948
|
this.clearReconnectTimer(entry);
|
|
53509
|
-
|
|
53949
|
+
this.teardownEntry(entry);
|
|
53510
53950
|
this.entries.delete(deviceId);
|
|
53511
53951
|
}
|
|
53512
53952
|
}
|
|
@@ -53522,7 +53962,8 @@ class PushSupervisor {
|
|
|
53522
53962
|
generation: 1,
|
|
53523
53963
|
reconnectAttempts: 0,
|
|
53524
53964
|
reconnectTimer: null,
|
|
53525
|
-
|
|
53965
|
+
runtime: null,
|
|
53966
|
+
detachRuntime: null,
|
|
53526
53967
|
lastSnapshot: null
|
|
53527
53968
|
};
|
|
53528
53969
|
this.entries.set(deviceId, entry);
|
|
@@ -53549,8 +53990,13 @@ class PushSupervisor {
|
|
|
53549
53990
|
}
|
|
53550
53991
|
teardownEntry(entry) {
|
|
53551
53992
|
this.clearReconnectTimer(entry);
|
|
53552
|
-
entry.
|
|
53553
|
-
entry.
|
|
53993
|
+
const runtime = entry.runtime;
|
|
53994
|
+
entry.detachRuntime?.();
|
|
53995
|
+
entry.detachRuntime = null;
|
|
53996
|
+
entry.runtime = null;
|
|
53997
|
+
if (runtime) {
|
|
53998
|
+
this.deps.releaseRuntime(entry.deviceId, runtime);
|
|
53999
|
+
}
|
|
53554
54000
|
}
|
|
53555
54001
|
clearReconnectTimer(entry) {
|
|
53556
54002
|
if (!entry.reconnectTimer) {
|
|
@@ -53573,42 +54019,46 @@ class PushSupervisor {
|
|
|
53573
54019
|
return;
|
|
53574
54020
|
}
|
|
53575
54021
|
const generation = entry.generation;
|
|
53576
|
-
|
|
53577
|
-
|
|
53578
|
-
deviceId: entry.deviceId,
|
|
54022
|
+
const runtime = await this.deps.acquireRuntime(entry.deviceId);
|
|
54023
|
+
const detachRuntime = runtime.subscribe({
|
|
53579
54024
|
onEvent: (event) => {
|
|
53580
|
-
this.handleTmuxEvent(entry.deviceId, generation,
|
|
54025
|
+
this.handleTmuxEvent(entry.deviceId, generation, runtime, event);
|
|
53581
54026
|
},
|
|
53582
|
-
onTerminalOutput: () => {},
|
|
53583
|
-
onTerminalHistory: () => {},
|
|
53584
54027
|
onSnapshot: (payload) => {
|
|
53585
|
-
this.handleSnapshot(entry.deviceId, generation,
|
|
54028
|
+
this.handleSnapshot(entry.deviceId, generation, runtime, payload);
|
|
53586
54029
|
},
|
|
53587
|
-
onError: (
|
|
53588
|
-
console.error(`[push] tmux error on device ${entry.deviceId}:`,
|
|
54030
|
+
onError: (error) => {
|
|
54031
|
+
console.error(`[push] tmux error on device ${entry.deviceId}:`, error);
|
|
53589
54032
|
},
|
|
53590
54033
|
onClose: () => {
|
|
53591
|
-
this.handleClose(entry.deviceId, generation,
|
|
54034
|
+
this.handleClose(entry.deviceId, generation, runtime);
|
|
53592
54035
|
}
|
|
53593
54036
|
});
|
|
53594
|
-
entry.
|
|
54037
|
+
entry.runtime = runtime;
|
|
54038
|
+
entry.detachRuntime = detachRuntime;
|
|
53595
54039
|
try {
|
|
53596
|
-
await
|
|
54040
|
+
await runtime.connect();
|
|
53597
54041
|
const latest = this.entries.get(entry.deviceId);
|
|
53598
54042
|
if (!this.running || latest !== entry || entry.generation !== generation) {
|
|
53599
|
-
|
|
54043
|
+
detachRuntime();
|
|
54044
|
+
entry.detachRuntime = null;
|
|
54045
|
+
entry.runtime = null;
|
|
54046
|
+
await this.deps.releaseRuntime(entry.deviceId, runtime);
|
|
53600
54047
|
return;
|
|
53601
54048
|
}
|
|
53602
54049
|
entry.reconnectAttempts = 0;
|
|
53603
54050
|
entry.lastSnapshot = null;
|
|
53604
|
-
|
|
54051
|
+
runtime.requestSnapshot();
|
|
53605
54052
|
} catch (err) {
|
|
53606
54053
|
const latest = this.entries.get(entry.deviceId);
|
|
53607
54054
|
if (!this.running || latest !== entry || entry.generation !== generation) {
|
|
53608
54055
|
return;
|
|
53609
54056
|
}
|
|
53610
54057
|
console.error(`[push] failed connecting device ${entry.deviceId}:`, err);
|
|
53611
|
-
|
|
54058
|
+
detachRuntime();
|
|
54059
|
+
entry.detachRuntime = null;
|
|
54060
|
+
entry.runtime = null;
|
|
54061
|
+
await this.deps.releaseRuntime(entry.deviceId, runtime);
|
|
53612
54062
|
this.scheduleReconnect(entry);
|
|
53613
54063
|
}
|
|
53614
54064
|
}
|
|
@@ -53634,31 +54084,35 @@ class PushSupervisor {
|
|
|
53634
54084
|
entry.reconnectAttempts += 1;
|
|
53635
54085
|
}
|
|
53636
54086
|
this.clearReconnectTimer(entry);
|
|
53637
|
-
entry.
|
|
54087
|
+
entry.runtime = null;
|
|
54088
|
+
entry.detachRuntime = null;
|
|
53638
54089
|
entry.generation += 1;
|
|
53639
54090
|
entry.reconnectTimer = setTimeout(() => {
|
|
53640
54091
|
entry.reconnectTimer = null;
|
|
53641
54092
|
this.connectEntry(entry);
|
|
53642
54093
|
}, delayMs);
|
|
53643
54094
|
}
|
|
53644
|
-
async handleClose(deviceId, generation,
|
|
54095
|
+
async handleClose(deviceId, generation, runtime) {
|
|
53645
54096
|
const entry = this.entries.get(deviceId);
|
|
53646
|
-
if (!entry || entry.generation !== generation || entry.
|
|
54097
|
+
if (!entry || entry.generation !== generation || entry.runtime !== runtime) {
|
|
53647
54098
|
return;
|
|
53648
54099
|
}
|
|
53649
|
-
entry.
|
|
54100
|
+
entry.detachRuntime?.();
|
|
54101
|
+
entry.detachRuntime = null;
|
|
54102
|
+
entry.runtime = null;
|
|
54103
|
+
await this.deps.releaseRuntime(deviceId, runtime);
|
|
53650
54104
|
this.scheduleReconnect(entry);
|
|
53651
54105
|
}
|
|
53652
|
-
handleSnapshot(deviceId, generation,
|
|
54106
|
+
handleSnapshot(deviceId, generation, runtime, payload) {
|
|
53653
54107
|
const entry = this.entries.get(deviceId);
|
|
53654
|
-
if (!entry || entry.generation !== generation || entry.
|
|
54108
|
+
if (!entry || entry.generation !== generation || entry.runtime !== runtime) {
|
|
53655
54109
|
return;
|
|
53656
54110
|
}
|
|
53657
54111
|
entry.lastSnapshot = payload;
|
|
53658
54112
|
}
|
|
53659
|
-
async handleTmuxEvent(deviceId, generation,
|
|
54113
|
+
async handleTmuxEvent(deviceId, generation, runtime, event) {
|
|
53660
54114
|
const entry = this.entries.get(deviceId);
|
|
53661
|
-
if (!entry || entry.generation !== generation || entry.
|
|
54115
|
+
if (!entry || entry.generation !== generation || entry.runtime !== runtime) {
|
|
53662
54116
|
return;
|
|
53663
54117
|
}
|
|
53664
54118
|
if (event.type !== "bell") {
|
|
@@ -54134,7 +54588,7 @@ function json(data, status = 200, headers = {}) {
|
|
|
54134
54588
|
import { existsSync as existsSync2 } from "fs";
|
|
54135
54589
|
import { resolve } from "path";
|
|
54136
54590
|
|
|
54137
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
54591
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/migrator.js
|
|
54138
54592
|
import crypto2 from "crypto";
|
|
54139
54593
|
import fs3 from "fs";
|
|
54140
54594
|
function readMigrationFiles(config2) {
|
|
@@ -54166,7 +54620,7 @@ function readMigrationFiles(config2) {
|
|
|
54166
54620
|
return migrationQueries;
|
|
54167
54621
|
}
|
|
54168
54622
|
|
|
54169
|
-
// ../../node_modules/.bun/drizzle-orm@0.45.1+
|
|
54623
|
+
// ../../node_modules/.bun/drizzle-orm@0.45.1+1608dc8003c5413e/node_modules/drizzle-orm/bun-sqlite/migrator.js
|
|
54170
54624
|
function migrate(db2, config2) {
|
|
54171
54625
|
const migrations = readMigrationFiles(config2);
|
|
54172
54626
|
db2.dialect.migrate(migrations, db2.session, config2);
|
|
@@ -54188,82 +54642,6 @@ function runMigrations() {
|
|
|
54188
54642
|
}
|
|
54189
54643
|
if (false) {}
|
|
54190
54644
|
|
|
54191
|
-
// ../../apps/gateway/src/ws/error-classify.ts
|
|
54192
|
-
function classifySshError(error) {
|
|
54193
|
-
const msg = error.message.toLowerCase();
|
|
54194
|
-
if (msg.includes("ssh_config_ref_not_supported")) {
|
|
54195
|
-
return {
|
|
54196
|
-
type: "ssh_config_ref_not_supported",
|
|
54197
|
-
messageKey: "sshError.configRefNotSupported"
|
|
54198
|
-
};
|
|
54199
|
-
}
|
|
54200
|
-
if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
|
|
54201
|
-
return {
|
|
54202
|
-
type: "agent_unavailable",
|
|
54203
|
-
messageKey: "sshError.agentUnavailable"
|
|
54204
|
-
};
|
|
54205
|
-
}
|
|
54206
|
-
if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
|
|
54207
|
-
return {
|
|
54208
|
-
type: "agent_no_identity",
|
|
54209
|
-
messageKey: "sshError.agentNoIdentities"
|
|
54210
|
-
};
|
|
54211
|
-
}
|
|
54212
|
-
if (msg.includes("permission denied")) {
|
|
54213
|
-
return {
|
|
54214
|
-
type: "auth_failed",
|
|
54215
|
-
messageKey: "sshError.authFailed"
|
|
54216
|
-
};
|
|
54217
|
-
}
|
|
54218
|
-
if (msg.includes("all configured authentication methods failed")) {
|
|
54219
|
-
return {
|
|
54220
|
-
type: "auth_failed",
|
|
54221
|
-
messageKey: "sshError.authFailedGeneric"
|
|
54222
|
-
};
|
|
54223
|
-
}
|
|
54224
|
-
if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
|
|
54225
|
-
return {
|
|
54226
|
-
type: "network_unreachable",
|
|
54227
|
-
messageKey: "sshError.networkUnreachable"
|
|
54228
|
-
};
|
|
54229
|
-
}
|
|
54230
|
-
if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
|
|
54231
|
-
return {
|
|
54232
|
-
type: "connection_refused",
|
|
54233
|
-
messageKey: "sshError.connectionRefused"
|
|
54234
|
-
};
|
|
54235
|
-
}
|
|
54236
|
-
if (msg.includes("timeout") || msg.includes("etimedout")) {
|
|
54237
|
-
return {
|
|
54238
|
-
type: "timeout",
|
|
54239
|
-
messageKey: "sshError.connectionTimeout"
|
|
54240
|
-
};
|
|
54241
|
-
}
|
|
54242
|
-
if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
|
|
54243
|
-
return {
|
|
54244
|
-
type: "host_not_found",
|
|
54245
|
-
messageKey: "sshError.hostNotFound"
|
|
54246
|
-
};
|
|
54247
|
-
}
|
|
54248
|
-
if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
|
|
54249
|
-
return {
|
|
54250
|
-
type: "handshake_failed",
|
|
54251
|
-
messageKey: "sshError.handshakeFailed"
|
|
54252
|
-
};
|
|
54253
|
-
}
|
|
54254
|
-
if (msg.includes("tmux: command not found") || msg.includes("tmux control mode not ready") || msg.includes("tmux exited") || msg.includes("tmux_exec_failed")) {
|
|
54255
|
-
return {
|
|
54256
|
-
type: "tmux_unavailable",
|
|
54257
|
-
messageKey: "sshError.tmuxUnavailable"
|
|
54258
|
-
};
|
|
54259
|
-
}
|
|
54260
|
-
return {
|
|
54261
|
-
type: "unknown",
|
|
54262
|
-
messageKey: "sshError.unknown",
|
|
54263
|
-
messageParams: { message: error.message }
|
|
54264
|
-
};
|
|
54265
|
-
}
|
|
54266
|
-
|
|
54267
54645
|
// ../../apps/gateway/src/ws/borsh/codec-borsh.ts
|
|
54268
54646
|
function createBorshClientState() {
|
|
54269
54647
|
return {
|
|
@@ -54641,6 +55019,10 @@ class SwitchBarrier {
|
|
|
54641
55019
|
const pending = this.getPending(ws, deviceId);
|
|
54642
55020
|
if (!pending)
|
|
54643
55021
|
return;
|
|
55022
|
+
const selectState = sessionStateStore.getOrCreateSelectTransaction(ws, deviceId)?.state;
|
|
55023
|
+
if (selectState !== "SELECTING") {
|
|
55024
|
+
return;
|
|
55025
|
+
}
|
|
54644
55026
|
const { context } = pending;
|
|
54645
55027
|
const borshState = ws.data?.borshState;
|
|
54646
55028
|
if (!borshState)
|
|
@@ -54648,7 +55030,9 @@ class SwitchBarrier {
|
|
|
54648
55030
|
const ackTimer = pending.timers.shift();
|
|
54649
55031
|
if (ackTimer)
|
|
54650
55032
|
clearTimeout(ackTimer);
|
|
54651
|
-
sessionStateStore.transitionSelectState(ws, deviceId, "ACKED")
|
|
55033
|
+
if (!sessionStateStore.transitionSelectState(ws, deviceId, "ACKED")) {
|
|
55034
|
+
return;
|
|
55035
|
+
}
|
|
54652
55036
|
const seq = borshState.seqGen();
|
|
54653
55037
|
const ackData = encodeSwitchAck({
|
|
54654
55038
|
deviceId,
|
|
@@ -54673,6 +55057,10 @@ class SwitchBarrier {
|
|
|
54673
55057
|
const pending = this.getPending(ws, deviceId);
|
|
54674
55058
|
if (!pending)
|
|
54675
55059
|
return;
|
|
55060
|
+
const selectState = sessionStateStore.getOrCreateSelectTransaction(ws, deviceId)?.state;
|
|
55061
|
+
if (selectState !== "ACKED") {
|
|
55062
|
+
return;
|
|
55063
|
+
}
|
|
54676
55064
|
const { context } = pending;
|
|
54677
55065
|
if (context.paneId !== paneId) {
|
|
54678
55066
|
return;
|
|
@@ -54683,7 +55071,9 @@ class SwitchBarrier {
|
|
|
54683
55071
|
const historyTimer = pending.timers.shift();
|
|
54684
55072
|
if (historyTimer)
|
|
54685
55073
|
clearTimeout(historyTimer);
|
|
54686
|
-
sessionStateStore.transitionSelectState(ws, deviceId, "HISTORY_APPLIED")
|
|
55074
|
+
if (!sessionStateStore.transitionSelectState(ws, deviceId, "HISTORY_APPLIED")) {
|
|
55075
|
+
return;
|
|
55076
|
+
}
|
|
54687
55077
|
const historyMessages = encodeTermHistory({
|
|
54688
55078
|
deviceId,
|
|
54689
55079
|
paneId: context.paneId,
|
|
@@ -54702,6 +55092,10 @@ class SwitchBarrier {
|
|
|
54702
55092
|
const pending = this.getPending(ws, deviceId);
|
|
54703
55093
|
if (!pending)
|
|
54704
55094
|
return;
|
|
55095
|
+
const selectState = sessionStateStore.getOrCreateSelectTransaction(ws, deviceId)?.state;
|
|
55096
|
+
if (selectState !== "ACKED" && selectState !== "HISTORY_APPLIED") {
|
|
55097
|
+
return;
|
|
55098
|
+
}
|
|
54705
55099
|
const { context } = pending;
|
|
54706
55100
|
if (expectedToken && !this.tokensEqual(context.selectToken, expectedToken)) {
|
|
54707
55101
|
return;
|
|
@@ -54713,7 +55107,9 @@ class SwitchBarrier {
|
|
|
54713
55107
|
clearTimeout(timer);
|
|
54714
55108
|
}
|
|
54715
55109
|
pending.timers = [];
|
|
54716
|
-
sessionStateStore.transitionSelectState(ws, deviceId, "LIVE")
|
|
55110
|
+
if (!sessionStateStore.transitionSelectState(ws, deviceId, "LIVE")) {
|
|
55111
|
+
return;
|
|
55112
|
+
}
|
|
54717
55113
|
const bufferedOutput = sessionStateStore.stopOutputBuffering(ws, deviceId);
|
|
54718
55114
|
const seq = borshState.seqGen();
|
|
54719
55115
|
const liveResumeData = encodeLiveResume({
|
|
@@ -54785,7 +55181,9 @@ class SwitchBarrier {
|
|
|
54785
55181
|
this.cleanupTransaction(ws, deviceId);
|
|
54786
55182
|
}
|
|
54787
55183
|
completeTransaction(ws, deviceId) {
|
|
54788
|
-
sessionStateStore.transitionSelectState(ws, deviceId, "STABLE")
|
|
55184
|
+
if (!sessionStateStore.transitionSelectState(ws, deviceId, "STABLE")) {
|
|
55185
|
+
return;
|
|
55186
|
+
}
|
|
54789
55187
|
this.cleanupTransaction(ws, deviceId);
|
|
54790
55188
|
}
|
|
54791
55189
|
cleanupTransaction(ws, deviceId) {
|
|
@@ -54802,10 +55200,100 @@ class SwitchBarrier {
|
|
|
54802
55200
|
}
|
|
54803
55201
|
var switchBarrier = new SwitchBarrier;
|
|
54804
55202
|
|
|
55203
|
+
// ../../apps/gateway/src/ws/error-classify.ts
|
|
55204
|
+
function classifySshError(error) {
|
|
55205
|
+
const msg = error.message.toLowerCase();
|
|
55206
|
+
if (msg.includes("ssh_config_ref_not_supported")) {
|
|
55207
|
+
return {
|
|
55208
|
+
type: "ssh_config_ref_not_supported",
|
|
55209
|
+
messageKey: "sshError.configRefNotSupported"
|
|
55210
|
+
};
|
|
55211
|
+
}
|
|
55212
|
+
if (msg.includes("ssh_auth_sock") || msg.includes("auth_sock")) {
|
|
55213
|
+
return {
|
|
55214
|
+
type: "agent_unavailable",
|
|
55215
|
+
messageKey: "sshError.agentUnavailable"
|
|
55216
|
+
};
|
|
55217
|
+
}
|
|
55218
|
+
if (msg.includes("agent") && (msg.includes("no identities") || msg.includes("failure"))) {
|
|
55219
|
+
return {
|
|
55220
|
+
type: "agent_no_identity",
|
|
55221
|
+
messageKey: "sshError.agentNoIdentities"
|
|
55222
|
+
};
|
|
55223
|
+
}
|
|
55224
|
+
if (msg.includes("permission denied")) {
|
|
55225
|
+
return {
|
|
55226
|
+
type: "auth_failed",
|
|
55227
|
+
messageKey: "sshError.authFailed"
|
|
55228
|
+
};
|
|
55229
|
+
}
|
|
55230
|
+
if (msg.includes("all configured authentication methods failed")) {
|
|
55231
|
+
return {
|
|
55232
|
+
type: "auth_failed",
|
|
55233
|
+
messageKey: "sshError.authFailedGeneric"
|
|
55234
|
+
};
|
|
55235
|
+
}
|
|
55236
|
+
if (msg.includes("enetunreach") || msg.includes("ehostunreach")) {
|
|
55237
|
+
return {
|
|
55238
|
+
type: "network_unreachable",
|
|
55239
|
+
messageKey: "sshError.networkUnreachable"
|
|
55240
|
+
};
|
|
55241
|
+
}
|
|
55242
|
+
if (msg.includes("connect refused") || msg.includes("connection refused") || msg.includes("econnrefused")) {
|
|
55243
|
+
return {
|
|
55244
|
+
type: "connection_refused",
|
|
55245
|
+
messageKey: "sshError.connectionRefused"
|
|
55246
|
+
};
|
|
55247
|
+
}
|
|
55248
|
+
if (msg.includes("timeout") || msg.includes("etimedout")) {
|
|
55249
|
+
return {
|
|
55250
|
+
type: "timeout",
|
|
55251
|
+
messageKey: "sshError.connectionTimeout"
|
|
55252
|
+
};
|
|
55253
|
+
}
|
|
55254
|
+
if (msg.includes("host not found") || msg.includes("getaddrinfo") || msg.includes("enotfound")) {
|
|
55255
|
+
return {
|
|
55256
|
+
type: "host_not_found",
|
|
55257
|
+
messageKey: "sshError.hostNotFound"
|
|
55258
|
+
};
|
|
55259
|
+
}
|
|
55260
|
+
if (msg.includes("handshake failed") || msg.includes("unable to verify")) {
|
|
55261
|
+
return {
|
|
55262
|
+
type: "handshake_failed",
|
|
55263
|
+
messageKey: "sshError.handshakeFailed"
|
|
55264
|
+
};
|
|
55265
|
+
}
|
|
55266
|
+
if (msg.includes("remote tmux unavailable") || msg.includes("tmux_not_found") || msg.includes("tmux: command not found") || msg.includes("tmux control mode not ready") || msg.includes("tmux exited") || msg.includes("tmux_exec_failed")) {
|
|
55267
|
+
return {
|
|
55268
|
+
type: "tmux_unavailable",
|
|
55269
|
+
messageKey: "sshError.tmuxUnavailable"
|
|
55270
|
+
};
|
|
55271
|
+
}
|
|
55272
|
+
return {
|
|
55273
|
+
type: "unknown",
|
|
55274
|
+
messageKey: "sshError.unknown",
|
|
55275
|
+
messageParams: { message: error.message }
|
|
55276
|
+
};
|
|
55277
|
+
}
|
|
55278
|
+
|
|
54805
55279
|
// ../../apps/gateway/src/ws/index.ts
|
|
55280
|
+
var defaultDeps2 = {
|
|
55281
|
+
acquireRuntime: async (deviceId) => tmuxRuntimeRegistry.acquire(deviceId),
|
|
55282
|
+
releaseRuntime: async (deviceId) => {
|
|
55283
|
+
await tmuxRuntimeRegistry.release(deviceId);
|
|
55284
|
+
}
|
|
55285
|
+
};
|
|
55286
|
+
|
|
54806
55287
|
class WebSocketServer {
|
|
54807
55288
|
connections = new Map;
|
|
54808
55289
|
pendingConnectionEntries = new Map;
|
|
55290
|
+
deps;
|
|
55291
|
+
constructor(options = {}) {
|
|
55292
|
+
this.deps = {
|
|
55293
|
+
...defaultDeps2,
|
|
55294
|
+
...options.deps ?? {}
|
|
55295
|
+
};
|
|
55296
|
+
}
|
|
54809
55297
|
clearSnapshotTimer(entry) {
|
|
54810
55298
|
if (!entry.snapshotTimer)
|
|
54811
55299
|
return;
|
|
@@ -54824,6 +55312,37 @@ class WebSocketServer {
|
|
|
54824
55312
|
clearTimeout(entry.reconnectTimer);
|
|
54825
55313
|
entry.reconnectTimer = null;
|
|
54826
55314
|
}
|
|
55315
|
+
releaseConnectionEntry(deviceId, entry) {
|
|
55316
|
+
this.clearSnapshotTimer(entry);
|
|
55317
|
+
this.clearSnapshotPollTimer(entry);
|
|
55318
|
+
this.clearReconnectTimer(entry);
|
|
55319
|
+
entry.detachRuntime?.();
|
|
55320
|
+
entry.detachRuntime = null;
|
|
55321
|
+
this.deps.releaseRuntime(deviceId, entry.runtime);
|
|
55322
|
+
}
|
|
55323
|
+
attachRuntime(deviceId, runtime) {
|
|
55324
|
+
const listener = {
|
|
55325
|
+
onEvent: (event) => {
|
|
55326
|
+
this.broadcastTmuxEvent(deviceId, event);
|
|
55327
|
+
},
|
|
55328
|
+
onTerminalOutput: (paneId, data) => {
|
|
55329
|
+
this.broadcastTerminalOutput(deviceId, paneId, data);
|
|
55330
|
+
},
|
|
55331
|
+
onTerminalHistory: (paneId, data) => {
|
|
55332
|
+
this.broadcastTerminalHistory(deviceId, paneId, data);
|
|
55333
|
+
},
|
|
55334
|
+
onSnapshot: (payload) => {
|
|
55335
|
+
this.broadcastStateSnapshot(deviceId, payload);
|
|
55336
|
+
},
|
|
55337
|
+
onError: (error) => {
|
|
55338
|
+
this.broadcastError(deviceId, error);
|
|
55339
|
+
},
|
|
55340
|
+
onClose: () => {
|
|
55341
|
+
this.handleConnectionClose(deviceId);
|
|
55342
|
+
}
|
|
55343
|
+
};
|
|
55344
|
+
return runtime.subscribe(listener);
|
|
55345
|
+
}
|
|
54827
55346
|
refreshSnapshotPolling(deviceId) {
|
|
54828
55347
|
const entry = this.connections.get(deviceId);
|
|
54829
55348
|
if (!entry)
|
|
@@ -54841,7 +55360,7 @@ class WebSocketServer {
|
|
|
54841
55360
|
return;
|
|
54842
55361
|
}
|
|
54843
55362
|
try {
|
|
54844
|
-
entry.
|
|
55363
|
+
entry.runtime.requestSnapshot();
|
|
54845
55364
|
} catch (err) {
|
|
54846
55365
|
console.error("[ws] polling snapshot failed:", err);
|
|
54847
55366
|
}
|
|
@@ -54859,7 +55378,7 @@ class WebSocketServer {
|
|
|
54859
55378
|
}
|
|
54860
55379
|
entry.snapshotTimer = null;
|
|
54861
55380
|
try {
|
|
54862
|
-
entry.
|
|
55381
|
+
entry.runtime.requestSnapshot();
|
|
54863
55382
|
} catch (err) {
|
|
54864
55383
|
console.error("[ws] failed to request snapshot:", err);
|
|
54865
55384
|
}
|
|
@@ -54928,10 +55447,7 @@ class WebSocketServer {
|
|
|
54928
55447
|
delete ws.data.borshState.selectedPanes[deviceId];
|
|
54929
55448
|
if (entry.clients.size === 0) {
|
|
54930
55449
|
console.log(`[ws] no more clients for device ${deviceId}, disconnecting`);
|
|
54931
|
-
this.
|
|
54932
|
-
this.clearSnapshotPollTimer(entry);
|
|
54933
|
-
this.clearReconnectTimer(entry);
|
|
54934
|
-
entry.connection.disconnect();
|
|
55450
|
+
this.releaseConnectionEntry(deviceId, entry);
|
|
54935
55451
|
toDelete.push(deviceId);
|
|
54936
55452
|
} else {
|
|
54937
55453
|
this.refreshSnapshotPolling(deviceId);
|
|
@@ -54943,10 +55459,7 @@ class WebSocketServer {
|
|
|
54943
55459
|
}
|
|
54944
55460
|
closeAll() {
|
|
54945
55461
|
for (const [deviceId, entry] of this.connections) {
|
|
54946
|
-
this.
|
|
54947
|
-
this.clearSnapshotPollTimer(entry);
|
|
54948
|
-
this.clearReconnectTimer(entry);
|
|
54949
|
-
entry.connection.disconnect();
|
|
55462
|
+
this.releaseConnectionEntry(deviceId, entry);
|
|
54950
55463
|
this.connections.delete(deviceId);
|
|
54951
55464
|
}
|
|
54952
55465
|
this.pendingConnectionEntries.clear();
|
|
@@ -55109,8 +55622,7 @@ class WebSocketServer {
|
|
|
55109
55622
|
if (pending) {
|
|
55110
55623
|
return pending;
|
|
55111
55624
|
}
|
|
55112
|
-
|
|
55113
|
-
creationPromise = this.createDeviceConnectionEntry(deviceId, ws).then((createdEntry) => {
|
|
55625
|
+
const creationPromise = this.createDeviceConnectionEntry(deviceId, ws).then((createdEntry) => {
|
|
55114
55626
|
if (createdEntry) {
|
|
55115
55627
|
this.connections.set(deviceId, createdEntry);
|
|
55116
55628
|
}
|
|
@@ -55129,13 +55641,15 @@ class WebSocketServer {
|
|
|
55129
55641
|
return;
|
|
55130
55642
|
entry.clients.add(ws);
|
|
55131
55643
|
ws.data.borshState.selectedPanes[deviceId] ??= null;
|
|
55132
|
-
const connectedPayload = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.DeviceConnectedSchema, {
|
|
55644
|
+
const connectedPayload = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.DeviceConnectedSchema, {
|
|
55645
|
+
deviceId
|
|
55646
|
+
});
|
|
55133
55647
|
this.sendEnvelope(ws, exports_ws_borsh.KIND_DEVICE_CONNECTED, connectedPayload);
|
|
55134
55648
|
if (entry.lastSnapshot) {
|
|
55135
55649
|
const snapshotBytes = exports_ws_borsh.encodeStateSnapshot(entry.lastSnapshot);
|
|
55136
55650
|
this.sendChunked(ws, exports_ws_borsh.KIND_STATE_SNAPSHOT, snapshotBytes);
|
|
55137
55651
|
} else {
|
|
55138
|
-
entry.
|
|
55652
|
+
entry.runtime.requestSnapshot();
|
|
55139
55653
|
}
|
|
55140
55654
|
}
|
|
55141
55655
|
handleDeviceDisconnect(ws, deviceId) {
|
|
@@ -55144,15 +55658,14 @@ class WebSocketServer {
|
|
|
55144
55658
|
entry.clients.delete(ws);
|
|
55145
55659
|
this.refreshSnapshotPolling(deviceId);
|
|
55146
55660
|
if (entry.clients.size === 0) {
|
|
55147
|
-
this.
|
|
55148
|
-
this.clearSnapshotPollTimer(entry);
|
|
55149
|
-
this.clearReconnectTimer(entry);
|
|
55150
|
-
entry.connection.disconnect();
|
|
55661
|
+
this.releaseConnectionEntry(deviceId, entry);
|
|
55151
55662
|
this.connections.delete(deviceId);
|
|
55152
55663
|
}
|
|
55153
55664
|
}
|
|
55154
55665
|
delete ws.data.borshState.selectedPanes[deviceId];
|
|
55155
|
-
const disconnectedPayload = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.DeviceDisconnectedSchema, {
|
|
55666
|
+
const disconnectedPayload = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.DeviceDisconnectedSchema, {
|
|
55667
|
+
deviceId
|
|
55668
|
+
});
|
|
55156
55669
|
this.sendEnvelope(ws, exports_ws_borsh.KIND_DEVICE_DISCONNECTED, disconnectedPayload);
|
|
55157
55670
|
}
|
|
55158
55671
|
handleTmuxSelect(ws, data) {
|
|
@@ -55182,30 +55695,31 @@ class WebSocketServer {
|
|
|
55182
55695
|
return;
|
|
55183
55696
|
}
|
|
55184
55697
|
switchBarrier.sendSwitchAck(ws, deviceId);
|
|
55185
|
-
entry.connection.selectPane(windowId, paneId);
|
|
55186
55698
|
const cols = data.cols ?? null;
|
|
55187
55699
|
const rows = data.rows ?? null;
|
|
55188
55700
|
if (cols !== null && rows !== null) {
|
|
55189
|
-
entry.
|
|
55701
|
+
entry.runtime.selectPaneWithSize(windowId, paneId, cols, rows);
|
|
55702
|
+
} else {
|
|
55703
|
+
entry.runtime.selectPane(windowId, paneId);
|
|
55190
55704
|
}
|
|
55191
55705
|
}
|
|
55192
55706
|
handleTmuxSelectWindow(deviceId, windowId) {
|
|
55193
55707
|
const entry = this.connections.get(deviceId);
|
|
55194
55708
|
if (!entry)
|
|
55195
55709
|
return;
|
|
55196
|
-
entry.
|
|
55710
|
+
entry.runtime.selectWindow(windowId);
|
|
55197
55711
|
}
|
|
55198
55712
|
handleTermInput(deviceId, paneId, data) {
|
|
55199
55713
|
const entry = this.connections.get(deviceId);
|
|
55200
55714
|
if (!entry)
|
|
55201
55715
|
return;
|
|
55202
|
-
entry.
|
|
55716
|
+
entry.runtime.sendInput(paneId, data);
|
|
55203
55717
|
}
|
|
55204
55718
|
handleTermResize(deviceId, paneId, cols, rows) {
|
|
55205
55719
|
const entry = this.connections.get(deviceId);
|
|
55206
55720
|
if (!entry)
|
|
55207
55721
|
return;
|
|
55208
|
-
entry.
|
|
55722
|
+
entry.runtime.resizePane(paneId, cols, rows);
|
|
55209
55723
|
}
|
|
55210
55724
|
handleTermPaste(deviceId, paneId, data) {
|
|
55211
55725
|
const entry = this.connections.get(deviceId);
|
|
@@ -55214,51 +55728,43 @@ class WebSocketServer {
|
|
|
55214
55728
|
const chunkSize = 1024;
|
|
55215
55729
|
for (let i = 0;i < data.length; i += chunkSize) {
|
|
55216
55730
|
const chunk2 = data.slice(i, i + chunkSize);
|
|
55217
|
-
entry.
|
|
55731
|
+
entry.runtime.sendInput(paneId, chunk2);
|
|
55218
55732
|
}
|
|
55219
55733
|
}
|
|
55220
55734
|
handleCreateWindow(deviceId, name) {
|
|
55221
55735
|
const entry = this.connections.get(deviceId);
|
|
55222
55736
|
if (!entry)
|
|
55223
55737
|
return;
|
|
55224
|
-
entry.
|
|
55738
|
+
entry.runtime.createWindow(name);
|
|
55225
55739
|
}
|
|
55226
55740
|
handleCloseWindow(deviceId, windowId) {
|
|
55227
55741
|
const entry = this.connections.get(deviceId);
|
|
55228
55742
|
if (!entry)
|
|
55229
55743
|
return;
|
|
55230
|
-
entry.
|
|
55744
|
+
entry.runtime.closeWindow(windowId);
|
|
55231
55745
|
}
|
|
55232
55746
|
handleClosePane(deviceId, paneId) {
|
|
55233
55747
|
const entry = this.connections.get(deviceId);
|
|
55234
55748
|
if (!entry)
|
|
55235
55749
|
return;
|
|
55236
|
-
entry.
|
|
55750
|
+
entry.runtime.closePane(paneId);
|
|
55237
55751
|
}
|
|
55238
55752
|
handleRenameWindow(deviceId, windowId, name) {
|
|
55239
55753
|
const entry = this.connections.get(deviceId);
|
|
55240
55754
|
if (!entry)
|
|
55241
55755
|
return;
|
|
55242
|
-
entry.
|
|
55756
|
+
entry.runtime.renameWindow(windowId, name);
|
|
55243
55757
|
}
|
|
55244
55758
|
async createDeviceConnectionEntry(deviceId, ws) {
|
|
55245
|
-
|
|
55246
|
-
|
|
55247
|
-
onEvent: (event) => {
|
|
55248
|
-
this.broadcastTmuxEvent(deviceId, event);
|
|
55249
|
-
},
|
|
55250
|
-
onTerminalOutput: (paneId, data) => this.broadcastTerminalOutput(deviceId, paneId, data),
|
|
55251
|
-
onTerminalHistory: (paneId, data) => this.broadcastTerminalHistory(deviceId, paneId, data),
|
|
55252
|
-
onSnapshot: (payload) => this.broadcastStateSnapshot(deviceId, payload),
|
|
55253
|
-
onError: (err) => this.broadcastError(deviceId, err),
|
|
55254
|
-
onClose: () => {
|
|
55255
|
-
this.handleConnectionClose(deviceId);
|
|
55256
|
-
}
|
|
55257
|
-
});
|
|
55759
|
+
let runtime = null;
|
|
55760
|
+
let detachRuntime = null;
|
|
55258
55761
|
try {
|
|
55259
|
-
await
|
|
55762
|
+
runtime = await this.deps.acquireRuntime(deviceId);
|
|
55763
|
+
detachRuntime = this.attachRuntime(deviceId, runtime);
|
|
55764
|
+
await runtime.connect();
|
|
55260
55765
|
return {
|
|
55261
|
-
|
|
55766
|
+
runtime,
|
|
55767
|
+
detachRuntime,
|
|
55262
55768
|
clients: new Set,
|
|
55263
55769
|
lastSnapshot: null,
|
|
55264
55770
|
snapshotTimer: null,
|
|
@@ -55267,6 +55773,10 @@ class WebSocketServer {
|
|
|
55267
55773
|
reconnectTimer: null
|
|
55268
55774
|
};
|
|
55269
55775
|
} catch (err) {
|
|
55776
|
+
detachRuntime?.();
|
|
55777
|
+
if (runtime) {
|
|
55778
|
+
this.deps.releaseRuntime(deviceId, runtime);
|
|
55779
|
+
}
|
|
55270
55780
|
const errorInfo = classifySshError(err instanceof Error ? err : new Error(String(err)));
|
|
55271
55781
|
ws.send(exports_ws_borsh.encodeEnvelope(exports_ws_borsh.KIND_DEVICE_EVENT, exports_ws_borsh.encodeDeviceEventPayload({
|
|
55272
55782
|
deviceId,
|
|
@@ -55394,6 +55904,10 @@ class WebSocketServer {
|
|
|
55394
55904
|
}
|
|
55395
55905
|
this.clearSnapshotTimer(entry);
|
|
55396
55906
|
this.clearSnapshotPollTimer(entry);
|
|
55907
|
+
entry.detachRuntime?.();
|
|
55908
|
+
entry.detachRuntime = null;
|
|
55909
|
+
const closedRuntime = entry.runtime;
|
|
55910
|
+
this.deps.releaseRuntime(deviceId, closedRuntime);
|
|
55397
55911
|
const { sshReconnectMaxRetries, sshReconnectDelaySeconds } = getSiteSettings();
|
|
55398
55912
|
if (entry.clients.size > 0 && entry.reconnectAttempts < sshReconnectMaxRetries) {
|
|
55399
55913
|
entry.reconnectAttempts += 1;
|
|
@@ -55440,7 +55954,7 @@ class WebSocketServer {
|
|
|
55440
55954
|
message: t2("sshError.reconnected")
|
|
55441
55955
|
};
|
|
55442
55956
|
this.broadcastDeviceEvent(retryConnection, reconnected);
|
|
55443
|
-
retryConnection.
|
|
55957
|
+
retryConnection.runtime.requestSnapshot();
|
|
55444
55958
|
}, delay);
|
|
55445
55959
|
return;
|
|
55446
55960
|
}
|
|
@@ -55513,6 +56027,7 @@ async function createGatewayRuntime(options = {}) {
|
|
|
55513
56027
|
async stop() {
|
|
55514
56028
|
wsServer.closeAll();
|
|
55515
56029
|
await pushSupervisor.stopAll();
|
|
56030
|
+
await tmuxRuntimeRegistry.shutdownAll();
|
|
55516
56031
|
await telegramService.stopAll();
|
|
55517
56032
|
}
|
|
55518
56033
|
};
|
|
@@ -55779,7 +56294,7 @@ async function serveFrontend(req, staticRoot) {
|
|
|
55779
56294
|
if (!requestedPath) {
|
|
55780
56295
|
return new Response(t3("runtime.forbidden"), { status: 403 });
|
|
55781
56296
|
}
|
|
55782
|
-
const indexPath =
|
|
56297
|
+
const indexPath = join4(staticRoot, "index.html");
|
|
55783
56298
|
const targetPath = existsSync3(requestedPath) ? requestedPath : indexPath;
|
|
55784
56299
|
if (!existsSync3(targetPath)) {
|
|
55785
56300
|
return new Response(t3("runtime.frontendMissing"), { status: 500 });
|