ragent-cli 1.4.2 → 1.4.4
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/index.js +292 -29
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "ragent-cli",
|
|
34
|
-
version: "1.4.
|
|
34
|
+
version: "1.4.4",
|
|
35
35
|
description: "CLI agent for rAgent Live \u2014 browser-first terminal control plane for AI coding agents",
|
|
36
36
|
main: "dist/index.js",
|
|
37
37
|
bin: {
|
|
@@ -581,6 +581,9 @@ async function collectSessionInventory(hostId, command) {
|
|
|
581
581
|
};
|
|
582
582
|
return [...tmux, ...screen, ...zellij, ...bare, ptySession];
|
|
583
583
|
}
|
|
584
|
+
function isElectronProcess(cmd) {
|
|
585
|
+
return cmd.includes("--type=") || cmd.includes("--user-data-dir=") || cmd.includes("--crashpad-handler-pid=");
|
|
586
|
+
}
|
|
584
587
|
function detectAgentType(command) {
|
|
585
588
|
if (!command) return void 0;
|
|
586
589
|
const cmd = command.toLowerCase();
|
|
@@ -593,8 +596,14 @@ function detectAgentType(command) {
|
|
|
593
596
|
}
|
|
594
597
|
if (binary === "codex" || scriptArg === "codex" || cmd.includes("codex-cli")) return "Codex CLI";
|
|
595
598
|
if (binary === "aider") return "aider";
|
|
596
|
-
if (binary === "cursor")
|
|
597
|
-
|
|
599
|
+
if (binary === "cursor") {
|
|
600
|
+
if (isElectronProcess(cmd)) return void 0;
|
|
601
|
+
return "Cursor";
|
|
602
|
+
}
|
|
603
|
+
if (binary === "windsurf") {
|
|
604
|
+
if (isElectronProcess(cmd)) return void 0;
|
|
605
|
+
return "Windsurf";
|
|
606
|
+
}
|
|
598
607
|
if (binary === "gemini") return "Gemini CLI";
|
|
599
608
|
if (binary === "amazon-q" || binary === "amazon_q") return "Amazon Q";
|
|
600
609
|
if (binary === "copilot") return "Copilot CLI";
|
|
@@ -830,24 +839,71 @@ var OutputBuffer = class {
|
|
|
830
839
|
// src/websocket.ts
|
|
831
840
|
var import_ws = __toESM(require("ws"));
|
|
832
841
|
var BACKPRESSURE_HIGH_WATER = 256 * 1024;
|
|
842
|
+
var BACKPRESSURE_LOW_WATER = 64 * 1024;
|
|
843
|
+
var MAX_PENDING_QUEUE = 500;
|
|
844
|
+
var DRAIN_INTERVAL_MS = 50;
|
|
833
845
|
function sanitizeForJson(str) {
|
|
834
846
|
return str.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
835
847
|
}
|
|
848
|
+
var pendingQueue = [];
|
|
849
|
+
var drainTimer = null;
|
|
850
|
+
var drainWs = null;
|
|
851
|
+
var droppedFrames = 0;
|
|
852
|
+
function drainQueue() {
|
|
853
|
+
if (!drainWs || drainWs.readyState !== import_ws.default.OPEN) {
|
|
854
|
+
pendingQueue.length = 0;
|
|
855
|
+
stopDrainTimer();
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
while (pendingQueue.length > 0 && drainWs.bufferedAmount < BACKPRESSURE_LOW_WATER) {
|
|
859
|
+
const frame = pendingQueue.shift();
|
|
860
|
+
drainWs.send(frame);
|
|
861
|
+
}
|
|
862
|
+
if (pendingQueue.length === 0) {
|
|
863
|
+
stopDrainTimer();
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
function startDrainTimer(ws) {
|
|
867
|
+
drainWs = ws;
|
|
868
|
+
if (!drainTimer) {
|
|
869
|
+
drainTimer = setInterval(drainQueue, DRAIN_INTERVAL_MS);
|
|
870
|
+
}
|
|
871
|
+
}
|
|
872
|
+
function stopDrainTimer() {
|
|
873
|
+
if (drainTimer) {
|
|
874
|
+
clearInterval(drainTimer);
|
|
875
|
+
drainTimer = null;
|
|
876
|
+
}
|
|
877
|
+
drainWs = null;
|
|
878
|
+
}
|
|
836
879
|
function sendToGroup(ws, group, data) {
|
|
837
880
|
if (!group || ws.readyState !== import_ws.default.OPEN) return;
|
|
881
|
+
const sanitized = sanitizePayload(data);
|
|
882
|
+
const frame = JSON.stringify({
|
|
883
|
+
type: "sendToGroup",
|
|
884
|
+
group,
|
|
885
|
+
dataType: "json",
|
|
886
|
+
data: sanitized,
|
|
887
|
+
noEcho: true
|
|
888
|
+
});
|
|
838
889
|
if (ws.bufferedAmount > BACKPRESSURE_HIGH_WATER) {
|
|
890
|
+
if (pendingQueue.length >= MAX_PENDING_QUEUE) {
|
|
891
|
+
pendingQueue.shift();
|
|
892
|
+
droppedFrames++;
|
|
893
|
+
if (droppedFrames % 100 === 1) {
|
|
894
|
+
console.warn(`[rAgent] Backpressure: dropped ${droppedFrames} frames (queue full at ${MAX_PENDING_QUEUE})`);
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
pendingQueue.push(frame);
|
|
898
|
+
startDrainTimer(ws);
|
|
839
899
|
return;
|
|
840
900
|
}
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
data: sanitized,
|
|
848
|
-
noEcho: true
|
|
849
|
-
})
|
|
850
|
-
);
|
|
901
|
+
if (pendingQueue.length > 0) {
|
|
902
|
+
pendingQueue.push(frame);
|
|
903
|
+
drainQueue();
|
|
904
|
+
return;
|
|
905
|
+
}
|
|
906
|
+
ws.send(frame);
|
|
851
907
|
}
|
|
852
908
|
function sanitizePayload(obj) {
|
|
853
909
|
const result = {};
|
|
@@ -868,6 +924,7 @@ var import_node_child_process2 = require("child_process");
|
|
|
868
924
|
var import_node_fs = require("fs");
|
|
869
925
|
var import_node_path = require("path");
|
|
870
926
|
var import_node_os = require("os");
|
|
927
|
+
var import_node_string_decoder = require("string_decoder");
|
|
871
928
|
var pty = __toESM(require("node-pty"));
|
|
872
929
|
var STOP_DEBOUNCE_MS = 2e3;
|
|
873
930
|
function parsePaneTarget(sessionId) {
|
|
@@ -889,6 +946,37 @@ function parseZellijSession(sessionId) {
|
|
|
889
946
|
if (!rest) return null;
|
|
890
947
|
return rest.split(":")[0] || null;
|
|
891
948
|
}
|
|
949
|
+
function parseProcessPid(sessionId) {
|
|
950
|
+
if (!sessionId.startsWith("process:")) return null;
|
|
951
|
+
const rest = sessionId.slice("process:".length);
|
|
952
|
+
if (!rest) return null;
|
|
953
|
+
const pidStr = rest.split(":")[0];
|
|
954
|
+
const pid = parseInt(pidStr, 10);
|
|
955
|
+
return isNaN(pid) ? null : pid;
|
|
956
|
+
}
|
|
957
|
+
function unescapeStrace(s) {
|
|
958
|
+
return s.replace(/\\x([0-9a-fA-F]{2})|\\n|\\r|\\t|\\\\|\\"|\\0/g, (match) => {
|
|
959
|
+
if (match.startsWith("\\x")) {
|
|
960
|
+
return String.fromCharCode(parseInt(match.slice(2), 16));
|
|
961
|
+
}
|
|
962
|
+
switch (match) {
|
|
963
|
+
case "\\n":
|
|
964
|
+
return "\n";
|
|
965
|
+
case "\\r":
|
|
966
|
+
return "\r";
|
|
967
|
+
case "\\t":
|
|
968
|
+
return " ";
|
|
969
|
+
case "\\\\":
|
|
970
|
+
return "\\";
|
|
971
|
+
case '\\"':
|
|
972
|
+
return '"';
|
|
973
|
+
case "\\0":
|
|
974
|
+
return "\0";
|
|
975
|
+
default:
|
|
976
|
+
return match;
|
|
977
|
+
}
|
|
978
|
+
});
|
|
979
|
+
}
|
|
892
980
|
var SessionStreamer = class {
|
|
893
981
|
active = /* @__PURE__ */ new Map();
|
|
894
982
|
pendingStops = /* @__PURE__ */ new Map();
|
|
@@ -946,6 +1034,9 @@ var SessionStreamer = class {
|
|
|
946
1034
|
if (sessionId.startsWith("zellij:")) {
|
|
947
1035
|
return this.startZellijStream(sessionId);
|
|
948
1036
|
}
|
|
1037
|
+
if (sessionId.startsWith("process:")) {
|
|
1038
|
+
return this.startProcessStream(sessionId);
|
|
1039
|
+
}
|
|
949
1040
|
return false;
|
|
950
1041
|
}
|
|
951
1042
|
/**
|
|
@@ -1020,6 +1111,64 @@ var SessionStreamer = class {
|
|
|
1020
1111
|
get activeCount() {
|
|
1021
1112
|
return this.active.size;
|
|
1022
1113
|
}
|
|
1114
|
+
/**
|
|
1115
|
+
* Re-send capture-pane data for an already-active tmux stream.
|
|
1116
|
+
* Used when a viewer missed the initial burst (e.g. joinGroup race)
|
|
1117
|
+
* or reconnected ("Retry stream"). Does NOT restart pipe-pane.
|
|
1118
|
+
*/
|
|
1119
|
+
resyncStream(sessionId) {
|
|
1120
|
+
const stream = this.active.get(sessionId);
|
|
1121
|
+
if (!stream || stream.stopped || stream.streamType !== "tmux-pipe") return false;
|
|
1122
|
+
const { paneTarget, cleanEnv } = stream;
|
|
1123
|
+
if (!paneTarget || !cleanEnv) return false;
|
|
1124
|
+
try {
|
|
1125
|
+
try {
|
|
1126
|
+
const scrollback = (0, import_node_child_process2.execFileSync)(
|
|
1127
|
+
"tmux",
|
|
1128
|
+
["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
|
|
1129
|
+
{ env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
|
|
1130
|
+
);
|
|
1131
|
+
if (scrollback && scrollback.length > 0) {
|
|
1132
|
+
this.sendFn(sessionId, scrollback);
|
|
1133
|
+
}
|
|
1134
|
+
} catch {
|
|
1135
|
+
}
|
|
1136
|
+
this.sendFn(sessionId, "\x1B[0m\x1B[2J\x1B[H");
|
|
1137
|
+
try {
|
|
1138
|
+
const initial = (0, import_node_child_process2.execFileSync)(
|
|
1139
|
+
"tmux",
|
|
1140
|
+
["capture-pane", "-t", paneTarget, "-p", "-e"],
|
|
1141
|
+
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
1142
|
+
);
|
|
1143
|
+
if (initial) {
|
|
1144
|
+
this.sendFn(sessionId, initial);
|
|
1145
|
+
}
|
|
1146
|
+
} catch {
|
|
1147
|
+
}
|
|
1148
|
+
try {
|
|
1149
|
+
const cursorInfo = (0, import_node_child_process2.execFileSync)(
|
|
1150
|
+
"tmux",
|
|
1151
|
+
["display-message", "-t", paneTarget, "-p", "#{cursor_x} #{cursor_y}"],
|
|
1152
|
+
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
1153
|
+
).trim();
|
|
1154
|
+
const parts = cursorInfo.split(" ");
|
|
1155
|
+
if (parts.length === 2) {
|
|
1156
|
+
const x = parseInt(parts[0], 10);
|
|
1157
|
+
const y = parseInt(parts[1], 10);
|
|
1158
|
+
if (!isNaN(x) && !isNaN(y)) {
|
|
1159
|
+
this.sendFn(sessionId, `\x1B[${y + 1};${x + 1}H`);
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
} catch {
|
|
1163
|
+
}
|
|
1164
|
+
console.log(`[rAgent] Resync capture for: ${sessionId}`);
|
|
1165
|
+
return true;
|
|
1166
|
+
} catch (error) {
|
|
1167
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1168
|
+
console.warn(`[rAgent] Failed to resync stream for ${sessionId}: ${message}`);
|
|
1169
|
+
return false;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1023
1172
|
// ---------------------------------------------------------------------------
|
|
1024
1173
|
// tmux streaming (pipe-pane with cursor sync)
|
|
1025
1174
|
// ---------------------------------------------------------------------------
|
|
@@ -1054,7 +1203,9 @@ var SessionStreamer = class {
|
|
|
1054
1203
|
initializing: true,
|
|
1055
1204
|
initBuffer: [],
|
|
1056
1205
|
cleanEnv,
|
|
1057
|
-
ptyProc: null
|
|
1206
|
+
ptyProc: null,
|
|
1207
|
+
straceProc: null,
|
|
1208
|
+
utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
|
|
1058
1209
|
};
|
|
1059
1210
|
this.active.set(sessionId, stream);
|
|
1060
1211
|
try {
|
|
@@ -1073,7 +1224,8 @@ var SessionStreamer = class {
|
|
|
1073
1224
|
stream.catProc = catProc;
|
|
1074
1225
|
catProc.stdout.on("data", (chunk) => {
|
|
1075
1226
|
if (stream.stopped) return;
|
|
1076
|
-
const data =
|
|
1227
|
+
const data = stream.utf8Decoder.write(chunk);
|
|
1228
|
+
if (!data) return;
|
|
1077
1229
|
if (stream.initializing) {
|
|
1078
1230
|
stream.initBuffer.push(data);
|
|
1079
1231
|
} else {
|
|
@@ -1082,6 +1234,8 @@ var SessionStreamer = class {
|
|
|
1082
1234
|
});
|
|
1083
1235
|
catProc.on("exit", () => {
|
|
1084
1236
|
if (!stream.stopped) {
|
|
1237
|
+
const remaining = stream.utf8Decoder.end();
|
|
1238
|
+
if (remaining) this.sendFn(sessionId, remaining);
|
|
1085
1239
|
this.cleanupStream(stream);
|
|
1086
1240
|
this.active.delete(sessionId);
|
|
1087
1241
|
this.pendingStops.delete(sessionId);
|
|
@@ -1162,7 +1316,9 @@ var SessionStreamer = class {
|
|
|
1162
1316
|
catProc: null,
|
|
1163
1317
|
initializing: false,
|
|
1164
1318
|
initBuffer: [],
|
|
1165
|
-
ptyProc: proc
|
|
1319
|
+
ptyProc: proc,
|
|
1320
|
+
straceProc: null,
|
|
1321
|
+
utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
|
|
1166
1322
|
};
|
|
1167
1323
|
this.active.set(sessionId, stream);
|
|
1168
1324
|
proc.onData((data) => {
|
|
@@ -1211,7 +1367,9 @@ var SessionStreamer = class {
|
|
|
1211
1367
|
catProc: null,
|
|
1212
1368
|
initializing: false,
|
|
1213
1369
|
initBuffer: [],
|
|
1214
|
-
ptyProc: proc
|
|
1370
|
+
ptyProc: proc,
|
|
1371
|
+
straceProc: null,
|
|
1372
|
+
utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
|
|
1215
1373
|
};
|
|
1216
1374
|
this.active.set(sessionId, stream);
|
|
1217
1375
|
proc.onData((data) => {
|
|
@@ -1237,6 +1395,89 @@ var SessionStreamer = class {
|
|
|
1237
1395
|
}
|
|
1238
1396
|
}
|
|
1239
1397
|
// ---------------------------------------------------------------------------
|
|
1398
|
+
// process streaming (strace -p PID write interception)
|
|
1399
|
+
// ---------------------------------------------------------------------------
|
|
1400
|
+
startProcessStream(sessionId) {
|
|
1401
|
+
const pid = parseProcessPid(sessionId);
|
|
1402
|
+
if (!pid) return false;
|
|
1403
|
+
if (!(0, import_node_fs.existsSync)(`/proc/${pid}`)) {
|
|
1404
|
+
console.warn(`[rAgent] Process ${pid} does not exist (no /proc/${pid}).`);
|
|
1405
|
+
return false;
|
|
1406
|
+
}
|
|
1407
|
+
try {
|
|
1408
|
+
const straceProc = (0, import_node_child_process2.spawn)(
|
|
1409
|
+
"strace",
|
|
1410
|
+
["-p", String(pid), "-e", "trace=write", "-e", "signal=none", "-s", "1000000", "-x"],
|
|
1411
|
+
{ stdio: ["ignore", "ignore", "pipe"] }
|
|
1412
|
+
);
|
|
1413
|
+
const stream = {
|
|
1414
|
+
sessionId,
|
|
1415
|
+
streamType: "process-trace",
|
|
1416
|
+
stopped: false,
|
|
1417
|
+
paneTarget: "",
|
|
1418
|
+
fifoPath: "",
|
|
1419
|
+
tmpDir: "",
|
|
1420
|
+
catProc: null,
|
|
1421
|
+
initializing: false,
|
|
1422
|
+
initBuffer: [],
|
|
1423
|
+
ptyProc: null,
|
|
1424
|
+
straceProc,
|
|
1425
|
+
utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
|
|
1426
|
+
};
|
|
1427
|
+
this.active.set(sessionId, stream);
|
|
1428
|
+
const writeRegex = /^write\(([12]),\s*"((?:[^"\\]|\\.)*)"/;
|
|
1429
|
+
let lineBuf = "";
|
|
1430
|
+
straceProc.stderr.on("data", (chunk) => {
|
|
1431
|
+
if (stream.stopped) return;
|
|
1432
|
+
lineBuf += chunk.toString("utf-8");
|
|
1433
|
+
const lines = lineBuf.split("\n");
|
|
1434
|
+
lineBuf = lines.pop() ?? "";
|
|
1435
|
+
for (const line of lines) {
|
|
1436
|
+
const match = writeRegex.exec(line.trim());
|
|
1437
|
+
if (!match) continue;
|
|
1438
|
+
const escaped = match[2];
|
|
1439
|
+
const unescaped = unescapeStrace(escaped);
|
|
1440
|
+
if (unescaped) {
|
|
1441
|
+
const decoded = stream.utf8Decoder.write(Buffer.from(unescaped, "binary"));
|
|
1442
|
+
if (decoded) {
|
|
1443
|
+
this.sendFn(sessionId, decoded);
|
|
1444
|
+
}
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
});
|
|
1448
|
+
straceProc.on("error", (err) => {
|
|
1449
|
+
if (stream.stopped) return;
|
|
1450
|
+
const message = err.message;
|
|
1451
|
+
if (message.includes("ENOENT")) {
|
|
1452
|
+
this.sendFn(sessionId, "\r\n[rAgent] strace is not installed. Install it to enable process streaming:\r\n sudo apt install strace (Debian/Ubuntu)\r\n sudo dnf install strace (Fedora/RHEL)\r\n");
|
|
1453
|
+
}
|
|
1454
|
+
stream.stopped = true;
|
|
1455
|
+
this.active.delete(sessionId);
|
|
1456
|
+
this.pendingStops.delete(sessionId);
|
|
1457
|
+
this.onStreamStopped?.(sessionId);
|
|
1458
|
+
});
|
|
1459
|
+
straceProc.on("exit", (code) => {
|
|
1460
|
+
if (stream.stopped) return;
|
|
1461
|
+
const remaining = stream.utf8Decoder.end();
|
|
1462
|
+
if (remaining) this.sendFn(sessionId, remaining);
|
|
1463
|
+
if (code === 1) {
|
|
1464
|
+
this.sendFn(sessionId, "\r\n[rAgent] Permission denied: cannot attach to process.\r\nTo enable process tracing, run:\r\n sudo sysctl kernel.yama.ptrace_scope=0\r\n");
|
|
1465
|
+
}
|
|
1466
|
+
stream.stopped = true;
|
|
1467
|
+
this.active.delete(sessionId);
|
|
1468
|
+
this.pendingStops.delete(sessionId);
|
|
1469
|
+
console.log(`[rAgent] Session stream ended: ${sessionId}`);
|
|
1470
|
+
this.onStreamStopped?.(sessionId);
|
|
1471
|
+
});
|
|
1472
|
+
console.log(`[rAgent] Started streaming: ${sessionId} (strace PID: ${pid})`);
|
|
1473
|
+
return true;
|
|
1474
|
+
} catch (error) {
|
|
1475
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1476
|
+
console.warn(`[rAgent] Failed to start process stream for ${sessionId}: ${message}`);
|
|
1477
|
+
return false;
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
// ---------------------------------------------------------------------------
|
|
1240
1481
|
// Cleanup
|
|
1241
1482
|
// ---------------------------------------------------------------------------
|
|
1242
1483
|
cleanupStream(stream) {
|
|
@@ -1269,6 +1510,14 @@ var SessionStreamer = class {
|
|
|
1269
1510
|
}
|
|
1270
1511
|
stream.ptyProc = null;
|
|
1271
1512
|
}
|
|
1513
|
+
} else if (stream.streamType === "process-trace") {
|
|
1514
|
+
if (stream.straceProc && !stream.straceProc.killed) {
|
|
1515
|
+
try {
|
|
1516
|
+
stream.straceProc.kill("SIGTERM");
|
|
1517
|
+
} catch {
|
|
1518
|
+
}
|
|
1519
|
+
}
|
|
1520
|
+
stream.straceProc = null;
|
|
1272
1521
|
}
|
|
1273
1522
|
}
|
|
1274
1523
|
};
|
|
@@ -2198,7 +2447,8 @@ var ControlDispatcher = class {
|
|
|
2198
2447
|
"restart-shell",
|
|
2199
2448
|
"stop-session",
|
|
2200
2449
|
"stop-detached",
|
|
2201
|
-
"disconnect"
|
|
2450
|
+
"disconnect",
|
|
2451
|
+
"kill-process"
|
|
2202
2452
|
]);
|
|
2203
2453
|
if (dangerousActions.has(action) && this.connection.sessionKey) {
|
|
2204
2454
|
if (!this.verifyMessageHmac(payload)) {
|
|
@@ -2245,6 +2495,25 @@ var ControlDispatcher = class {
|
|
|
2245
2495
|
await this.syncInventory();
|
|
2246
2496
|
}
|
|
2247
2497
|
return;
|
|
2498
|
+
case "kill-process":
|
|
2499
|
+
if (!sessionId) return;
|
|
2500
|
+
{
|
|
2501
|
+
const pid = parseProcessPid(sessionId);
|
|
2502
|
+
if (pid === null) {
|
|
2503
|
+
console.warn(`[rAgent] kill-process: could not parse PID from ${sessionId}`);
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
this.streamer.stopStream(sessionId);
|
|
2507
|
+
try {
|
|
2508
|
+
process.kill(pid, "SIGTERM");
|
|
2509
|
+
console.log(`[rAgent] Sent SIGTERM to process ${pid} (${sessionId}).`);
|
|
2510
|
+
} catch (error) {
|
|
2511
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2512
|
+
console.warn(`[rAgent] Failed to kill process ${pid}: ${message}`);
|
|
2513
|
+
}
|
|
2514
|
+
await this.syncInventory();
|
|
2515
|
+
}
|
|
2516
|
+
return;
|
|
2248
2517
|
case "stop-detached": {
|
|
2249
2518
|
const killed = await stopAllDetachedTmuxSessions();
|
|
2250
2519
|
console.log(`[rAgent] Killed ${killed} detached tmux session(s).`);
|
|
@@ -2375,20 +2644,14 @@ var ControlDispatcher = class {
|
|
|
2375
2644
|
if (!sessionId) return;
|
|
2376
2645
|
const ws = this.connection.activeSocket;
|
|
2377
2646
|
const group = this.connection.activeGroups.privateGroup;
|
|
2378
|
-
if (sessionId.startsWith("process:")) {
|
|
2379
|
-
|
|
2380
|
-
sendToGroup(ws, group, {
|
|
2381
|
-
type: "stream-error",
|
|
2382
|
-
sessionId,
|
|
2383
|
-
error: "This agent is running outside a terminal multiplexer. Stop and relaunch via Start Agent to enable live streaming."
|
|
2384
|
-
});
|
|
2385
|
-
}
|
|
2386
|
-
return;
|
|
2387
|
-
}
|
|
2388
|
-
if (sessionId.startsWith("tmux:") || sessionId.startsWith("screen:") || sessionId.startsWith("zellij:")) {
|
|
2647
|
+
if (sessionId.startsWith("tmux:") || sessionId.startsWith("screen:") || sessionId.startsWith("zellij:") || sessionId.startsWith("process:")) {
|
|
2648
|
+
const alreadyStreaming = this.streamer.isStreaming(sessionId);
|
|
2389
2649
|
const started = this.streamer.startStream(sessionId);
|
|
2390
2650
|
if (ws && ws.readyState === import_ws4.default.OPEN && group) {
|
|
2391
2651
|
if (started) {
|
|
2652
|
+
if (alreadyStreaming) {
|
|
2653
|
+
this.streamer.resyncStream(sessionId);
|
|
2654
|
+
}
|
|
2392
2655
|
sendToGroup(ws, group, { type: "stream-started", sessionId });
|
|
2393
2656
|
} else {
|
|
2394
2657
|
sendToGroup(ws, group, {
|