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