ragent-cli 1.11.13 → 1.11.14
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 +230 -226
- package/dist/sbom.json +5 -5
- 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.11.
|
|
34
|
+
version: "1.11.14",
|
|
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: {
|
|
@@ -9692,6 +9692,7 @@ function resolveRuntimeTranscriptSource(sessionId) {
|
|
|
9692
9692
|
}
|
|
9693
9693
|
|
|
9694
9694
|
// src/sessions.ts
|
|
9695
|
+
var log15 = createLogger("sessions");
|
|
9695
9696
|
var isMac = os5.platform() === "darwin";
|
|
9696
9697
|
var MARKDOWN_AGENT_TYPES = /* @__PURE__ */ new Set(["Claude Code", "Codex CLI", "Gemini CLI", "Antigravity CLI"]);
|
|
9697
9698
|
var isTmuxAvailable = null;
|
|
@@ -10089,18 +10090,15 @@ function transcriptSourceForSession(agentType, sessionId) {
|
|
|
10089
10090
|
}
|
|
10090
10091
|
async function collectBareAgentProcesses(excludePids, processList, processByPid) {
|
|
10091
10092
|
try {
|
|
10092
|
-
const tStart = performance.now();
|
|
10093
10093
|
if (!processList || !processByPid) {
|
|
10094
10094
|
const list = await getProcessTable();
|
|
10095
10095
|
processList = list;
|
|
10096
10096
|
processByPid = new Map(list.map((p) => [p.pid, p]));
|
|
10097
10097
|
}
|
|
10098
|
-
const tInit = performance.now();
|
|
10099
10098
|
const sessions = [];
|
|
10100
10099
|
const seen = /* @__PURE__ */ new Set();
|
|
10101
10100
|
const excluded = excludePids ?? /* @__PURE__ */ new Set();
|
|
10102
10101
|
const parentByPid = new Map(processList.map((row) => [row.pid, row.ppid]));
|
|
10103
|
-
const tParentMap = performance.now();
|
|
10104
10102
|
const hasTrackedAncestor = (pid, tracked) => {
|
|
10105
10103
|
const visited = /* @__PURE__ */ new Set();
|
|
10106
10104
|
let current = parentByPid.get(pid) ?? 0;
|
|
@@ -10111,7 +10109,6 @@ async function collectBareAgentProcesses(excludePids, processList, processByPid)
|
|
|
10111
10109
|
}
|
|
10112
10110
|
return false;
|
|
10113
10111
|
};
|
|
10114
|
-
const tFilterStart = performance.now();
|
|
10115
10112
|
const candidates = processList.filter((row) => {
|
|
10116
10113
|
const { pid, tty, stat, args } = row;
|
|
10117
10114
|
if (!hasControllingTty(tty)) return false;
|
|
@@ -10122,8 +10119,6 @@ async function collectBareAgentProcesses(excludePids, processList, processByPid)
|
|
|
10122
10119
|
if (!hasKeyword) return false;
|
|
10123
10120
|
return true;
|
|
10124
10121
|
});
|
|
10125
|
-
const tFilterEnd = performance.now();
|
|
10126
|
-
const tCwdsStart = performance.now();
|
|
10127
10122
|
const cwds = await Promise.all(
|
|
10128
10123
|
candidates.map(async (row) => {
|
|
10129
10124
|
const cwd = await getProcessWorkingDir(row.pid);
|
|
@@ -10131,40 +10126,22 @@ async function collectBareAgentProcesses(excludePids, processList, processByPid)
|
|
|
10131
10126
|
})
|
|
10132
10127
|
);
|
|
10133
10128
|
const cwdByPid = new Map(cwds.map((c) => [c.pid, c.cwd]));
|
|
10134
|
-
const tCwdsEnd = performance.now();
|
|
10135
|
-
let tLoopAncestor = 0;
|
|
10136
|
-
let tLoopClassify = 0;
|
|
10137
|
-
let tLoopVscode = 0;
|
|
10138
|
-
let tLoopMarkdown = 0;
|
|
10139
|
-
let tLoopStartTimes = 0;
|
|
10140
|
-
const tLoopStart = performance.now();
|
|
10141
10129
|
for (const row of candidates) {
|
|
10142
10130
|
const { pid, args } = row;
|
|
10143
|
-
|
|
10144
|
-
const skip = seen.has(pid) || hasTrackedAncestor(pid, seen);
|
|
10145
|
-
tLoopAncestor += performance.now() - t0;
|
|
10146
|
-
if (skip) continue;
|
|
10131
|
+
if (seen.has(pid) || hasTrackedAncestor(pid, seen)) continue;
|
|
10147
10132
|
const workingDir = cwdByPid.get(pid) || null;
|
|
10148
|
-
const t1 = performance.now();
|
|
10149
10133
|
let classification = classifyAgent(args);
|
|
10150
10134
|
if (!classification) {
|
|
10151
10135
|
classification = classifyAgent(args, { workingDir: workingDir ?? void 0 });
|
|
10152
10136
|
}
|
|
10153
|
-
tLoopClassify += performance.now() - t1;
|
|
10154
10137
|
if (!classification) continue;
|
|
10155
10138
|
seen.add(pid);
|
|
10156
10139
|
const dirName = workingDir ? workingDir.split("/").pop() : null;
|
|
10157
10140
|
const displayName = dirName && dirName !== "/" && dirName !== "" ? `${classification.agentType} (${dirName})` : `${classification.agentType} (pid ${pid})`;
|
|
10158
|
-
const t2 = performance.now();
|
|
10159
10141
|
const vscodeDetected = await detectVscodeEnvironment(pid, processByPid);
|
|
10160
|
-
tLoopVscode += performance.now() - t2;
|
|
10161
10142
|
const id = `process:${pid}`;
|
|
10162
|
-
const t3 = performance.now();
|
|
10163
10143
|
const mdAvail = markdownAvailableForSession(id, classification.agentType);
|
|
10164
|
-
tLoopMarkdown += performance.now() - t3;
|
|
10165
|
-
const t4 = performance.now();
|
|
10166
10144
|
const startTimes = collectStartTimes([pid]);
|
|
10167
|
-
tLoopStartTimes += performance.now() - t4;
|
|
10168
10145
|
sessions.push({
|
|
10169
10146
|
id,
|
|
10170
10147
|
type: "process",
|
|
@@ -10182,20 +10159,11 @@ async function collectBareAgentProcesses(excludePids, processList, processByPid)
|
|
|
10182
10159
|
startTimes
|
|
10183
10160
|
});
|
|
10184
10161
|
}
|
|
10185
|
-
const tLoopEnd = performance.now();
|
|
10186
|
-
console.log(`[PROFILE-BARE] Init took ${(tInit - tStart).toFixed(2)} ms`);
|
|
10187
|
-
console.log(`[PROFILE-BARE] Parent map took ${(tParentMap - tInit).toFixed(2)} ms`);
|
|
10188
|
-
console.log(`[PROFILE-BARE] Filtering took ${(tFilterEnd - tFilterStart).toFixed(2)} ms`);
|
|
10189
|
-
console.log(`[PROFILE-BARE] CWDs took ${(tCwdsEnd - tCwdsStart).toFixed(2)} ms`);
|
|
10190
|
-
console.log(`[PROFILE-BARE] Loop total took ${(tLoopEnd - tLoopStart).toFixed(2)} ms`);
|
|
10191
|
-
console.log(` [PROFILE-BARE-LOOP] Ancestor checks: ${tLoopAncestor.toFixed(2)} ms`);
|
|
10192
|
-
console.log(` [PROFILE-BARE-LOOP] Classify: ${tLoopClassify.toFixed(2)} ms`);
|
|
10193
|
-
console.log(` [PROFILE-BARE-LOOP] VS Code env: ${tLoopVscode.toFixed(2)} ms`);
|
|
10194
|
-
console.log(` [PROFILE-BARE-LOOP] Markdown check: ${tLoopMarkdown.toFixed(2)} ms`);
|
|
10195
|
-
console.log(` [PROFILE-BARE-LOOP] Start times: ${tLoopStartTimes.toFixed(2)} ms`);
|
|
10196
10162
|
return sessions;
|
|
10197
10163
|
} catch (err) {
|
|
10198
|
-
|
|
10164
|
+
log15.error("collectBareAgentProcesses failed", {
|
|
10165
|
+
error: err instanceof Error ? err.stack || err.message : String(err)
|
|
10166
|
+
});
|
|
10199
10167
|
return [];
|
|
10200
10168
|
}
|
|
10201
10169
|
}
|
|
@@ -10361,7 +10329,7 @@ async function getChildAgentInfo(parentPid, childrenByPpid, visited = /* @__PURE
|
|
|
10361
10329
|
}
|
|
10362
10330
|
|
|
10363
10331
|
// src/auth.ts
|
|
10364
|
-
var
|
|
10332
|
+
var log16 = createLogger("auth");
|
|
10365
10333
|
var AuthError = class extends Error {
|
|
10366
10334
|
constructor(message) {
|
|
10367
10335
|
super(message);
|
|
@@ -10379,7 +10347,7 @@ function decodeJwtExp(token) {
|
|
|
10379
10347
|
}
|
|
10380
10348
|
}
|
|
10381
10349
|
async function startSessionWithMachineSecret(params) {
|
|
10382
|
-
|
|
10350
|
+
log16.info("attempting session recovery with machine credential");
|
|
10383
10351
|
const response = await fetch(`${params.portal}/api/agent/session/start`, {
|
|
10384
10352
|
method: "POST",
|
|
10385
10353
|
headers: { "Content-Type": "application/json" },
|
|
@@ -10412,7 +10380,7 @@ async function startSessionWithMachineSecret(params) {
|
|
|
10412
10380
|
patch.refreshExpiresAt = data.refreshExpiresAt || "";
|
|
10413
10381
|
}
|
|
10414
10382
|
saveConfigPatch(patch);
|
|
10415
|
-
|
|
10383
|
+
log16.info("session recovered via machine credential");
|
|
10416
10384
|
return data.agentToken;
|
|
10417
10385
|
}
|
|
10418
10386
|
async function refreshTokenIfNeeded(params) {
|
|
@@ -10423,7 +10391,7 @@ async function refreshTokenIfNeeded(params) {
|
|
|
10423
10391
|
const remainingSeconds = exp - Math.floor(Date.now() / 1e3);
|
|
10424
10392
|
const threshold2 = remainingSeconds < 3600 ? 120 : 3600;
|
|
10425
10393
|
if (remainingSeconds > threshold2) return params.agentToken;
|
|
10426
|
-
|
|
10394
|
+
log16.info("token expiring \u2014 refreshing", { minutesRemaining: Math.round(remainingSeconds / 60) });
|
|
10427
10395
|
try {
|
|
10428
10396
|
const headers = { "Content-Type": "application/json" };
|
|
10429
10397
|
let body = {};
|
|
@@ -10439,7 +10407,7 @@ async function refreshTokenIfNeeded(params) {
|
|
|
10439
10407
|
});
|
|
10440
10408
|
if (!response.ok) {
|
|
10441
10409
|
const errorData = await response.json().catch(() => ({}));
|
|
10442
|
-
|
|
10410
|
+
log16.warn("token refresh failed", { error: errorData.error || response.status });
|
|
10443
10411
|
if (config.machineSecret && config.hostId) {
|
|
10444
10412
|
try {
|
|
10445
10413
|
return await startSessionWithMachineSecret({
|
|
@@ -10450,7 +10418,7 @@ async function refreshTokenIfNeeded(params) {
|
|
|
10450
10418
|
} catch (mcError) {
|
|
10451
10419
|
if (mcError instanceof AuthError) throw mcError;
|
|
10452
10420
|
const mcMessage = mcError instanceof Error ? mcError.message : String(mcError);
|
|
10453
|
-
|
|
10421
|
+
log16.warn("machine credential recovery failed", { error: mcMessage });
|
|
10454
10422
|
}
|
|
10455
10423
|
}
|
|
10456
10424
|
return params.agentToken;
|
|
@@ -10469,12 +10437,12 @@ async function refreshTokenIfNeeded(params) {
|
|
|
10469
10437
|
patch.machineSecret = data.machineSecret;
|
|
10470
10438
|
}
|
|
10471
10439
|
saveConfigPatch(patch);
|
|
10472
|
-
|
|
10440
|
+
log16.info("token refreshed successfully");
|
|
10473
10441
|
return data.agentToken;
|
|
10474
10442
|
} catch (error) {
|
|
10475
10443
|
if (error instanceof AuthError) throw error;
|
|
10476
10444
|
const message = error instanceof Error ? error.message : String(error);
|
|
10477
|
-
|
|
10445
|
+
log16.warn("token refresh error", { error: message });
|
|
10478
10446
|
return params.agentToken;
|
|
10479
10447
|
}
|
|
10480
10448
|
}
|
|
@@ -10537,7 +10505,7 @@ async function deregisterHost(params) {
|
|
|
10537
10505
|
body: JSON.stringify({})
|
|
10538
10506
|
});
|
|
10539
10507
|
if (response.status === 401 || response.status === 403) {
|
|
10540
|
-
|
|
10508
|
+
log16.warn("token expired \u2014 could not deregister from server. Host may remain visible in dashboard until manually removed");
|
|
10541
10509
|
return false;
|
|
10542
10510
|
}
|
|
10543
10511
|
return response.ok;
|
|
@@ -10677,6 +10645,9 @@ function getQueuePressure() {
|
|
|
10677
10645
|
else if (level < PRESSURE_LOW) pressureIsHigh = false;
|
|
10678
10646
|
return { level, isHigh: pressureIsHigh };
|
|
10679
10647
|
}
|
|
10648
|
+
function getDroppedFrameCount() {
|
|
10649
|
+
return droppedFrames;
|
|
10650
|
+
}
|
|
10680
10651
|
function drainQueue() {
|
|
10681
10652
|
if (!drainWs || drainWs.readyState !== import_ws.default.OPEN) {
|
|
10682
10653
|
stopDrainTimer();
|
|
@@ -10760,7 +10731,7 @@ var import_node_path = require("path");
|
|
|
10760
10731
|
var import_node_os = require("os");
|
|
10761
10732
|
var import_node_string_decoder = require("string_decoder");
|
|
10762
10733
|
var pty = __toESM(require("node-pty"));
|
|
10763
|
-
var
|
|
10734
|
+
var log17 = createLogger("session-streamer");
|
|
10764
10735
|
var BACKPRESSURE_RESUME_CHECK_MS = 100;
|
|
10765
10736
|
var STOP_DEBOUNCE_MS = 2e3;
|
|
10766
10737
|
var MAX_CONCURRENT_STREAMS = 20;
|
|
@@ -10862,6 +10833,10 @@ var SessionStreamer = class {
|
|
|
10862
10833
|
disconnectBufferBytes = /* @__PURE__ */ new Map();
|
|
10863
10834
|
/** Timer that resumes paused PTYs once the WS queue drains. See #332. */
|
|
10864
10835
|
backpressureResumeTimer = null;
|
|
10836
|
+
/** Dropped-frame count at the last resume-timer check. */
|
|
10837
|
+
lastSeenDroppedFrames = 0;
|
|
10838
|
+
/** Sessions whose viewers need a capture-pane resync after frame drops. */
|
|
10839
|
+
pendingDropResync = /* @__PURE__ */ new Set();
|
|
10865
10840
|
constructor(sendFn, onStreamStopped) {
|
|
10866
10841
|
this.sendFn = sendFn;
|
|
10867
10842
|
this.onStreamStopped = onStreamStopped;
|
|
@@ -10872,6 +10847,14 @@ var SessionStreamer = class {
|
|
|
10872
10847
|
* When the WS queue is saturated, PTY onData stops firing after we call
|
|
10873
10848
|
* ptyProc.pause(). We need an independent loop to check pressure and
|
|
10874
10849
|
* resume each paused PTY once the queue drains. See #332.
|
|
10850
|
+
*
|
|
10851
|
+
* The same loop heals tmux-pipe streams after frame drops: tmux pipes
|
|
10852
|
+
* have no pause primitive, so under sustained pressure the send queue
|
|
10853
|
+
* overflows and drops frames — severing ANSI sequences and leaving
|
|
10854
|
+
* every viewer garbled until the next full repaint. Once pressure
|
|
10855
|
+
* clears, any drop since the last check triggers a capture-pane resync
|
|
10856
|
+
* (one stream per tick, so the resyncs themselves don't re-saturate
|
|
10857
|
+
* the queue).
|
|
10875
10858
|
*/
|
|
10876
10859
|
startBackpressureResumeTimer() {
|
|
10877
10860
|
if (this.backpressureResumeTimer) return;
|
|
@@ -10883,11 +10866,32 @@ var SessionStreamer = class {
|
|
|
10883
10866
|
try {
|
|
10884
10867
|
stream.ptyProc.resume();
|
|
10885
10868
|
stream.ptyPaused = false;
|
|
10886
|
-
|
|
10869
|
+
log17.info("pty-attach resumed after WS queue drained", { sessionId: stream.sessionId });
|
|
10887
10870
|
} catch {
|
|
10888
10871
|
}
|
|
10889
10872
|
}
|
|
10890
10873
|
}
|
|
10874
|
+
const dropped = getDroppedFrameCount();
|
|
10875
|
+
if (dropped > this.lastSeenDroppedFrames) {
|
|
10876
|
+
for (const stream of this.active.values()) {
|
|
10877
|
+
if (stream.streamType === "tmux-pipe" && !stream.stopped && !stream.initializing) {
|
|
10878
|
+
this.pendingDropResync.add(stream.sessionId);
|
|
10879
|
+
}
|
|
10880
|
+
}
|
|
10881
|
+
this.lastSeenDroppedFrames = dropped;
|
|
10882
|
+
log17.warn("frames dropped under backpressure; scheduling stream resyncs", {
|
|
10883
|
+
droppedTotal: dropped,
|
|
10884
|
+
streams: this.pendingDropResync.size
|
|
10885
|
+
});
|
|
10886
|
+
}
|
|
10887
|
+
const next = this.pendingDropResync.values().next();
|
|
10888
|
+
if (!next.done) {
|
|
10889
|
+
this.pendingDropResync.delete(next.value);
|
|
10890
|
+
const stream = this.active.get(next.value);
|
|
10891
|
+
if (stream && !stream.stopped) {
|
|
10892
|
+
this.resyncStream(next.value);
|
|
10893
|
+
}
|
|
10894
|
+
}
|
|
10891
10895
|
}, BACKPRESSURE_RESUME_CHECK_MS);
|
|
10892
10896
|
}
|
|
10893
10897
|
/**
|
|
@@ -10902,7 +10906,7 @@ var SessionStreamer = class {
|
|
|
10902
10906
|
try {
|
|
10903
10907
|
stream.ptyProc.pause();
|
|
10904
10908
|
stream.ptyPaused = true;
|
|
10905
|
-
|
|
10909
|
+
log17.warn("pty-attach paused under WS backpressure", {
|
|
10906
10910
|
sessionId: stream.sessionId,
|
|
10907
10911
|
queueLevel: pressure.level.toFixed(2)
|
|
10908
10912
|
});
|
|
@@ -10926,7 +10930,7 @@ var SessionStreamer = class {
|
|
|
10926
10930
|
}
|
|
10927
10931
|
}
|
|
10928
10932
|
if (entries.some((e) => e.startsWith("s-"))) {
|
|
10929
|
-
|
|
10933
|
+
log17.info("cleaned up stale stream directories from previous run");
|
|
10930
10934
|
}
|
|
10931
10935
|
} catch {
|
|
10932
10936
|
}
|
|
@@ -10944,7 +10948,7 @@ var SessionStreamer = class {
|
|
|
10944
10948
|
if (viewerKey) {
|
|
10945
10949
|
this.active.get(sessionId)?.viewerIds.add(viewerKey);
|
|
10946
10950
|
}
|
|
10947
|
-
|
|
10951
|
+
log17.info("cancelled pending stop (re-mounted)", { sessionId });
|
|
10948
10952
|
return true;
|
|
10949
10953
|
}
|
|
10950
10954
|
}
|
|
@@ -10955,7 +10959,7 @@ var SessionStreamer = class {
|
|
|
10955
10959
|
return true;
|
|
10956
10960
|
}
|
|
10957
10961
|
if (this.active.size >= MAX_CONCURRENT_STREAMS) {
|
|
10958
|
-
|
|
10962
|
+
log17.warn("stream limit reached; rejecting", {
|
|
10959
10963
|
limit: MAX_CONCURRENT_STREAMS,
|
|
10960
10964
|
sessionId
|
|
10961
10965
|
});
|
|
@@ -10986,7 +10990,7 @@ var SessionStreamer = class {
|
|
|
10986
10990
|
stream.ptyProc.write(data);
|
|
10987
10991
|
} catch (error) {
|
|
10988
10992
|
const message = error instanceof Error ? error.message : String(error);
|
|
10989
|
-
|
|
10993
|
+
log17.warn("failed to write input", { sessionId, error: message });
|
|
10990
10994
|
}
|
|
10991
10995
|
}
|
|
10992
10996
|
/**
|
|
@@ -11000,7 +11004,7 @@ var SessionStreamer = class {
|
|
|
11000
11004
|
} catch (error) {
|
|
11001
11005
|
const message = error instanceof Error ? error.message : String(error);
|
|
11002
11006
|
if (!message.includes("EBADF")) {
|
|
11003
|
-
|
|
11007
|
+
log17.warn("resize failed", { sessionId, error: message });
|
|
11004
11008
|
}
|
|
11005
11009
|
}
|
|
11006
11010
|
}
|
|
@@ -11026,7 +11030,7 @@ var SessionStreamer = class {
|
|
|
11026
11030
|
this.active.delete(sessionId);
|
|
11027
11031
|
this.disconnectBuffers.delete(sessionId);
|
|
11028
11032
|
this.disconnectBufferBytes.delete(sessionId);
|
|
11029
|
-
|
|
11033
|
+
log17.info("stopped streaming", { sessionId });
|
|
11030
11034
|
}, STOP_DEBOUNCE_MS);
|
|
11031
11035
|
this.pendingStops.set(sessionId, timer);
|
|
11032
11036
|
}
|
|
@@ -11051,7 +11055,7 @@ var SessionStreamer = class {
|
|
|
11051
11055
|
this.pendingStops.clear();
|
|
11052
11056
|
for (const [id, stream] of this.active) {
|
|
11053
11057
|
this.cleanupStream(stream);
|
|
11054
|
-
|
|
11058
|
+
log17.info("stopped streaming", { sessionId: id });
|
|
11055
11059
|
}
|
|
11056
11060
|
this.active.clear();
|
|
11057
11061
|
this.disconnectBuffers.clear();
|
|
@@ -11189,13 +11193,13 @@ var SessionStreamer = class {
|
|
|
11189
11193
|
}
|
|
11190
11194
|
stream.initializing = false;
|
|
11191
11195
|
stream.initBuffer = [];
|
|
11192
|
-
|
|
11196
|
+
log17.info("resync capture", { sessionId });
|
|
11193
11197
|
return true;
|
|
11194
11198
|
} catch (error) {
|
|
11195
11199
|
stream.initializing = false;
|
|
11196
11200
|
stream.initBuffer = [];
|
|
11197
11201
|
const message = error instanceof Error ? error.message : String(error);
|
|
11198
|
-
|
|
11202
|
+
log17.warn("failed to resync stream", { sessionId, error: message });
|
|
11199
11203
|
return false;
|
|
11200
11204
|
}
|
|
11201
11205
|
}
|
|
@@ -11249,7 +11253,7 @@ var SessionStreamer = class {
|
|
|
11249
11253
|
} catch (error) {
|
|
11250
11254
|
this.cleanupStream(stream);
|
|
11251
11255
|
const message = error instanceof Error ? error.message : String(error);
|
|
11252
|
-
|
|
11256
|
+
log17.warn("failed pipe-pane", { sessionId, error: message });
|
|
11253
11257
|
return false;
|
|
11254
11258
|
}
|
|
11255
11259
|
const catProc = (0, import_node_child_process2.spawn)("cat", [fifoPath], { env: cleanEnv, stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -11277,7 +11281,7 @@ var SessionStreamer = class {
|
|
|
11277
11281
|
this.cleanupStream(stream);
|
|
11278
11282
|
this.active.delete(sessionId);
|
|
11279
11283
|
this.pendingStops.delete(sessionId);
|
|
11280
|
-
|
|
11284
|
+
log17.info("session stream ended", { sessionId });
|
|
11281
11285
|
this.onStreamStopped?.(sessionId);
|
|
11282
11286
|
}
|
|
11283
11287
|
});
|
|
@@ -11334,11 +11338,11 @@ var SessionStreamer = class {
|
|
|
11334
11338
|
}
|
|
11335
11339
|
stream.initializing = false;
|
|
11336
11340
|
stream.initBuffer = [];
|
|
11337
|
-
|
|
11341
|
+
log17.info("started streaming (tmux)", { sessionId, pane: paneTarget });
|
|
11338
11342
|
return true;
|
|
11339
11343
|
} catch (error) {
|
|
11340
11344
|
const message = error instanceof Error ? error.message : String(error);
|
|
11341
|
-
|
|
11345
|
+
log17.warn("failed to start stream", { sessionId, error: message });
|
|
11342
11346
|
return false;
|
|
11343
11347
|
}
|
|
11344
11348
|
}
|
|
@@ -11384,15 +11388,15 @@ var SessionStreamer = class {
|
|
|
11384
11388
|
stream.stopped = true;
|
|
11385
11389
|
this.active.delete(sessionId);
|
|
11386
11390
|
this.pendingStops.delete(sessionId);
|
|
11387
|
-
|
|
11391
|
+
log17.info("session stream ended", { sessionId });
|
|
11388
11392
|
this.onStreamStopped?.(sessionId);
|
|
11389
11393
|
}
|
|
11390
11394
|
});
|
|
11391
|
-
|
|
11395
|
+
log17.info("started streaming (screen)", { sessionId, screen: sessionName });
|
|
11392
11396
|
return true;
|
|
11393
11397
|
} catch (error) {
|
|
11394
11398
|
const message = error instanceof Error ? error.message : String(error);
|
|
11395
|
-
|
|
11399
|
+
log17.warn("failed to start screen stream", { sessionId, error: message });
|
|
11396
11400
|
return false;
|
|
11397
11401
|
}
|
|
11398
11402
|
}
|
|
@@ -11438,15 +11442,15 @@ var SessionStreamer = class {
|
|
|
11438
11442
|
stream.stopped = true;
|
|
11439
11443
|
this.active.delete(sessionId);
|
|
11440
11444
|
this.pendingStops.delete(sessionId);
|
|
11441
|
-
|
|
11445
|
+
log17.info("session stream ended", { sessionId });
|
|
11442
11446
|
this.onStreamStopped?.(sessionId);
|
|
11443
11447
|
}
|
|
11444
11448
|
});
|
|
11445
|
-
|
|
11449
|
+
log17.info("started streaming (zellij)", { sessionId, zellij: sessionName });
|
|
11446
11450
|
return true;
|
|
11447
11451
|
} catch (error) {
|
|
11448
11452
|
const message = error instanceof Error ? error.message : String(error);
|
|
11449
|
-
|
|
11453
|
+
log17.warn("failed to start zellij stream", { sessionId, error: message });
|
|
11450
11454
|
return false;
|
|
11451
11455
|
}
|
|
11452
11456
|
}
|
|
@@ -11457,14 +11461,14 @@ var SessionStreamer = class {
|
|
|
11457
11461
|
const pid = parseProcessPid2(sessionId);
|
|
11458
11462
|
if (!pid) return false;
|
|
11459
11463
|
if (!(0, import_node_fs.existsSync)(`/proc/${pid}`)) {
|
|
11460
|
-
|
|
11464
|
+
log17.warn("process does not exist (no /proc entry)", { pid });
|
|
11461
11465
|
this.sendFn(sessionId, "\r\n[rAgent] Process is no longer running. It will be removed on next sync.\r\n");
|
|
11462
11466
|
return false;
|
|
11463
11467
|
}
|
|
11464
11468
|
try {
|
|
11465
11469
|
const stat = (0, import_node_fs.readFileSync)(`/proc/${pid}/stat`, "utf8");
|
|
11466
11470
|
if (stat.includes(") Z")) {
|
|
11467
|
-
|
|
11471
|
+
log17.warn("process is a zombie", { pid });
|
|
11468
11472
|
this.sendFn(sessionId, "\r\n[rAgent] Process has exited (zombie). It will be removed on next sync.\r\n");
|
|
11469
11473
|
return false;
|
|
11470
11474
|
}
|
|
@@ -11538,14 +11542,14 @@ var SessionStreamer = class {
|
|
|
11538
11542
|
stream.stopped = true;
|
|
11539
11543
|
this.active.delete(sessionId);
|
|
11540
11544
|
this.pendingStops.delete(sessionId);
|
|
11541
|
-
|
|
11545
|
+
log17.info("session stream ended", { sessionId });
|
|
11542
11546
|
this.onStreamStopped?.(sessionId);
|
|
11543
11547
|
});
|
|
11544
|
-
|
|
11548
|
+
log17.info("started streaming (process trace)", { sessionId, pid });
|
|
11545
11549
|
return true;
|
|
11546
11550
|
} catch (error) {
|
|
11547
11551
|
const message = error instanceof Error ? error.message : String(error);
|
|
11548
|
-
|
|
11552
|
+
log17.warn("failed to start process stream", { sessionId, error: message });
|
|
11549
11553
|
return false;
|
|
11550
11554
|
}
|
|
11551
11555
|
}
|
|
@@ -11596,7 +11600,7 @@ var SessionStreamer = class {
|
|
|
11596
11600
|
|
|
11597
11601
|
// src/crypto-channel.ts
|
|
11598
11602
|
var import_node_crypto = require("crypto");
|
|
11599
|
-
var
|
|
11603
|
+
var log18 = createLogger("crypto-channel");
|
|
11600
11604
|
var AAD_BYTES = 4;
|
|
11601
11605
|
function deriveAesKey(sessionKey) {
|
|
11602
11606
|
return Buffer.from(sessionKey.slice(0, 64), "hex");
|
|
@@ -11638,7 +11642,7 @@ function decryptPayload(enc, iv, sessionKey, seq) {
|
|
|
11638
11642
|
return decrypted.toString("utf-8");
|
|
11639
11643
|
} catch (error) {
|
|
11640
11644
|
const message = error instanceof Error ? error.message : String(error);
|
|
11641
|
-
|
|
11645
|
+
log18.warn("decrypt failed", { seq, encBytes: enc.length, error: message });
|
|
11642
11646
|
return null;
|
|
11643
11647
|
}
|
|
11644
11648
|
}
|
|
@@ -11672,7 +11676,7 @@ var SequenceTracker = class {
|
|
|
11672
11676
|
// src/pty.ts
|
|
11673
11677
|
var import_node_child_process3 = require("child_process");
|
|
11674
11678
|
var pty2 = __toESM(require("node-pty"));
|
|
11675
|
-
var
|
|
11679
|
+
var log19 = createLogger("pty");
|
|
11676
11680
|
var MIN_TMUX_COLS = 20;
|
|
11677
11681
|
var MAX_TMUX_COLS = 500;
|
|
11678
11682
|
var MIN_TMUX_ROWS = 5;
|
|
@@ -11820,7 +11824,7 @@ async function sendInputToTmux(sessionId, data) {
|
|
|
11820
11824
|
if (!target) return;
|
|
11821
11825
|
if (!isValidTmuxSessionName(target)) {
|
|
11822
11826
|
const sessionName = target.split(":")[0].split(".")[0];
|
|
11823
|
-
|
|
11827
|
+
log19.warn("invalid tmux session name", { sessionName });
|
|
11824
11828
|
return;
|
|
11825
11829
|
}
|
|
11826
11830
|
try {
|
|
@@ -11851,7 +11855,7 @@ async function sendInputToTmux(sessionId, data) {
|
|
|
11851
11855
|
}
|
|
11852
11856
|
} catch (error) {
|
|
11853
11857
|
const message = error instanceof Error ? error.message : String(error);
|
|
11854
|
-
|
|
11858
|
+
log19.warn("failed to send input", { sessionId, error: message });
|
|
11855
11859
|
}
|
|
11856
11860
|
}
|
|
11857
11861
|
async function resizeTmuxPaneBySessionId(sessionId, cols, rows) {
|
|
@@ -11860,7 +11864,7 @@ async function resizeTmuxPaneBySessionId(sessionId, cols, rows) {
|
|
|
11860
11864
|
if (!target) return false;
|
|
11861
11865
|
if (!isValidTmuxSessionName(target)) {
|
|
11862
11866
|
const sessionName = target.split(":")[0].split(".")[0];
|
|
11863
|
-
|
|
11867
|
+
log19.warn("invalid tmux session name", { sessionName });
|
|
11864
11868
|
return false;
|
|
11865
11869
|
}
|
|
11866
11870
|
if (!Number.isFinite(cols) || !Number.isFinite(rows)) return false;
|
|
@@ -11874,7 +11878,7 @@ async function resizeTmuxPaneBySessionId(sessionId, cols, rows) {
|
|
|
11874
11878
|
(err) => {
|
|
11875
11879
|
if (err) {
|
|
11876
11880
|
const message = err instanceof Error ? err.message : String(err);
|
|
11877
|
-
|
|
11881
|
+
log19.warn("failed to resize tmux pane", { sessionId, error: message });
|
|
11878
11882
|
resolve(false);
|
|
11879
11883
|
return;
|
|
11880
11884
|
}
|
|
@@ -11908,7 +11912,7 @@ async function stopAllDetachedTmuxSessions() {
|
|
|
11908
11912
|
}
|
|
11909
11913
|
|
|
11910
11914
|
// src/shell-manager.ts
|
|
11911
|
-
var
|
|
11915
|
+
var log20 = createLogger("shell");
|
|
11912
11916
|
var ShellManager = class {
|
|
11913
11917
|
ptyProcess = null;
|
|
11914
11918
|
suppressNextRespawn = false;
|
|
@@ -11949,7 +11953,7 @@ var ShellManager = class {
|
|
|
11949
11953
|
} catch (error) {
|
|
11950
11954
|
const message = error instanceof Error ? error.message : String(error);
|
|
11951
11955
|
if (!message.includes("EBADF")) {
|
|
11952
|
-
|
|
11956
|
+
log20.warn("resize failed", { error: message });
|
|
11953
11957
|
}
|
|
11954
11958
|
}
|
|
11955
11959
|
}
|
|
@@ -11965,7 +11969,7 @@ var ShellManager = class {
|
|
|
11965
11969
|
return;
|
|
11966
11970
|
}
|
|
11967
11971
|
if (!this.shouldRun) return;
|
|
11968
|
-
|
|
11972
|
+
log20.warn("shell exited; restarting shell process");
|
|
11969
11973
|
setTimeout(() => {
|
|
11970
11974
|
if (this.shouldRun) this.spawnOrRespawn();
|
|
11971
11975
|
}, 200);
|
|
@@ -12071,7 +12075,7 @@ var InventoryManager = class {
|
|
|
12071
12075
|
|
|
12072
12076
|
// src/connection-manager.ts
|
|
12073
12077
|
var import_ws3 = __toESM(require("ws"));
|
|
12074
|
-
var
|
|
12078
|
+
var log21 = createLogger("connection");
|
|
12075
12079
|
var OPPORTUNISTIC_INVENTORY_SYNC_MS = 1200;
|
|
12076
12080
|
var MIN_INVENTORY_SYNC_GAP_MS = 1500;
|
|
12077
12081
|
var ConnectionManager = class {
|
|
@@ -12103,7 +12107,7 @@ var ConnectionManager = class {
|
|
|
12103
12107
|
tracker = new SequenceTracker();
|
|
12104
12108
|
this.rxSeqByViewer.set(key, tracker);
|
|
12105
12109
|
if (!isValid) {
|
|
12106
|
-
|
|
12110
|
+
log21.warn("rxSeq fell back to _legacy bucket \u2014 portal sent empty viewerId", { typeofViewerId: typeof viewerId });
|
|
12107
12111
|
}
|
|
12108
12112
|
}
|
|
12109
12113
|
return tracker;
|
|
@@ -12189,14 +12193,14 @@ var ConnectionManager = class {
|
|
|
12189
12193
|
try {
|
|
12190
12194
|
this.activeSocket.send(JSON.stringify({ type: "ping" }));
|
|
12191
12195
|
} catch (err) {
|
|
12192
|
-
|
|
12196
|
+
log21.error("failed to send JSON ping", { error: err.message });
|
|
12193
12197
|
}
|
|
12194
12198
|
try {
|
|
12195
12199
|
this.activeSocket.ping();
|
|
12196
12200
|
} catch {
|
|
12197
12201
|
}
|
|
12198
12202
|
this.wsPongTimeout = setTimeout(() => {
|
|
12199
|
-
|
|
12203
|
+
log21.warn("no pong received within 10s; closing stale connection");
|
|
12200
12204
|
try {
|
|
12201
12205
|
this.activeSocket?.terminate();
|
|
12202
12206
|
} catch {
|
|
@@ -12266,7 +12270,7 @@ var ConnectionManager = class {
|
|
|
12266
12270
|
replayBufferedOutput(sendChunk) {
|
|
12267
12271
|
const buffered = this.outputBuffer.drain();
|
|
12268
12272
|
if (buffered.length > 0) {
|
|
12269
|
-
|
|
12273
|
+
log21.info("replaying buffered output chunks", {
|
|
12270
12274
|
chunks: buffered.length,
|
|
12271
12275
|
bytes: buffered.reduce((sum, c) => sum + Buffer.byteLength(c, "utf8"), 0)
|
|
12272
12276
|
});
|
|
@@ -12308,7 +12312,7 @@ var import_child_process6 = require("child_process");
|
|
|
12308
12312
|
var fs5 = __toESM(require("fs"));
|
|
12309
12313
|
var os8 = __toESM(require("os"));
|
|
12310
12314
|
var path4 = __toESM(require("path"));
|
|
12311
|
-
var
|
|
12315
|
+
var log22 = createLogger("service");
|
|
12312
12316
|
function assertConfiguredAgentToken() {
|
|
12313
12317
|
const config = loadConfig();
|
|
12314
12318
|
if (!config.agentToken) {
|
|
@@ -12374,7 +12378,7 @@ async function installSystemdService(opts = {}) {
|
|
|
12374
12378
|
await runSystemctlUser(["restart", SERVICE_NAME]);
|
|
12375
12379
|
}
|
|
12376
12380
|
saveConfigPatch({ serviceBackend: "systemd" });
|
|
12377
|
-
|
|
12381
|
+
log22.info("installed systemd user service", { path: SERVICE_FILE });
|
|
12378
12382
|
}
|
|
12379
12383
|
function readFallbackPid() {
|
|
12380
12384
|
try {
|
|
@@ -12413,7 +12417,7 @@ async function startPidfileService() {
|
|
|
12413
12417
|
assertConfiguredAgentToken();
|
|
12414
12418
|
const existingPid = readFallbackPid();
|
|
12415
12419
|
if (existingPid && isProcessRunning(existingPid)) {
|
|
12416
|
-
|
|
12420
|
+
log22.info("service already running", { pid: existingPid });
|
|
12417
12421
|
return;
|
|
12418
12422
|
}
|
|
12419
12423
|
ensureConfigDir();
|
|
@@ -12430,8 +12434,8 @@ async function startPidfileService() {
|
|
|
12430
12434
|
fs5.writeFileSync(FALLBACK_PID_FILE, `${child.pid}
|
|
12431
12435
|
`, "utf8");
|
|
12432
12436
|
saveConfigPatch({ serviceBackend: "pidfile" });
|
|
12433
|
-
|
|
12434
|
-
|
|
12437
|
+
log22.info("started fallback background service", { pid: child.pid });
|
|
12438
|
+
log22.info("logs", { path: FALLBACK_LOG_FILE });
|
|
12435
12439
|
}
|
|
12436
12440
|
async function stopPidfileService() {
|
|
12437
12441
|
const pid = readFallbackPid();
|
|
@@ -12440,7 +12444,7 @@ async function stopPidfileService() {
|
|
|
12440
12444
|
fs5.unlinkSync(FALLBACK_PID_FILE);
|
|
12441
12445
|
} catch {
|
|
12442
12446
|
}
|
|
12443
|
-
|
|
12447
|
+
log22.info("service is not running");
|
|
12444
12448
|
return;
|
|
12445
12449
|
}
|
|
12446
12450
|
process.kill(pid, "SIGTERM");
|
|
@@ -12455,7 +12459,7 @@ async function stopPidfileService() {
|
|
|
12455
12459
|
fs5.unlinkSync(FALLBACK_PID_FILE);
|
|
12456
12460
|
} catch {
|
|
12457
12461
|
}
|
|
12458
|
-
|
|
12462
|
+
log22.info("stopped fallback background service", { pid });
|
|
12459
12463
|
}
|
|
12460
12464
|
async function ensureServiceInstalled(opts = {}) {
|
|
12461
12465
|
const wantsSystemd = await canUseSystemdUser();
|
|
@@ -12467,7 +12471,7 @@ async function ensureServiceInstalled(opts = {}) {
|
|
|
12467
12471
|
if (opts.start) {
|
|
12468
12472
|
await startPidfileService();
|
|
12469
12473
|
} else {
|
|
12470
|
-
|
|
12474
|
+
log22.info("systemd user manager unavailable; using fallback pidfile backend");
|
|
12471
12475
|
}
|
|
12472
12476
|
return "pidfile";
|
|
12473
12477
|
}
|
|
@@ -12475,7 +12479,7 @@ async function startService() {
|
|
|
12475
12479
|
const backend = getConfiguredServiceBackend();
|
|
12476
12480
|
if (backend === "systemd") {
|
|
12477
12481
|
await runSystemctlUser(["start", SERVICE_NAME]);
|
|
12478
|
-
|
|
12482
|
+
log22.info("started service via systemd");
|
|
12479
12483
|
return;
|
|
12480
12484
|
}
|
|
12481
12485
|
if (backend === "pidfile") {
|
|
@@ -12488,7 +12492,7 @@ async function stopService() {
|
|
|
12488
12492
|
const backend = getConfiguredServiceBackend();
|
|
12489
12493
|
if (backend === "systemd") {
|
|
12490
12494
|
await runSystemctlUser(["stop", SERVICE_NAME]);
|
|
12491
|
-
|
|
12495
|
+
log22.info("stopped service via systemd");
|
|
12492
12496
|
return;
|
|
12493
12497
|
}
|
|
12494
12498
|
await stopPidfileService();
|
|
@@ -12506,7 +12510,7 @@ function killStaleAgentProcesses() {
|
|
|
12506
12510
|
try {
|
|
12507
12511
|
process.kill(pid, 0);
|
|
12508
12512
|
process.kill(pid, "SIGTERM");
|
|
12509
|
-
|
|
12513
|
+
log22.info("stopped stale agent process", { pid });
|
|
12510
12514
|
} catch {
|
|
12511
12515
|
}
|
|
12512
12516
|
fs5.unlinkSync(pidPath);
|
|
@@ -12521,7 +12525,7 @@ async function restartService() {
|
|
|
12521
12525
|
killStaleAgentProcesses();
|
|
12522
12526
|
if (backend === "systemd") {
|
|
12523
12527
|
await runSystemctlUser(["restart", SERVICE_NAME]);
|
|
12524
|
-
|
|
12528
|
+
log22.info("restarted service via systemd");
|
|
12525
12529
|
return;
|
|
12526
12530
|
}
|
|
12527
12531
|
await stopPidfileService();
|
|
@@ -12537,17 +12541,17 @@ async function printServiceStatus() {
|
|
|
12537
12541
|
if (status) {
|
|
12538
12542
|
process.stdout.write(status);
|
|
12539
12543
|
} else {
|
|
12540
|
-
|
|
12544
|
+
log22.info("systemd service is not installed or has no status");
|
|
12541
12545
|
}
|
|
12542
12546
|
return;
|
|
12543
12547
|
}
|
|
12544
12548
|
const pid = readFallbackPid();
|
|
12545
12549
|
if (pid && isProcessRunning(pid)) {
|
|
12546
|
-
|
|
12547
|
-
|
|
12550
|
+
log22.info("fallback service running", { pid });
|
|
12551
|
+
log22.info("logs", { path: FALLBACK_LOG_FILE });
|
|
12548
12552
|
return;
|
|
12549
12553
|
}
|
|
12550
|
-
|
|
12554
|
+
log22.info("service is not running");
|
|
12551
12555
|
}
|
|
12552
12556
|
async function printServiceLogs(opts) {
|
|
12553
12557
|
const lines = Number.parseInt(String(opts.lines || 100), 10) || 100;
|
|
@@ -12573,7 +12577,7 @@ async function printServiceLogs(opts) {
|
|
|
12573
12577
|
return;
|
|
12574
12578
|
}
|
|
12575
12579
|
if (!fs5.existsSync(FALLBACK_LOG_FILE)) {
|
|
12576
|
-
|
|
12580
|
+
log22.info("no log file found", { path: FALLBACK_LOG_FILE });
|
|
12577
12581
|
return;
|
|
12578
12582
|
}
|
|
12579
12583
|
if (follow) {
|
|
@@ -12605,7 +12609,7 @@ async function uninstallService() {
|
|
|
12605
12609
|
const config = loadConfig();
|
|
12606
12610
|
delete config.serviceBackend;
|
|
12607
12611
|
saveConfig(config);
|
|
12608
|
-
|
|
12612
|
+
log22.info("service uninstalled");
|
|
12609
12613
|
}
|
|
12610
12614
|
function requestStopSelfService() {
|
|
12611
12615
|
const backend = getConfiguredServiceBackend();
|
|
@@ -12956,7 +12960,7 @@ async function installLatestCliFromNpm() {
|
|
|
12956
12960
|
}
|
|
12957
12961
|
|
|
12958
12962
|
// src/control-dispatcher.ts
|
|
12959
|
-
var
|
|
12963
|
+
var log23 = createLogger("control");
|
|
12960
12964
|
var TMUX_RESYNC_DEBOUNCE_MS = 150;
|
|
12961
12965
|
var TMUX_INPUT_COALESCE_MS = 4;
|
|
12962
12966
|
function collectProcessTreePids(rootPid, visited = /* @__PURE__ */ new Set()) {
|
|
@@ -13028,7 +13032,7 @@ var ControlDispatcher = class {
|
|
|
13028
13032
|
/** Arrow form so callers can pass it directly without `.bind(this)`. */
|
|
13029
13033
|
emitNativeOps = (sessionId, ops, seq) => {
|
|
13030
13034
|
if (!this.sendNativeOps) {
|
|
13031
|
-
|
|
13035
|
+
log23.warn("native-stream ops produced but no transport sink installed", {
|
|
13032
13036
|
sessionId,
|
|
13033
13037
|
opCount: ops.length
|
|
13034
13038
|
});
|
|
@@ -13097,11 +13101,11 @@ var ControlDispatcher = class {
|
|
|
13097
13101
|
if (!sessionKey) return true;
|
|
13098
13102
|
const receivedHmac = payload.hmac;
|
|
13099
13103
|
if (typeof receivedHmac !== "string") {
|
|
13100
|
-
|
|
13104
|
+
log23.warn("control message missing HMAC signature");
|
|
13101
13105
|
return false;
|
|
13102
13106
|
}
|
|
13103
13107
|
if (!/^[0-9a-f]{64}$/i.test(receivedHmac)) {
|
|
13104
|
-
|
|
13108
|
+
log23.warn("control message has malformed HMAC signature");
|
|
13105
13109
|
return false;
|
|
13106
13110
|
}
|
|
13107
13111
|
const { hmac: _, ...payloadWithoutHmac } = payload;
|
|
@@ -13129,7 +13133,7 @@ var ControlDispatcher = class {
|
|
|
13129
13133
|
]);
|
|
13130
13134
|
if (dangerousActions.has(action) && this.connection.sessionKey && payload._verifiedEncrypted !== true) {
|
|
13131
13135
|
if (!this.verifyMessageHmac(payload)) {
|
|
13132
|
-
|
|
13136
|
+
log23.warn("rejecting control action \u2014 HMAC verification failed", { action });
|
|
13133
13137
|
return;
|
|
13134
13138
|
}
|
|
13135
13139
|
}
|
|
@@ -13175,10 +13179,10 @@ var ControlDispatcher = class {
|
|
|
13175
13179
|
this.transcriptWatcher?.disableMarkdown(sessionId);
|
|
13176
13180
|
try {
|
|
13177
13181
|
await stopTmuxPaneBySessionId(sessionId);
|
|
13178
|
-
|
|
13182
|
+
log23.info("closed remote session", { sessionId });
|
|
13179
13183
|
} catch (error) {
|
|
13180
13184
|
const message = error instanceof Error ? error.message : String(error);
|
|
13181
|
-
|
|
13185
|
+
log23.warn("failed to close session", { sessionId, error: message });
|
|
13182
13186
|
}
|
|
13183
13187
|
await delay(300);
|
|
13184
13188
|
await this.syncInventory(true);
|
|
@@ -13189,7 +13193,7 @@ var ControlDispatcher = class {
|
|
|
13189
13193
|
{
|
|
13190
13194
|
const pid = parseProcessPid2(sessionId);
|
|
13191
13195
|
if (pid === null) {
|
|
13192
|
-
|
|
13196
|
+
log23.warn("kill-process: could not parse PID", { sessionId });
|
|
13193
13197
|
return;
|
|
13194
13198
|
}
|
|
13195
13199
|
this.transcriptWatcher?.disableMarkdown(sessionId);
|
|
@@ -13205,11 +13209,11 @@ var ControlDispatcher = class {
|
|
|
13205
13209
|
const code = error.code;
|
|
13206
13210
|
if (code !== "ESRCH") {
|
|
13207
13211
|
const message = error instanceof Error ? error.message : String(error);
|
|
13208
|
-
|
|
13212
|
+
log23.warn("failed to send SIGTERM", { pid: targetPid, error: message });
|
|
13209
13213
|
}
|
|
13210
13214
|
}
|
|
13211
13215
|
}
|
|
13212
|
-
|
|
13216
|
+
log23.info("sent SIGTERM to processes", { signaled, sessionId });
|
|
13213
13217
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
13214
13218
|
for (const targetPid of processTree) {
|
|
13215
13219
|
if (!isProcessAlive(targetPid)) continue;
|
|
@@ -13219,13 +13223,13 @@ var ControlDispatcher = class {
|
|
|
13219
13223
|
const code = error.code;
|
|
13220
13224
|
if (code !== "ESRCH") {
|
|
13221
13225
|
const message = error instanceof Error ? error.message : String(error);
|
|
13222
|
-
|
|
13226
|
+
log23.warn("failed to send SIGKILL", { pid: targetPid, error: message });
|
|
13223
13227
|
}
|
|
13224
13228
|
}
|
|
13225
13229
|
}
|
|
13226
13230
|
} catch (error) {
|
|
13227
13231
|
const message = error instanceof Error ? error.message : String(error);
|
|
13228
|
-
|
|
13232
|
+
log23.warn("failed to kill session", { sessionId, error: message });
|
|
13229
13233
|
}
|
|
13230
13234
|
await this.syncInventory(true);
|
|
13231
13235
|
}
|
|
@@ -13240,7 +13244,7 @@ var ControlDispatcher = class {
|
|
|
13240
13244
|
}
|
|
13241
13245
|
}
|
|
13242
13246
|
const killed = await stopAllDetachedTmuxSessions();
|
|
13243
|
-
|
|
13247
|
+
log23.info("killed detached tmux sessions", { count: killed });
|
|
13244
13248
|
await delay(300);
|
|
13245
13249
|
await this.syncInventory(true);
|
|
13246
13250
|
return;
|
|
@@ -13280,18 +13284,18 @@ var ControlDispatcher = class {
|
|
|
13280
13284
|
const fileName = typeof payload.fileName === "string" ? payload.fileName : "attachment";
|
|
13281
13285
|
const contentBase64 = typeof payload.contentBase64 === "string" ? payload.contentBase64 : "";
|
|
13282
13286
|
if (!contentBase64 || contentBase64.length > 8 * 1024 * 1024) {
|
|
13283
|
-
|
|
13287
|
+
log23.warn("rejecting attachment upload \u2014 invalid size", { sessionId, fileName });
|
|
13284
13288
|
return;
|
|
13285
13289
|
}
|
|
13286
13290
|
let content;
|
|
13287
13291
|
try {
|
|
13288
13292
|
content = Buffer.from(contentBase64, "base64");
|
|
13289
13293
|
} catch {
|
|
13290
|
-
|
|
13294
|
+
log23.warn("rejecting attachment upload \u2014 invalid base64", { sessionId, fileName });
|
|
13291
13295
|
return;
|
|
13292
13296
|
}
|
|
13293
13297
|
if (content.length === 0 || content.length > 6 * 1024 * 1024) {
|
|
13294
|
-
|
|
13298
|
+
log23.warn("rejecting attachment upload \u2014 decoded size out of bounds", {
|
|
13295
13299
|
sessionId,
|
|
13296
13300
|
fileName,
|
|
13297
13301
|
bytes: content.length
|
|
@@ -13302,7 +13306,7 @@ var ControlDispatcher = class {
|
|
|
13302
13306
|
fs6.mkdirSync(uploadDir, { recursive: true, mode: 448 });
|
|
13303
13307
|
const writtenPath = path5.join(uploadDir, `${Date.now()}-${safeAttachmentName(fileName)}`);
|
|
13304
13308
|
fs6.writeFileSync(writtenPath, content, { mode: 384 });
|
|
13305
|
-
|
|
13309
|
+
log23.info("attachment uploaded", { sessionId, path: writtenPath, bytes: content.length });
|
|
13306
13310
|
this.handleInput(writtenPath, sessionId);
|
|
13307
13311
|
}
|
|
13308
13312
|
/** Handle input routing to the correct target. */
|
|
@@ -13365,7 +13369,7 @@ var ControlDispatcher = class {
|
|
|
13365
13369
|
this.tmuxResizeState.delete(sessionId);
|
|
13366
13370
|
}
|
|
13367
13371
|
const message = error instanceof Error ? error.message : String(error);
|
|
13368
|
-
|
|
13372
|
+
log23.warn("tmux resize failed", { sessionId, error: message });
|
|
13369
13373
|
});
|
|
13370
13374
|
} else if (sessionId.startsWith("screen:") || sessionId.startsWith("zellij:")) {
|
|
13371
13375
|
this.streamer.resize(sessionId, cols, rows);
|
|
@@ -13373,30 +13377,30 @@ var ControlDispatcher = class {
|
|
|
13373
13377
|
}
|
|
13374
13378
|
/** Update the installed connector package, then restart when a service backend exists. */
|
|
13375
13379
|
async updateAgentFromNpm() {
|
|
13376
|
-
|
|
13380
|
+
log23.info("updating ragent CLI", { current: CURRENT_VERSION });
|
|
13377
13381
|
try {
|
|
13378
13382
|
await installLatestCliFromNpm();
|
|
13379
13383
|
} catch (error) {
|
|
13380
13384
|
const message = error instanceof Error ? error.message : String(error);
|
|
13381
|
-
|
|
13385
|
+
log23.error("failed to update ragent CLI", { error: message });
|
|
13382
13386
|
return;
|
|
13383
13387
|
}
|
|
13384
13388
|
const backend = getConfiguredServiceBackend();
|
|
13385
13389
|
if (!backend) {
|
|
13386
|
-
|
|
13390
|
+
log23.warn("ragent CLI updated, but no service backend is configured; restart the connector manually to use the new version");
|
|
13387
13391
|
return;
|
|
13388
13392
|
}
|
|
13389
|
-
|
|
13393
|
+
log23.info("ragent CLI updated; restarting connector service", { backend });
|
|
13390
13394
|
requestRestartSelfService();
|
|
13391
13395
|
}
|
|
13392
13396
|
/** Handle provision request from dashboard. */
|
|
13393
13397
|
async handleProvision(payload) {
|
|
13394
13398
|
const provReq = validateProvisionRequest(payload);
|
|
13395
13399
|
if (!provReq) {
|
|
13396
|
-
|
|
13400
|
+
log23.warn("rejecting provision request \u2014 malformed payload");
|
|
13397
13401
|
return;
|
|
13398
13402
|
}
|
|
13399
|
-
|
|
13403
|
+
log23.info("provision request", { name: provReq.manifest.name, provisionId: provReq.provisionId });
|
|
13400
13404
|
const sendProgress = (progress) => {
|
|
13401
13405
|
const ws = this.connection.activeSocket;
|
|
13402
13406
|
if (ws && ws.readyState === import_ws4.default.OPEN && this.connection.activeGroups.registryGroup) {
|
|
@@ -13444,7 +13448,7 @@ var ControlDispatcher = class {
|
|
|
13444
13448
|
status: "unavailable",
|
|
13445
13449
|
reason: result.reason
|
|
13446
13450
|
});
|
|
13447
|
-
|
|
13451
|
+
log23.info("could not enable markdown", { sessionId, reason: result.reason });
|
|
13448
13452
|
} else {
|
|
13449
13453
|
this.sendMarkdownStatus({
|
|
13450
13454
|
type: "markdown",
|
|
@@ -13478,24 +13482,24 @@ var ControlDispatcher = class {
|
|
|
13478
13482
|
const sessionName = typeof payload?.sessionName === "string" && payload.sessionName.trim().length > 0 ? payload.sessionName.trim() : `agent-${Date.now().toString(36)}`;
|
|
13479
13483
|
const cmd = typeof payload?.command === "string" && payload.command.trim().length > 0 ? payload.command.trim() : null;
|
|
13480
13484
|
if (!cmd) {
|
|
13481
|
-
|
|
13485
|
+
log23.warn("start-agent: no command provided, ignoring");
|
|
13482
13486
|
return;
|
|
13483
13487
|
}
|
|
13484
13488
|
if (sessionName.length > 128 || !/^[a-zA-Z0-9_-]+$/.test(sessionName)) {
|
|
13485
|
-
|
|
13489
|
+
log23.warn("start-agent: invalid session name, ignoring");
|
|
13486
13490
|
return;
|
|
13487
13491
|
}
|
|
13488
13492
|
const danger = detectDangerousCommand(cmd);
|
|
13489
13493
|
if (danger) {
|
|
13490
13494
|
if (!this._approvalEnforcer) {
|
|
13491
|
-
|
|
13495
|
+
log23.warn("start-agent: rejected dangerous command (no approval enforcer)", {
|
|
13492
13496
|
category: danger.category,
|
|
13493
13497
|
pattern: danger.pattern,
|
|
13494
13498
|
command: cmd
|
|
13495
13499
|
});
|
|
13496
13500
|
return;
|
|
13497
13501
|
}
|
|
13498
|
-
|
|
13502
|
+
log23.info("start-agent: dangerous command \u2014 awaiting approval", {
|
|
13499
13503
|
category: danger.category,
|
|
13500
13504
|
pattern: danger.pattern,
|
|
13501
13505
|
sessionName
|
|
@@ -13506,28 +13510,28 @@ var ControlDispatcher = class {
|
|
|
13506
13510
|
sessionName
|
|
13507
13511
|
);
|
|
13508
13512
|
if (response === null) {
|
|
13509
|
-
|
|
13513
|
+
log23.warn("start-agent: dangerous command not covered by approval policy, rejecting", {
|
|
13510
13514
|
category: danger.category,
|
|
13511
13515
|
command: cmd
|
|
13512
13516
|
});
|
|
13513
13517
|
return;
|
|
13514
13518
|
}
|
|
13515
13519
|
if (response.type !== "approved") {
|
|
13516
|
-
|
|
13520
|
+
log23.warn("start-agent: dangerous command denied or timed out", {
|
|
13517
13521
|
category: danger.category,
|
|
13518
13522
|
sessionName,
|
|
13519
13523
|
responseType: response.type
|
|
13520
13524
|
});
|
|
13521
13525
|
return;
|
|
13522
13526
|
}
|
|
13523
|
-
|
|
13527
|
+
log23.info("start-agent: dangerous command approved", {
|
|
13524
13528
|
category: danger.category,
|
|
13525
13529
|
sessionName
|
|
13526
13530
|
});
|
|
13527
13531
|
}
|
|
13528
13532
|
const rawWorkingDir = typeof payload?.workingDir === "string" && payload.workingDir.trim().length > 0 ? payload.workingDir.trim() : void 0;
|
|
13529
13533
|
if (rawWorkingDir !== void 0 && !isSafeWorkingDir(rawWorkingDir)) {
|
|
13530
|
-
|
|
13534
|
+
log23.warn("start-agent: rejected unsafe workingDir", { workingDir: rawWorkingDir });
|
|
13531
13535
|
return;
|
|
13532
13536
|
}
|
|
13533
13537
|
const workingDir = rawWorkingDir;
|
|
@@ -13576,7 +13580,7 @@ var ControlDispatcher = class {
|
|
|
13576
13580
|
}
|
|
13577
13581
|
if (outcome) {
|
|
13578
13582
|
if (outcome.ok) {
|
|
13579
|
-
|
|
13583
|
+
log23.info("start-agent: launched native-stream", {
|
|
13580
13584
|
sessionId: outcome.sessionId,
|
|
13581
13585
|
kind: outcome.kind,
|
|
13582
13586
|
source: outcome.transcriptSource
|
|
@@ -13584,7 +13588,7 @@ var ControlDispatcher = class {
|
|
|
13584
13588
|
await this.syncInventory(true);
|
|
13585
13589
|
return;
|
|
13586
13590
|
}
|
|
13587
|
-
|
|
13591
|
+
log23.info(
|
|
13588
13592
|
"start-agent: native-stream unavailable; falling through to tmux file-watch",
|
|
13589
13593
|
{ reason: outcome.reason, kind: outcome.kind }
|
|
13590
13594
|
);
|
|
@@ -13604,11 +13608,11 @@ var ControlDispatcher = class {
|
|
|
13604
13608
|
let tmuxLaunchOk = false;
|
|
13605
13609
|
try {
|
|
13606
13610
|
(0, import_child_process8.execFileSync)("tmux", tmuxArgs, { stdio: "ignore" });
|
|
13607
|
-
|
|
13611
|
+
log23.info("started agent session", { sessionName, command: cmd });
|
|
13608
13612
|
tmuxLaunchOk = true;
|
|
13609
13613
|
} catch (error) {
|
|
13610
13614
|
const message = error instanceof Error ? error.message : String(error);
|
|
13611
|
-
|
|
13615
|
+
log23.error("failed to start agent session", { sessionName, error: message });
|
|
13612
13616
|
}
|
|
13613
13617
|
if (tmuxLaunchOk && pendingFallbackOutcome && pendingFallbackOutcome.userMessage) {
|
|
13614
13618
|
const tmuxSessionId = this.discoverNewlyCreatedPaneId(sessionName);
|
|
@@ -13647,7 +13651,7 @@ var ControlDispatcher = class {
|
|
|
13647
13651
|
return `tmux:${sessionName}:${info}`;
|
|
13648
13652
|
}
|
|
13649
13653
|
} catch (err) {
|
|
13650
|
-
|
|
13654
|
+
log23.warn("display-message failed; status block target may not bind", {
|
|
13651
13655
|
sessionName,
|
|
13652
13656
|
err: err instanceof Error ? err.message : String(err)
|
|
13653
13657
|
});
|
|
@@ -13915,7 +13919,7 @@ function isGrantExpired(grant) {
|
|
|
13915
13919
|
}
|
|
13916
13920
|
|
|
13917
13921
|
// src/approval-enforcer.ts
|
|
13918
|
-
var
|
|
13922
|
+
var log24 = createLogger("approval");
|
|
13919
13923
|
var ApprovalEnforcer = class {
|
|
13920
13924
|
policy;
|
|
13921
13925
|
secret;
|
|
@@ -13939,7 +13943,7 @@ var ApprovalEnforcer = class {
|
|
|
13939
13943
|
async checkCommand(command, hostId, sessionId) {
|
|
13940
13944
|
const request = matchPolicy(command, this.policy, hostId, sessionId);
|
|
13941
13945
|
if (!request) return null;
|
|
13942
|
-
|
|
13946
|
+
log24.info("approval pending", {
|
|
13943
13947
|
action: request.action,
|
|
13944
13948
|
severity: request.severity,
|
|
13945
13949
|
requestId: request.requestId
|
|
@@ -13948,7 +13952,7 @@ var ApprovalEnforcer = class {
|
|
|
13948
13952
|
const timer = setTimeout(() => {
|
|
13949
13953
|
this.pendingRequests.delete(request.requestId);
|
|
13950
13954
|
const response = { type: "timeout" };
|
|
13951
|
-
|
|
13955
|
+
log24.info("approval timeout", {
|
|
13952
13956
|
action: request.action,
|
|
13953
13957
|
severity: request.severity,
|
|
13954
13958
|
requestId: request.requestId
|
|
@@ -13970,7 +13974,7 @@ var ApprovalEnforcer = class {
|
|
|
13970
13974
|
* {@link handleResponse}.
|
|
13971
13975
|
*/
|
|
13972
13976
|
async requestApproval(request) {
|
|
13973
|
-
|
|
13977
|
+
log24.info("approval pending (direct)", {
|
|
13974
13978
|
action: request.action,
|
|
13975
13979
|
severity: request.severity,
|
|
13976
13980
|
requestId: request.requestId
|
|
@@ -13979,7 +13983,7 @@ var ApprovalEnforcer = class {
|
|
|
13979
13983
|
const timer = setTimeout(() => {
|
|
13980
13984
|
this.pendingRequests.delete(request.requestId);
|
|
13981
13985
|
const response = { type: "timeout" };
|
|
13982
|
-
|
|
13986
|
+
log24.info("approval timeout (direct)", {
|
|
13983
13987
|
action: request.action,
|
|
13984
13988
|
severity: request.severity,
|
|
13985
13989
|
requestId: request.requestId
|
|
@@ -13999,7 +14003,7 @@ var ApprovalEnforcer = class {
|
|
|
13999
14003
|
if (!requestId) return;
|
|
14000
14004
|
const pending = this.pendingRequests.get(requestId);
|
|
14001
14005
|
if (!pending) {
|
|
14002
|
-
|
|
14006
|
+
log24.warn("received response for unknown approval request", { requestId });
|
|
14003
14007
|
return;
|
|
14004
14008
|
}
|
|
14005
14009
|
if (response.type === "approved") {
|
|
@@ -14008,7 +14012,7 @@ var ApprovalEnforcer = class {
|
|
|
14008
14012
|
pending.request
|
|
14009
14013
|
);
|
|
14010
14014
|
if (bindingMismatch) {
|
|
14011
|
-
|
|
14015
|
+
log24.info("approval rejected (grant binding mismatch)", {
|
|
14012
14016
|
action: pending.request.action,
|
|
14013
14017
|
severity: pending.request.severity,
|
|
14014
14018
|
requestId,
|
|
@@ -14028,7 +14032,7 @@ var ApprovalEnforcer = class {
|
|
|
14028
14032
|
return;
|
|
14029
14033
|
}
|
|
14030
14034
|
if (!this.acceptGrant(response.grant)) {
|
|
14031
|
-
|
|
14035
|
+
log24.info("approval rejected (invalid grant)", {
|
|
14032
14036
|
action: pending.request.action,
|
|
14033
14037
|
severity: pending.request.severity,
|
|
14034
14038
|
requestId
|
|
@@ -14038,13 +14042,13 @@ var ApprovalEnforcer = class {
|
|
|
14038
14042
|
pending.resolve({ type: "denied", denial: { requestId, deniedBy: "system", deniedAt: (/* @__PURE__ */ new Date()).toISOString(), reason: "Invalid or expired grant" } });
|
|
14039
14043
|
return;
|
|
14040
14044
|
}
|
|
14041
|
-
|
|
14045
|
+
log24.info("approval approved", {
|
|
14042
14046
|
action: pending.request.action,
|
|
14043
14047
|
severity: pending.request.severity,
|
|
14044
14048
|
requestId
|
|
14045
14049
|
});
|
|
14046
14050
|
} else if (response.type === "denied") {
|
|
14047
|
-
|
|
14051
|
+
log24.info("approval denied", {
|
|
14048
14052
|
action: pending.request.action,
|
|
14049
14053
|
severity: pending.request.severity,
|
|
14050
14054
|
requestId,
|
|
@@ -14085,7 +14089,7 @@ var ApprovalEnforcer = class {
|
|
|
14085
14089
|
for (const [id, entry] of this.pendingRequests) {
|
|
14086
14090
|
clearTimeout(entry.timer);
|
|
14087
14091
|
this.pendingRequests.delete(id);
|
|
14088
|
-
|
|
14092
|
+
log24.info("approval stopped", {
|
|
14089
14093
|
action: entry.request.action,
|
|
14090
14094
|
severity: entry.request.severity,
|
|
14091
14095
|
requestId: id
|
|
@@ -14121,7 +14125,7 @@ var ApprovalEnforcer = class {
|
|
|
14121
14125
|
if (entry.request.sessionId !== sessionId) continue;
|
|
14122
14126
|
clearTimeout(entry.timer);
|
|
14123
14127
|
this.pendingRequests.delete(id);
|
|
14124
|
-
|
|
14128
|
+
log24.info("approval cancelled (session torn down)", {
|
|
14125
14129
|
action: entry.request.action,
|
|
14126
14130
|
severity: entry.request.severity,
|
|
14127
14131
|
requestId: id,
|
|
@@ -14176,7 +14180,7 @@ var ApprovalEnforcer = class {
|
|
|
14176
14180
|
};
|
|
14177
14181
|
|
|
14178
14182
|
// src/agent.ts
|
|
14179
|
-
var
|
|
14183
|
+
var log25 = createLogger("agent");
|
|
14180
14184
|
var MAX_TRANSPORT_CHUNK_BYTES = 24 * 1024;
|
|
14181
14185
|
function pidFilePath(hostId) {
|
|
14182
14186
|
return path6.join(CONFIG_DIR, `agent-${hostId}.pid`);
|
|
@@ -14261,15 +14265,15 @@ async function runAgent(rawOptions) {
|
|
|
14261
14265
|
const config = loadConfig();
|
|
14262
14266
|
if (config.redaction?.enabled === false || rawOptions.redact === false) {
|
|
14263
14267
|
setRedactionEnabled(false);
|
|
14264
|
-
|
|
14268
|
+
log25.info("secret redaction disabled");
|
|
14265
14269
|
}
|
|
14266
|
-
|
|
14267
|
-
|
|
14270
|
+
log25.info("connector started", { hostName: options.hostName, hostId: options.hostId });
|
|
14271
|
+
log25.info("portal", { portal: options.portal });
|
|
14268
14272
|
try {
|
|
14269
14273
|
const existingSessions = await collectSessionInventory(options.hostId, options.command);
|
|
14270
14274
|
const tmuxCount = existingSessions.filter((s) => s.type === "tmux").length;
|
|
14271
14275
|
if (tmuxCount > 0) {
|
|
14272
|
-
|
|
14276
|
+
log25.info("found existing tmux sessions", { count: tmuxCount });
|
|
14273
14277
|
}
|
|
14274
14278
|
} catch {
|
|
14275
14279
|
}
|
|
@@ -14503,7 +14507,7 @@ async function runAgent(rawOptions) {
|
|
|
14503
14507
|
dispatcher.setApprovalEnforcer(null);
|
|
14504
14508
|
}
|
|
14505
14509
|
ws.on("open", async () => {
|
|
14506
|
-
|
|
14510
|
+
log25.info("connector connected to relay");
|
|
14507
14511
|
conn.resetReconnectDelay();
|
|
14508
14512
|
ws.send(JSON.stringify({ type: "joinGroup", group: groups.privateGroup }));
|
|
14509
14513
|
ws.send(JSON.stringify({ type: "joinGroup", group: groups.registryGroup }));
|
|
@@ -14571,9 +14575,9 @@ async function runAgent(rawOptions) {
|
|
|
14571
14575
|
const inSeq = typeof payload.seq === "number" ? payload.seq : null;
|
|
14572
14576
|
const rxSeq = conn.getRxSeqForViewer(payload.viewerId);
|
|
14573
14577
|
if (inSeq === null) {
|
|
14574
|
-
|
|
14578
|
+
log25.warn("rejecting encrypted input frame without seq (#310)");
|
|
14575
14579
|
} else if (!rxSeq.accept(inSeq)) {
|
|
14576
|
-
|
|
14580
|
+
log25.warn("rejecting replayed/out-of-order input", {
|
|
14577
14581
|
seq: inSeq,
|
|
14578
14582
|
lastSeen: rxSeq.lastSeq,
|
|
14579
14583
|
viewerId: typeof payload.viewerId === "string" ? payload.viewerId : "_legacy"
|
|
@@ -14586,13 +14590,13 @@ async function runAgent(rawOptions) {
|
|
|
14586
14590
|
inSeq
|
|
14587
14591
|
);
|
|
14588
14592
|
if (inputData === null) {
|
|
14589
|
-
|
|
14593
|
+
log25.warn("failed to decrypt input \u2014 ignoring");
|
|
14590
14594
|
}
|
|
14591
14595
|
}
|
|
14592
14596
|
} else if (typeof payload.data === "string" && !conn.sessionKey) {
|
|
14593
14597
|
inputData = payload.data;
|
|
14594
14598
|
} else if (conn.sessionKey) {
|
|
14595
|
-
|
|
14599
|
+
log25.warn("rejecting plaintext input frame while encrypted channel is active");
|
|
14596
14600
|
}
|
|
14597
14601
|
if (inputData !== null) {
|
|
14598
14602
|
const sid = typeof payload.sessionId === "string" ? payload.sessionId.trim() : "";
|
|
@@ -14600,7 +14604,7 @@ async function runAgent(rawOptions) {
|
|
|
14600
14604
|
}
|
|
14601
14605
|
} else if (payload.type === "resize" && Number.isInteger(payload.cols) && Number.isInteger(payload.rows)) {
|
|
14602
14606
|
if (conn.sessionKey && !dispatcher.verifyMessageHmac(payload)) {
|
|
14603
|
-
|
|
14607
|
+
log25.warn("rejecting resize frame \u2014 HMAC verification failed");
|
|
14604
14608
|
return;
|
|
14605
14609
|
}
|
|
14606
14610
|
const sid = typeof payload.sessionId === "string" ? payload.sessionId.trim() : "";
|
|
@@ -14612,11 +14616,11 @@ async function runAgent(rawOptions) {
|
|
|
14612
14616
|
const inSeq = typeof payload.seq === "number" ? payload.seq : null;
|
|
14613
14617
|
const rxSeq = conn.getRxSeqForViewer(payload.viewerId);
|
|
14614
14618
|
if (inSeq === null) {
|
|
14615
|
-
|
|
14619
|
+
log25.warn("rejecting encrypted control frame without seq");
|
|
14616
14620
|
return;
|
|
14617
14621
|
}
|
|
14618
14622
|
if (!rxSeq.accept(inSeq)) {
|
|
14619
|
-
|
|
14623
|
+
log25.warn("rejecting replayed/out-of-order control", {
|
|
14620
14624
|
seq: inSeq,
|
|
14621
14625
|
lastSeen: rxSeq.lastSeq,
|
|
14622
14626
|
viewerId: typeof payload.viewerId === "string" ? payload.viewerId : "_legacy"
|
|
@@ -14630,7 +14634,7 @@ async function runAgent(rawOptions) {
|
|
|
14630
14634
|
inSeq
|
|
14631
14635
|
);
|
|
14632
14636
|
if (decrypted === null) {
|
|
14633
|
-
|
|
14637
|
+
log25.warn("failed to decrypt control \u2014 ignoring");
|
|
14634
14638
|
return;
|
|
14635
14639
|
}
|
|
14636
14640
|
try {
|
|
@@ -14639,11 +14643,11 @@ async function runAgent(rawOptions) {
|
|
|
14639
14643
|
_verifiedEncrypted: true
|
|
14640
14644
|
};
|
|
14641
14645
|
} catch {
|
|
14642
|
-
|
|
14646
|
+
log25.warn("failed to parse encrypted control payload");
|
|
14643
14647
|
return;
|
|
14644
14648
|
}
|
|
14645
14649
|
} else if (conn.sessionKey && payload.action === "upload-file") {
|
|
14646
|
-
|
|
14650
|
+
log25.warn("rejecting plaintext attachment upload while encrypted channel is active");
|
|
14647
14651
|
return;
|
|
14648
14652
|
}
|
|
14649
14653
|
await dispatcher.handleControlAction(controlPayload);
|
|
@@ -14663,13 +14667,13 @@ async function runAgent(rawOptions) {
|
|
|
14663
14667
|
}
|
|
14664
14668
|
});
|
|
14665
14669
|
ws.on("error", (error) => {
|
|
14666
|
-
|
|
14670
|
+
log25.error("websocket error", { error: error.message });
|
|
14667
14671
|
});
|
|
14668
14672
|
ws.on("close", (code, reason) => {
|
|
14669
14673
|
const fields = {};
|
|
14670
14674
|
if (code) fields.code = code;
|
|
14671
14675
|
if (reason.length > 0) fields.reason = reason.toString();
|
|
14672
|
-
|
|
14676
|
+
log25.info("relay disconnected; output will be buffered until reconnect", fields);
|
|
14673
14677
|
conn.cleanup();
|
|
14674
14678
|
resolve();
|
|
14675
14679
|
});
|
|
@@ -14686,35 +14690,35 @@ async function runAgent(rawOptions) {
|
|
|
14686
14690
|
});
|
|
14687
14691
|
inventory.updateOptions(options);
|
|
14688
14692
|
dispatcher.updateOptions(options);
|
|
14689
|
-
|
|
14693
|
+
log25.info("recovered from auth failure via machine credential; reconnecting");
|
|
14690
14694
|
continue;
|
|
14691
14695
|
} catch (mcError) {
|
|
14692
14696
|
if (mcError instanceof AuthError) {
|
|
14693
|
-
|
|
14697
|
+
log25.error("auth recovery failed", { error: mcError.message });
|
|
14694
14698
|
} else {
|
|
14695
14699
|
const mcMsg = mcError instanceof Error ? mcError.message : String(mcError);
|
|
14696
|
-
|
|
14700
|
+
log25.error("machine credential recovery failed", { error: mcMsg });
|
|
14697
14701
|
}
|
|
14698
14702
|
}
|
|
14699
14703
|
}
|
|
14700
|
-
|
|
14701
|
-
|
|
14704
|
+
log25.error("auth error", { error: error.message });
|
|
14705
|
+
log25.error(
|
|
14702
14706
|
"connector token is invalid or revoked. Stopping. Re-connect with: ragent connect --token <token>"
|
|
14703
14707
|
);
|
|
14704
14708
|
dispatcher.shouldRun = false;
|
|
14705
14709
|
break;
|
|
14706
14710
|
}
|
|
14707
14711
|
const message = error instanceof Error ? error.message : String(error);
|
|
14708
|
-
|
|
14712
|
+
log25.error("relay connect failed", { error: message });
|
|
14709
14713
|
}
|
|
14710
14714
|
if (!dispatcher.shouldRun) break;
|
|
14711
14715
|
if (dispatcher.reconnectRequested) {
|
|
14712
|
-
|
|
14716
|
+
log25.info("reconnecting to relay");
|
|
14713
14717
|
await wait(300);
|
|
14714
14718
|
continue;
|
|
14715
14719
|
}
|
|
14716
14720
|
const jitteredDelay = conn.reconnectDelay * (0.5 + Math.random());
|
|
14717
|
-
|
|
14721
|
+
log25.info("disconnected; reconnecting", { seconds: Math.round(jitteredDelay / 1e3) });
|
|
14718
14722
|
await wait(jitteredDelay);
|
|
14719
14723
|
conn.reconnectDelay = Math.min(conn.reconnectDelay * 1.5, MAX_RECONNECT_DELAY_MS);
|
|
14720
14724
|
}
|
|
@@ -14814,7 +14818,7 @@ function printCommandArt(title, subtitle = "remote agent control") {
|
|
|
14814
14818
|
}
|
|
14815
14819
|
|
|
14816
14820
|
// src/commands/connect.ts
|
|
14817
|
-
var
|
|
14821
|
+
var log26 = createLogger("connect");
|
|
14818
14822
|
async function connectMachine(opts) {
|
|
14819
14823
|
const portal = opts.portal || DEFAULT_PORTAL;
|
|
14820
14824
|
const hostId = sanitizeHostId(opts.id || inferHostId());
|
|
@@ -14846,12 +14850,12 @@ async function connectMachine(opts) {
|
|
|
14846
14850
|
nextConfig.refreshExpiresAt = claimed.refreshExpiresAt;
|
|
14847
14851
|
}
|
|
14848
14852
|
saveConfig(nextConfig);
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14853
|
+
log26.info("machine connected", { hostName, hostId });
|
|
14854
|
+
log26.info("machine name (override with --name <friendly-name>)", { hostName });
|
|
14855
|
+
log26.info("config saved", { path: CONFIG_FILE });
|
|
14852
14856
|
if (opts.asService) {
|
|
14853
14857
|
await ensureServiceInstalled({ enable: true, start: true });
|
|
14854
|
-
|
|
14858
|
+
log26.info("service mode enabled");
|
|
14855
14859
|
return;
|
|
14856
14860
|
}
|
|
14857
14861
|
if (opts.run !== false) {
|
|
@@ -14871,14 +14875,14 @@ function registerConnectCommand(program2) {
|
|
|
14871
14875
|
await connectMachine(opts);
|
|
14872
14876
|
} catch (error) {
|
|
14873
14877
|
const message = error instanceof Error ? error.message : String(error);
|
|
14874
|
-
|
|
14878
|
+
log26.error("connect failed", { error: message });
|
|
14875
14879
|
process.exit(1);
|
|
14876
14880
|
}
|
|
14877
14881
|
});
|
|
14878
14882
|
}
|
|
14879
14883
|
|
|
14880
14884
|
// src/commands/run.ts
|
|
14881
|
-
var
|
|
14885
|
+
var log27 = createLogger("run");
|
|
14882
14886
|
function registerRunCommand(program2) {
|
|
14883
14887
|
program2.command("run").description("Run connector for a connected machine").option("--portal <url>", "Portal base URL").option("--agent-token <token>", "Connector token").option("-i, --id <id>", "Machine ID").option("-n, --name <name>", "Machine name").option("-c, --command <command>", "CLI command to run").option("--no-redact", "Disable secret redaction in terminal output").action(async (opts) => {
|
|
14884
14888
|
try {
|
|
@@ -14886,7 +14890,7 @@ function registerRunCommand(program2) {
|
|
|
14886
14890
|
await runAgent(opts);
|
|
14887
14891
|
} catch (error) {
|
|
14888
14892
|
const message = error instanceof Error ? error.message : String(error);
|
|
14889
|
-
|
|
14893
|
+
log27.error("run failed", { error: message });
|
|
14890
14894
|
process.exit(1);
|
|
14891
14895
|
}
|
|
14892
14896
|
});
|
|
@@ -14894,7 +14898,7 @@ function registerRunCommand(program2) {
|
|
|
14894
14898
|
|
|
14895
14899
|
// src/commands/doctor.ts
|
|
14896
14900
|
var os12 = __toESM(require("os"));
|
|
14897
|
-
var
|
|
14901
|
+
var log28 = createLogger("doctor");
|
|
14898
14902
|
async function runDoctor(opts) {
|
|
14899
14903
|
const options = resolveRunOptions(opts);
|
|
14900
14904
|
const checks = [];
|
|
@@ -14930,27 +14934,27 @@ async function runDoctor(opts) {
|
|
|
14930
14934
|
console.log(`${check.ok ? "PASS" : "FAIL"} ${check.name}: ${check.detail}`);
|
|
14931
14935
|
});
|
|
14932
14936
|
if (!platformOk) {
|
|
14933
|
-
|
|
14937
|
+
log28.info("Linux is required for the connector");
|
|
14934
14938
|
}
|
|
14935
14939
|
if (!tmuxOk) {
|
|
14936
14940
|
const recipe = await chooseTmuxInstallCommand();
|
|
14937
14941
|
if (recipe) {
|
|
14938
|
-
|
|
14942
|
+
log28.info("tmux install suggestion", { command: recipe.command });
|
|
14939
14943
|
}
|
|
14940
14944
|
if (opts.fix) {
|
|
14941
14945
|
try {
|
|
14942
14946
|
const installed = await installTmuxInteractively();
|
|
14943
14947
|
if (installed) {
|
|
14944
|
-
|
|
14948
|
+
log28.info("tmux installation complete");
|
|
14945
14949
|
} else {
|
|
14946
|
-
|
|
14950
|
+
log28.info("tmux installation skipped or incomplete");
|
|
14947
14951
|
}
|
|
14948
14952
|
} catch (error) {
|
|
14949
14953
|
const message = error instanceof Error ? error.message : String(error);
|
|
14950
|
-
|
|
14954
|
+
log28.error("failed to install tmux", { error: message });
|
|
14951
14955
|
}
|
|
14952
14956
|
} else {
|
|
14953
|
-
|
|
14957
|
+
log28.info("run `ragent doctor --fix` to install missing dependencies interactively");
|
|
14954
14958
|
}
|
|
14955
14959
|
}
|
|
14956
14960
|
}
|
|
@@ -14962,30 +14966,30 @@ function registerDoctorCommand(program2) {
|
|
|
14962
14966
|
}
|
|
14963
14967
|
|
|
14964
14968
|
// src/commands/update.ts
|
|
14965
|
-
var
|
|
14969
|
+
var log29 = createLogger("update");
|
|
14966
14970
|
function registerUpdateCommand(program2) {
|
|
14967
14971
|
program2.command("update").description("Update ragent CLI from npm").option("--check", "Check for updates only; do not install").option("--no-restart", "Do not restart the service after updating").action(async (opts) => {
|
|
14968
14972
|
printCommandArt("Update", "checking npm registry for newer ragent-cli versions");
|
|
14969
14973
|
const latestVersion = await checkForUpdate({ force: true });
|
|
14970
14974
|
if (!latestVersion) {
|
|
14971
|
-
|
|
14975
|
+
log29.info("you are up to date", { version: CURRENT_VERSION });
|
|
14972
14976
|
return;
|
|
14973
14977
|
}
|
|
14974
|
-
|
|
14978
|
+
log29.info("update available", { current: CURRENT_VERSION, latest: latestVersion });
|
|
14975
14979
|
if (opts.check) return;
|
|
14976
14980
|
await installLatestCliFromNpm();
|
|
14977
|
-
|
|
14981
|
+
log29.info("updated", { version: latestVersion });
|
|
14978
14982
|
const backend = getConfiguredServiceBackend();
|
|
14979
14983
|
if (backend && opts.restart) {
|
|
14980
|
-
|
|
14984
|
+
log29.info("restarting service", { backend });
|
|
14981
14985
|
try {
|
|
14982
14986
|
await restartService();
|
|
14983
14987
|
} catch (err) {
|
|
14984
|
-
|
|
14985
|
-
|
|
14988
|
+
log29.error("failed to restart service", { error: err instanceof Error ? err.message : String(err) });
|
|
14989
|
+
log29.info("please restart manually: ragent service restart");
|
|
14986
14990
|
}
|
|
14987
14991
|
} else if (!backend) {
|
|
14988
|
-
|
|
14992
|
+
log29.info("no service backend detected. If ragent is running manually, restart it to use the new version");
|
|
14989
14993
|
}
|
|
14990
14994
|
});
|
|
14991
14995
|
}
|
|
@@ -15009,7 +15013,7 @@ function registerSessionsCommand(program2) {
|
|
|
15009
15013
|
}
|
|
15010
15014
|
|
|
15011
15015
|
// src/commands/service.ts
|
|
15012
|
-
var
|
|
15016
|
+
var log30 = createLogger("service-cmd");
|
|
15013
15017
|
function registerServiceCommand(program2) {
|
|
15014
15018
|
const service = program2.command("service").description("Manage background connector service");
|
|
15015
15019
|
service.command("install").description("Install service (systemd user unit when available)").option("--start", "Start service immediately").option("--no-enable", "Do not enable autostart in systemd").action(async (opts) => {
|
|
@@ -15020,7 +15024,7 @@ function registerServiceCommand(program2) {
|
|
|
15020
15024
|
});
|
|
15021
15025
|
} catch (error) {
|
|
15022
15026
|
const message = error instanceof Error ? error.message : String(error);
|
|
15023
|
-
|
|
15027
|
+
log30.error("service install failed", { error: message });
|
|
15024
15028
|
process.exit(1);
|
|
15025
15029
|
}
|
|
15026
15030
|
});
|
|
@@ -15029,7 +15033,7 @@ function registerServiceCommand(program2) {
|
|
|
15029
15033
|
await startService();
|
|
15030
15034
|
} catch (error) {
|
|
15031
15035
|
const message = error instanceof Error ? error.message : String(error);
|
|
15032
|
-
|
|
15036
|
+
log30.error("service start failed", { error: message });
|
|
15033
15037
|
process.exit(1);
|
|
15034
15038
|
}
|
|
15035
15039
|
});
|
|
@@ -15038,7 +15042,7 @@ function registerServiceCommand(program2) {
|
|
|
15038
15042
|
await stopService();
|
|
15039
15043
|
} catch (error) {
|
|
15040
15044
|
const message = error instanceof Error ? error.message : String(error);
|
|
15041
|
-
|
|
15045
|
+
log30.error("service stop failed", { error: message });
|
|
15042
15046
|
process.exit(1);
|
|
15043
15047
|
}
|
|
15044
15048
|
});
|
|
@@ -15047,7 +15051,7 @@ function registerServiceCommand(program2) {
|
|
|
15047
15051
|
await restartService();
|
|
15048
15052
|
} catch (error) {
|
|
15049
15053
|
const message = error instanceof Error ? error.message : String(error);
|
|
15050
|
-
|
|
15054
|
+
log30.error("service restart failed", { error: message });
|
|
15051
15055
|
process.exit(1);
|
|
15052
15056
|
}
|
|
15053
15057
|
});
|
|
@@ -15056,7 +15060,7 @@ function registerServiceCommand(program2) {
|
|
|
15056
15060
|
await printServiceStatus();
|
|
15057
15061
|
} catch (error) {
|
|
15058
15062
|
const message = error instanceof Error ? error.message : String(error);
|
|
15059
|
-
|
|
15063
|
+
log30.error("service status failed", { error: message });
|
|
15060
15064
|
process.exit(1);
|
|
15061
15065
|
}
|
|
15062
15066
|
});
|
|
@@ -15065,7 +15069,7 @@ function registerServiceCommand(program2) {
|
|
|
15065
15069
|
await printServiceLogs(opts);
|
|
15066
15070
|
} catch (error) {
|
|
15067
15071
|
const message = error instanceof Error ? error.message : String(error);
|
|
15068
|
-
|
|
15072
|
+
log30.error("service logs failed", { error: message });
|
|
15069
15073
|
process.exit(1);
|
|
15070
15074
|
}
|
|
15071
15075
|
});
|
|
@@ -15074,7 +15078,7 @@ function registerServiceCommand(program2) {
|
|
|
15074
15078
|
await uninstallService();
|
|
15075
15079
|
} catch (error) {
|
|
15076
15080
|
const message = error instanceof Error ? error.message : String(error);
|
|
15077
|
-
|
|
15081
|
+
log30.error("service uninstall failed", { error: message });
|
|
15078
15082
|
process.exit(1);
|
|
15079
15083
|
}
|
|
15080
15084
|
});
|
|
@@ -15082,7 +15086,7 @@ function registerServiceCommand(program2) {
|
|
|
15082
15086
|
|
|
15083
15087
|
// src/commands/uninstall.ts
|
|
15084
15088
|
var fs8 = __toESM(require("fs"));
|
|
15085
|
-
var
|
|
15089
|
+
var log31 = createLogger("uninstall");
|
|
15086
15090
|
async function uninstallAgent(opts) {
|
|
15087
15091
|
const config = loadConfig();
|
|
15088
15092
|
const hostName = config.hostName || config.hostId || "this machine";
|
|
@@ -15092,12 +15096,12 @@ async function uninstallAgent(opts) {
|
|
|
15092
15096
|
false
|
|
15093
15097
|
);
|
|
15094
15098
|
if (!confirmed) {
|
|
15095
|
-
|
|
15099
|
+
log31.info("uninstall cancelled");
|
|
15096
15100
|
return;
|
|
15097
15101
|
}
|
|
15098
15102
|
}
|
|
15099
15103
|
if (config.portal && config.agentToken) {
|
|
15100
|
-
|
|
15104
|
+
log31.info("deregistering from server");
|
|
15101
15105
|
try {
|
|
15102
15106
|
const token = await refreshTokenIfNeeded({
|
|
15103
15107
|
portal: config.portal,
|
|
@@ -15105,24 +15109,24 @@ async function uninstallAgent(opts) {
|
|
|
15105
15109
|
});
|
|
15106
15110
|
const ok = await deregisterHost({ portal: config.portal, agentToken: token });
|
|
15107
15111
|
if (ok) {
|
|
15108
|
-
|
|
15112
|
+
log31.info("host removed from server");
|
|
15109
15113
|
}
|
|
15110
15114
|
} catch {
|
|
15111
|
-
|
|
15115
|
+
log31.warn("could not reach server \u2014 host may need manual removal from dashboard");
|
|
15112
15116
|
}
|
|
15113
15117
|
}
|
|
15114
|
-
|
|
15118
|
+
log31.info("stopping and removing service");
|
|
15115
15119
|
await uninstallService().catch(() => void 0);
|
|
15116
15120
|
if (fs8.existsSync(CONFIG_DIR)) {
|
|
15117
15121
|
fs8.rmSync(CONFIG_DIR, { recursive: true, force: true });
|
|
15118
|
-
|
|
15122
|
+
log31.info("removed config directory", { path: CONFIG_DIR });
|
|
15119
15123
|
}
|
|
15120
15124
|
try {
|
|
15121
15125
|
await execAsync(`npm unlink -g ${PACKAGE_NAME}`, { timeout: 15e3 });
|
|
15122
|
-
|
|
15126
|
+
log31.info("unlinked global package", { package: PACKAGE_NAME });
|
|
15123
15127
|
} catch {
|
|
15124
15128
|
}
|
|
15125
|
-
|
|
15129
|
+
log31.info("uninstall complete. rAgent has been removed from this machine");
|
|
15126
15130
|
}
|
|
15127
15131
|
function registerUninstallCommand(parent) {
|
|
15128
15132
|
parent.command("uninstall").description("Remove rAgent from this machine (stops service, deletes config, unlinks CLI)").option("-y, --yes", "Skip confirmation prompt").action(async (opts) => {
|
|
@@ -15131,7 +15135,7 @@ function registerUninstallCommand(parent) {
|
|
|
15131
15135
|
await uninstallAgent(opts);
|
|
15132
15136
|
} catch (error) {
|
|
15133
15137
|
const message = error instanceof Error ? error.message : String(error);
|
|
15134
|
-
|
|
15138
|
+
log31.error("uninstall failed", { error: message });
|
|
15135
15139
|
process.exit(1);
|
|
15136
15140
|
}
|
|
15137
15141
|
});
|
|
@@ -15190,10 +15194,10 @@ function registerDiscoverCommand(program2) {
|
|
|
15190
15194
|
}
|
|
15191
15195
|
|
|
15192
15196
|
// src/index.ts
|
|
15193
|
-
var
|
|
15197
|
+
var log32 = createLogger("cli");
|
|
15194
15198
|
process.on("unhandledRejection", (reason) => {
|
|
15195
15199
|
const message = reason instanceof Error ? reason.stack || reason.message : String(reason);
|
|
15196
|
-
|
|
15200
|
+
log32.error("unhandled promise rejection", { error: message });
|
|
15197
15201
|
});
|
|
15198
15202
|
import_commander.program.name("ragent").description("Connect machines to rAgent Live").version(CURRENT_VERSION);
|
|
15199
15203
|
registerConnectCommand(import_commander.program);
|