ragent-cli 1.5.2 → 1.6.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/index.js +225 -32
- 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.
|
|
34
|
+
version: "1.6.0",
|
|
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: {
|
|
@@ -257,15 +257,18 @@ async function maybeWarnUpdate() {
|
|
|
257
257
|
}
|
|
258
258
|
|
|
259
259
|
// src/commands/connect.ts
|
|
260
|
-
var
|
|
260
|
+
var os9 = __toESM(require("os"));
|
|
261
261
|
|
|
262
262
|
// src/agent.ts
|
|
263
263
|
var fs4 = __toESM(require("fs"));
|
|
264
|
-
var
|
|
264
|
+
var os8 = __toESM(require("os"));
|
|
265
265
|
var path3 = __toESM(require("path"));
|
|
266
266
|
var import_ws5 = __toESM(require("ws"));
|
|
267
267
|
|
|
268
268
|
// src/auth.ts
|
|
269
|
+
var os4 = __toESM(require("os"));
|
|
270
|
+
|
|
271
|
+
// src/sessions.ts
|
|
269
272
|
var os3 = __toESM(require("os"));
|
|
270
273
|
|
|
271
274
|
// src/system.ts
|
|
@@ -357,6 +360,7 @@ async function installTmuxInteractively() {
|
|
|
357
360
|
}
|
|
358
361
|
|
|
359
362
|
// src/sessions.ts
|
|
363
|
+
var isMac = os3.platform() === "darwin";
|
|
360
364
|
async function collectTmuxSessions() {
|
|
361
365
|
try {
|
|
362
366
|
await execAsync("tmux -V");
|
|
@@ -467,7 +471,7 @@ async function collectZellijSessions() {
|
|
|
467
471
|
let serverPids;
|
|
468
472
|
try {
|
|
469
473
|
const psOut = await execAsync(
|
|
470
|
-
`ps axo pid,args
|
|
474
|
+
`ps axo pid,args | grep 'zellij.*--session ${sessionName}' | grep -v grep`
|
|
471
475
|
);
|
|
472
476
|
serverPids = psOut.split("\n").map((l) => Number(l.trim().split(/\s+/)[0])).filter((p) => p > 0);
|
|
473
477
|
} catch {
|
|
@@ -502,6 +506,17 @@ async function collectZellijSessions() {
|
|
|
502
506
|
}
|
|
503
507
|
async function getProcessWorkingDir(pid) {
|
|
504
508
|
try {
|
|
509
|
+
if (isMac) {
|
|
510
|
+
const raw = await execAsync(
|
|
511
|
+
`lsof -a -p ${pid} -d cwd -Fn 2>/dev/null`
|
|
512
|
+
);
|
|
513
|
+
for (const line of raw.split("\n")) {
|
|
514
|
+
if (line.startsWith("n/")) {
|
|
515
|
+
return line.slice(1).trim() || null;
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
return null;
|
|
519
|
+
}
|
|
505
520
|
const cwd = await execAsync(`readlink -f /proc/${pid}/cwd`);
|
|
506
521
|
return cwd.trim() || null;
|
|
507
522
|
} catch {
|
|
@@ -510,12 +525,14 @@ async function getProcessWorkingDir(pid) {
|
|
|
510
525
|
}
|
|
511
526
|
async function collectBareAgentProcesses(excludePids) {
|
|
512
527
|
try {
|
|
513
|
-
const raw = await execAsync("ps axo pid,ppid,stat,comm,args
|
|
528
|
+
const raw = await execAsync("ps axo pid,ppid,stat,comm,args");
|
|
514
529
|
const sessions = [];
|
|
515
530
|
const seen = /* @__PURE__ */ new Set();
|
|
516
|
-
|
|
517
|
-
|
|
531
|
+
const lines = raw.split("\n");
|
|
532
|
+
for (let i = 0; i < lines.length; i++) {
|
|
533
|
+
const trimmed = lines[i].trim();
|
|
518
534
|
if (!trimmed) continue;
|
|
535
|
+
if (i === 0 && /^\s*PID\b/i.test(lines[i])) continue;
|
|
519
536
|
const parts = trimmed.split(/\s+/);
|
|
520
537
|
if (parts.length < 5) continue;
|
|
521
538
|
const pid = Number(parts[0]);
|
|
@@ -618,11 +635,16 @@ function sessionInventoryFingerprint(sessions) {
|
|
|
618
635
|
}
|
|
619
636
|
async function getChildAgentInfo(parentPid) {
|
|
620
637
|
try {
|
|
621
|
-
const
|
|
638
|
+
const pgrepOut = await execAsync(`pgrep -P ${parentPid}`);
|
|
639
|
+
const pidList = pgrepOut.split("\n").map((l) => l.trim()).filter(Boolean).map(Number).filter((p) => p > 0);
|
|
640
|
+
if (pidList.length === 0) return null;
|
|
641
|
+
const raw = await execAsync(`ps -p ${pidList.join(",")} -o pid,args`);
|
|
622
642
|
const childPids = [];
|
|
623
|
-
|
|
624
|
-
|
|
643
|
+
const lines = raw.split("\n");
|
|
644
|
+
for (let i = 0; i < lines.length; i++) {
|
|
645
|
+
const trimmed = lines[i].trim();
|
|
625
646
|
if (!trimmed) continue;
|
|
647
|
+
if (i === 0 && /^\s*PID\b/i.test(lines[i])) continue;
|
|
626
648
|
const spaceIdx = trimmed.indexOf(" ");
|
|
627
649
|
if (spaceIdx < 0) continue;
|
|
628
650
|
const pid = Number(trimmed.slice(0, spaceIdx));
|
|
@@ -719,7 +741,7 @@ async function claimHost(params) {
|
|
|
719
741
|
connectionToken: params.connectionToken,
|
|
720
742
|
hostId: params.hostId,
|
|
721
743
|
hostName: params.hostName,
|
|
722
|
-
environment:
|
|
744
|
+
environment: os4.platform(),
|
|
723
745
|
sessions,
|
|
724
746
|
agentVersion: CURRENT_VERSION
|
|
725
747
|
})
|
|
@@ -781,7 +803,7 @@ async function postHeartbeat(params) {
|
|
|
781
803
|
Authorization: `Bearer ${params.agentToken}`
|
|
782
804
|
},
|
|
783
805
|
body: JSON.stringify({
|
|
784
|
-
environment:
|
|
806
|
+
environment: os4.platform(),
|
|
785
807
|
hostName: params.hostName,
|
|
786
808
|
sessions,
|
|
787
809
|
agentVersion: CURRENT_VERSION
|
|
@@ -845,6 +867,33 @@ var DRAIN_INTERVAL_MS = 50;
|
|
|
845
867
|
function sanitizeForJson(str) {
|
|
846
868
|
return str.replace(/[\uD800-\uDBFF](?![\uDC00-\uDFFF])|(?<![\uD800-\uDBFF])[\uDC00-\uDFFF]/g, "\uFFFD");
|
|
847
869
|
}
|
|
870
|
+
var DANGEROUS_OSC_RE = /\x1b\](?:52|7|8);[^\x07\x1b]*(?:\x07|\x1b\\)/g;
|
|
871
|
+
function stripDangerousEscapes(str) {
|
|
872
|
+
return str.replace(DANGEROUS_OSC_RE, "");
|
|
873
|
+
}
|
|
874
|
+
var SECRET_PATTERNS = [
|
|
875
|
+
/AKIA[0-9A-Z]{16}/g,
|
|
876
|
+
// AWS access key
|
|
877
|
+
/gh[ps]_[A-Za-z0-9_]{36,}/g,
|
|
878
|
+
// GitHub token
|
|
879
|
+
/sk_(?:live|test)_[A-Za-z0-9]{24,}/g,
|
|
880
|
+
// Stripe secret key
|
|
881
|
+
/eyJ[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}\.[A-Za-z0-9_-]{20,}/g
|
|
882
|
+
// JWT
|
|
883
|
+
];
|
|
884
|
+
var redactionEnabled = false;
|
|
885
|
+
function setRedactionEnabled(enabled) {
|
|
886
|
+
redactionEnabled = enabled;
|
|
887
|
+
}
|
|
888
|
+
function redactSecrets(str) {
|
|
889
|
+
if (!redactionEnabled) return str;
|
|
890
|
+
let result = str;
|
|
891
|
+
for (const pattern of SECRET_PATTERNS) {
|
|
892
|
+
pattern.lastIndex = 0;
|
|
893
|
+
result = result.replace(pattern, "[REDACTED]");
|
|
894
|
+
}
|
|
895
|
+
return result;
|
|
896
|
+
}
|
|
848
897
|
var pendingQueue = [];
|
|
849
898
|
var drainTimer = null;
|
|
850
899
|
var drainWs = null;
|
|
@@ -909,7 +958,7 @@ function sanitizePayload(obj) {
|
|
|
909
958
|
const result = {};
|
|
910
959
|
for (const [key, value] of Object.entries(obj)) {
|
|
911
960
|
if (typeof value === "string") {
|
|
912
|
-
result[key] = sanitizeForJson(value);
|
|
961
|
+
result[key] = redactSecrets(stripDangerousEscapes(sanitizeForJson(value)));
|
|
913
962
|
} else if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
914
963
|
result[key] = sanitizePayload(value);
|
|
915
964
|
} else {
|
|
@@ -927,6 +976,7 @@ var import_node_os = require("os");
|
|
|
927
976
|
var import_node_string_decoder = require("string_decoder");
|
|
928
977
|
var pty = __toESM(require("node-pty"));
|
|
929
978
|
var STOP_DEBOUNCE_MS = 2e3;
|
|
979
|
+
var MAX_SESSION_BUFFER_BYTES = 64 * 1024;
|
|
930
980
|
function parsePaneTarget(sessionId) {
|
|
931
981
|
if (!sessionId.startsWith("tmux:")) return null;
|
|
932
982
|
const rest = sessionId.slice("tmux:".length);
|
|
@@ -982,6 +1032,13 @@ var SessionStreamer = class {
|
|
|
982
1032
|
pendingStops = /* @__PURE__ */ new Map();
|
|
983
1033
|
sendFn;
|
|
984
1034
|
onStreamStopped;
|
|
1035
|
+
/**
|
|
1036
|
+
* Per-session ring buffer for output captured while the WebSocket is disconnected.
|
|
1037
|
+
* Each entry is a string chunk; oldest entries are evicted when the total byte
|
|
1038
|
+
* size exceeds MAX_SESSION_BUFFER_BYTES.
|
|
1039
|
+
*/
|
|
1040
|
+
disconnectBuffers = /* @__PURE__ */ new Map();
|
|
1041
|
+
disconnectBufferBytes = /* @__PURE__ */ new Map();
|
|
985
1042
|
constructor(sendFn, onStreamStopped) {
|
|
986
1043
|
this.sendFn = sendFn;
|
|
987
1044
|
this.onStreamStopped = onStreamStopped;
|
|
@@ -1081,6 +1138,8 @@ var SessionStreamer = class {
|
|
|
1081
1138
|
if (!s) return;
|
|
1082
1139
|
this.cleanupStream(s);
|
|
1083
1140
|
this.active.delete(sessionId);
|
|
1141
|
+
this.disconnectBuffers.delete(sessionId);
|
|
1142
|
+
this.disconnectBufferBytes.delete(sessionId);
|
|
1084
1143
|
console.log(`[rAgent] Stopped streaming: ${sessionId}`);
|
|
1085
1144
|
}, STOP_DEBOUNCE_MS);
|
|
1086
1145
|
this.pendingStops.set(sessionId, timer);
|
|
@@ -1104,6 +1163,8 @@ var SessionStreamer = class {
|
|
|
1104
1163
|
console.log(`[rAgent] Stopped streaming: ${id}`);
|
|
1105
1164
|
}
|
|
1106
1165
|
this.active.clear();
|
|
1166
|
+
this.disconnectBuffers.clear();
|
|
1167
|
+
this.disconnectBufferBytes.clear();
|
|
1107
1168
|
}
|
|
1108
1169
|
/**
|
|
1109
1170
|
* Get the number of active streams.
|
|
@@ -1111,6 +1172,54 @@ var SessionStreamer = class {
|
|
|
1111
1172
|
get activeCount() {
|
|
1112
1173
|
return this.active.size;
|
|
1113
1174
|
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Return an iterator over active stream entries (sessionId -> ActiveStream).
|
|
1177
|
+
* Used by the agent to replay disconnect-buffered output on reconnect.
|
|
1178
|
+
*/
|
|
1179
|
+
activeStreams() {
|
|
1180
|
+
return this.active.entries();
|
|
1181
|
+
}
|
|
1182
|
+
// ---------------------------------------------------------------------------
|
|
1183
|
+
// Disconnect buffering — captures output when WebSocket is down
|
|
1184
|
+
// ---------------------------------------------------------------------------
|
|
1185
|
+
/**
|
|
1186
|
+
* Append a chunk of output to the per-session disconnect buffer.
|
|
1187
|
+
* Oldest entries are evicted when the buffer exceeds MAX_SESSION_BUFFER_BYTES.
|
|
1188
|
+
*/
|
|
1189
|
+
bufferOutput(sessionId, data) {
|
|
1190
|
+
let chunks = this.disconnectBuffers.get(sessionId);
|
|
1191
|
+
if (!chunks) {
|
|
1192
|
+
chunks = [];
|
|
1193
|
+
this.disconnectBuffers.set(sessionId, chunks);
|
|
1194
|
+
this.disconnectBufferBytes.set(sessionId, 0);
|
|
1195
|
+
}
|
|
1196
|
+
const dataBytes = Buffer.byteLength(data, "utf8");
|
|
1197
|
+
let currentBytes = this.disconnectBufferBytes.get(sessionId) ?? 0;
|
|
1198
|
+
while (chunks.length > 0 && currentBytes + dataBytes > MAX_SESSION_BUFFER_BYTES) {
|
|
1199
|
+
const evicted = chunks.shift();
|
|
1200
|
+
currentBytes -= Buffer.byteLength(evicted, "utf8");
|
|
1201
|
+
}
|
|
1202
|
+
if (dataBytes > MAX_SESSION_BUFFER_BYTES) {
|
|
1203
|
+
const tail = data.slice(data.length - MAX_SESSION_BUFFER_BYTES);
|
|
1204
|
+
chunks.push(tail);
|
|
1205
|
+
this.disconnectBufferBytes.set(sessionId, Buffer.byteLength(tail, "utf8"));
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
chunks.push(data);
|
|
1209
|
+
this.disconnectBufferBytes.set(sessionId, currentBytes + dataBytes);
|
|
1210
|
+
}
|
|
1211
|
+
/**
|
|
1212
|
+
* Drain the disconnect buffer for a session, returning all buffered chunks
|
|
1213
|
+
* and clearing the buffer. Returns an empty array if nothing was buffered.
|
|
1214
|
+
*/
|
|
1215
|
+
drainBuffer(sessionId) {
|
|
1216
|
+
const chunks = this.disconnectBuffers.get(sessionId);
|
|
1217
|
+
if (!chunks || chunks.length === 0) return [];
|
|
1218
|
+
const result = [...chunks];
|
|
1219
|
+
this.disconnectBuffers.delete(sessionId);
|
|
1220
|
+
this.disconnectBufferBytes.delete(sessionId);
|
|
1221
|
+
return result;
|
|
1222
|
+
}
|
|
1114
1223
|
/**
|
|
1115
1224
|
* Re-send capture-pane data for an already-active tmux stream.
|
|
1116
1225
|
* Used when a viewer missed the initial burst (e.g. joinGroup race)
|
|
@@ -1707,7 +1816,7 @@ var ShellManager = class {
|
|
|
1707
1816
|
};
|
|
1708
1817
|
|
|
1709
1818
|
// src/inventory-manager.ts
|
|
1710
|
-
var
|
|
1819
|
+
var os5 = __toESM(require("os"));
|
|
1711
1820
|
var import_child_process = require("child_process");
|
|
1712
1821
|
var import_ws2 = __toESM(require("ws"));
|
|
1713
1822
|
var InventoryManager = class {
|
|
@@ -1731,7 +1840,7 @@ var InventoryManager = class {
|
|
|
1731
1840
|
type,
|
|
1732
1841
|
hostId: this.options.hostId,
|
|
1733
1842
|
hostName: this.options.hostName,
|
|
1734
|
-
environment:
|
|
1843
|
+
environment: os5.hostname(),
|
|
1735
1844
|
sessions,
|
|
1736
1845
|
vitals,
|
|
1737
1846
|
agentVersion: CURRENT_VERSION,
|
|
@@ -1774,8 +1883,8 @@ var InventoryManager = class {
|
|
|
1774
1883
|
cpuUsage = totalDelta > 0 ? Math.round((totalDelta - idleDelta) / totalDelta * 100) : 0;
|
|
1775
1884
|
}
|
|
1776
1885
|
this.prevCpuSnapshot = currentSnapshot;
|
|
1777
|
-
const totalMem =
|
|
1778
|
-
const freeMem =
|
|
1886
|
+
const totalMem = os5.totalmem();
|
|
1887
|
+
const freeMem = os5.freemem();
|
|
1779
1888
|
const memUsedPct = totalMem > 0 ? Math.round((totalMem - freeMem) / totalMem * 100) : 0;
|
|
1780
1889
|
let diskUsedPct = 0;
|
|
1781
1890
|
try {
|
|
@@ -1789,7 +1898,7 @@ var InventoryManager = class {
|
|
|
1789
1898
|
return { cpu: cpuUsage, memUsedPct, diskUsedPct };
|
|
1790
1899
|
}
|
|
1791
1900
|
takeCpuSnapshot() {
|
|
1792
|
-
const cpus2 =
|
|
1901
|
+
const cpus2 = os5.cpus();
|
|
1793
1902
|
let idle = 0;
|
|
1794
1903
|
let total = 0;
|
|
1795
1904
|
for (const cpu of cpus2) {
|
|
@@ -1920,7 +2029,7 @@ var import_ws4 = __toESM(require("ws"));
|
|
|
1920
2029
|
// src/service.ts
|
|
1921
2030
|
var import_child_process2 = require("child_process");
|
|
1922
2031
|
var fs2 = __toESM(require("fs"));
|
|
1923
|
-
var
|
|
2032
|
+
var os6 = __toESM(require("os"));
|
|
1924
2033
|
function assertConfiguredAgentToken() {
|
|
1925
2034
|
const config = loadConfig();
|
|
1926
2035
|
if (!config.agentToken) {
|
|
@@ -1937,7 +2046,7 @@ function getConfiguredServiceBackend() {
|
|
|
1937
2046
|
return null;
|
|
1938
2047
|
}
|
|
1939
2048
|
async function canUseSystemdUser() {
|
|
1940
|
-
if (
|
|
2049
|
+
if (os6.platform() !== "linux") return false;
|
|
1941
2050
|
try {
|
|
1942
2051
|
await execAsync("systemctl --user --version", { timeout: 4e3 });
|
|
1943
2052
|
await execAsync("systemctl --user show-environment", { timeout: 4e3 });
|
|
@@ -1960,7 +2069,9 @@ Wants=network-online.target
|
|
|
1960
2069
|
Type=simple
|
|
1961
2070
|
ExecStart=${process.execPath} ${__filename} run
|
|
1962
2071
|
Restart=always
|
|
1963
|
-
RestartSec=
|
|
2072
|
+
RestartSec=5
|
|
2073
|
+
StartLimitBurst=5
|
|
2074
|
+
StartLimitIntervalSec=300
|
|
1964
2075
|
Environment=NODE_ENV=production
|
|
1965
2076
|
NoNewPrivileges=true
|
|
1966
2077
|
PrivateTmp=true
|
|
@@ -2004,6 +2115,21 @@ function isProcessRunning(pid) {
|
|
|
2004
2115
|
return false;
|
|
2005
2116
|
}
|
|
2006
2117
|
}
|
|
2118
|
+
var LOG_ROTATION_MAX_BYTES = 10 * 1024 * 1024;
|
|
2119
|
+
function rotateLogIfNeeded() {
|
|
2120
|
+
try {
|
|
2121
|
+
const stat = fs2.statSync(FALLBACK_LOG_FILE);
|
|
2122
|
+
if (stat.size > LOG_ROTATION_MAX_BYTES) {
|
|
2123
|
+
const rotated = `${FALLBACK_LOG_FILE}.1`;
|
|
2124
|
+
try {
|
|
2125
|
+
fs2.unlinkSync(rotated);
|
|
2126
|
+
} catch {
|
|
2127
|
+
}
|
|
2128
|
+
fs2.renameSync(FALLBACK_LOG_FILE, rotated);
|
|
2129
|
+
}
|
|
2130
|
+
} catch {
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2007
2133
|
async function startPidfileService() {
|
|
2008
2134
|
assertConfiguredAgentToken();
|
|
2009
2135
|
const existingPid = readFallbackPid();
|
|
@@ -2012,11 +2138,12 @@ async function startPidfileService() {
|
|
|
2012
2138
|
return;
|
|
2013
2139
|
}
|
|
2014
2140
|
ensureConfigDir();
|
|
2141
|
+
rotateLogIfNeeded();
|
|
2015
2142
|
const logFd = fs2.openSync(FALLBACK_LOG_FILE, "a");
|
|
2016
2143
|
const child = (0, import_child_process2.spawn)(process.execPath, [__filename, "run"], {
|
|
2017
2144
|
detached: true,
|
|
2018
2145
|
stdio: ["ignore", logFd, logFd],
|
|
2019
|
-
cwd:
|
|
2146
|
+
cwd: os6.homedir(),
|
|
2020
2147
|
env: process.env
|
|
2021
2148
|
});
|
|
2022
2149
|
child.unref();
|
|
@@ -2716,7 +2843,7 @@ var ControlDispatcher = class {
|
|
|
2716
2843
|
// src/transcript-watcher.ts
|
|
2717
2844
|
var fs3 = __toESM(require("fs"));
|
|
2718
2845
|
var path2 = __toESM(require("path"));
|
|
2719
|
-
var
|
|
2846
|
+
var os7 = __toESM(require("os"));
|
|
2720
2847
|
var import_child_process5 = require("child_process");
|
|
2721
2848
|
var ClaudeCodeParser = class {
|
|
2722
2849
|
name = "claude-code";
|
|
@@ -2889,7 +3016,7 @@ function discoverViaProc(panePid) {
|
|
|
2889
3016
|
return null;
|
|
2890
3017
|
}
|
|
2891
3018
|
function discoverViaCwd(paneCwd) {
|
|
2892
|
-
const claudeProjectsDir = path2.join(
|
|
3019
|
+
const claudeProjectsDir = path2.join(os7.homedir(), ".claude", "projects");
|
|
2893
3020
|
if (!fs3.existsSync(claudeProjectsDir)) return null;
|
|
2894
3021
|
const resolvedCwd = fs3.realpathSync(paneCwd);
|
|
2895
3022
|
const expectedDirName = resolvedCwd.replace(/\//g, "-");
|
|
@@ -2907,7 +3034,7 @@ function discoverViaCwd(paneCwd) {
|
|
|
2907
3034
|
}
|
|
2908
3035
|
}
|
|
2909
3036
|
function discoverCodexTranscript() {
|
|
2910
|
-
const codexSessionsDir = path2.join(
|
|
3037
|
+
const codexSessionsDir = path2.join(os7.homedir(), ".codex", "sessions");
|
|
2911
3038
|
if (!fs3.existsSync(codexSessionsDir)) return null;
|
|
2912
3039
|
try {
|
|
2913
3040
|
const dateDirs = fs3.readdirSync(codexSessionsDir).filter((d) => /^\d{4}-\d{2}-\d{2}/.test(d)).sort().reverse();
|
|
@@ -2962,6 +3089,7 @@ function discoverTranscriptFile(sessionId, agentType) {
|
|
|
2962
3089
|
var MAX_PARTIAL_BUFFER = 256 * 1024;
|
|
2963
3090
|
var MAX_REPLAY_TURNS = 200;
|
|
2964
3091
|
var POLL_INTERVAL_MS = 800;
|
|
3092
|
+
var REDISCOVERY_INTERVAL_MS = 5e3;
|
|
2965
3093
|
var TranscriptWatcher = class {
|
|
2966
3094
|
filePath;
|
|
2967
3095
|
parser;
|
|
@@ -3132,6 +3260,7 @@ function getParser(agentType) {
|
|
|
3132
3260
|
}
|
|
3133
3261
|
var TranscriptWatcherManager = class {
|
|
3134
3262
|
active = /* @__PURE__ */ new Map();
|
|
3263
|
+
rediscoveryTimers = /* @__PURE__ */ new Map();
|
|
3135
3264
|
sendFn;
|
|
3136
3265
|
sendSnapshotFn;
|
|
3137
3266
|
constructor(sendFn, sendSnapshotFn) {
|
|
@@ -3170,6 +3299,7 @@ var TranscriptWatcherManager = class {
|
|
|
3170
3299
|
}
|
|
3171
3300
|
this.active.set(sessionId, { watcher, filePath, agentType: agentType ?? "" });
|
|
3172
3301
|
watcher.replayFromStart();
|
|
3302
|
+
this.startRediscovery(sessionId, agentType);
|
|
3173
3303
|
return true;
|
|
3174
3304
|
}
|
|
3175
3305
|
/** Disable markdown streaming for a session. */
|
|
@@ -3178,8 +3308,50 @@ var TranscriptWatcherManager = class {
|
|
|
3178
3308
|
if (!session) return;
|
|
3179
3309
|
session.watcher.removeSubscriber();
|
|
3180
3310
|
this.active.delete(sessionId);
|
|
3311
|
+
this.stopRediscovery(sessionId);
|
|
3181
3312
|
console.log(`[rAgent] Stopped watching transcript for ${sessionId}`);
|
|
3182
3313
|
}
|
|
3314
|
+
/** Periodically check if the transcript file has changed (new conversation). */
|
|
3315
|
+
startRediscovery(sessionId, agentType) {
|
|
3316
|
+
this.stopRediscovery(sessionId);
|
|
3317
|
+
const timer = setInterval(() => {
|
|
3318
|
+
const session = this.active.get(sessionId);
|
|
3319
|
+
if (!session) {
|
|
3320
|
+
this.stopRediscovery(sessionId);
|
|
3321
|
+
return;
|
|
3322
|
+
}
|
|
3323
|
+
const newPath = discoverTranscriptFile(sessionId, agentType);
|
|
3324
|
+
if (!newPath || newPath === session.filePath) return;
|
|
3325
|
+
console.log(`[rAgent] Transcript file changed for ${sessionId}: ${newPath}`);
|
|
3326
|
+
session.watcher.stop();
|
|
3327
|
+
const parser = getParser(agentType);
|
|
3328
|
+
if (!parser) return;
|
|
3329
|
+
const newWatcher = new TranscriptWatcher(newPath, parser, {
|
|
3330
|
+
onTurn: (turn, seq) => {
|
|
3331
|
+
this.sendFn(sessionId, turn, seq);
|
|
3332
|
+
},
|
|
3333
|
+
onError: (error) => {
|
|
3334
|
+
console.warn(`[rAgent] Transcript watcher error (${sessionId}): ${error}`);
|
|
3335
|
+
}
|
|
3336
|
+
});
|
|
3337
|
+
if (!newWatcher.start()) {
|
|
3338
|
+
console.warn(`[rAgent] Failed to start watching new transcript ${newPath}`);
|
|
3339
|
+
return;
|
|
3340
|
+
}
|
|
3341
|
+
this.sendSnapshotFn(sessionId, [], 0);
|
|
3342
|
+
newWatcher.replayFromStart();
|
|
3343
|
+
this.active.set(sessionId, { watcher: newWatcher, filePath: newPath, agentType: agentType ?? "" });
|
|
3344
|
+
}, REDISCOVERY_INTERVAL_MS);
|
|
3345
|
+
this.rediscoveryTimers.set(sessionId, timer);
|
|
3346
|
+
}
|
|
3347
|
+
/** Stop the re-discovery timer for a session. */
|
|
3348
|
+
stopRediscovery(sessionId) {
|
|
3349
|
+
const timer = this.rediscoveryTimers.get(sessionId);
|
|
3350
|
+
if (timer) {
|
|
3351
|
+
clearInterval(timer);
|
|
3352
|
+
this.rediscoveryTimers.delete(sessionId);
|
|
3353
|
+
}
|
|
3354
|
+
}
|
|
3183
3355
|
/** Handle sync-markdown request — send replay snapshot. */
|
|
3184
3356
|
handleSyncRequest(sessionId, fromSeq) {
|
|
3185
3357
|
const session = this.active.get(sessionId);
|
|
@@ -3191,9 +3363,11 @@ var TranscriptWatcherManager = class {
|
|
|
3191
3363
|
stopAll() {
|
|
3192
3364
|
for (const [sessionId, session] of this.active) {
|
|
3193
3365
|
session.watcher.stop();
|
|
3366
|
+
this.stopRediscovery(sessionId);
|
|
3194
3367
|
console.log(`[rAgent] Stopped transcript watcher for ${sessionId}`);
|
|
3195
3368
|
}
|
|
3196
3369
|
this.active.clear();
|
|
3370
|
+
this.rediscoveryTimers.clear();
|
|
3197
3371
|
}
|
|
3198
3372
|
/** Check if a session has an active watcher. */
|
|
3199
3373
|
isWatching(sessionId) {
|
|
@@ -3249,7 +3423,7 @@ function resolveRunOptions(commandOptions) {
|
|
|
3249
3423
|
const config = loadConfig();
|
|
3250
3424
|
const portal = commandOptions.portal || config.portal || DEFAULT_PORTAL;
|
|
3251
3425
|
const hostId = sanitizeHostId(commandOptions.id || config.hostId || inferHostId());
|
|
3252
|
-
const hostName = commandOptions.name || config.hostName ||
|
|
3426
|
+
const hostName = commandOptions.name || config.hostName || os8.hostname();
|
|
3253
3427
|
const command = commandOptions.command || config.command || "bash";
|
|
3254
3428
|
const agentToken = commandOptions.agentToken || config.agentToken || "";
|
|
3255
3429
|
return { portal, hostId, hostName, command, agentToken };
|
|
@@ -3260,6 +3434,11 @@ async function runAgent(rawOptions) {
|
|
|
3260
3434
|
throw new Error("No agent token found. Run `ragent connect` first.");
|
|
3261
3435
|
}
|
|
3262
3436
|
const lockPath = acquirePidLock(options.hostId);
|
|
3437
|
+
const config = loadConfig();
|
|
3438
|
+
if (config.redaction?.enabled) {
|
|
3439
|
+
setRedactionEnabled(true);
|
|
3440
|
+
console.log("[rAgent] Secret redaction enabled.");
|
|
3441
|
+
}
|
|
3263
3442
|
console.log(`[rAgent] Connector started for ${options.hostName} (${options.hostId})`);
|
|
3264
3443
|
console.log(`[rAgent] Portal: ${options.portal}`);
|
|
3265
3444
|
try {
|
|
@@ -3274,7 +3453,10 @@ async function runAgent(rawOptions) {
|
|
|
3274
3453
|
const ptySessionId = `pty:${options.hostId}`;
|
|
3275
3454
|
const sessionStreamer = new SessionStreamer(
|
|
3276
3455
|
(sessionId, data) => {
|
|
3277
|
-
if (!conn.isReady())
|
|
3456
|
+
if (!conn.isReady()) {
|
|
3457
|
+
sessionStreamer.bufferOutput(sessionId, data);
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3278
3460
|
if (conn.sessionKey) {
|
|
3279
3461
|
const { enc, iv } = encryptPayload(data, conn.sessionKey);
|
|
3280
3462
|
sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { type: "output", enc, iv, sessionId });
|
|
@@ -3382,7 +3564,7 @@ async function runAgent(rawOptions) {
|
|
|
3382
3564
|
sendToGroup(ws, groups.privateGroup, {
|
|
3383
3565
|
type: "register",
|
|
3384
3566
|
hostName: options.hostName,
|
|
3385
|
-
environment:
|
|
3567
|
+
environment: os8.platform()
|
|
3386
3568
|
});
|
|
3387
3569
|
conn.replayBufferedOutput((chunk) => {
|
|
3388
3570
|
sendToGroup(ws, groups.privateGroup, {
|
|
@@ -3391,6 +3573,17 @@ async function runAgent(rawOptions) {
|
|
|
3391
3573
|
sessionId: ptySessionId
|
|
3392
3574
|
});
|
|
3393
3575
|
});
|
|
3576
|
+
for (const [sessionId] of sessionStreamer.activeStreams()) {
|
|
3577
|
+
const buffered = sessionStreamer.drainBuffer(sessionId);
|
|
3578
|
+
for (const chunk of buffered) {
|
|
3579
|
+
if (conn.sessionKey) {
|
|
3580
|
+
const { enc, iv } = encryptPayload(chunk, conn.sessionKey);
|
|
3581
|
+
sendToGroup(ws, groups.privateGroup, { type: "output", enc, iv, sessionId });
|
|
3582
|
+
} else {
|
|
3583
|
+
sendToGroup(ws, groups.privateGroup, { type: "output", data: chunk, sessionId });
|
|
3584
|
+
}
|
|
3585
|
+
}
|
|
3586
|
+
}
|
|
3394
3587
|
await inventory.syncInventory(ws, groups, true);
|
|
3395
3588
|
conn.startTimers(
|
|
3396
3589
|
() => inventory.announceToRegistry(ws, groups.registryGroup, "heartbeat"),
|
|
@@ -3569,7 +3762,7 @@ function printCommandArt(title, subtitle = "remote agent control") {
|
|
|
3569
3762
|
async function connectMachine(opts) {
|
|
3570
3763
|
const portal = opts.portal || DEFAULT_PORTAL;
|
|
3571
3764
|
const hostId = sanitizeHostId(opts.id || inferHostId());
|
|
3572
|
-
const hostName = opts.name ||
|
|
3765
|
+
const hostName = opts.name || os9.hostname();
|
|
3573
3766
|
const command = opts.command || "bash";
|
|
3574
3767
|
if (!opts.token) {
|
|
3575
3768
|
throw new Error("Connection token is required.");
|
|
@@ -3643,12 +3836,12 @@ function registerRunCommand(program2) {
|
|
|
3643
3836
|
}
|
|
3644
3837
|
|
|
3645
3838
|
// src/commands/doctor.ts
|
|
3646
|
-
var
|
|
3839
|
+
var os10 = __toESM(require("os"));
|
|
3647
3840
|
async function runDoctor(opts) {
|
|
3648
3841
|
const options = resolveRunOptions(opts);
|
|
3649
3842
|
const checks = [];
|
|
3650
|
-
const platformOk =
|
|
3651
|
-
checks.push({ name: "platform", ok: platformOk, detail:
|
|
3843
|
+
const platformOk = os10.platform() === "linux";
|
|
3844
|
+
checks.push({ name: "platform", ok: platformOk, detail: os10.platform() });
|
|
3652
3845
|
checks.push({
|
|
3653
3846
|
name: "node",
|
|
3654
3847
|
ok: Number(process.versions.node.split(".")[0]) >= 20,
|