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