claude-relay 2.1.3 → 2.2.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/README.md +31 -6
- package/bin/cli.js +7 -3
- package/lib/project.js +130 -39
- package/lib/public/app.js +381 -10
- package/lib/public/css/base.css +7 -0
- package/lib/public/css/filebrowser.css +149 -2
- package/lib/public/css/input.css +83 -10
- package/lib/public/css/menus.css +281 -1
- package/lib/public/css/messages.css +191 -0
- package/lib/public/css/sidebar.css +93 -1
- package/lib/public/index.html +90 -9
- package/lib/public/modules/filebrowser.js +33 -2
- package/lib/public/modules/input.js +98 -1
- package/lib/public/modules/notifications.js +19 -1
- package/lib/public/modules/qrcode.js +7 -1
- package/lib/public/modules/sidebar.js +233 -3
- package/lib/public/modules/terminal.js +484 -74
- package/lib/public/modules/tools.js +346 -2
- package/lib/public/sw.js +2 -5
- package/lib/push.js +16 -0
- package/lib/sdk-bridge.js +56 -6
- package/lib/sessions.js +34 -0
- package/lib/terminal-manager.js +187 -0
- package/lib/terminal.js +3 -3
- package/lib/usage.js +90 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -6,13 +6,14 @@
|
|
|
6
6
|
|
|
7
7
|
<h3 align="center">Web UI for Claude Code. Any device. Push notifications.</h3>
|
|
8
8
|
|
|
9
|
+
[](https://www.npmjs.com/package/claude-relay) [](https://www.npmjs.com/package/claude-relay) [](https://github.com/chadbyte/claude-relay)
|
|
10
|
+
|
|
9
11
|
Claude Code. Anywhere.
|
|
10
12
|
Same session. Same files. Same machine.
|
|
11
13
|
Your files stay on your computer. Nothing leaves for the cloud.
|
|
12
14
|
|
|
13
15
|
Pick up the same Claude Code session on your phone.
|
|
14
|
-
Start in the terminal, continue on your phone, switch back anytime.
|
|
15
|
-
Same session, same files, now in your pocket.
|
|
16
|
+
Start in the terminal, continue on your phone, switch back anytime.
|
|
16
17
|
|
|
17
18
|
Claude Code is automating more of your editing and execution workflow.
|
|
18
19
|
But when it needs approval or asks a question, it halts in the terminal. If you walk away, it just sits there waiting.
|
|
@@ -45,6 +46,10 @@ It works in browser tabs too. When input is awaited, the favicon blinks and the
|
|
|
45
46
|
|
|
46
47
|
## Side by Side Workflow
|
|
47
48
|
|
|
49
|
+
<p align="center">
|
|
50
|
+
<img src="media/split.gif" alt="split-screen workflow" width="700">
|
|
51
|
+
</p>
|
|
52
|
+
|
|
48
53
|
Keep claude-relay on one side and your localhost on the other.
|
|
49
54
|
Watch the results update live while Claude Code fixes your source files.
|
|
50
55
|
|
|
@@ -82,8 +87,10 @@ Scan the QR code with your phone to connect instantly, or open the URL displayed
|
|
|
82
87
|
|
|
83
88
|
* **Push Approvals** - Approve or reject from your phone while away, so Claude Code does not get stuck waiting.
|
|
84
89
|
* **Multi Project Daemon** - Manage all projects via a single port.
|
|
90
|
+
* **Usage and Model Switching** - View token usage, rate limit bars, and switch models from the browser.
|
|
91
|
+
* **Session Search** - Full-text search across all session messages with hit timeline.
|
|
85
92
|
* **Auto Session Logs (JSONL)** - Conversations and execution history are always saved locally. No data loss on crashes or restarts. Location: `./.claude-relay/sessions/`
|
|
86
|
-
* **File Browser and Terminal** - Inspect files
|
|
93
|
+
* **File Browser and Terminal** - Inspect files, execute commands, and manage multiple terminal tabs from the browser.
|
|
87
94
|
|
|
88
95
|
> Note: Session logs may contain prompts, outputs, and commands. Do not share this folder.
|
|
89
96
|
|
|
@@ -102,22 +109,30 @@ Scan the QR code with your phone to connect instantly, or open the URL displayed
|
|
|
102
109
|
* **Project Names** - Custom names make it easy to distinguish tabs.
|
|
103
110
|
* **Session Persistence** - Sessions survive server restarts, browser crashes, and network drops.
|
|
104
111
|
* **Session Handoff** - Start in the terminal, continue on your phone, pass back to desktop.
|
|
112
|
+
* **Session Search** - Full-text search across all session content with highlighted results and a rewind-style hit timeline.
|
|
105
113
|
* **Rewind (Native Claude Code)** - Accessible directly from the browser UI.
|
|
114
|
+
* **Draft Persistence** - Unsent messages are saved per session and restored when you switch back.
|
|
106
115
|
|
|
107
116
|
**Rendering and Tools**
|
|
108
117
|
|
|
109
118
|
* **Mermaid and Markdown** - Proper rendering for diagrams, tables, and code blocks.
|
|
110
119
|
* **Syntax Highlighting** - Support for over 180 languages with copy buttons on every block.
|
|
111
|
-
* **File Browser** - Sidebar navigation with file previews and
|
|
112
|
-
* **Built in Terminal** -
|
|
120
|
+
* **File Browser** - Sidebar navigation with file previews, markdown rendering, and live-reload on external changes.
|
|
121
|
+
* **Built in Terminal** - Multi-tab terminal sessions with rename, reorder, and a mobile special-key toolbar.
|
|
113
122
|
* **Slash Commands** - Execute standard Claude Code commands from the browser, with autocomplete.
|
|
123
|
+
* **Usage Panel** - View token counts and rate limit progress bars via `/usage` command or header button.
|
|
124
|
+
* **Model Switching** - Change the active model directly from the browser header.
|
|
125
|
+
* **Plan Approval** - Review and approve Claude implementation plans from the browser UI.
|
|
114
126
|
|
|
115
127
|
**UI**
|
|
116
128
|
|
|
117
129
|
* **Mobile Optimized** - Large approve and reject buttons. Behaves like a native app via PWA.
|
|
118
130
|
* **Real time Sync** - All devices view the exact same session state.
|
|
119
131
|
* **QR Code** - Scan to connect instantly.
|
|
120
|
-
* **Image Paste** - Paste images directly from your
|
|
132
|
+
* **Image Paste and Camera** - Paste images from clipboard or attach photos directly from your camera.
|
|
133
|
+
* **Send While Processing** - Queue messages without waiting for the current response to finish.
|
|
134
|
+
* **Sticky Todo Overlay** - TodoWrite tasks float as a progress bar while you scroll through the conversation.
|
|
135
|
+
* **Scroll Position Hold** - Reading earlier messages will not get interrupted by new content arriving.
|
|
121
136
|
|
|
122
137
|
**Server and Security**
|
|
123
138
|
|
|
@@ -205,6 +220,16 @@ graph LR
|
|
|
205
220
|
|
|
206
221
|
For a detailed sequence diagram, daemon structure, and design decisions, refer to [docs/architecture.md](docs/architecture.md).
|
|
207
222
|
|
|
223
|
+
## Star History
|
|
224
|
+
|
|
225
|
+
<a href="https://star-history.com/#chadbyte/claude-relay&Date">
|
|
226
|
+
<picture>
|
|
227
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=chadbyte/claude-relay&type=Date&theme=dark" />
|
|
228
|
+
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=chadbyte/claude-relay&type=Date" />
|
|
229
|
+
<img alt="Star History Chart" src="https://api.star-history.com/svg?repos=chadbyte/claude-relay&type=Date" width="600" />
|
|
230
|
+
</picture>
|
|
231
|
+
</a>
|
|
232
|
+
|
|
208
233
|
---
|
|
209
234
|
|
|
210
235
|
## Contributing
|
package/bin/cli.js
CHANGED
|
@@ -78,6 +78,7 @@ if (shutdownMode) {
|
|
|
78
78
|
var cwd = process.cwd();
|
|
79
79
|
|
|
80
80
|
// --- ANSI helpers ---
|
|
81
|
+
var isBasicTerm = process.env.TERM_PROGRAM === "Apple_Terminal";
|
|
81
82
|
var a = {
|
|
82
83
|
reset: "\x1b[0m",
|
|
83
84
|
bold: "\x1b[1m",
|
|
@@ -89,6 +90,9 @@ var a = {
|
|
|
89
90
|
};
|
|
90
91
|
|
|
91
92
|
function gradient(text) {
|
|
93
|
+
if (isBasicTerm) {
|
|
94
|
+
return a.yellow + text + a.reset;
|
|
95
|
+
}
|
|
92
96
|
// Orange (#DA7756) → Gold (#D4A574)
|
|
93
97
|
var r0 = 218, g0 = 119, b0 = 86;
|
|
94
98
|
var r1 = 212, g1 = 165, b1 = 116;
|
|
@@ -258,7 +262,7 @@ function ensureCerts(ip) {
|
|
|
258
262
|
|
|
259
263
|
// --- Logo ---
|
|
260
264
|
function printLogo() {
|
|
261
|
-
var c = "\x1b[38;2;218;119;86m";
|
|
265
|
+
var c = isBasicTerm ? a.yellow : "\x1b[38;2;218;119;86m";
|
|
262
266
|
var r = a.reset;
|
|
263
267
|
var lines = [
|
|
264
268
|
" ██████╗ ██╗ █████╗ ██╗ ██╗ ██████╗ ███████╗ ██████╗ ███████╗ ██╗ █████╗ ██╗ ██╗",
|
|
@@ -1029,7 +1033,7 @@ function showMainMenu(config, ip) {
|
|
|
1029
1033
|
}
|
|
1030
1034
|
|
|
1031
1035
|
if (ip !== "localhost") {
|
|
1032
|
-
qrcode.generate(url, { small:
|
|
1036
|
+
qrcode.generate(url, { small: !isBasicTerm }, function (code) {
|
|
1033
1037
|
var lines = code.split("\n").map(function (l) { return " " + l; }).join("\n");
|
|
1034
1038
|
console.log(lines);
|
|
1035
1039
|
afterQr();
|
|
@@ -1464,7 +1468,7 @@ function showSetupGuide(config, ip, goBack) {
|
|
|
1464
1468
|
log(sym.bar + " " + a.dim + "Scan the QR code or open:" + a.reset);
|
|
1465
1469
|
log(sym.bar + " " + a.bold + setupUrl + a.reset);
|
|
1466
1470
|
log(sym.bar);
|
|
1467
|
-
qrcode.generate(setupUrl, { small:
|
|
1471
|
+
qrcode.generate(setupUrl, { small: !isBasicTerm }, function (code) {
|
|
1468
1472
|
var lines = code.split("\n").map(function (l) { return " " + sym.bar + " " + l; }).join("\n");
|
|
1469
1473
|
console.log(lines);
|
|
1470
1474
|
log(sym.bar);
|
package/lib/project.js
CHANGED
|
@@ -2,8 +2,9 @@ var fs = require("fs");
|
|
|
2
2
|
var path = require("path");
|
|
3
3
|
var { createSessionManager } = require("./sessions");
|
|
4
4
|
var { createSDKBridge } = require("./sdk-bridge");
|
|
5
|
-
var {
|
|
5
|
+
var { createTerminalManager } = require("./terminal-manager");
|
|
6
6
|
var { fetchLatestVersion, isNewer } = require("./updater");
|
|
7
|
+
var { fetchUsageData } = require("./usage");
|
|
7
8
|
var { execFileSync } = require("child_process");
|
|
8
9
|
|
|
9
10
|
// SDK loaded dynamically (ESM module)
|
|
@@ -94,6 +95,48 @@ function createProjectContext(opts) {
|
|
|
94
95
|
}
|
|
95
96
|
}
|
|
96
97
|
|
|
98
|
+
// --- File watcher ---
|
|
99
|
+
var fileWatcher = null;
|
|
100
|
+
var watchedPath = null;
|
|
101
|
+
var watchDebounce = null;
|
|
102
|
+
|
|
103
|
+
function startFileWatch(relPath) {
|
|
104
|
+
var absPath = safePath(cwd, relPath);
|
|
105
|
+
if (!absPath) return;
|
|
106
|
+
if (watchedPath === relPath) return;
|
|
107
|
+
stopFileWatch();
|
|
108
|
+
watchedPath = relPath;
|
|
109
|
+
try {
|
|
110
|
+
fileWatcher = fs.watch(absPath, function () {
|
|
111
|
+
clearTimeout(watchDebounce);
|
|
112
|
+
watchDebounce = setTimeout(function () {
|
|
113
|
+
try {
|
|
114
|
+
var stat = fs.statSync(absPath);
|
|
115
|
+
var ext = path.extname(absPath).toLowerCase();
|
|
116
|
+
if (stat.size > FS_MAX_SIZE || BINARY_EXTS.has(ext)) return;
|
|
117
|
+
var content = fs.readFileSync(absPath, "utf8");
|
|
118
|
+
send({ type: "fs_file_changed", path: relPath, content: content, size: stat.size });
|
|
119
|
+
} catch (e) {
|
|
120
|
+
stopFileWatch();
|
|
121
|
+
}
|
|
122
|
+
}, 200);
|
|
123
|
+
});
|
|
124
|
+
fileWatcher.on("error", function () { stopFileWatch(); });
|
|
125
|
+
} catch (e) {
|
|
126
|
+
watchedPath = null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function stopFileWatch() {
|
|
131
|
+
if (fileWatcher) {
|
|
132
|
+
try { fileWatcher.close(); } catch (e) {}
|
|
133
|
+
fileWatcher = null;
|
|
134
|
+
}
|
|
135
|
+
clearTimeout(watchDebounce);
|
|
136
|
+
watchDebounce = null;
|
|
137
|
+
watchedPath = null;
|
|
138
|
+
}
|
|
139
|
+
|
|
97
140
|
// --- Session manager ---
|
|
98
141
|
var sm = createSessionManager({ cwd: cwd, send: send });
|
|
99
142
|
|
|
@@ -106,6 +149,9 @@ function createProjectContext(opts) {
|
|
|
106
149
|
getSDK: getSDK,
|
|
107
150
|
});
|
|
108
151
|
|
|
152
|
+
// --- Terminal manager ---
|
|
153
|
+
var tm = createTerminalManager({ cwd: cwd, send: send, sendTo: sendTo });
|
|
154
|
+
|
|
109
155
|
// Check for updates in background
|
|
110
156
|
fetchLatestVersion().then(function (v) {
|
|
111
157
|
if (v && isNewer(v, currentVersion)) {
|
|
@@ -127,6 +173,10 @@ function createProjectContext(opts) {
|
|
|
127
173
|
if (sm.slashCommands) {
|
|
128
174
|
sendTo(ws, { type: "slash_commands", commands: sm.slashCommands });
|
|
129
175
|
}
|
|
176
|
+
if (sm.currentModel) {
|
|
177
|
+
sendTo(ws, { type: "model_info", model: sm.currentModel, models: sm.availableModels || [] });
|
|
178
|
+
}
|
|
179
|
+
sendTo(ws, { type: "term_list", terminals: tm.list() });
|
|
130
180
|
|
|
131
181
|
// Session list
|
|
132
182
|
sendTo(ws, {
|
|
@@ -243,6 +293,12 @@ function createProjectContext(opts) {
|
|
|
243
293
|
return;
|
|
244
294
|
}
|
|
245
295
|
|
|
296
|
+
if (msg.type === "search_sessions") {
|
|
297
|
+
var results = sm.searchSessions(msg.query || "");
|
|
298
|
+
sendTo(ws, { type: "search_results", query: msg.query || "", results: results });
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
246
302
|
if (msg.type === "check_update") {
|
|
247
303
|
fetchLatestVersion().then(function (v) {
|
|
248
304
|
if (v && isNewer(v, currentVersion)) {
|
|
@@ -261,6 +317,23 @@ function createProjectContext(opts) {
|
|
|
261
317
|
return;
|
|
262
318
|
}
|
|
263
319
|
|
|
320
|
+
if (msg.type === "get_usage") {
|
|
321
|
+
fetchUsageData().then(function (data) {
|
|
322
|
+
sendTo(ws, { type: "usage_data", data: data });
|
|
323
|
+
}).catch(function (err) {
|
|
324
|
+
sendTo(ws, { type: "usage_data", error: err.message || "Failed to fetch usage data" });
|
|
325
|
+
});
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (msg.type === "set_model" && msg.model) {
|
|
330
|
+
var session = sm.getActiveSession();
|
|
331
|
+
if (session) {
|
|
332
|
+
sdk.setModel(session, msg.model);
|
|
333
|
+
}
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
|
|
264
337
|
if (msg.type === "rewind_preview") {
|
|
265
338
|
var session = sm.getActiveSession();
|
|
266
339
|
if (!session || !session.cliSessionId || !msg.uuid) return;
|
|
@@ -358,6 +431,7 @@ function createProjectContext(opts) {
|
|
|
358
431
|
var pending = session.pendingAskUser[toolId];
|
|
359
432
|
if (!pending) return;
|
|
360
433
|
delete session.pendingAskUser[toolId];
|
|
434
|
+
sm.sendAndRecord(session, { type: "ask_user_answered", toolId: toolId });
|
|
361
435
|
pending.resolve({
|
|
362
436
|
behavior: "allow",
|
|
363
437
|
updatedInput: Object.assign({}, pending.input, { answers: answers }),
|
|
@@ -450,41 +524,64 @@ function createProjectContext(opts) {
|
|
|
450
524
|
return;
|
|
451
525
|
}
|
|
452
526
|
|
|
527
|
+
// --- File watcher ---
|
|
528
|
+
if (msg.type === "fs_watch") {
|
|
529
|
+
if (msg.path) startFileWatch(msg.path);
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (msg.type === "fs_unwatch") {
|
|
534
|
+
stopFileWatch();
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
|
|
453
538
|
// --- Web terminal ---
|
|
454
|
-
if (msg.type === "
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
sendTo(ws, { type: "term_output", data: "\r\n[node-pty not available]\r\n" });
|
|
539
|
+
if (msg.type === "term_create") {
|
|
540
|
+
var t = tm.create(msg.cols || 80, msg.rows || 24);
|
|
541
|
+
if (!t) {
|
|
542
|
+
sendTo(ws, { type: "term_error", error: "Cannot create terminal (node-pty not available or limit reached)" });
|
|
459
543
|
return;
|
|
460
544
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
545
|
+
tm.attach(t.id, ws);
|
|
546
|
+
send({ type: "term_list", terminals: tm.list() });
|
|
547
|
+
sendTo(ws, { type: "term_created", id: t.id });
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (msg.type === "term_attach") {
|
|
552
|
+
if (msg.id) tm.attach(msg.id, ws);
|
|
553
|
+
return;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
if (msg.type === "term_detach") {
|
|
557
|
+
if (msg.id) tm.detach(msg.id, ws);
|
|
469
558
|
return;
|
|
470
559
|
}
|
|
471
560
|
|
|
472
561
|
if (msg.type === "term_input") {
|
|
473
|
-
if (
|
|
562
|
+
if (msg.id) tm.write(msg.id, msg.data);
|
|
474
563
|
return;
|
|
475
564
|
}
|
|
476
565
|
|
|
477
566
|
if (msg.type === "term_resize") {
|
|
478
|
-
if (
|
|
479
|
-
|
|
567
|
+
if (msg.id && msg.cols > 0 && msg.rows > 0) {
|
|
568
|
+
tm.resize(msg.id, msg.cols, msg.rows);
|
|
480
569
|
}
|
|
481
570
|
return;
|
|
482
571
|
}
|
|
483
572
|
|
|
484
573
|
if (msg.type === "term_close") {
|
|
485
|
-
if (
|
|
486
|
-
|
|
487
|
-
|
|
574
|
+
if (msg.id) {
|
|
575
|
+
tm.close(msg.id);
|
|
576
|
+
send({ type: "term_list", terminals: tm.list() });
|
|
577
|
+
}
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
if (msg.type === "term_rename") {
|
|
582
|
+
if (msg.id && msg.title) {
|
|
583
|
+
tm.rename(msg.id, msg.title);
|
|
584
|
+
send({ type: "term_list", terminals: tm.list() });
|
|
488
585
|
}
|
|
489
586
|
return;
|
|
490
587
|
}
|
|
@@ -495,14 +592,6 @@ function createProjectContext(opts) {
|
|
|
495
592
|
var session = sm.getActiveSession();
|
|
496
593
|
if (!session) return;
|
|
497
594
|
|
|
498
|
-
if (session.isProcessing) {
|
|
499
|
-
send({ type: "error", text: "Still processing previous message. Please wait." });
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
|
|
503
|
-
session.isProcessing = true;
|
|
504
|
-
session.sentToolResults = {};
|
|
505
|
-
|
|
506
595
|
var userMsg = { type: "user_message", text: msg.text || "" };
|
|
507
596
|
if (msg.images && msg.images.length > 0) {
|
|
508
597
|
userMsg.imageCount = msg.images.length;
|
|
@@ -513,7 +602,6 @@ function createProjectContext(opts) {
|
|
|
513
602
|
session.history.push(userMsg);
|
|
514
603
|
sm.appendToSessionFile(session, userMsg);
|
|
515
604
|
sendToOthers(ws, userMsg);
|
|
516
|
-
send({ type: "status", status: "processing" });
|
|
517
605
|
|
|
518
606
|
if (!session.title) {
|
|
519
607
|
session.title = (msg.text || "Image").substring(0, 50);
|
|
@@ -529,8 +617,15 @@ function createProjectContext(opts) {
|
|
|
529
617
|
}
|
|
530
618
|
}
|
|
531
619
|
|
|
532
|
-
if (!session.
|
|
533
|
-
|
|
620
|
+
if (!session.isProcessing) {
|
|
621
|
+
session.isProcessing = true;
|
|
622
|
+
session.sentToolResults = {};
|
|
623
|
+
send({ type: "status", status: "processing" });
|
|
624
|
+
if (!session.queryInstance) {
|
|
625
|
+
sdk.startQuery(session, fullText, msg.images);
|
|
626
|
+
} else {
|
|
627
|
+
sdk.pushMessage(session, fullText, msg.images);
|
|
628
|
+
}
|
|
534
629
|
} else {
|
|
535
630
|
sdk.pushMessage(session, fullText, msg.images);
|
|
536
631
|
}
|
|
@@ -539,11 +634,9 @@ function createProjectContext(opts) {
|
|
|
539
634
|
|
|
540
635
|
// --- WS disconnection handler ---
|
|
541
636
|
function handleDisconnection(ws) {
|
|
542
|
-
|
|
543
|
-
try { ws._term.kill(); } catch (e) {}
|
|
544
|
-
ws._term = null;
|
|
545
|
-
}
|
|
637
|
+
tm.detachAll(ws);
|
|
546
638
|
clients.delete(ws);
|
|
639
|
+
if (clients.size === 0) stopFileWatch();
|
|
547
640
|
broadcastClientCount();
|
|
548
641
|
}
|
|
549
642
|
|
|
@@ -651,6 +744,7 @@ function createProjectContext(opts) {
|
|
|
651
744
|
|
|
652
745
|
// --- Destroy ---
|
|
653
746
|
function destroy() {
|
|
747
|
+
stopFileWatch();
|
|
654
748
|
// Abort all active sessions
|
|
655
749
|
sm.sessions.forEach(function (session) {
|
|
656
750
|
if (session.abortController) {
|
|
@@ -661,11 +755,8 @@ function createProjectContext(opts) {
|
|
|
661
755
|
}
|
|
662
756
|
});
|
|
663
757
|
// Kill all terminals
|
|
758
|
+
tm.destroyAll();
|
|
664
759
|
for (var ws of clients) {
|
|
665
|
-
if (ws._term) {
|
|
666
|
-
try { ws._term.kill(); } catch (e) {}
|
|
667
|
-
ws._term = null;
|
|
668
|
-
}
|
|
669
760
|
try { ws.close(); } catch (e) {}
|
|
670
761
|
}
|
|
671
762
|
clients.clear();
|