ragent-cli 1.5.1 → 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 +247 -46
- 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";
|
|
@@ -2775,25 +2902,33 @@ var ClaudeCodeParser = class {
|
|
|
2775
2902
|
parseUserToolResults(obj) {
|
|
2776
2903
|
const content = obj.message.content;
|
|
2777
2904
|
const tools = [];
|
|
2905
|
+
const textBlocks = [];
|
|
2778
2906
|
for (const block of content) {
|
|
2779
|
-
if (block.type
|
|
2780
|
-
|
|
2781
|
-
|
|
2782
|
-
|
|
2783
|
-
|
|
2784
|
-
|
|
2785
|
-
|
|
2786
|
-
|
|
2787
|
-
|
|
2788
|
-
|
|
2789
|
-
|
|
2907
|
+
if (block.type === "tool_result") {
|
|
2908
|
+
const toolName = (block.tool_use_id && this.pendingTools.get(block.tool_use_id)) ?? "Tool";
|
|
2909
|
+
const resultText = this.extractToolResultText(block);
|
|
2910
|
+
if (block.tool_use_id) {
|
|
2911
|
+
this.pendingTools.delete(block.tool_use_id);
|
|
2912
|
+
}
|
|
2913
|
+
tools.push({
|
|
2914
|
+
name: toolName,
|
|
2915
|
+
status: block.is_error ? "error" : "completed",
|
|
2916
|
+
result: resultText || void 0
|
|
2917
|
+
});
|
|
2918
|
+
} else if (block.type === "text" && typeof block.text === "string") {
|
|
2919
|
+
const t = block.text.trim();
|
|
2920
|
+
if (t && !t.startsWith("<") && !t.startsWith("[Request interrupted")) {
|
|
2921
|
+
textBlocks.push(t);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2790
2924
|
}
|
|
2791
|
-
|
|
2925
|
+
const text = textBlocks.join("\n").trim();
|
|
2926
|
+
if (tools.length === 0 && !text) return null;
|
|
2792
2927
|
return {
|
|
2793
2928
|
turnId: obj.uuid ?? `result-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
|
|
2794
2929
|
role: "user",
|
|
2795
|
-
content:
|
|
2796
|
-
tools,
|
|
2930
|
+
content: text,
|
|
2931
|
+
tools: tools.length > 0 ? tools : void 0,
|
|
2797
2932
|
timestamp: obj.timestamp ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
2798
2933
|
};
|
|
2799
2934
|
}
|
|
@@ -2881,7 +3016,7 @@ function discoverViaProc(panePid) {
|
|
|
2881
3016
|
return null;
|
|
2882
3017
|
}
|
|
2883
3018
|
function discoverViaCwd(paneCwd) {
|
|
2884
|
-
const claudeProjectsDir = path2.join(
|
|
3019
|
+
const claudeProjectsDir = path2.join(os7.homedir(), ".claude", "projects");
|
|
2885
3020
|
if (!fs3.existsSync(claudeProjectsDir)) return null;
|
|
2886
3021
|
const resolvedCwd = fs3.realpathSync(paneCwd);
|
|
2887
3022
|
const expectedDirName = resolvedCwd.replace(/\//g, "-");
|
|
@@ -2899,7 +3034,7 @@ function discoverViaCwd(paneCwd) {
|
|
|
2899
3034
|
}
|
|
2900
3035
|
}
|
|
2901
3036
|
function discoverCodexTranscript() {
|
|
2902
|
-
const codexSessionsDir = path2.join(
|
|
3037
|
+
const codexSessionsDir = path2.join(os7.homedir(), ".codex", "sessions");
|
|
2903
3038
|
if (!fs3.existsSync(codexSessionsDir)) return null;
|
|
2904
3039
|
try {
|
|
2905
3040
|
const dateDirs = fs3.readdirSync(codexSessionsDir).filter((d) => /^\d{4}-\d{2}-\d{2}/.test(d)).sort().reverse();
|
|
@@ -2954,6 +3089,7 @@ function discoverTranscriptFile(sessionId, agentType) {
|
|
|
2954
3089
|
var MAX_PARTIAL_BUFFER = 256 * 1024;
|
|
2955
3090
|
var MAX_REPLAY_TURNS = 200;
|
|
2956
3091
|
var POLL_INTERVAL_MS = 800;
|
|
3092
|
+
var REDISCOVERY_INTERVAL_MS = 5e3;
|
|
2957
3093
|
var TranscriptWatcher = class {
|
|
2958
3094
|
filePath;
|
|
2959
3095
|
parser;
|
|
@@ -3124,6 +3260,7 @@ function getParser(agentType) {
|
|
|
3124
3260
|
}
|
|
3125
3261
|
var TranscriptWatcherManager = class {
|
|
3126
3262
|
active = /* @__PURE__ */ new Map();
|
|
3263
|
+
rediscoveryTimers = /* @__PURE__ */ new Map();
|
|
3127
3264
|
sendFn;
|
|
3128
3265
|
sendSnapshotFn;
|
|
3129
3266
|
constructor(sendFn, sendSnapshotFn) {
|
|
@@ -3162,6 +3299,7 @@ var TranscriptWatcherManager = class {
|
|
|
3162
3299
|
}
|
|
3163
3300
|
this.active.set(sessionId, { watcher, filePath, agentType: agentType ?? "" });
|
|
3164
3301
|
watcher.replayFromStart();
|
|
3302
|
+
this.startRediscovery(sessionId, agentType);
|
|
3165
3303
|
return true;
|
|
3166
3304
|
}
|
|
3167
3305
|
/** Disable markdown streaming for a session. */
|
|
@@ -3170,8 +3308,50 @@ var TranscriptWatcherManager = class {
|
|
|
3170
3308
|
if (!session) return;
|
|
3171
3309
|
session.watcher.removeSubscriber();
|
|
3172
3310
|
this.active.delete(sessionId);
|
|
3311
|
+
this.stopRediscovery(sessionId);
|
|
3173
3312
|
console.log(`[rAgent] Stopped watching transcript for ${sessionId}`);
|
|
3174
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
|
+
}
|
|
3175
3355
|
/** Handle sync-markdown request — send replay snapshot. */
|
|
3176
3356
|
handleSyncRequest(sessionId, fromSeq) {
|
|
3177
3357
|
const session = this.active.get(sessionId);
|
|
@@ -3183,9 +3363,11 @@ var TranscriptWatcherManager = class {
|
|
|
3183
3363
|
stopAll() {
|
|
3184
3364
|
for (const [sessionId, session] of this.active) {
|
|
3185
3365
|
session.watcher.stop();
|
|
3366
|
+
this.stopRediscovery(sessionId);
|
|
3186
3367
|
console.log(`[rAgent] Stopped transcript watcher for ${sessionId}`);
|
|
3187
3368
|
}
|
|
3188
3369
|
this.active.clear();
|
|
3370
|
+
this.rediscoveryTimers.clear();
|
|
3189
3371
|
}
|
|
3190
3372
|
/** Check if a session has an active watcher. */
|
|
3191
3373
|
isWatching(sessionId) {
|
|
@@ -3241,7 +3423,7 @@ function resolveRunOptions(commandOptions) {
|
|
|
3241
3423
|
const config = loadConfig();
|
|
3242
3424
|
const portal = commandOptions.portal || config.portal || DEFAULT_PORTAL;
|
|
3243
3425
|
const hostId = sanitizeHostId(commandOptions.id || config.hostId || inferHostId());
|
|
3244
|
-
const hostName = commandOptions.name || config.hostName ||
|
|
3426
|
+
const hostName = commandOptions.name || config.hostName || os8.hostname();
|
|
3245
3427
|
const command = commandOptions.command || config.command || "bash";
|
|
3246
3428
|
const agentToken = commandOptions.agentToken || config.agentToken || "";
|
|
3247
3429
|
return { portal, hostId, hostName, command, agentToken };
|
|
@@ -3252,6 +3434,11 @@ async function runAgent(rawOptions) {
|
|
|
3252
3434
|
throw new Error("No agent token found. Run `ragent connect` first.");
|
|
3253
3435
|
}
|
|
3254
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
|
+
}
|
|
3255
3442
|
console.log(`[rAgent] Connector started for ${options.hostName} (${options.hostId})`);
|
|
3256
3443
|
console.log(`[rAgent] Portal: ${options.portal}`);
|
|
3257
3444
|
try {
|
|
@@ -3266,7 +3453,10 @@ async function runAgent(rawOptions) {
|
|
|
3266
3453
|
const ptySessionId = `pty:${options.hostId}`;
|
|
3267
3454
|
const sessionStreamer = new SessionStreamer(
|
|
3268
3455
|
(sessionId, data) => {
|
|
3269
|
-
if (!conn.isReady())
|
|
3456
|
+
if (!conn.isReady()) {
|
|
3457
|
+
sessionStreamer.bufferOutput(sessionId, data);
|
|
3458
|
+
return;
|
|
3459
|
+
}
|
|
3270
3460
|
if (conn.sessionKey) {
|
|
3271
3461
|
const { enc, iv } = encryptPayload(data, conn.sessionKey);
|
|
3272
3462
|
sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { type: "output", enc, iv, sessionId });
|
|
@@ -3374,7 +3564,7 @@ async function runAgent(rawOptions) {
|
|
|
3374
3564
|
sendToGroup(ws, groups.privateGroup, {
|
|
3375
3565
|
type: "register",
|
|
3376
3566
|
hostName: options.hostName,
|
|
3377
|
-
environment:
|
|
3567
|
+
environment: os8.platform()
|
|
3378
3568
|
});
|
|
3379
3569
|
conn.replayBufferedOutput((chunk) => {
|
|
3380
3570
|
sendToGroup(ws, groups.privateGroup, {
|
|
@@ -3383,6 +3573,17 @@ async function runAgent(rawOptions) {
|
|
|
3383
3573
|
sessionId: ptySessionId
|
|
3384
3574
|
});
|
|
3385
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
|
+
}
|
|
3386
3587
|
await inventory.syncInventory(ws, groups, true);
|
|
3387
3588
|
conn.startTimers(
|
|
3388
3589
|
() => inventory.announceToRegistry(ws, groups.registryGroup, "heartbeat"),
|
|
@@ -3561,7 +3762,7 @@ function printCommandArt(title, subtitle = "remote agent control") {
|
|
|
3561
3762
|
async function connectMachine(opts) {
|
|
3562
3763
|
const portal = opts.portal || DEFAULT_PORTAL;
|
|
3563
3764
|
const hostId = sanitizeHostId(opts.id || inferHostId());
|
|
3564
|
-
const hostName = opts.name ||
|
|
3765
|
+
const hostName = opts.name || os9.hostname();
|
|
3565
3766
|
const command = opts.command || "bash";
|
|
3566
3767
|
if (!opts.token) {
|
|
3567
3768
|
throw new Error("Connection token is required.");
|
|
@@ -3635,12 +3836,12 @@ function registerRunCommand(program2) {
|
|
|
3635
3836
|
}
|
|
3636
3837
|
|
|
3637
3838
|
// src/commands/doctor.ts
|
|
3638
|
-
var
|
|
3839
|
+
var os10 = __toESM(require("os"));
|
|
3639
3840
|
async function runDoctor(opts) {
|
|
3640
3841
|
const options = resolveRunOptions(opts);
|
|
3641
3842
|
const checks = [];
|
|
3642
|
-
const platformOk =
|
|
3643
|
-
checks.push({ name: "platform", ok: platformOk, detail:
|
|
3843
|
+
const platformOk = os10.platform() === "linux";
|
|
3844
|
+
checks.push({ name: "platform", ok: platformOk, detail: os10.platform() });
|
|
3644
3845
|
checks.push({
|
|
3645
3846
|
name: "node",
|
|
3646
3847
|
ok: Number(process.versions.node.split(".")[0]) >= 20,
|