claude-relay 2.2.4 → 2.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/README.md +12 -0
- package/bin/cli.js +135 -4
- package/lib/config.js +31 -0
- package/lib/daemon.js +49 -1
- package/lib/pages.js +15 -1
- package/lib/project.js +323 -26
- package/lib/public/app.js +313 -7
- package/lib/public/css/base.css +5 -0
- package/lib/public/css/diff.css +128 -0
- package/lib/public/css/filebrowser.css +541 -0
- package/lib/public/css/input.css +1 -0
- package/lib/public/css/menus.css +89 -5
- package/lib/public/css/messages.css +84 -49
- package/lib/public/css/overlays.css +40 -0
- package/lib/public/index.html +100 -17
- package/lib/public/modules/diff.js +398 -0
- package/lib/public/modules/filebrowser.js +1023 -11
- package/lib/public/modules/input.js +96 -2
- package/lib/public/modules/notifications.js +29 -3
- package/lib/public/modules/qrcode.js +11 -2
- package/lib/public/modules/rewind.js +51 -2
- package/lib/public/modules/tools.js +43 -104
- package/lib/public/modules/utils.js +10 -2
- package/lib/public/style.css +1 -0
- package/lib/public/sw.js +21 -7
- package/lib/push.js +5 -1
- package/lib/sdk-bridge.js +38 -5
- package/lib/server.js +37 -4
- package/lib/sessions.js +14 -5
- package/package.json +1 -1
package/lib/project.js
CHANGED
|
@@ -55,7 +55,7 @@ function safePath(base, requested) {
|
|
|
55
55
|
|
|
56
56
|
/**
|
|
57
57
|
* Create a project context — per-project state and handlers.
|
|
58
|
-
* opts: { cwd, slug, title, pushModule, debug, currentVersion }
|
|
58
|
+
* opts: { cwd, slug, title, pushModule, debug, dangerouslySkipPermissions, currentVersion }
|
|
59
59
|
*/
|
|
60
60
|
function createProjectContext(opts) {
|
|
61
61
|
var cwd = opts.cwd;
|
|
@@ -64,7 +64,9 @@ function createProjectContext(opts) {
|
|
|
64
64
|
var title = opts.title || null;
|
|
65
65
|
var pushModule = opts.pushModule || null;
|
|
66
66
|
var debug = opts.debug || false;
|
|
67
|
+
var dangerouslySkipPermissions = opts.dangerouslySkipPermissions || false;
|
|
67
68
|
var currentVersion = opts.currentVersion;
|
|
69
|
+
var lanHost = opts.lanHost || null;
|
|
68
70
|
var getProjectCount = opts.getProjectCount || function () { return 1; };
|
|
69
71
|
var getProjectList = opts.getProjectList || function () { return []; };
|
|
70
72
|
var latestVersion = null;
|
|
@@ -136,16 +138,69 @@ function createProjectContext(opts) {
|
|
|
136
138
|
watchedPath = null;
|
|
137
139
|
}
|
|
138
140
|
|
|
141
|
+
// --- Directory watcher ---
|
|
142
|
+
var dirWatchers = {}; // relPath -> { watcher, debounce }
|
|
143
|
+
|
|
144
|
+
function startDirWatch(relPath) {
|
|
145
|
+
if (dirWatchers[relPath]) return;
|
|
146
|
+
var absPath = safePath(cwd, relPath);
|
|
147
|
+
if (!absPath) return;
|
|
148
|
+
try {
|
|
149
|
+
var debounce = null;
|
|
150
|
+
var watcher = fs.watch(absPath, function () {
|
|
151
|
+
clearTimeout(debounce);
|
|
152
|
+
debounce = setTimeout(function () {
|
|
153
|
+
// Re-read directory and broadcast to all clients
|
|
154
|
+
try {
|
|
155
|
+
var items = fs.readdirSync(absPath, { withFileTypes: true });
|
|
156
|
+
var entries = [];
|
|
157
|
+
for (var i = 0; i < items.length; i++) {
|
|
158
|
+
if (items[i].isDirectory() && IGNORED_DIRS.has(items[i].name)) continue;
|
|
159
|
+
entries.push({
|
|
160
|
+
name: items[i].name,
|
|
161
|
+
type: items[i].isDirectory() ? "dir" : "file",
|
|
162
|
+
path: path.relative(cwd, path.join(absPath, items[i].name)).split(path.sep).join("/"),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
send({ type: "fs_dir_changed", path: relPath, entries: entries });
|
|
166
|
+
} catch (e) {
|
|
167
|
+
stopDirWatch(relPath);
|
|
168
|
+
}
|
|
169
|
+
}, 300);
|
|
170
|
+
});
|
|
171
|
+
watcher.on("error", function () { stopDirWatch(relPath); });
|
|
172
|
+
dirWatchers[relPath] = { watcher: watcher, debounce: debounce };
|
|
173
|
+
} catch (e) {}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function stopDirWatch(relPath) {
|
|
177
|
+
var entry = dirWatchers[relPath];
|
|
178
|
+
if (entry) {
|
|
179
|
+
clearTimeout(entry.debounce);
|
|
180
|
+
try { entry.watcher.close(); } catch (e) {}
|
|
181
|
+
delete dirWatchers[relPath];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function stopAllDirWatches() {
|
|
186
|
+
var paths = Object.keys(dirWatchers);
|
|
187
|
+
for (var i = 0; i < paths.length; i++) {
|
|
188
|
+
stopDirWatch(paths[i]);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
139
192
|
// --- Session manager ---
|
|
140
193
|
var sm = createSessionManager({ cwd: cwd, send: send });
|
|
141
194
|
|
|
142
195
|
// --- SDK bridge ---
|
|
143
196
|
var sdk = createSDKBridge({
|
|
144
197
|
cwd: cwd,
|
|
198
|
+
slug: slug,
|
|
145
199
|
sessionManager: sm,
|
|
146
200
|
send: send,
|
|
147
201
|
pushModule: pushModule,
|
|
148
202
|
getSDK: getSDK,
|
|
203
|
+
dangerouslySkipPermissions: dangerouslySkipPermissions,
|
|
149
204
|
});
|
|
150
205
|
|
|
151
206
|
// --- Terminal manager ---
|
|
@@ -165,7 +220,7 @@ function createProjectContext(opts) {
|
|
|
165
220
|
broadcastClientCount();
|
|
166
221
|
|
|
167
222
|
// Send cached state
|
|
168
|
-
sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, projectCount: getProjectCount(), projects: getProjectList() });
|
|
223
|
+
sendTo(ws, { type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, dangerouslySkipPermissions: dangerouslySkipPermissions, lanHost: lanHost, projectCount: getProjectCount(), projects: getProjectList() });
|
|
169
224
|
if (latestVersion) {
|
|
170
225
|
sendTo(ws, { type: "update_available", version: latestVersion });
|
|
171
226
|
}
|
|
@@ -206,6 +261,7 @@ function createProjectContext(opts) {
|
|
|
206
261
|
for (var i = fromIndex; i < total; i++) {
|
|
207
262
|
sendTo(ws, active.history[i]);
|
|
208
263
|
}
|
|
264
|
+
sendTo(ws, { type: "history_done" });
|
|
209
265
|
|
|
210
266
|
if (active.isProcessing) {
|
|
211
267
|
sendTo(ws, { type: "status", status: "processing" });
|
|
@@ -238,7 +294,7 @@ function createProjectContext(opts) {
|
|
|
238
294
|
// --- WS message handler ---
|
|
239
295
|
function handleMessage(ws, msg) {
|
|
240
296
|
if (msg.type === "push_subscribe") {
|
|
241
|
-
if (pushModule && msg.subscription) pushModule.addSubscription(msg.subscription);
|
|
297
|
+
if (pushModule && msg.subscription) pushModule.addSubscription(msg.subscription, msg.replaceEndpoint);
|
|
242
298
|
return;
|
|
243
299
|
}
|
|
244
300
|
|
|
@@ -308,6 +364,31 @@ function createProjectContext(opts) {
|
|
|
308
364
|
return;
|
|
309
365
|
}
|
|
310
366
|
|
|
367
|
+
if (msg.type === "process_stats") {
|
|
368
|
+
var sessionCount = sm.sessions.size;
|
|
369
|
+
var processingCount = 0;
|
|
370
|
+
sm.sessions.forEach(function (s) {
|
|
371
|
+
if (s.isProcessing) processingCount++;
|
|
372
|
+
});
|
|
373
|
+
var mem = process.memoryUsage();
|
|
374
|
+
sendTo(ws, {
|
|
375
|
+
type: "process_stats",
|
|
376
|
+
pid: process.pid,
|
|
377
|
+
uptime: process.uptime(),
|
|
378
|
+
memory: {
|
|
379
|
+
rss: mem.rss,
|
|
380
|
+
heapUsed: mem.heapUsed,
|
|
381
|
+
heapTotal: mem.heapTotal,
|
|
382
|
+
external: mem.external,
|
|
383
|
+
},
|
|
384
|
+
sessions: sessionCount,
|
|
385
|
+
processing: processingCount,
|
|
386
|
+
clients: clients.size,
|
|
387
|
+
terminals: tm.list().length,
|
|
388
|
+
});
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
311
392
|
if (msg.type === "stop") {
|
|
312
393
|
var session = sm.getActiveSession();
|
|
313
394
|
if (session && session.abortController && session.isProcessing) {
|
|
@@ -357,34 +438,41 @@ function createProjectContext(opts) {
|
|
|
357
438
|
if (msg.type === "rewind_execute") {
|
|
358
439
|
var session = sm.getActiveSession();
|
|
359
440
|
if (!session || !session.cliSessionId || !msg.uuid) return;
|
|
441
|
+
var mode = msg.mode || "both";
|
|
360
442
|
|
|
361
443
|
(async function () {
|
|
362
444
|
var result;
|
|
363
445
|
try {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
for (var i = 0; i < session.messageUUIDs.length; i++) {
|
|
369
|
-
if (session.messageUUIDs[i].uuid === msg.uuid) {
|
|
370
|
-
targetIdx = i;
|
|
371
|
-
break;
|
|
372
|
-
}
|
|
446
|
+
// File restoration (skip for chat-only mode)
|
|
447
|
+
if (mode !== "chat") {
|
|
448
|
+
result = await sdk.getOrCreateRewindQuery(session);
|
|
449
|
+
await result.query.rewindFiles(msg.uuid, { dryRun: false });
|
|
373
450
|
}
|
|
374
451
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
452
|
+
// Conversation rollback (skip for files-only mode)
|
|
453
|
+
if (mode !== "files") {
|
|
454
|
+
var targetIdx = -1;
|
|
455
|
+
for (var i = 0; i < session.messageUUIDs.length; i++) {
|
|
456
|
+
if (session.messageUUIDs[i].uuid === msg.uuid) {
|
|
457
|
+
targetIdx = i;
|
|
380
458
|
break;
|
|
381
459
|
}
|
|
382
460
|
}
|
|
383
|
-
session.history = session.history.slice(0, trimTo);
|
|
384
|
-
session.messageUUIDs = session.messageUUIDs.slice(0, targetIdx);
|
|
385
|
-
}
|
|
386
461
|
|
|
387
|
-
|
|
462
|
+
if (targetIdx >= 0) {
|
|
463
|
+
var trimTo = session.messageUUIDs[targetIdx].historyIndex;
|
|
464
|
+
for (var k = trimTo - 1; k >= 0; k--) {
|
|
465
|
+
if (session.history[k].type === "user_message") {
|
|
466
|
+
trimTo = k;
|
|
467
|
+
break;
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
session.history = session.history.slice(0, trimTo);
|
|
471
|
+
session.messageUUIDs = session.messageUUIDs.slice(0, targetIdx);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
session.lastRewindUuid = msg.uuid;
|
|
475
|
+
}
|
|
388
476
|
|
|
389
477
|
if (session.abortController) {
|
|
390
478
|
try { session.abortController.abort(); } catch (e) {}
|
|
@@ -403,7 +491,7 @@ function createProjectContext(opts) {
|
|
|
403
491
|
|
|
404
492
|
sm.saveSessionFile(session);
|
|
405
493
|
sm.switchSession(session.localId);
|
|
406
|
-
sm.sendAndRecord(session, { type: "rewind_complete" });
|
|
494
|
+
sm.sendAndRecord(session, { type: "rewind_complete", mode: mode });
|
|
407
495
|
sm.broadcastSessionList();
|
|
408
496
|
} catch (err) {
|
|
409
497
|
send({ type: "rewind_error", text: "Rewind failed: " + err.message });
|
|
@@ -482,6 +570,8 @@ function createProjectContext(opts) {
|
|
|
482
570
|
});
|
|
483
571
|
}
|
|
484
572
|
sendTo(ws, { type: "fs_list_result", path: msg.path || ".", entries: entries });
|
|
573
|
+
// Auto-watch the directory for changes
|
|
574
|
+
startDirWatch(msg.path || ".");
|
|
485
575
|
} catch (e) {
|
|
486
576
|
sendTo(ws, { type: "fs_list_result", path: msg.path, entries: [], error: e.message });
|
|
487
577
|
}
|
|
@@ -526,6 +616,197 @@ function createProjectContext(opts) {
|
|
|
526
616
|
return;
|
|
527
617
|
}
|
|
528
618
|
|
|
619
|
+
// --- File edit history ---
|
|
620
|
+
if (msg.type === "fs_file_history") {
|
|
621
|
+
var histPath = msg.path;
|
|
622
|
+
if (!histPath) {
|
|
623
|
+
sendTo(ws, { type: "fs_file_history_result", path: histPath, entries: [] });
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
var absHistPath = path.resolve(cwd, histPath);
|
|
627
|
+
var entries = [];
|
|
628
|
+
|
|
629
|
+
// Collect session edits
|
|
630
|
+
sm.sessions.forEach(function (session) {
|
|
631
|
+
var sessionLocalId = session.localId;
|
|
632
|
+
var sessionTitle = session.title || "Untitled";
|
|
633
|
+
var histLen = session.history.length || 1;
|
|
634
|
+
|
|
635
|
+
for (var hi = 0; hi < session.history.length; hi++) {
|
|
636
|
+
var entry = session.history[hi];
|
|
637
|
+
if (entry.type !== "tool_executing") continue;
|
|
638
|
+
if (entry.name !== "Edit" && entry.name !== "Write") continue;
|
|
639
|
+
if (!entry.input || !entry.input.file_path) continue;
|
|
640
|
+
if (entry.input.file_path !== absHistPath) continue;
|
|
641
|
+
|
|
642
|
+
// Find parent assistant UUID + message snippet by scanning backwards
|
|
643
|
+
var assistantUuid = null;
|
|
644
|
+
var uuidIndex = -1;
|
|
645
|
+
for (var hj = hi - 1; hj >= 0; hj--) {
|
|
646
|
+
if (session.history[hj].type === "message_uuid" && session.history[hj].messageType === "assistant") {
|
|
647
|
+
assistantUuid = session.history[hj].uuid;
|
|
648
|
+
uuidIndex = hj;
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// Find user prompt by scanning backwards from the assistant uuid
|
|
654
|
+
var messageSnippet = "";
|
|
655
|
+
var searchFrom = uuidIndex >= 0 ? uuidIndex : hi;
|
|
656
|
+
for (var hk = searchFrom - 1; hk >= 0; hk--) {
|
|
657
|
+
if (session.history[hk].type === "user_message" && session.history[hk].text) {
|
|
658
|
+
messageSnippet = session.history[hk].text.trim().substring(0, 100);
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
// Collect Claude's explanation: scan backwards from tool_executing
|
|
664
|
+
// to find the nearest delta text block (skipping tool_start).
|
|
665
|
+
// If no delta found immediately before this tool, scan past
|
|
666
|
+
// intervening tool blocks to find the last delta text within
|
|
667
|
+
// the same assistant turn.
|
|
668
|
+
var assistantSnippet = "";
|
|
669
|
+
var deltaChunks = [];
|
|
670
|
+
for (var hd = hi - 1; hd >= 0; hd--) {
|
|
671
|
+
var hEntry = session.history[hd];
|
|
672
|
+
if (hEntry.type === "tool_start") continue;
|
|
673
|
+
if (hEntry.type === "delta" && hEntry.text) {
|
|
674
|
+
deltaChunks.unshift(hEntry.text);
|
|
675
|
+
} else {
|
|
676
|
+
break;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (deltaChunks.length === 0) {
|
|
680
|
+
// No delta immediately before; scan past tool blocks
|
|
681
|
+
// to find the nearest preceding delta in the same turn
|
|
682
|
+
for (var hd2 = hi - 1; hd2 >= 0; hd2--) {
|
|
683
|
+
var hEntry2 = session.history[hd2];
|
|
684
|
+
if (hEntry2.type === "tool_start" || hEntry2.type === "tool_executing" || hEntry2.type === "tool_result") continue;
|
|
685
|
+
if (hEntry2.type === "delta" && hEntry2.text) {
|
|
686
|
+
// Found a delta before an earlier tool in the same turn.
|
|
687
|
+
// Collect this contiguous block of deltas.
|
|
688
|
+
for (var hd3 = hd2; hd3 >= 0; hd3--) {
|
|
689
|
+
var hEntry3 = session.history[hd3];
|
|
690
|
+
if (hEntry3.type === "tool_start") continue;
|
|
691
|
+
if (hEntry3.type === "delta" && hEntry3.text) {
|
|
692
|
+
deltaChunks.unshift(hEntry3.text);
|
|
693
|
+
} else {
|
|
694
|
+
break;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
break;
|
|
698
|
+
} else {
|
|
699
|
+
// Hit message_uuid, user_message, etc. Stop.
|
|
700
|
+
break;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
assistantSnippet = deltaChunks.join("").trim().substring(0, 150);
|
|
705
|
+
|
|
706
|
+
// Approximate timestamp: interpolate between session creation and last activity
|
|
707
|
+
var tStart = session.createdAt || 0;
|
|
708
|
+
var tEnd = session.lastActivity || tStart;
|
|
709
|
+
var ts = tStart + Math.floor((hi / histLen) * (tEnd - tStart));
|
|
710
|
+
|
|
711
|
+
var editRecord = {
|
|
712
|
+
source: "session",
|
|
713
|
+
timestamp: ts,
|
|
714
|
+
sessionLocalId: sessionLocalId,
|
|
715
|
+
sessionTitle: sessionTitle,
|
|
716
|
+
assistantUuid: assistantUuid,
|
|
717
|
+
toolId: entry.id,
|
|
718
|
+
messageSnippet: messageSnippet,
|
|
719
|
+
assistantSnippet: assistantSnippet,
|
|
720
|
+
toolName: entry.name,
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
if (entry.name === "Edit") {
|
|
724
|
+
editRecord.old_string = entry.input.old_string || "";
|
|
725
|
+
editRecord.new_string = entry.input.new_string || "";
|
|
726
|
+
} else {
|
|
727
|
+
editRecord.isFullWrite = true;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
entries.push(editRecord);
|
|
731
|
+
}
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
// Collect git commits
|
|
735
|
+
try {
|
|
736
|
+
var gitLog = execFileSync(
|
|
737
|
+
"git", ["log", "--format=%H|%at|%an|%s", "--follow", "--", histPath],
|
|
738
|
+
{ cwd: cwd, encoding: "utf8", timeout: 5000 }
|
|
739
|
+
);
|
|
740
|
+
var gitLines = gitLog.trim().split("\n");
|
|
741
|
+
for (var gi = 0; gi < gitLines.length; gi++) {
|
|
742
|
+
if (!gitLines[gi]) continue;
|
|
743
|
+
var parts = gitLines[gi].split("|");
|
|
744
|
+
if (parts.length < 4) continue;
|
|
745
|
+
entries.push({
|
|
746
|
+
source: "git",
|
|
747
|
+
hash: parts[0],
|
|
748
|
+
timestamp: parseInt(parts[1], 10) * 1000,
|
|
749
|
+
author: parts[2],
|
|
750
|
+
message: parts.slice(3).join("|"),
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
} catch (e) {
|
|
754
|
+
// Not a git repo or file not tracked, that's fine
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// Sort by timestamp descending (newest first)
|
|
758
|
+
entries.sort(function (a, b) { return b.timestamp - a.timestamp; });
|
|
759
|
+
|
|
760
|
+
sendTo(ws, { type: "fs_file_history_result", path: histPath, entries: entries });
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
// --- Git diff for file history ---
|
|
765
|
+
if (msg.type === "fs_git_diff") {
|
|
766
|
+
var diffPath = msg.path;
|
|
767
|
+
var hash = msg.hash;
|
|
768
|
+
var hash2 = msg.hash2 || null;
|
|
769
|
+
if (!diffPath || !hash) {
|
|
770
|
+
sendTo(ws, { type: "fs_git_diff_result", hash: hash, path: diffPath, diff: "", error: "Missing params" });
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
try {
|
|
774
|
+
var diff;
|
|
775
|
+
if (hash2) {
|
|
776
|
+
diff = execFileSync("git", ["diff", hash, hash2, "--", diffPath],
|
|
777
|
+
{ cwd: cwd, encoding: "utf8", timeout: 5000 });
|
|
778
|
+
} else {
|
|
779
|
+
diff = execFileSync("git", ["show", hash, "--format=", "--", diffPath],
|
|
780
|
+
{ cwd: cwd, encoding: "utf8", timeout: 5000 });
|
|
781
|
+
}
|
|
782
|
+
sendTo(ws, { type: "fs_git_diff_result", hash: hash, hash2: hash2, path: diffPath, diff: diff || "" });
|
|
783
|
+
} catch (e) {
|
|
784
|
+
sendTo(ws, { type: "fs_git_diff_result", hash: hash, hash2: hash2, path: diffPath, diff: "", error: e.message });
|
|
785
|
+
}
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// --- File content at a git commit ---
|
|
790
|
+
if (msg.type === "fs_file_at") {
|
|
791
|
+
var atPath = msg.path;
|
|
792
|
+
var atHash = msg.hash;
|
|
793
|
+
if (!atPath || !atHash) {
|
|
794
|
+
sendTo(ws, { type: "fs_file_at_result", hash: atHash, path: atPath, content: "", error: "Missing params" });
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
try {
|
|
798
|
+
// Convert to repo-relative path (git show requires hash:relative/path)
|
|
799
|
+
var atAbsPath = path.resolve(cwd, atPath);
|
|
800
|
+
var atRelPath = path.relative(cwd, atAbsPath);
|
|
801
|
+
var content = execFileSync("git", ["show", atHash + ":" + atRelPath],
|
|
802
|
+
{ cwd: cwd, encoding: "utf8", timeout: 5000 });
|
|
803
|
+
sendTo(ws, { type: "fs_file_at_result", hash: atHash, path: atPath, content: content });
|
|
804
|
+
} catch (e) {
|
|
805
|
+
sendTo(ws, { type: "fs_file_at_result", hash: atHash, path: atPath, content: "", error: e.message });
|
|
806
|
+
}
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
|
|
529
810
|
// --- Web terminal ---
|
|
530
811
|
if (msg.type === "term_create") {
|
|
531
812
|
var t = tm.create(msg.cols || 80, msg.rows || 24);
|
|
@@ -627,7 +908,21 @@ function createProjectContext(opts) {
|
|
|
627
908
|
function handleDisconnection(ws) {
|
|
628
909
|
tm.detachAll(ws);
|
|
629
910
|
clients.delete(ws);
|
|
630
|
-
if (clients.size === 0)
|
|
911
|
+
if (clients.size === 0) {
|
|
912
|
+
stopFileWatch();
|
|
913
|
+
stopAllDirWatches();
|
|
914
|
+
// Abort all running queries when no clients are connected
|
|
915
|
+
var aborted = 0;
|
|
916
|
+
sm.sessions.forEach(function (session) {
|
|
917
|
+
if (session.isProcessing && session.abortController) {
|
|
918
|
+
try { session.abortController.abort(); } catch (e) {}
|
|
919
|
+
aborted++;
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
if (aborted > 0) {
|
|
923
|
+
console.log("[project:" + slug + "] No clients connected, aborted " + aborted + " active queries");
|
|
924
|
+
}
|
|
925
|
+
}
|
|
631
926
|
broadcastClientCount();
|
|
632
927
|
}
|
|
633
928
|
|
|
@@ -635,8 +930,9 @@ function createProjectContext(opts) {
|
|
|
635
930
|
function handleHTTP(req, res, urlPath) {
|
|
636
931
|
// Push subscribe
|
|
637
932
|
if (req.method === "POST" && urlPath === "/api/push-subscribe") {
|
|
638
|
-
parseJsonBody(req).then(function (
|
|
639
|
-
|
|
933
|
+
parseJsonBody(req).then(function (body) {
|
|
934
|
+
var sub = body.subscription || body;
|
|
935
|
+
if (pushModule) pushModule.addSubscription(sub, body.replaceEndpoint);
|
|
640
936
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
641
937
|
res.end('{"ok":true}');
|
|
642
938
|
}).catch(function () {
|
|
@@ -736,6 +1032,7 @@ function createProjectContext(opts) {
|
|
|
736
1032
|
// --- Destroy ---
|
|
737
1033
|
function destroy() {
|
|
738
1034
|
stopFileWatch();
|
|
1035
|
+
stopAllDirWatches();
|
|
739
1036
|
// Abort all active sessions
|
|
740
1037
|
sm.sessions.forEach(function (session) {
|
|
741
1038
|
if (session.abortController) {
|
|
@@ -773,7 +1070,7 @@ function createProjectContext(opts) {
|
|
|
773
1070
|
|
|
774
1071
|
function setTitle(newTitle) {
|
|
775
1072
|
title = newTitle || null;
|
|
776
|
-
send({ type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, projectCount: getProjectCount(), projects: getProjectList() });
|
|
1073
|
+
send({ type: "info", cwd: cwd, slug: slug, project: title || project, version: currentVersion, debug: !!debug, lanHost: lanHost, projectCount: getProjectCount(), projects: getProjectList() });
|
|
777
1074
|
}
|
|
778
1075
|
|
|
779
1076
|
return {
|