ragent-cli 1.11.13 → 1.11.15
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 +400 -331
- 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.15",
|
|
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,20 +10731,21 @@ 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;
|
|
10767
10738
|
var MAX_SESSION_BUFFER_BYTES = 64 * 1024;
|
|
10768
10739
|
var PRIVATE_MODE_SEQUENCE_RE = /\x1b\[\?([0-9;]*)([hl])/g;
|
|
10769
10740
|
var ALT_SCREEN_PARAMS = /* @__PURE__ */ new Set(["47", "1047", "1048", "1049"]);
|
|
10741
|
+
var MOUSE_MODE_PARAMS = /* @__PURE__ */ new Set(["9", "1000", "1002", "1003", "1005", "1006", "1015", "1016"]);
|
|
10770
10742
|
function stripAlternateScreenModeSwitches(data) {
|
|
10771
10743
|
return data.replace(
|
|
10772
10744
|
PRIVATE_MODE_SEQUENCE_RE,
|
|
10773
10745
|
(full, params, action) => {
|
|
10774
10746
|
if (!params) return full;
|
|
10775
10747
|
const parts = params.split(";");
|
|
10776
|
-
const filtered = parts.filter((p) => !ALT_SCREEN_PARAMS.has(p));
|
|
10748
|
+
const filtered = parts.filter((p) => !ALT_SCREEN_PARAMS.has(p) && !MOUSE_MODE_PARAMS.has(p));
|
|
10777
10749
|
if (filtered.length === parts.length) return full;
|
|
10778
10750
|
if (filtered.length === 0) return "";
|
|
10779
10751
|
return `\x1B[?${filtered.join(";")}${action}`;
|
|
@@ -10798,6 +10770,18 @@ var AlternateScreenModeStripper = class {
|
|
|
10798
10770
|
return stripAlternateScreenModeSwitches(pending);
|
|
10799
10771
|
}
|
|
10800
10772
|
};
|
|
10773
|
+
function buildRelativeCursorSync(lineCount, x, y) {
|
|
10774
|
+
if (lineCount <= 0) return "";
|
|
10775
|
+
let seq = "";
|
|
10776
|
+
if (y <= lineCount - 1) {
|
|
10777
|
+
const up = lineCount - 1 - y;
|
|
10778
|
+
if (up > 0) seq += `\x1B[${up}A`;
|
|
10779
|
+
} else {
|
|
10780
|
+
seq += "\r\n".repeat(y - (lineCount - 1));
|
|
10781
|
+
}
|
|
10782
|
+
seq += `\x1B[${x + 1}G`;
|
|
10783
|
+
return seq;
|
|
10784
|
+
}
|
|
10801
10785
|
function parsePaneTarget(sessionId) {
|
|
10802
10786
|
if (!sessionId.startsWith("tmux:")) return null;
|
|
10803
10787
|
const rest = sessionId.slice("tmux:".length);
|
|
@@ -10853,6 +10837,7 @@ var SessionStreamer = class {
|
|
|
10853
10837
|
pendingStops = /* @__PURE__ */ new Map();
|
|
10854
10838
|
sendFn;
|
|
10855
10839
|
onStreamStopped;
|
|
10840
|
+
sendResetFn;
|
|
10856
10841
|
/**
|
|
10857
10842
|
* Per-session ring buffer for output captured while the WebSocket is disconnected.
|
|
10858
10843
|
* Each entry is a string chunk; oldest entries are evicted when the total byte
|
|
@@ -10862,9 +10847,14 @@ var SessionStreamer = class {
|
|
|
10862
10847
|
disconnectBufferBytes = /* @__PURE__ */ new Map();
|
|
10863
10848
|
/** Timer that resumes paused PTYs once the WS queue drains. See #332. */
|
|
10864
10849
|
backpressureResumeTimer = null;
|
|
10865
|
-
|
|
10850
|
+
/** Dropped-frame count at the last resume-timer check. */
|
|
10851
|
+
lastSeenDroppedFrames = 0;
|
|
10852
|
+
/** Sessions whose viewers need a capture-pane resync after frame drops. */
|
|
10853
|
+
pendingDropResync = /* @__PURE__ */ new Set();
|
|
10854
|
+
constructor(sendFn, onStreamStopped, sendResetFn) {
|
|
10866
10855
|
this.sendFn = sendFn;
|
|
10867
10856
|
this.onStreamStopped = onStreamStopped;
|
|
10857
|
+
this.sendResetFn = sendResetFn;
|
|
10868
10858
|
this.cleanupStaleStreams();
|
|
10869
10859
|
this.startBackpressureResumeTimer();
|
|
10870
10860
|
}
|
|
@@ -10872,6 +10862,14 @@ var SessionStreamer = class {
|
|
|
10872
10862
|
* When the WS queue is saturated, PTY onData stops firing after we call
|
|
10873
10863
|
* ptyProc.pause(). We need an independent loop to check pressure and
|
|
10874
10864
|
* resume each paused PTY once the queue drains. See #332.
|
|
10865
|
+
*
|
|
10866
|
+
* The same loop heals tmux-pipe streams after frame drops: tmux pipes
|
|
10867
|
+
* have no pause primitive, so under sustained pressure the send queue
|
|
10868
|
+
* overflows and drops frames — severing ANSI sequences and leaving
|
|
10869
|
+
* every viewer garbled until the next full repaint. Once pressure
|
|
10870
|
+
* clears, any drop since the last check triggers a capture-pane resync
|
|
10871
|
+
* (one stream per tick, so the resyncs themselves don't re-saturate
|
|
10872
|
+
* the queue).
|
|
10875
10873
|
*/
|
|
10876
10874
|
startBackpressureResumeTimer() {
|
|
10877
10875
|
if (this.backpressureResumeTimer) return;
|
|
@@ -10883,11 +10881,32 @@ var SessionStreamer = class {
|
|
|
10883
10881
|
try {
|
|
10884
10882
|
stream.ptyProc.resume();
|
|
10885
10883
|
stream.ptyPaused = false;
|
|
10886
|
-
|
|
10884
|
+
log17.info("pty-attach resumed after WS queue drained", { sessionId: stream.sessionId });
|
|
10887
10885
|
} catch {
|
|
10888
10886
|
}
|
|
10889
10887
|
}
|
|
10890
10888
|
}
|
|
10889
|
+
const dropped = getDroppedFrameCount();
|
|
10890
|
+
if (dropped > this.lastSeenDroppedFrames) {
|
|
10891
|
+
for (const stream of this.active.values()) {
|
|
10892
|
+
if (stream.streamType === "tmux-pipe" && !stream.stopped && !stream.initializing) {
|
|
10893
|
+
this.pendingDropResync.add(stream.sessionId);
|
|
10894
|
+
}
|
|
10895
|
+
}
|
|
10896
|
+
this.lastSeenDroppedFrames = dropped;
|
|
10897
|
+
log17.warn("frames dropped under backpressure; scheduling stream resyncs", {
|
|
10898
|
+
droppedTotal: dropped,
|
|
10899
|
+
streams: this.pendingDropResync.size
|
|
10900
|
+
});
|
|
10901
|
+
}
|
|
10902
|
+
const next = this.pendingDropResync.values().next();
|
|
10903
|
+
if (!next.done) {
|
|
10904
|
+
this.pendingDropResync.delete(next.value);
|
|
10905
|
+
const stream = this.active.get(next.value);
|
|
10906
|
+
if (stream && !stream.stopped) {
|
|
10907
|
+
this.resyncStream(next.value);
|
|
10908
|
+
}
|
|
10909
|
+
}
|
|
10891
10910
|
}, BACKPRESSURE_RESUME_CHECK_MS);
|
|
10892
10911
|
}
|
|
10893
10912
|
/**
|
|
@@ -10902,7 +10921,7 @@ var SessionStreamer = class {
|
|
|
10902
10921
|
try {
|
|
10903
10922
|
stream.ptyProc.pause();
|
|
10904
10923
|
stream.ptyPaused = true;
|
|
10905
|
-
|
|
10924
|
+
log17.warn("pty-attach paused under WS backpressure", {
|
|
10906
10925
|
sessionId: stream.sessionId,
|
|
10907
10926
|
queueLevel: pressure.level.toFixed(2)
|
|
10908
10927
|
});
|
|
@@ -10926,7 +10945,7 @@ var SessionStreamer = class {
|
|
|
10926
10945
|
}
|
|
10927
10946
|
}
|
|
10928
10947
|
if (entries.some((e) => e.startsWith("s-"))) {
|
|
10929
|
-
|
|
10948
|
+
log17.info("cleaned up stale stream directories from previous run");
|
|
10930
10949
|
}
|
|
10931
10950
|
} catch {
|
|
10932
10951
|
}
|
|
@@ -10944,7 +10963,7 @@ var SessionStreamer = class {
|
|
|
10944
10963
|
if (viewerKey) {
|
|
10945
10964
|
this.active.get(sessionId)?.viewerIds.add(viewerKey);
|
|
10946
10965
|
}
|
|
10947
|
-
|
|
10966
|
+
log17.info("cancelled pending stop (re-mounted)", { sessionId });
|
|
10948
10967
|
return true;
|
|
10949
10968
|
}
|
|
10950
10969
|
}
|
|
@@ -10955,7 +10974,7 @@ var SessionStreamer = class {
|
|
|
10955
10974
|
return true;
|
|
10956
10975
|
}
|
|
10957
10976
|
if (this.active.size >= MAX_CONCURRENT_STREAMS) {
|
|
10958
|
-
|
|
10977
|
+
log17.warn("stream limit reached; rejecting", {
|
|
10959
10978
|
limit: MAX_CONCURRENT_STREAMS,
|
|
10960
10979
|
sessionId
|
|
10961
10980
|
});
|
|
@@ -10986,7 +11005,7 @@ var SessionStreamer = class {
|
|
|
10986
11005
|
stream.ptyProc.write(data);
|
|
10987
11006
|
} catch (error) {
|
|
10988
11007
|
const message = error instanceof Error ? error.message : String(error);
|
|
10989
|
-
|
|
11008
|
+
log17.warn("failed to write input", { sessionId, error: message });
|
|
10990
11009
|
}
|
|
10991
11010
|
}
|
|
10992
11011
|
/**
|
|
@@ -11000,7 +11019,7 @@ var SessionStreamer = class {
|
|
|
11000
11019
|
} catch (error) {
|
|
11001
11020
|
const message = error instanceof Error ? error.message : String(error);
|
|
11002
11021
|
if (!message.includes("EBADF")) {
|
|
11003
|
-
|
|
11022
|
+
log17.warn("resize failed", { sessionId, error: message });
|
|
11004
11023
|
}
|
|
11005
11024
|
}
|
|
11006
11025
|
}
|
|
@@ -11026,7 +11045,7 @@ var SessionStreamer = class {
|
|
|
11026
11045
|
this.active.delete(sessionId);
|
|
11027
11046
|
this.disconnectBuffers.delete(sessionId);
|
|
11028
11047
|
this.disconnectBufferBytes.delete(sessionId);
|
|
11029
|
-
|
|
11048
|
+
log17.info("stopped streaming", { sessionId });
|
|
11030
11049
|
}, STOP_DEBOUNCE_MS);
|
|
11031
11050
|
this.pendingStops.set(sessionId, timer);
|
|
11032
11051
|
}
|
|
@@ -11051,7 +11070,7 @@ var SessionStreamer = class {
|
|
|
11051
11070
|
this.pendingStops.clear();
|
|
11052
11071
|
for (const [id, stream] of this.active) {
|
|
11053
11072
|
this.cleanupStream(stream);
|
|
11054
|
-
|
|
11073
|
+
log17.info("stopped streaming", { sessionId: id });
|
|
11055
11074
|
}
|
|
11056
11075
|
this.active.clear();
|
|
11057
11076
|
this.disconnectBuffers.clear();
|
|
@@ -11119,84 +11138,141 @@ var SessionStreamer = class {
|
|
|
11119
11138
|
}
|
|
11120
11139
|
/**
|
|
11121
11140
|
* Re-send capture-pane data for an already-active tmux stream.
|
|
11122
|
-
* Used when a viewer missed the initial burst (e.g. joinGroup race)
|
|
11123
|
-
* or
|
|
11141
|
+
* Used when a viewer missed the initial burst (e.g. joinGroup race),
|
|
11142
|
+
* reconnected, resized, or after backpressure frame drops corrupted the
|
|
11143
|
+
* stream. Does NOT restart pipe-pane.
|
|
11144
|
+
*
|
|
11145
|
+
* Default is a LEAN repaint: visible pane + cursor only. Replaying
|
|
11146
|
+
* scrollback mid-session shoved thousands of lines of old output into
|
|
11147
|
+
* live viewers ("ghost text") on every resize/heal. Pass
|
|
11148
|
+
* `{ scrollback: true }` only for viewer (re)attach, where the replay is
|
|
11149
|
+
* prefixed with ED3 (`\x1b[3J`) so the viewer's existing scrollback is
|
|
11150
|
+
* cleared first instead of duplicated.
|
|
11124
11151
|
*/
|
|
11125
|
-
resyncStream(sessionId, viewerId) {
|
|
11152
|
+
resyncStream(sessionId, viewerId, opts) {
|
|
11126
11153
|
const stream = this.active.get(sessionId);
|
|
11127
11154
|
if (!stream || stream.stopped || stream.streamType !== "tmux-pipe") return false;
|
|
11128
11155
|
const { paneTarget, cleanEnv } = stream;
|
|
11129
11156
|
if (!paneTarget || !cleanEnv) return false;
|
|
11130
11157
|
const targetViewerId = viewerId?.trim() || void 0;
|
|
11131
|
-
const
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
11158
|
+
const withScrollback = opts?.scrollback === true;
|
|
11159
|
+
if (stream.initializing) {
|
|
11160
|
+
const pending = stream.pendingResyncs ??= [];
|
|
11161
|
+
const duplicate = pending.some(
|
|
11162
|
+
(p) => p.viewerId === targetViewerId && p.scrollback === withScrollback
|
|
11163
|
+
);
|
|
11164
|
+
if (!duplicate) pending.push({ viewerId: targetViewerId, scrollback: withScrollback });
|
|
11165
|
+
return true;
|
|
11166
|
+
}
|
|
11167
|
+
stream.initBuffer = [];
|
|
11138
11168
|
stream.initializing = true;
|
|
11139
|
-
|
|
11140
|
-
|
|
11141
|
-
|
|
11142
|
-
|
|
11143
|
-
|
|
11144
|
-
["display-message", "-t", paneTarget, "-p", "#{alternate_on}"],
|
|
11145
|
-
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
11146
|
-
).trim();
|
|
11147
|
-
alternateScreen = altFlag === "1";
|
|
11148
|
-
} catch {
|
|
11169
|
+
setImmediate(() => {
|
|
11170
|
+
if (stream.stopped) {
|
|
11171
|
+
stream.initializing = false;
|
|
11172
|
+
stream.initBuffer = [];
|
|
11173
|
+
return;
|
|
11149
11174
|
}
|
|
11175
|
+
const send = (data) => {
|
|
11176
|
+
if (targetViewerId) {
|
|
11177
|
+
this.sendFn(sessionId, data, targetViewerId);
|
|
11178
|
+
} else {
|
|
11179
|
+
this.sendFn(sessionId, data);
|
|
11180
|
+
}
|
|
11181
|
+
};
|
|
11150
11182
|
try {
|
|
11151
|
-
|
|
11152
|
-
|
|
11153
|
-
|
|
11154
|
-
|
|
11155
|
-
|
|
11156
|
-
|
|
11157
|
-
|
|
11183
|
+
this.sendResetFn?.(sessionId, withScrollback ? "full" : "repaint", targetViewerId);
|
|
11184
|
+
const alternateScreen = this.detectAlternateScreen(paneTarget, cleanEnv);
|
|
11185
|
+
if (withScrollback) {
|
|
11186
|
+
send("\x1B[3J");
|
|
11187
|
+
try {
|
|
11188
|
+
const scrollback = (0, import_node_child_process2.execFileSync)(
|
|
11189
|
+
"tmux",
|
|
11190
|
+
["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
|
|
11191
|
+
{ env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
|
|
11192
|
+
);
|
|
11193
|
+
if (scrollback && scrollback.length > 0) {
|
|
11194
|
+
send(scrollback.replace(/\n/g, "\r\n"));
|
|
11195
|
+
}
|
|
11196
|
+
} catch {
|
|
11197
|
+
}
|
|
11198
|
+
}
|
|
11199
|
+
send("\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
|
|
11200
|
+
const lineCount = this.sendVisiblePane(send, paneTarget, cleanEnv, alternateScreen);
|
|
11201
|
+
this.sendCursorSync(send, paneTarget, cleanEnv, lineCount);
|
|
11202
|
+
log17.info("resync capture", { sessionId, mode: withScrollback ? "full" : "repaint" });
|
|
11203
|
+
} catch (error) {
|
|
11204
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
11205
|
+
log17.warn("failed to resync stream", { sessionId, error: message });
|
|
11206
|
+
} finally {
|
|
11207
|
+
stream.initializing = false;
|
|
11208
|
+
stream.initBuffer = [];
|
|
11209
|
+
const next = stream.pendingResyncs?.shift();
|
|
11210
|
+
if (next && !stream.stopped) {
|
|
11211
|
+
this.resyncStream(sessionId, next.viewerId, { scrollback: next.scrollback });
|
|
11158
11212
|
}
|
|
11159
|
-
} catch {
|
|
11160
11213
|
}
|
|
11161
|
-
|
|
11162
|
-
|
|
11163
|
-
|
|
11164
|
-
|
|
11165
|
-
|
|
11166
|
-
|
|
11167
|
-
|
|
11214
|
+
});
|
|
11215
|
+
return true;
|
|
11216
|
+
}
|
|
11217
|
+
/** Detect whether the pane is using the alternate screen buffer. */
|
|
11218
|
+
detectAlternateScreen(paneTarget, cleanEnv) {
|
|
11219
|
+
try {
|
|
11220
|
+
const altFlag = (0, import_node_child_process2.execFileSync)(
|
|
11221
|
+
"tmux",
|
|
11222
|
+
["display-message", "-t", paneTarget, "-p", "#{alternate_on}"],
|
|
11223
|
+
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
11224
|
+
).trim();
|
|
11225
|
+
return altFlag === "1";
|
|
11226
|
+
} catch {
|
|
11227
|
+
return false;
|
|
11228
|
+
}
|
|
11229
|
+
}
|
|
11230
|
+
/**
|
|
11231
|
+
* Capture and send the visible pane content. Converts bare \n to \r\n for
|
|
11232
|
+
* xterm.js and strips the trailing \r\n to prevent an extra scroll that
|
|
11233
|
+
* would shift the viewport by 1 row. Returns the number of lines sent
|
|
11234
|
+
* (0 when the capture failed or was empty) for relative cursor sync.
|
|
11235
|
+
*/
|
|
11236
|
+
sendVisiblePane(send, paneTarget, cleanEnv, alternateScreen) {
|
|
11237
|
+
try {
|
|
11238
|
+
const captureArgs = alternateScreen ? ["capture-pane", "-a", "-t", paneTarget, "-p", "-e"] : ["capture-pane", "-t", paneTarget, "-p", "-e"];
|
|
11239
|
+
const initial = (0, import_node_child_process2.execFileSync)(
|
|
11240
|
+
"tmux",
|
|
11241
|
+
captureArgs,
|
|
11242
|
+
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
11243
|
+
);
|
|
11244
|
+
if (initial) {
|
|
11245
|
+
const converted = stripAlternateScreenModeSwitches(
|
|
11246
|
+
initial.replace(/\n/g, "\r\n").replace(/\r\n$/, "")
|
|
11168
11247
|
);
|
|
11169
|
-
if (
|
|
11170
|
-
send(
|
|
11248
|
+
if (converted) {
|
|
11249
|
+
send(converted);
|
|
11250
|
+
return converted.split("\r\n").length;
|
|
11171
11251
|
}
|
|
11172
|
-
} catch {
|
|
11173
11252
|
}
|
|
11174
|
-
|
|
11175
|
-
|
|
11176
|
-
|
|
11177
|
-
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11185
|
-
|
|
11186
|
-
|
|
11253
|
+
} catch {
|
|
11254
|
+
}
|
|
11255
|
+
return 0;
|
|
11256
|
+
}
|
|
11257
|
+
/** Query the tmux cursor position and send a relative cursor sync. */
|
|
11258
|
+
sendCursorSync(send, paneTarget, cleanEnv, lineCount) {
|
|
11259
|
+
if (lineCount <= 0) return;
|
|
11260
|
+
try {
|
|
11261
|
+
const cursorInfo = (0, import_node_child_process2.execFileSync)(
|
|
11262
|
+
"tmux",
|
|
11263
|
+
["display-message", "-t", paneTarget, "-p", "#{cursor_x} #{cursor_y}"],
|
|
11264
|
+
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
11265
|
+
).trim();
|
|
11266
|
+
const parts = cursorInfo.split(" ");
|
|
11267
|
+
if (parts.length === 2) {
|
|
11268
|
+
const x = parseInt(parts[0], 10);
|
|
11269
|
+
const y = parseInt(parts[1], 10);
|
|
11270
|
+
if (!isNaN(x) && !isNaN(y)) {
|
|
11271
|
+
const seq = buildRelativeCursorSync(lineCount, x, y);
|
|
11272
|
+
if (seq) send(seq);
|
|
11187
11273
|
}
|
|
11188
|
-
} catch {
|
|
11189
11274
|
}
|
|
11190
|
-
|
|
11191
|
-
stream.initBuffer = [];
|
|
11192
|
-
log16.info("resync capture", { sessionId });
|
|
11193
|
-
return true;
|
|
11194
|
-
} catch (error) {
|
|
11195
|
-
stream.initializing = false;
|
|
11196
|
-
stream.initBuffer = [];
|
|
11197
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
11198
|
-
log16.warn("failed to resync stream", { sessionId, error: message });
|
|
11199
|
-
return false;
|
|
11275
|
+
} catch {
|
|
11200
11276
|
}
|
|
11201
11277
|
}
|
|
11202
11278
|
// ---------------------------------------------------------------------------
|
|
@@ -11249,7 +11325,7 @@ var SessionStreamer = class {
|
|
|
11249
11325
|
} catch (error) {
|
|
11250
11326
|
this.cleanupStream(stream);
|
|
11251
11327
|
const message = error instanceof Error ? error.message : String(error);
|
|
11252
|
-
|
|
11328
|
+
log17.warn("failed pipe-pane", { sessionId, error: message });
|
|
11253
11329
|
return false;
|
|
11254
11330
|
}
|
|
11255
11331
|
const catProc = (0, import_node_child_process2.spawn)("cat", [fifoPath], { env: cleanEnv, stdio: ["ignore", "pipe", "ignore"] });
|
|
@@ -11277,21 +11353,14 @@ var SessionStreamer = class {
|
|
|
11277
11353
|
this.cleanupStream(stream);
|
|
11278
11354
|
this.active.delete(sessionId);
|
|
11279
11355
|
this.pendingStops.delete(sessionId);
|
|
11280
|
-
|
|
11356
|
+
log17.info("session stream ended", { sessionId });
|
|
11281
11357
|
this.onStreamStopped?.(sessionId);
|
|
11282
11358
|
}
|
|
11283
11359
|
});
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
const altFlag = (0, import_node_child_process2.execFileSync)(
|
|
11287
|
-
"tmux",
|
|
11288
|
-
["display-message", "-t", paneTarget, "-p", "#{alternate_on}"],
|
|
11289
|
-
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
11290
|
-
).trim();
|
|
11291
|
-
alternateScreen = altFlag === "1";
|
|
11292
|
-
} catch {
|
|
11293
|
-
}
|
|
11360
|
+
this.sendResetFn?.(sessionId, "full");
|
|
11361
|
+
const alternateScreen = this.detectAlternateScreen(paneTarget, cleanEnv);
|
|
11294
11362
|
stream.alternateScreen = alternateScreen;
|
|
11363
|
+
this.sendFn(sessionId, "\x1B[3J");
|
|
11295
11364
|
try {
|
|
11296
11365
|
const scrollback = (0, import_node_child_process2.execFileSync)(
|
|
11297
11366
|
"tmux",
|
|
@@ -11304,41 +11373,16 @@ var SessionStreamer = class {
|
|
|
11304
11373
|
} catch {
|
|
11305
11374
|
}
|
|
11306
11375
|
this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
|
|
11307
|
-
|
|
11308
|
-
|
|
11309
|
-
|
|
11310
|
-
"tmux",
|
|
11311
|
-
captureArgs,
|
|
11312
|
-
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
11313
|
-
);
|
|
11314
|
-
if (initial) {
|
|
11315
|
-
this.sendFn(sessionId, stripAlternateScreenModeSwitches(initial.replace(/\n/g, "\r\n").replace(/\r\n$/, "")));
|
|
11316
|
-
}
|
|
11317
|
-
} catch {
|
|
11318
|
-
}
|
|
11319
|
-
try {
|
|
11320
|
-
const cursorInfo = (0, import_node_child_process2.execFileSync)(
|
|
11321
|
-
"tmux",
|
|
11322
|
-
["display-message", "-t", paneTarget, "-p", "#{cursor_x} #{cursor_y}"],
|
|
11323
|
-
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
11324
|
-
).trim();
|
|
11325
|
-
const parts = cursorInfo.split(" ");
|
|
11326
|
-
if (parts.length === 2) {
|
|
11327
|
-
const x = parseInt(parts[0], 10);
|
|
11328
|
-
const y = parseInt(parts[1], 10);
|
|
11329
|
-
if (!isNaN(x) && !isNaN(y)) {
|
|
11330
|
-
this.sendFn(sessionId, `\x1B[${y + 1};${x + 1}H`);
|
|
11331
|
-
}
|
|
11332
|
-
}
|
|
11333
|
-
} catch {
|
|
11334
|
-
}
|
|
11376
|
+
const sendBroadcast = (data) => this.sendFn(sessionId, data);
|
|
11377
|
+
const lineCount = this.sendVisiblePane(sendBroadcast, paneTarget, cleanEnv, alternateScreen);
|
|
11378
|
+
this.sendCursorSync(sendBroadcast, paneTarget, cleanEnv, lineCount);
|
|
11335
11379
|
stream.initializing = false;
|
|
11336
11380
|
stream.initBuffer = [];
|
|
11337
|
-
|
|
11381
|
+
log17.info("started streaming (tmux)", { sessionId, pane: paneTarget });
|
|
11338
11382
|
return true;
|
|
11339
11383
|
} catch (error) {
|
|
11340
11384
|
const message = error instanceof Error ? error.message : String(error);
|
|
11341
|
-
|
|
11385
|
+
log17.warn("failed to start stream", { sessionId, error: message });
|
|
11342
11386
|
return false;
|
|
11343
11387
|
}
|
|
11344
11388
|
}
|
|
@@ -11384,15 +11428,15 @@ var SessionStreamer = class {
|
|
|
11384
11428
|
stream.stopped = true;
|
|
11385
11429
|
this.active.delete(sessionId);
|
|
11386
11430
|
this.pendingStops.delete(sessionId);
|
|
11387
|
-
|
|
11431
|
+
log17.info("session stream ended", { sessionId });
|
|
11388
11432
|
this.onStreamStopped?.(sessionId);
|
|
11389
11433
|
}
|
|
11390
11434
|
});
|
|
11391
|
-
|
|
11435
|
+
log17.info("started streaming (screen)", { sessionId, screen: sessionName });
|
|
11392
11436
|
return true;
|
|
11393
11437
|
} catch (error) {
|
|
11394
11438
|
const message = error instanceof Error ? error.message : String(error);
|
|
11395
|
-
|
|
11439
|
+
log17.warn("failed to start screen stream", { sessionId, error: message });
|
|
11396
11440
|
return false;
|
|
11397
11441
|
}
|
|
11398
11442
|
}
|
|
@@ -11438,15 +11482,15 @@ var SessionStreamer = class {
|
|
|
11438
11482
|
stream.stopped = true;
|
|
11439
11483
|
this.active.delete(sessionId);
|
|
11440
11484
|
this.pendingStops.delete(sessionId);
|
|
11441
|
-
|
|
11485
|
+
log17.info("session stream ended", { sessionId });
|
|
11442
11486
|
this.onStreamStopped?.(sessionId);
|
|
11443
11487
|
}
|
|
11444
11488
|
});
|
|
11445
|
-
|
|
11489
|
+
log17.info("started streaming (zellij)", { sessionId, zellij: sessionName });
|
|
11446
11490
|
return true;
|
|
11447
11491
|
} catch (error) {
|
|
11448
11492
|
const message = error instanceof Error ? error.message : String(error);
|
|
11449
|
-
|
|
11493
|
+
log17.warn("failed to start zellij stream", { sessionId, error: message });
|
|
11450
11494
|
return false;
|
|
11451
11495
|
}
|
|
11452
11496
|
}
|
|
@@ -11457,14 +11501,14 @@ var SessionStreamer = class {
|
|
|
11457
11501
|
const pid = parseProcessPid2(sessionId);
|
|
11458
11502
|
if (!pid) return false;
|
|
11459
11503
|
if (!(0, import_node_fs.existsSync)(`/proc/${pid}`)) {
|
|
11460
|
-
|
|
11504
|
+
log17.warn("process does not exist (no /proc entry)", { pid });
|
|
11461
11505
|
this.sendFn(sessionId, "\r\n[rAgent] Process is no longer running. It will be removed on next sync.\r\n");
|
|
11462
11506
|
return false;
|
|
11463
11507
|
}
|
|
11464
11508
|
try {
|
|
11465
11509
|
const stat = (0, import_node_fs.readFileSync)(`/proc/${pid}/stat`, "utf8");
|
|
11466
11510
|
if (stat.includes(") Z")) {
|
|
11467
|
-
|
|
11511
|
+
log17.warn("process is a zombie", { pid });
|
|
11468
11512
|
this.sendFn(sessionId, "\r\n[rAgent] Process has exited (zombie). It will be removed on next sync.\r\n");
|
|
11469
11513
|
return false;
|
|
11470
11514
|
}
|
|
@@ -11538,14 +11582,14 @@ var SessionStreamer = class {
|
|
|
11538
11582
|
stream.stopped = true;
|
|
11539
11583
|
this.active.delete(sessionId);
|
|
11540
11584
|
this.pendingStops.delete(sessionId);
|
|
11541
|
-
|
|
11585
|
+
log17.info("session stream ended", { sessionId });
|
|
11542
11586
|
this.onStreamStopped?.(sessionId);
|
|
11543
11587
|
});
|
|
11544
|
-
|
|
11588
|
+
log17.info("started streaming (process trace)", { sessionId, pid });
|
|
11545
11589
|
return true;
|
|
11546
11590
|
} catch (error) {
|
|
11547
11591
|
const message = error instanceof Error ? error.message : String(error);
|
|
11548
|
-
|
|
11592
|
+
log17.warn("failed to start process stream", { sessionId, error: message });
|
|
11549
11593
|
return false;
|
|
11550
11594
|
}
|
|
11551
11595
|
}
|
|
@@ -11596,7 +11640,7 @@ var SessionStreamer = class {
|
|
|
11596
11640
|
|
|
11597
11641
|
// src/crypto-channel.ts
|
|
11598
11642
|
var import_node_crypto = require("crypto");
|
|
11599
|
-
var
|
|
11643
|
+
var log18 = createLogger("crypto-channel");
|
|
11600
11644
|
var AAD_BYTES = 4;
|
|
11601
11645
|
function deriveAesKey(sessionKey) {
|
|
11602
11646
|
return Buffer.from(sessionKey.slice(0, 64), "hex");
|
|
@@ -11638,7 +11682,7 @@ function decryptPayload(enc, iv, sessionKey, seq) {
|
|
|
11638
11682
|
return decrypted.toString("utf-8");
|
|
11639
11683
|
} catch (error) {
|
|
11640
11684
|
const message = error instanceof Error ? error.message : String(error);
|
|
11641
|
-
|
|
11685
|
+
log18.warn("decrypt failed", { seq, encBytes: enc.length, error: message });
|
|
11642
11686
|
return null;
|
|
11643
11687
|
}
|
|
11644
11688
|
}
|
|
@@ -11672,7 +11716,7 @@ var SequenceTracker = class {
|
|
|
11672
11716
|
// src/pty.ts
|
|
11673
11717
|
var import_node_child_process3 = require("child_process");
|
|
11674
11718
|
var pty2 = __toESM(require("node-pty"));
|
|
11675
|
-
var
|
|
11719
|
+
var log19 = createLogger("pty");
|
|
11676
11720
|
var MIN_TMUX_COLS = 20;
|
|
11677
11721
|
var MAX_TMUX_COLS = 500;
|
|
11678
11722
|
var MIN_TMUX_ROWS = 5;
|
|
@@ -11693,6 +11737,11 @@ var ANSI_KEY_SEQUENCES = /* @__PURE__ */ new Map([
|
|
|
11693
11737
|
["\x1B[6~", "PageDown"]
|
|
11694
11738
|
]);
|
|
11695
11739
|
var ANSI_KEY_ENTRIES = Array.from(ANSI_KEY_SEQUENCES.entries());
|
|
11740
|
+
var MOUSE_OR_FOCUS_REPORT_RE = /\x1b\[(?:<\d+;\d+;\d+[Mm]|\d+;\d+;\d+M|M[\x20-\xFF]{3}|[IO])/g;
|
|
11741
|
+
function sanitizeRemoteInput(data) {
|
|
11742
|
+
if (!data.includes("\x1B[")) return data;
|
|
11743
|
+
return data.replace(MOUSE_OR_FOCUS_REPORT_RE, "");
|
|
11744
|
+
}
|
|
11696
11745
|
function controlKeyName(charCode) {
|
|
11697
11746
|
if (charCode >= 1 && charCode <= 26) {
|
|
11698
11747
|
return `C-${String.fromCharCode(charCode + 96)}`;
|
|
@@ -11723,7 +11772,8 @@ function appendLiteralSegment(segments, value) {
|
|
|
11723
11772
|
segments.push({ kind: "literal", value });
|
|
11724
11773
|
}
|
|
11725
11774
|
}
|
|
11726
|
-
function encodeTmuxInput(
|
|
11775
|
+
function encodeTmuxInput(rawData) {
|
|
11776
|
+
const data = sanitizeRemoteInput(rawData);
|
|
11727
11777
|
const segments = [];
|
|
11728
11778
|
let literal = "";
|
|
11729
11779
|
const flushLiteral = () => {
|
|
@@ -11820,7 +11870,7 @@ async function sendInputToTmux(sessionId, data) {
|
|
|
11820
11870
|
if (!target) return;
|
|
11821
11871
|
if (!isValidTmuxSessionName(target)) {
|
|
11822
11872
|
const sessionName = target.split(":")[0].split(".")[0];
|
|
11823
|
-
|
|
11873
|
+
log19.warn("invalid tmux session name", { sessionName });
|
|
11824
11874
|
return;
|
|
11825
11875
|
}
|
|
11826
11876
|
try {
|
|
@@ -11851,7 +11901,7 @@ async function sendInputToTmux(sessionId, data) {
|
|
|
11851
11901
|
}
|
|
11852
11902
|
} catch (error) {
|
|
11853
11903
|
const message = error instanceof Error ? error.message : String(error);
|
|
11854
|
-
|
|
11904
|
+
log19.warn("failed to send input", { sessionId, error: message });
|
|
11855
11905
|
}
|
|
11856
11906
|
}
|
|
11857
11907
|
async function resizeTmuxPaneBySessionId(sessionId, cols, rows) {
|
|
@@ -11860,7 +11910,7 @@ async function resizeTmuxPaneBySessionId(sessionId, cols, rows) {
|
|
|
11860
11910
|
if (!target) return false;
|
|
11861
11911
|
if (!isValidTmuxSessionName(target)) {
|
|
11862
11912
|
const sessionName = target.split(":")[0].split(".")[0];
|
|
11863
|
-
|
|
11913
|
+
log19.warn("invalid tmux session name", { sessionName });
|
|
11864
11914
|
return false;
|
|
11865
11915
|
}
|
|
11866
11916
|
if (!Number.isFinite(cols) || !Number.isFinite(rows)) return false;
|
|
@@ -11874,7 +11924,7 @@ async function resizeTmuxPaneBySessionId(sessionId, cols, rows) {
|
|
|
11874
11924
|
(err) => {
|
|
11875
11925
|
if (err) {
|
|
11876
11926
|
const message = err instanceof Error ? err.message : String(err);
|
|
11877
|
-
|
|
11927
|
+
log19.warn("failed to resize tmux pane", { sessionId, error: message });
|
|
11878
11928
|
resolve(false);
|
|
11879
11929
|
return;
|
|
11880
11930
|
}
|
|
@@ -11908,7 +11958,7 @@ async function stopAllDetachedTmuxSessions() {
|
|
|
11908
11958
|
}
|
|
11909
11959
|
|
|
11910
11960
|
// src/shell-manager.ts
|
|
11911
|
-
var
|
|
11961
|
+
var log20 = createLogger("shell");
|
|
11912
11962
|
var ShellManager = class {
|
|
11913
11963
|
ptyProcess = null;
|
|
11914
11964
|
suppressNextRespawn = false;
|
|
@@ -11949,7 +11999,7 @@ var ShellManager = class {
|
|
|
11949
11999
|
} catch (error) {
|
|
11950
12000
|
const message = error instanceof Error ? error.message : String(error);
|
|
11951
12001
|
if (!message.includes("EBADF")) {
|
|
11952
|
-
|
|
12002
|
+
log20.warn("resize failed", { error: message });
|
|
11953
12003
|
}
|
|
11954
12004
|
}
|
|
11955
12005
|
}
|
|
@@ -11965,7 +12015,7 @@ var ShellManager = class {
|
|
|
11965
12015
|
return;
|
|
11966
12016
|
}
|
|
11967
12017
|
if (!this.shouldRun) return;
|
|
11968
|
-
|
|
12018
|
+
log20.warn("shell exited; restarting shell process");
|
|
11969
12019
|
setTimeout(() => {
|
|
11970
12020
|
if (this.shouldRun) this.spawnOrRespawn();
|
|
11971
12021
|
}, 200);
|
|
@@ -12071,7 +12121,7 @@ var InventoryManager = class {
|
|
|
12071
12121
|
|
|
12072
12122
|
// src/connection-manager.ts
|
|
12073
12123
|
var import_ws3 = __toESM(require("ws"));
|
|
12074
|
-
var
|
|
12124
|
+
var log21 = createLogger("connection");
|
|
12075
12125
|
var OPPORTUNISTIC_INVENTORY_SYNC_MS = 1200;
|
|
12076
12126
|
var MIN_INVENTORY_SYNC_GAP_MS = 1500;
|
|
12077
12127
|
var ConnectionManager = class {
|
|
@@ -12103,7 +12153,7 @@ var ConnectionManager = class {
|
|
|
12103
12153
|
tracker = new SequenceTracker();
|
|
12104
12154
|
this.rxSeqByViewer.set(key, tracker);
|
|
12105
12155
|
if (!isValid) {
|
|
12106
|
-
|
|
12156
|
+
log21.warn("rxSeq fell back to _legacy bucket \u2014 portal sent empty viewerId", { typeofViewerId: typeof viewerId });
|
|
12107
12157
|
}
|
|
12108
12158
|
}
|
|
12109
12159
|
return tracker;
|
|
@@ -12189,14 +12239,14 @@ var ConnectionManager = class {
|
|
|
12189
12239
|
try {
|
|
12190
12240
|
this.activeSocket.send(JSON.stringify({ type: "ping" }));
|
|
12191
12241
|
} catch (err) {
|
|
12192
|
-
|
|
12242
|
+
log21.error("failed to send JSON ping", { error: err.message });
|
|
12193
12243
|
}
|
|
12194
12244
|
try {
|
|
12195
12245
|
this.activeSocket.ping();
|
|
12196
12246
|
} catch {
|
|
12197
12247
|
}
|
|
12198
12248
|
this.wsPongTimeout = setTimeout(() => {
|
|
12199
|
-
|
|
12249
|
+
log21.warn("no pong received within 10s; closing stale connection");
|
|
12200
12250
|
try {
|
|
12201
12251
|
this.activeSocket?.terminate();
|
|
12202
12252
|
} catch {
|
|
@@ -12266,7 +12316,7 @@ var ConnectionManager = class {
|
|
|
12266
12316
|
replayBufferedOutput(sendChunk) {
|
|
12267
12317
|
const buffered = this.outputBuffer.drain();
|
|
12268
12318
|
if (buffered.length > 0) {
|
|
12269
|
-
|
|
12319
|
+
log21.info("replaying buffered output chunks", {
|
|
12270
12320
|
chunks: buffered.length,
|
|
12271
12321
|
bytes: buffered.reduce((sum, c) => sum + Buffer.byteLength(c, "utf8"), 0)
|
|
12272
12322
|
});
|
|
@@ -12308,7 +12358,7 @@ var import_child_process6 = require("child_process");
|
|
|
12308
12358
|
var fs5 = __toESM(require("fs"));
|
|
12309
12359
|
var os8 = __toESM(require("os"));
|
|
12310
12360
|
var path4 = __toESM(require("path"));
|
|
12311
|
-
var
|
|
12361
|
+
var log22 = createLogger("service");
|
|
12312
12362
|
function assertConfiguredAgentToken() {
|
|
12313
12363
|
const config = loadConfig();
|
|
12314
12364
|
if (!config.agentToken) {
|
|
@@ -12374,7 +12424,7 @@ async function installSystemdService(opts = {}) {
|
|
|
12374
12424
|
await runSystemctlUser(["restart", SERVICE_NAME]);
|
|
12375
12425
|
}
|
|
12376
12426
|
saveConfigPatch({ serviceBackend: "systemd" });
|
|
12377
|
-
|
|
12427
|
+
log22.info("installed systemd user service", { path: SERVICE_FILE });
|
|
12378
12428
|
}
|
|
12379
12429
|
function readFallbackPid() {
|
|
12380
12430
|
try {
|
|
@@ -12413,7 +12463,7 @@ async function startPidfileService() {
|
|
|
12413
12463
|
assertConfiguredAgentToken();
|
|
12414
12464
|
const existingPid = readFallbackPid();
|
|
12415
12465
|
if (existingPid && isProcessRunning(existingPid)) {
|
|
12416
|
-
|
|
12466
|
+
log22.info("service already running", { pid: existingPid });
|
|
12417
12467
|
return;
|
|
12418
12468
|
}
|
|
12419
12469
|
ensureConfigDir();
|
|
@@ -12430,8 +12480,8 @@ async function startPidfileService() {
|
|
|
12430
12480
|
fs5.writeFileSync(FALLBACK_PID_FILE, `${child.pid}
|
|
12431
12481
|
`, "utf8");
|
|
12432
12482
|
saveConfigPatch({ serviceBackend: "pidfile" });
|
|
12433
|
-
|
|
12434
|
-
|
|
12483
|
+
log22.info("started fallback background service", { pid: child.pid });
|
|
12484
|
+
log22.info("logs", { path: FALLBACK_LOG_FILE });
|
|
12435
12485
|
}
|
|
12436
12486
|
async function stopPidfileService() {
|
|
12437
12487
|
const pid = readFallbackPid();
|
|
@@ -12440,7 +12490,7 @@ async function stopPidfileService() {
|
|
|
12440
12490
|
fs5.unlinkSync(FALLBACK_PID_FILE);
|
|
12441
12491
|
} catch {
|
|
12442
12492
|
}
|
|
12443
|
-
|
|
12493
|
+
log22.info("service is not running");
|
|
12444
12494
|
return;
|
|
12445
12495
|
}
|
|
12446
12496
|
process.kill(pid, "SIGTERM");
|
|
@@ -12455,7 +12505,7 @@ async function stopPidfileService() {
|
|
|
12455
12505
|
fs5.unlinkSync(FALLBACK_PID_FILE);
|
|
12456
12506
|
} catch {
|
|
12457
12507
|
}
|
|
12458
|
-
|
|
12508
|
+
log22.info("stopped fallback background service", { pid });
|
|
12459
12509
|
}
|
|
12460
12510
|
async function ensureServiceInstalled(opts = {}) {
|
|
12461
12511
|
const wantsSystemd = await canUseSystemdUser();
|
|
@@ -12467,7 +12517,7 @@ async function ensureServiceInstalled(opts = {}) {
|
|
|
12467
12517
|
if (opts.start) {
|
|
12468
12518
|
await startPidfileService();
|
|
12469
12519
|
} else {
|
|
12470
|
-
|
|
12520
|
+
log22.info("systemd user manager unavailable; using fallback pidfile backend");
|
|
12471
12521
|
}
|
|
12472
12522
|
return "pidfile";
|
|
12473
12523
|
}
|
|
@@ -12475,7 +12525,7 @@ async function startService() {
|
|
|
12475
12525
|
const backend = getConfiguredServiceBackend();
|
|
12476
12526
|
if (backend === "systemd") {
|
|
12477
12527
|
await runSystemctlUser(["start", SERVICE_NAME]);
|
|
12478
|
-
|
|
12528
|
+
log22.info("started service via systemd");
|
|
12479
12529
|
return;
|
|
12480
12530
|
}
|
|
12481
12531
|
if (backend === "pidfile") {
|
|
@@ -12488,7 +12538,7 @@ async function stopService() {
|
|
|
12488
12538
|
const backend = getConfiguredServiceBackend();
|
|
12489
12539
|
if (backend === "systemd") {
|
|
12490
12540
|
await runSystemctlUser(["stop", SERVICE_NAME]);
|
|
12491
|
-
|
|
12541
|
+
log22.info("stopped service via systemd");
|
|
12492
12542
|
return;
|
|
12493
12543
|
}
|
|
12494
12544
|
await stopPidfileService();
|
|
@@ -12506,7 +12556,7 @@ function killStaleAgentProcesses() {
|
|
|
12506
12556
|
try {
|
|
12507
12557
|
process.kill(pid, 0);
|
|
12508
12558
|
process.kill(pid, "SIGTERM");
|
|
12509
|
-
|
|
12559
|
+
log22.info("stopped stale agent process", { pid });
|
|
12510
12560
|
} catch {
|
|
12511
12561
|
}
|
|
12512
12562
|
fs5.unlinkSync(pidPath);
|
|
@@ -12521,7 +12571,7 @@ async function restartService() {
|
|
|
12521
12571
|
killStaleAgentProcesses();
|
|
12522
12572
|
if (backend === "systemd") {
|
|
12523
12573
|
await runSystemctlUser(["restart", SERVICE_NAME]);
|
|
12524
|
-
|
|
12574
|
+
log22.info("restarted service via systemd");
|
|
12525
12575
|
return;
|
|
12526
12576
|
}
|
|
12527
12577
|
await stopPidfileService();
|
|
@@ -12537,17 +12587,17 @@ async function printServiceStatus() {
|
|
|
12537
12587
|
if (status) {
|
|
12538
12588
|
process.stdout.write(status);
|
|
12539
12589
|
} else {
|
|
12540
|
-
|
|
12590
|
+
log22.info("systemd service is not installed or has no status");
|
|
12541
12591
|
}
|
|
12542
12592
|
return;
|
|
12543
12593
|
}
|
|
12544
12594
|
const pid = readFallbackPid();
|
|
12545
12595
|
if (pid && isProcessRunning(pid)) {
|
|
12546
|
-
|
|
12547
|
-
|
|
12596
|
+
log22.info("fallback service running", { pid });
|
|
12597
|
+
log22.info("logs", { path: FALLBACK_LOG_FILE });
|
|
12548
12598
|
return;
|
|
12549
12599
|
}
|
|
12550
|
-
|
|
12600
|
+
log22.info("service is not running");
|
|
12551
12601
|
}
|
|
12552
12602
|
async function printServiceLogs(opts) {
|
|
12553
12603
|
const lines = Number.parseInt(String(opts.lines || 100), 10) || 100;
|
|
@@ -12573,7 +12623,7 @@ async function printServiceLogs(opts) {
|
|
|
12573
12623
|
return;
|
|
12574
12624
|
}
|
|
12575
12625
|
if (!fs5.existsSync(FALLBACK_LOG_FILE)) {
|
|
12576
|
-
|
|
12626
|
+
log22.info("no log file found", { path: FALLBACK_LOG_FILE });
|
|
12577
12627
|
return;
|
|
12578
12628
|
}
|
|
12579
12629
|
if (follow) {
|
|
@@ -12605,7 +12655,7 @@ async function uninstallService() {
|
|
|
12605
12655
|
const config = loadConfig();
|
|
12606
12656
|
delete config.serviceBackend;
|
|
12607
12657
|
saveConfig(config);
|
|
12608
|
-
|
|
12658
|
+
log22.info("service uninstalled");
|
|
12609
12659
|
}
|
|
12610
12660
|
function requestStopSelfService() {
|
|
12611
12661
|
const backend = getConfiguredServiceBackend();
|
|
@@ -12956,7 +13006,7 @@ async function installLatestCliFromNpm() {
|
|
|
12956
13006
|
}
|
|
12957
13007
|
|
|
12958
13008
|
// src/control-dispatcher.ts
|
|
12959
|
-
var
|
|
13009
|
+
var log23 = createLogger("control");
|
|
12960
13010
|
var TMUX_RESYNC_DEBOUNCE_MS = 150;
|
|
12961
13011
|
var TMUX_INPUT_COALESCE_MS = 4;
|
|
12962
13012
|
function collectProcessTreePids(rootPid, visited = /* @__PURE__ */ new Set()) {
|
|
@@ -13028,7 +13078,7 @@ var ControlDispatcher = class {
|
|
|
13028
13078
|
/** Arrow form so callers can pass it directly without `.bind(this)`. */
|
|
13029
13079
|
emitNativeOps = (sessionId, ops, seq) => {
|
|
13030
13080
|
if (!this.sendNativeOps) {
|
|
13031
|
-
|
|
13081
|
+
log23.warn("native-stream ops produced but no transport sink installed", {
|
|
13032
13082
|
sessionId,
|
|
13033
13083
|
opCount: ops.length
|
|
13034
13084
|
});
|
|
@@ -13097,11 +13147,11 @@ var ControlDispatcher = class {
|
|
|
13097
13147
|
if (!sessionKey) return true;
|
|
13098
13148
|
const receivedHmac = payload.hmac;
|
|
13099
13149
|
if (typeof receivedHmac !== "string") {
|
|
13100
|
-
|
|
13150
|
+
log23.warn("control message missing HMAC signature");
|
|
13101
13151
|
return false;
|
|
13102
13152
|
}
|
|
13103
13153
|
if (!/^[0-9a-f]{64}$/i.test(receivedHmac)) {
|
|
13104
|
-
|
|
13154
|
+
log23.warn("control message has malformed HMAC signature");
|
|
13105
13155
|
return false;
|
|
13106
13156
|
}
|
|
13107
13157
|
const { hmac: _, ...payloadWithoutHmac } = payload;
|
|
@@ -13129,7 +13179,7 @@ var ControlDispatcher = class {
|
|
|
13129
13179
|
]);
|
|
13130
13180
|
if (dangerousActions.has(action) && this.connection.sessionKey && payload._verifiedEncrypted !== true) {
|
|
13131
13181
|
if (!this.verifyMessageHmac(payload)) {
|
|
13132
|
-
|
|
13182
|
+
log23.warn("rejecting control action \u2014 HMAC verification failed", { action });
|
|
13133
13183
|
return;
|
|
13134
13184
|
}
|
|
13135
13185
|
}
|
|
@@ -13175,10 +13225,10 @@ var ControlDispatcher = class {
|
|
|
13175
13225
|
this.transcriptWatcher?.disableMarkdown(sessionId);
|
|
13176
13226
|
try {
|
|
13177
13227
|
await stopTmuxPaneBySessionId(sessionId);
|
|
13178
|
-
|
|
13228
|
+
log23.info("closed remote session", { sessionId });
|
|
13179
13229
|
} catch (error) {
|
|
13180
13230
|
const message = error instanceof Error ? error.message : String(error);
|
|
13181
|
-
|
|
13231
|
+
log23.warn("failed to close session", { sessionId, error: message });
|
|
13182
13232
|
}
|
|
13183
13233
|
await delay(300);
|
|
13184
13234
|
await this.syncInventory(true);
|
|
@@ -13189,7 +13239,7 @@ var ControlDispatcher = class {
|
|
|
13189
13239
|
{
|
|
13190
13240
|
const pid = parseProcessPid2(sessionId);
|
|
13191
13241
|
if (pid === null) {
|
|
13192
|
-
|
|
13242
|
+
log23.warn("kill-process: could not parse PID", { sessionId });
|
|
13193
13243
|
return;
|
|
13194
13244
|
}
|
|
13195
13245
|
this.transcriptWatcher?.disableMarkdown(sessionId);
|
|
@@ -13205,11 +13255,11 @@ var ControlDispatcher = class {
|
|
|
13205
13255
|
const code = error.code;
|
|
13206
13256
|
if (code !== "ESRCH") {
|
|
13207
13257
|
const message = error instanceof Error ? error.message : String(error);
|
|
13208
|
-
|
|
13258
|
+
log23.warn("failed to send SIGTERM", { pid: targetPid, error: message });
|
|
13209
13259
|
}
|
|
13210
13260
|
}
|
|
13211
13261
|
}
|
|
13212
|
-
|
|
13262
|
+
log23.info("sent SIGTERM to processes", { signaled, sessionId });
|
|
13213
13263
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
13214
13264
|
for (const targetPid of processTree) {
|
|
13215
13265
|
if (!isProcessAlive(targetPid)) continue;
|
|
@@ -13219,13 +13269,13 @@ var ControlDispatcher = class {
|
|
|
13219
13269
|
const code = error.code;
|
|
13220
13270
|
if (code !== "ESRCH") {
|
|
13221
13271
|
const message = error instanceof Error ? error.message : String(error);
|
|
13222
|
-
|
|
13272
|
+
log23.warn("failed to send SIGKILL", { pid: targetPid, error: message });
|
|
13223
13273
|
}
|
|
13224
13274
|
}
|
|
13225
13275
|
}
|
|
13226
13276
|
} catch (error) {
|
|
13227
13277
|
const message = error instanceof Error ? error.message : String(error);
|
|
13228
|
-
|
|
13278
|
+
log23.warn("failed to kill session", { sessionId, error: message });
|
|
13229
13279
|
}
|
|
13230
13280
|
await this.syncInventory(true);
|
|
13231
13281
|
}
|
|
@@ -13240,7 +13290,7 @@ var ControlDispatcher = class {
|
|
|
13240
13290
|
}
|
|
13241
13291
|
}
|
|
13242
13292
|
const killed = await stopAllDetachedTmuxSessions();
|
|
13243
|
-
|
|
13293
|
+
log23.info("killed detached tmux sessions", { count: killed });
|
|
13244
13294
|
await delay(300);
|
|
13245
13295
|
await this.syncInventory(true);
|
|
13246
13296
|
return;
|
|
@@ -13280,18 +13330,18 @@ var ControlDispatcher = class {
|
|
|
13280
13330
|
const fileName = typeof payload.fileName === "string" ? payload.fileName : "attachment";
|
|
13281
13331
|
const contentBase64 = typeof payload.contentBase64 === "string" ? payload.contentBase64 : "";
|
|
13282
13332
|
if (!contentBase64 || contentBase64.length > 8 * 1024 * 1024) {
|
|
13283
|
-
|
|
13333
|
+
log23.warn("rejecting attachment upload \u2014 invalid size", { sessionId, fileName });
|
|
13284
13334
|
return;
|
|
13285
13335
|
}
|
|
13286
13336
|
let content;
|
|
13287
13337
|
try {
|
|
13288
13338
|
content = Buffer.from(contentBase64, "base64");
|
|
13289
13339
|
} catch {
|
|
13290
|
-
|
|
13340
|
+
log23.warn("rejecting attachment upload \u2014 invalid base64", { sessionId, fileName });
|
|
13291
13341
|
return;
|
|
13292
13342
|
}
|
|
13293
13343
|
if (content.length === 0 || content.length > 6 * 1024 * 1024) {
|
|
13294
|
-
|
|
13344
|
+
log23.warn("rejecting attachment upload \u2014 decoded size out of bounds", {
|
|
13295
13345
|
sessionId,
|
|
13296
13346
|
fileName,
|
|
13297
13347
|
bytes: content.length
|
|
@@ -13302,11 +13352,13 @@ var ControlDispatcher = class {
|
|
|
13302
13352
|
fs6.mkdirSync(uploadDir, { recursive: true, mode: 448 });
|
|
13303
13353
|
const writtenPath = path5.join(uploadDir, `${Date.now()}-${safeAttachmentName(fileName)}`);
|
|
13304
13354
|
fs6.writeFileSync(writtenPath, content, { mode: 384 });
|
|
13305
|
-
|
|
13355
|
+
log23.info("attachment uploaded", { sessionId, path: writtenPath, bytes: content.length });
|
|
13306
13356
|
this.handleInput(writtenPath, sessionId);
|
|
13307
13357
|
}
|
|
13308
13358
|
/** Handle input routing to the correct target. */
|
|
13309
|
-
handleInput(
|
|
13359
|
+
handleInput(rawData, sessionId) {
|
|
13360
|
+
const data = sanitizeRemoteInput(rawData);
|
|
13361
|
+
if (!data) return;
|
|
13310
13362
|
if (!sessionId || sessionId.startsWith("pty:")) {
|
|
13311
13363
|
this.shell.write(data);
|
|
13312
13364
|
} else if (sessionId.startsWith("tmux:")) {
|
|
@@ -13365,7 +13417,7 @@ var ControlDispatcher = class {
|
|
|
13365
13417
|
this.tmuxResizeState.delete(sessionId);
|
|
13366
13418
|
}
|
|
13367
13419
|
const message = error instanceof Error ? error.message : String(error);
|
|
13368
|
-
|
|
13420
|
+
log23.warn("tmux resize failed", { sessionId, error: message });
|
|
13369
13421
|
});
|
|
13370
13422
|
} else if (sessionId.startsWith("screen:") || sessionId.startsWith("zellij:")) {
|
|
13371
13423
|
this.streamer.resize(sessionId, cols, rows);
|
|
@@ -13373,30 +13425,30 @@ var ControlDispatcher = class {
|
|
|
13373
13425
|
}
|
|
13374
13426
|
/** Update the installed connector package, then restart when a service backend exists. */
|
|
13375
13427
|
async updateAgentFromNpm() {
|
|
13376
|
-
|
|
13428
|
+
log23.info("updating ragent CLI", { current: CURRENT_VERSION });
|
|
13377
13429
|
try {
|
|
13378
13430
|
await installLatestCliFromNpm();
|
|
13379
13431
|
} catch (error) {
|
|
13380
13432
|
const message = error instanceof Error ? error.message : String(error);
|
|
13381
|
-
|
|
13433
|
+
log23.error("failed to update ragent CLI", { error: message });
|
|
13382
13434
|
return;
|
|
13383
13435
|
}
|
|
13384
13436
|
const backend = getConfiguredServiceBackend();
|
|
13385
13437
|
if (!backend) {
|
|
13386
|
-
|
|
13438
|
+
log23.warn("ragent CLI updated, but no service backend is configured; restart the connector manually to use the new version");
|
|
13387
13439
|
return;
|
|
13388
13440
|
}
|
|
13389
|
-
|
|
13441
|
+
log23.info("ragent CLI updated; restarting connector service", { backend });
|
|
13390
13442
|
requestRestartSelfService();
|
|
13391
13443
|
}
|
|
13392
13444
|
/** Handle provision request from dashboard. */
|
|
13393
13445
|
async handleProvision(payload) {
|
|
13394
13446
|
const provReq = validateProvisionRequest(payload);
|
|
13395
13447
|
if (!provReq) {
|
|
13396
|
-
|
|
13448
|
+
log23.warn("rejecting provision request \u2014 malformed payload");
|
|
13397
13449
|
return;
|
|
13398
13450
|
}
|
|
13399
|
-
|
|
13451
|
+
log23.info("provision request", { name: provReq.manifest.name, provisionId: provReq.provisionId });
|
|
13400
13452
|
const sendProgress = (progress) => {
|
|
13401
13453
|
const ws = this.connection.activeSocket;
|
|
13402
13454
|
if (ws && ws.readyState === import_ws4.default.OPEN && this.connection.activeGroups.registryGroup) {
|
|
@@ -13444,7 +13496,7 @@ var ControlDispatcher = class {
|
|
|
13444
13496
|
status: "unavailable",
|
|
13445
13497
|
reason: result.reason
|
|
13446
13498
|
});
|
|
13447
|
-
|
|
13499
|
+
log23.info("could not enable markdown", { sessionId, reason: result.reason });
|
|
13448
13500
|
} else {
|
|
13449
13501
|
this.sendMarkdownStatus({
|
|
13450
13502
|
type: "markdown",
|
|
@@ -13478,24 +13530,24 @@ var ControlDispatcher = class {
|
|
|
13478
13530
|
const sessionName = typeof payload?.sessionName === "string" && payload.sessionName.trim().length > 0 ? payload.sessionName.trim() : `agent-${Date.now().toString(36)}`;
|
|
13479
13531
|
const cmd = typeof payload?.command === "string" && payload.command.trim().length > 0 ? payload.command.trim() : null;
|
|
13480
13532
|
if (!cmd) {
|
|
13481
|
-
|
|
13533
|
+
log23.warn("start-agent: no command provided, ignoring");
|
|
13482
13534
|
return;
|
|
13483
13535
|
}
|
|
13484
13536
|
if (sessionName.length > 128 || !/^[a-zA-Z0-9_-]+$/.test(sessionName)) {
|
|
13485
|
-
|
|
13537
|
+
log23.warn("start-agent: invalid session name, ignoring");
|
|
13486
13538
|
return;
|
|
13487
13539
|
}
|
|
13488
13540
|
const danger = detectDangerousCommand(cmd);
|
|
13489
13541
|
if (danger) {
|
|
13490
13542
|
if (!this._approvalEnforcer) {
|
|
13491
|
-
|
|
13543
|
+
log23.warn("start-agent: rejected dangerous command (no approval enforcer)", {
|
|
13492
13544
|
category: danger.category,
|
|
13493
13545
|
pattern: danger.pattern,
|
|
13494
13546
|
command: cmd
|
|
13495
13547
|
});
|
|
13496
13548
|
return;
|
|
13497
13549
|
}
|
|
13498
|
-
|
|
13550
|
+
log23.info("start-agent: dangerous command \u2014 awaiting approval", {
|
|
13499
13551
|
category: danger.category,
|
|
13500
13552
|
pattern: danger.pattern,
|
|
13501
13553
|
sessionName
|
|
@@ -13506,28 +13558,28 @@ var ControlDispatcher = class {
|
|
|
13506
13558
|
sessionName
|
|
13507
13559
|
);
|
|
13508
13560
|
if (response === null) {
|
|
13509
|
-
|
|
13561
|
+
log23.warn("start-agent: dangerous command not covered by approval policy, rejecting", {
|
|
13510
13562
|
category: danger.category,
|
|
13511
13563
|
command: cmd
|
|
13512
13564
|
});
|
|
13513
13565
|
return;
|
|
13514
13566
|
}
|
|
13515
13567
|
if (response.type !== "approved") {
|
|
13516
|
-
|
|
13568
|
+
log23.warn("start-agent: dangerous command denied or timed out", {
|
|
13517
13569
|
category: danger.category,
|
|
13518
13570
|
sessionName,
|
|
13519
13571
|
responseType: response.type
|
|
13520
13572
|
});
|
|
13521
13573
|
return;
|
|
13522
13574
|
}
|
|
13523
|
-
|
|
13575
|
+
log23.info("start-agent: dangerous command approved", {
|
|
13524
13576
|
category: danger.category,
|
|
13525
13577
|
sessionName
|
|
13526
13578
|
});
|
|
13527
13579
|
}
|
|
13528
13580
|
const rawWorkingDir = typeof payload?.workingDir === "string" && payload.workingDir.trim().length > 0 ? payload.workingDir.trim() : void 0;
|
|
13529
13581
|
if (rawWorkingDir !== void 0 && !isSafeWorkingDir(rawWorkingDir)) {
|
|
13530
|
-
|
|
13582
|
+
log23.warn("start-agent: rejected unsafe workingDir", { workingDir: rawWorkingDir });
|
|
13531
13583
|
return;
|
|
13532
13584
|
}
|
|
13533
13585
|
const workingDir = rawWorkingDir;
|
|
@@ -13576,7 +13628,7 @@ var ControlDispatcher = class {
|
|
|
13576
13628
|
}
|
|
13577
13629
|
if (outcome) {
|
|
13578
13630
|
if (outcome.ok) {
|
|
13579
|
-
|
|
13631
|
+
log23.info("start-agent: launched native-stream", {
|
|
13580
13632
|
sessionId: outcome.sessionId,
|
|
13581
13633
|
kind: outcome.kind,
|
|
13582
13634
|
source: outcome.transcriptSource
|
|
@@ -13584,7 +13636,7 @@ var ControlDispatcher = class {
|
|
|
13584
13636
|
await this.syncInventory(true);
|
|
13585
13637
|
return;
|
|
13586
13638
|
}
|
|
13587
|
-
|
|
13639
|
+
log23.info(
|
|
13588
13640
|
"start-agent: native-stream unavailable; falling through to tmux file-watch",
|
|
13589
13641
|
{ reason: outcome.reason, kind: outcome.kind }
|
|
13590
13642
|
);
|
|
@@ -13604,11 +13656,11 @@ var ControlDispatcher = class {
|
|
|
13604
13656
|
let tmuxLaunchOk = false;
|
|
13605
13657
|
try {
|
|
13606
13658
|
(0, import_child_process8.execFileSync)("tmux", tmuxArgs, { stdio: "ignore" });
|
|
13607
|
-
|
|
13659
|
+
log23.info("started agent session", { sessionName, command: cmd });
|
|
13608
13660
|
tmuxLaunchOk = true;
|
|
13609
13661
|
} catch (error) {
|
|
13610
13662
|
const message = error instanceof Error ? error.message : String(error);
|
|
13611
|
-
|
|
13663
|
+
log23.error("failed to start agent session", { sessionName, error: message });
|
|
13612
13664
|
}
|
|
13613
13665
|
if (tmuxLaunchOk && pendingFallbackOutcome && pendingFallbackOutcome.userMessage) {
|
|
13614
13666
|
const tmuxSessionId = this.discoverNewlyCreatedPaneId(sessionName);
|
|
@@ -13647,7 +13699,7 @@ var ControlDispatcher = class {
|
|
|
13647
13699
|
return `tmux:${sessionName}:${info}`;
|
|
13648
13700
|
}
|
|
13649
13701
|
} catch (err) {
|
|
13650
|
-
|
|
13702
|
+
log23.warn("display-message failed; status block target may not bind", {
|
|
13651
13703
|
sessionName,
|
|
13652
13704
|
err: err instanceof Error ? err.message : String(err)
|
|
13653
13705
|
});
|
|
@@ -13685,12 +13737,11 @@ var ControlDispatcher = class {
|
|
|
13685
13737
|
const viewerKey = viewerId?.trim() || "";
|
|
13686
13738
|
if (sessionId.startsWith("tmux:") || sessionId.startsWith("screen:") || sessionId.startsWith("zellij:") || sessionId.startsWith("process:")) {
|
|
13687
13739
|
const alreadyStreaming = this.streamer.isStreaming(sessionId);
|
|
13688
|
-
const viewerAlreadyAttached = alreadyStreaming && this.streamer.hasViewer(sessionId, viewerKey);
|
|
13689
13740
|
const started = this.streamer.startStream(sessionId, viewerId ?? void 0);
|
|
13690
13741
|
if (ws && ws.readyState === import_ws4.default.OPEN && group) {
|
|
13691
13742
|
if (started) {
|
|
13692
|
-
if (alreadyStreaming
|
|
13693
|
-
this.streamer.resyncStream(sessionId, viewerKey);
|
|
13743
|
+
if (alreadyStreaming) {
|
|
13744
|
+
this.streamer.resyncStream(sessionId, viewerKey, { scrollback: true });
|
|
13694
13745
|
}
|
|
13695
13746
|
const dims = this.queryPaneDimensions(sessionId);
|
|
13696
13747
|
sendToGroup(ws, group, { type: "stream-started", sessionId, ...dims });
|
|
@@ -13915,7 +13966,7 @@ function isGrantExpired(grant) {
|
|
|
13915
13966
|
}
|
|
13916
13967
|
|
|
13917
13968
|
// src/approval-enforcer.ts
|
|
13918
|
-
var
|
|
13969
|
+
var log24 = createLogger("approval");
|
|
13919
13970
|
var ApprovalEnforcer = class {
|
|
13920
13971
|
policy;
|
|
13921
13972
|
secret;
|
|
@@ -13939,7 +13990,7 @@ var ApprovalEnforcer = class {
|
|
|
13939
13990
|
async checkCommand(command, hostId, sessionId) {
|
|
13940
13991
|
const request = matchPolicy(command, this.policy, hostId, sessionId);
|
|
13941
13992
|
if (!request) return null;
|
|
13942
|
-
|
|
13993
|
+
log24.info("approval pending", {
|
|
13943
13994
|
action: request.action,
|
|
13944
13995
|
severity: request.severity,
|
|
13945
13996
|
requestId: request.requestId
|
|
@@ -13948,7 +13999,7 @@ var ApprovalEnforcer = class {
|
|
|
13948
13999
|
const timer = setTimeout(() => {
|
|
13949
14000
|
this.pendingRequests.delete(request.requestId);
|
|
13950
14001
|
const response = { type: "timeout" };
|
|
13951
|
-
|
|
14002
|
+
log24.info("approval timeout", {
|
|
13952
14003
|
action: request.action,
|
|
13953
14004
|
severity: request.severity,
|
|
13954
14005
|
requestId: request.requestId
|
|
@@ -13970,7 +14021,7 @@ var ApprovalEnforcer = class {
|
|
|
13970
14021
|
* {@link handleResponse}.
|
|
13971
14022
|
*/
|
|
13972
14023
|
async requestApproval(request) {
|
|
13973
|
-
|
|
14024
|
+
log24.info("approval pending (direct)", {
|
|
13974
14025
|
action: request.action,
|
|
13975
14026
|
severity: request.severity,
|
|
13976
14027
|
requestId: request.requestId
|
|
@@ -13979,7 +14030,7 @@ var ApprovalEnforcer = class {
|
|
|
13979
14030
|
const timer = setTimeout(() => {
|
|
13980
14031
|
this.pendingRequests.delete(request.requestId);
|
|
13981
14032
|
const response = { type: "timeout" };
|
|
13982
|
-
|
|
14033
|
+
log24.info("approval timeout (direct)", {
|
|
13983
14034
|
action: request.action,
|
|
13984
14035
|
severity: request.severity,
|
|
13985
14036
|
requestId: request.requestId
|
|
@@ -13999,7 +14050,7 @@ var ApprovalEnforcer = class {
|
|
|
13999
14050
|
if (!requestId) return;
|
|
14000
14051
|
const pending = this.pendingRequests.get(requestId);
|
|
14001
14052
|
if (!pending) {
|
|
14002
|
-
|
|
14053
|
+
log24.warn("received response for unknown approval request", { requestId });
|
|
14003
14054
|
return;
|
|
14004
14055
|
}
|
|
14005
14056
|
if (response.type === "approved") {
|
|
@@ -14008,7 +14059,7 @@ var ApprovalEnforcer = class {
|
|
|
14008
14059
|
pending.request
|
|
14009
14060
|
);
|
|
14010
14061
|
if (bindingMismatch) {
|
|
14011
|
-
|
|
14062
|
+
log24.info("approval rejected (grant binding mismatch)", {
|
|
14012
14063
|
action: pending.request.action,
|
|
14013
14064
|
severity: pending.request.severity,
|
|
14014
14065
|
requestId,
|
|
@@ -14028,7 +14079,7 @@ var ApprovalEnforcer = class {
|
|
|
14028
14079
|
return;
|
|
14029
14080
|
}
|
|
14030
14081
|
if (!this.acceptGrant(response.grant)) {
|
|
14031
|
-
|
|
14082
|
+
log24.info("approval rejected (invalid grant)", {
|
|
14032
14083
|
action: pending.request.action,
|
|
14033
14084
|
severity: pending.request.severity,
|
|
14034
14085
|
requestId
|
|
@@ -14038,13 +14089,13 @@ var ApprovalEnforcer = class {
|
|
|
14038
14089
|
pending.resolve({ type: "denied", denial: { requestId, deniedBy: "system", deniedAt: (/* @__PURE__ */ new Date()).toISOString(), reason: "Invalid or expired grant" } });
|
|
14039
14090
|
return;
|
|
14040
14091
|
}
|
|
14041
|
-
|
|
14092
|
+
log24.info("approval approved", {
|
|
14042
14093
|
action: pending.request.action,
|
|
14043
14094
|
severity: pending.request.severity,
|
|
14044
14095
|
requestId
|
|
14045
14096
|
});
|
|
14046
14097
|
} else if (response.type === "denied") {
|
|
14047
|
-
|
|
14098
|
+
log24.info("approval denied", {
|
|
14048
14099
|
action: pending.request.action,
|
|
14049
14100
|
severity: pending.request.severity,
|
|
14050
14101
|
requestId,
|
|
@@ -14085,7 +14136,7 @@ var ApprovalEnforcer = class {
|
|
|
14085
14136
|
for (const [id, entry] of this.pendingRequests) {
|
|
14086
14137
|
clearTimeout(entry.timer);
|
|
14087
14138
|
this.pendingRequests.delete(id);
|
|
14088
|
-
|
|
14139
|
+
log24.info("approval stopped", {
|
|
14089
14140
|
action: entry.request.action,
|
|
14090
14141
|
severity: entry.request.severity,
|
|
14091
14142
|
requestId: id
|
|
@@ -14121,7 +14172,7 @@ var ApprovalEnforcer = class {
|
|
|
14121
14172
|
if (entry.request.sessionId !== sessionId) continue;
|
|
14122
14173
|
clearTimeout(entry.timer);
|
|
14123
14174
|
this.pendingRequests.delete(id);
|
|
14124
|
-
|
|
14175
|
+
log24.info("approval cancelled (session torn down)", {
|
|
14125
14176
|
action: entry.request.action,
|
|
14126
14177
|
severity: entry.request.severity,
|
|
14127
14178
|
requestId: id,
|
|
@@ -14176,7 +14227,7 @@ var ApprovalEnforcer = class {
|
|
|
14176
14227
|
};
|
|
14177
14228
|
|
|
14178
14229
|
// src/agent.ts
|
|
14179
|
-
var
|
|
14230
|
+
var log25 = createLogger("agent");
|
|
14180
14231
|
var MAX_TRANSPORT_CHUNK_BYTES = 24 * 1024;
|
|
14181
14232
|
function pidFilePath(hostId) {
|
|
14182
14233
|
return path6.join(CONFIG_DIR, `agent-${hostId}.pid`);
|
|
@@ -14261,15 +14312,15 @@ async function runAgent(rawOptions) {
|
|
|
14261
14312
|
const config = loadConfig();
|
|
14262
14313
|
if (config.redaction?.enabled === false || rawOptions.redact === false) {
|
|
14263
14314
|
setRedactionEnabled(false);
|
|
14264
|
-
|
|
14315
|
+
log25.info("secret redaction disabled");
|
|
14265
14316
|
}
|
|
14266
|
-
|
|
14267
|
-
|
|
14317
|
+
log25.info("connector started", { hostName: options.hostName, hostId: options.hostId });
|
|
14318
|
+
log25.info("portal", { portal: options.portal });
|
|
14268
14319
|
try {
|
|
14269
14320
|
const existingSessions = await collectSessionInventory(options.hostId, options.command);
|
|
14270
14321
|
const tmuxCount = existingSessions.filter((s) => s.type === "tmux").length;
|
|
14271
14322
|
if (tmuxCount > 0) {
|
|
14272
|
-
|
|
14323
|
+
log25.info("found existing tmux sessions", { count: tmuxCount });
|
|
14273
14324
|
}
|
|
14274
14325
|
} catch {
|
|
14275
14326
|
}
|
|
@@ -14304,6 +14355,24 @@ async function runAgent(rawOptions) {
|
|
|
14304
14355
|
}
|
|
14305
14356
|
if (!conn.isReady()) return;
|
|
14306
14357
|
sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { type: "stream-stopped", sessionId });
|
|
14358
|
+
},
|
|
14359
|
+
(sessionId, mode, viewerId) => {
|
|
14360
|
+
if (!conn.isReady()) return;
|
|
14361
|
+
const tail = flushStreamingRedaction(sessionId);
|
|
14362
|
+
if (tail) {
|
|
14363
|
+
if (conn.sessionKey) {
|
|
14364
|
+
const { enc, iv, seq } = encryptPayload(tail, conn.sessionKey, conn.txSeq.next());
|
|
14365
|
+
sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { type: "output", enc, iv, seq, sessionId });
|
|
14366
|
+
} else {
|
|
14367
|
+
sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { type: "output", data: tail, sessionId });
|
|
14368
|
+
}
|
|
14369
|
+
}
|
|
14370
|
+
sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, {
|
|
14371
|
+
type: "stream-reset",
|
|
14372
|
+
sessionId,
|
|
14373
|
+
mode,
|
|
14374
|
+
...viewerId ? { viewerId } : {}
|
|
14375
|
+
});
|
|
14307
14376
|
}
|
|
14308
14377
|
);
|
|
14309
14378
|
const conn = new ConnectionManager(sessionStreamer, outputBuffer);
|
|
@@ -14503,7 +14572,7 @@ async function runAgent(rawOptions) {
|
|
|
14503
14572
|
dispatcher.setApprovalEnforcer(null);
|
|
14504
14573
|
}
|
|
14505
14574
|
ws.on("open", async () => {
|
|
14506
|
-
|
|
14575
|
+
log25.info("connector connected to relay");
|
|
14507
14576
|
conn.resetReconnectDelay();
|
|
14508
14577
|
ws.send(JSON.stringify({ type: "joinGroup", group: groups.privateGroup }));
|
|
14509
14578
|
ws.send(JSON.stringify({ type: "joinGroup", group: groups.registryGroup }));
|
|
@@ -14571,9 +14640,9 @@ async function runAgent(rawOptions) {
|
|
|
14571
14640
|
const inSeq = typeof payload.seq === "number" ? payload.seq : null;
|
|
14572
14641
|
const rxSeq = conn.getRxSeqForViewer(payload.viewerId);
|
|
14573
14642
|
if (inSeq === null) {
|
|
14574
|
-
|
|
14643
|
+
log25.warn("rejecting encrypted input frame without seq (#310)");
|
|
14575
14644
|
} else if (!rxSeq.accept(inSeq)) {
|
|
14576
|
-
|
|
14645
|
+
log25.warn("rejecting replayed/out-of-order input", {
|
|
14577
14646
|
seq: inSeq,
|
|
14578
14647
|
lastSeen: rxSeq.lastSeq,
|
|
14579
14648
|
viewerId: typeof payload.viewerId === "string" ? payload.viewerId : "_legacy"
|
|
@@ -14586,13 +14655,13 @@ async function runAgent(rawOptions) {
|
|
|
14586
14655
|
inSeq
|
|
14587
14656
|
);
|
|
14588
14657
|
if (inputData === null) {
|
|
14589
|
-
|
|
14658
|
+
log25.warn("failed to decrypt input \u2014 ignoring");
|
|
14590
14659
|
}
|
|
14591
14660
|
}
|
|
14592
14661
|
} else if (typeof payload.data === "string" && !conn.sessionKey) {
|
|
14593
14662
|
inputData = payload.data;
|
|
14594
14663
|
} else if (conn.sessionKey) {
|
|
14595
|
-
|
|
14664
|
+
log25.warn("rejecting plaintext input frame while encrypted channel is active");
|
|
14596
14665
|
}
|
|
14597
14666
|
if (inputData !== null) {
|
|
14598
14667
|
const sid = typeof payload.sessionId === "string" ? payload.sessionId.trim() : "";
|
|
@@ -14600,7 +14669,7 @@ async function runAgent(rawOptions) {
|
|
|
14600
14669
|
}
|
|
14601
14670
|
} else if (payload.type === "resize" && Number.isInteger(payload.cols) && Number.isInteger(payload.rows)) {
|
|
14602
14671
|
if (conn.sessionKey && !dispatcher.verifyMessageHmac(payload)) {
|
|
14603
|
-
|
|
14672
|
+
log25.warn("rejecting resize frame \u2014 HMAC verification failed");
|
|
14604
14673
|
return;
|
|
14605
14674
|
}
|
|
14606
14675
|
const sid = typeof payload.sessionId === "string" ? payload.sessionId.trim() : "";
|
|
@@ -14612,11 +14681,11 @@ async function runAgent(rawOptions) {
|
|
|
14612
14681
|
const inSeq = typeof payload.seq === "number" ? payload.seq : null;
|
|
14613
14682
|
const rxSeq = conn.getRxSeqForViewer(payload.viewerId);
|
|
14614
14683
|
if (inSeq === null) {
|
|
14615
|
-
|
|
14684
|
+
log25.warn("rejecting encrypted control frame without seq");
|
|
14616
14685
|
return;
|
|
14617
14686
|
}
|
|
14618
14687
|
if (!rxSeq.accept(inSeq)) {
|
|
14619
|
-
|
|
14688
|
+
log25.warn("rejecting replayed/out-of-order control", {
|
|
14620
14689
|
seq: inSeq,
|
|
14621
14690
|
lastSeen: rxSeq.lastSeq,
|
|
14622
14691
|
viewerId: typeof payload.viewerId === "string" ? payload.viewerId : "_legacy"
|
|
@@ -14630,7 +14699,7 @@ async function runAgent(rawOptions) {
|
|
|
14630
14699
|
inSeq
|
|
14631
14700
|
);
|
|
14632
14701
|
if (decrypted === null) {
|
|
14633
|
-
|
|
14702
|
+
log25.warn("failed to decrypt control \u2014 ignoring");
|
|
14634
14703
|
return;
|
|
14635
14704
|
}
|
|
14636
14705
|
try {
|
|
@@ -14639,11 +14708,11 @@ async function runAgent(rawOptions) {
|
|
|
14639
14708
|
_verifiedEncrypted: true
|
|
14640
14709
|
};
|
|
14641
14710
|
} catch {
|
|
14642
|
-
|
|
14711
|
+
log25.warn("failed to parse encrypted control payload");
|
|
14643
14712
|
return;
|
|
14644
14713
|
}
|
|
14645
14714
|
} else if (conn.sessionKey && payload.action === "upload-file") {
|
|
14646
|
-
|
|
14715
|
+
log25.warn("rejecting plaintext attachment upload while encrypted channel is active");
|
|
14647
14716
|
return;
|
|
14648
14717
|
}
|
|
14649
14718
|
await dispatcher.handleControlAction(controlPayload);
|
|
@@ -14663,13 +14732,13 @@ async function runAgent(rawOptions) {
|
|
|
14663
14732
|
}
|
|
14664
14733
|
});
|
|
14665
14734
|
ws.on("error", (error) => {
|
|
14666
|
-
|
|
14735
|
+
log25.error("websocket error", { error: error.message });
|
|
14667
14736
|
});
|
|
14668
14737
|
ws.on("close", (code, reason) => {
|
|
14669
14738
|
const fields = {};
|
|
14670
14739
|
if (code) fields.code = code;
|
|
14671
14740
|
if (reason.length > 0) fields.reason = reason.toString();
|
|
14672
|
-
|
|
14741
|
+
log25.info("relay disconnected; output will be buffered until reconnect", fields);
|
|
14673
14742
|
conn.cleanup();
|
|
14674
14743
|
resolve();
|
|
14675
14744
|
});
|
|
@@ -14686,35 +14755,35 @@ async function runAgent(rawOptions) {
|
|
|
14686
14755
|
});
|
|
14687
14756
|
inventory.updateOptions(options);
|
|
14688
14757
|
dispatcher.updateOptions(options);
|
|
14689
|
-
|
|
14758
|
+
log25.info("recovered from auth failure via machine credential; reconnecting");
|
|
14690
14759
|
continue;
|
|
14691
14760
|
} catch (mcError) {
|
|
14692
14761
|
if (mcError instanceof AuthError) {
|
|
14693
|
-
|
|
14762
|
+
log25.error("auth recovery failed", { error: mcError.message });
|
|
14694
14763
|
} else {
|
|
14695
14764
|
const mcMsg = mcError instanceof Error ? mcError.message : String(mcError);
|
|
14696
|
-
|
|
14765
|
+
log25.error("machine credential recovery failed", { error: mcMsg });
|
|
14697
14766
|
}
|
|
14698
14767
|
}
|
|
14699
14768
|
}
|
|
14700
|
-
|
|
14701
|
-
|
|
14769
|
+
log25.error("auth error", { error: error.message });
|
|
14770
|
+
log25.error(
|
|
14702
14771
|
"connector token is invalid or revoked. Stopping. Re-connect with: ragent connect --token <token>"
|
|
14703
14772
|
);
|
|
14704
14773
|
dispatcher.shouldRun = false;
|
|
14705
14774
|
break;
|
|
14706
14775
|
}
|
|
14707
14776
|
const message = error instanceof Error ? error.message : String(error);
|
|
14708
|
-
|
|
14777
|
+
log25.error("relay connect failed", { error: message });
|
|
14709
14778
|
}
|
|
14710
14779
|
if (!dispatcher.shouldRun) break;
|
|
14711
14780
|
if (dispatcher.reconnectRequested) {
|
|
14712
|
-
|
|
14781
|
+
log25.info("reconnecting to relay");
|
|
14713
14782
|
await wait(300);
|
|
14714
14783
|
continue;
|
|
14715
14784
|
}
|
|
14716
14785
|
const jitteredDelay = conn.reconnectDelay * (0.5 + Math.random());
|
|
14717
|
-
|
|
14786
|
+
log25.info("disconnected; reconnecting", { seconds: Math.round(jitteredDelay / 1e3) });
|
|
14718
14787
|
await wait(jitteredDelay);
|
|
14719
14788
|
conn.reconnectDelay = Math.min(conn.reconnectDelay * 1.5, MAX_RECONNECT_DELAY_MS);
|
|
14720
14789
|
}
|
|
@@ -14814,7 +14883,7 @@ function printCommandArt(title, subtitle = "remote agent control") {
|
|
|
14814
14883
|
}
|
|
14815
14884
|
|
|
14816
14885
|
// src/commands/connect.ts
|
|
14817
|
-
var
|
|
14886
|
+
var log26 = createLogger("connect");
|
|
14818
14887
|
async function connectMachine(opts) {
|
|
14819
14888
|
const portal = opts.portal || DEFAULT_PORTAL;
|
|
14820
14889
|
const hostId = sanitizeHostId(opts.id || inferHostId());
|
|
@@ -14846,12 +14915,12 @@ async function connectMachine(opts) {
|
|
|
14846
14915
|
nextConfig.refreshExpiresAt = claimed.refreshExpiresAt;
|
|
14847
14916
|
}
|
|
14848
14917
|
saveConfig(nextConfig);
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14918
|
+
log26.info("machine connected", { hostName, hostId });
|
|
14919
|
+
log26.info("machine name (override with --name <friendly-name>)", { hostName });
|
|
14920
|
+
log26.info("config saved", { path: CONFIG_FILE });
|
|
14852
14921
|
if (opts.asService) {
|
|
14853
14922
|
await ensureServiceInstalled({ enable: true, start: true });
|
|
14854
|
-
|
|
14923
|
+
log26.info("service mode enabled");
|
|
14855
14924
|
return;
|
|
14856
14925
|
}
|
|
14857
14926
|
if (opts.run !== false) {
|
|
@@ -14871,14 +14940,14 @@ function registerConnectCommand(program2) {
|
|
|
14871
14940
|
await connectMachine(opts);
|
|
14872
14941
|
} catch (error) {
|
|
14873
14942
|
const message = error instanceof Error ? error.message : String(error);
|
|
14874
|
-
|
|
14943
|
+
log26.error("connect failed", { error: message });
|
|
14875
14944
|
process.exit(1);
|
|
14876
14945
|
}
|
|
14877
14946
|
});
|
|
14878
14947
|
}
|
|
14879
14948
|
|
|
14880
14949
|
// src/commands/run.ts
|
|
14881
|
-
var
|
|
14950
|
+
var log27 = createLogger("run");
|
|
14882
14951
|
function registerRunCommand(program2) {
|
|
14883
14952
|
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
14953
|
try {
|
|
@@ -14886,7 +14955,7 @@ function registerRunCommand(program2) {
|
|
|
14886
14955
|
await runAgent(opts);
|
|
14887
14956
|
} catch (error) {
|
|
14888
14957
|
const message = error instanceof Error ? error.message : String(error);
|
|
14889
|
-
|
|
14958
|
+
log27.error("run failed", { error: message });
|
|
14890
14959
|
process.exit(1);
|
|
14891
14960
|
}
|
|
14892
14961
|
});
|
|
@@ -14894,7 +14963,7 @@ function registerRunCommand(program2) {
|
|
|
14894
14963
|
|
|
14895
14964
|
// src/commands/doctor.ts
|
|
14896
14965
|
var os12 = __toESM(require("os"));
|
|
14897
|
-
var
|
|
14966
|
+
var log28 = createLogger("doctor");
|
|
14898
14967
|
async function runDoctor(opts) {
|
|
14899
14968
|
const options = resolveRunOptions(opts);
|
|
14900
14969
|
const checks = [];
|
|
@@ -14930,27 +14999,27 @@ async function runDoctor(opts) {
|
|
|
14930
14999
|
console.log(`${check.ok ? "PASS" : "FAIL"} ${check.name}: ${check.detail}`);
|
|
14931
15000
|
});
|
|
14932
15001
|
if (!platformOk) {
|
|
14933
|
-
|
|
15002
|
+
log28.info("Linux is required for the connector");
|
|
14934
15003
|
}
|
|
14935
15004
|
if (!tmuxOk) {
|
|
14936
15005
|
const recipe = await chooseTmuxInstallCommand();
|
|
14937
15006
|
if (recipe) {
|
|
14938
|
-
|
|
15007
|
+
log28.info("tmux install suggestion", { command: recipe.command });
|
|
14939
15008
|
}
|
|
14940
15009
|
if (opts.fix) {
|
|
14941
15010
|
try {
|
|
14942
15011
|
const installed = await installTmuxInteractively();
|
|
14943
15012
|
if (installed) {
|
|
14944
|
-
|
|
15013
|
+
log28.info("tmux installation complete");
|
|
14945
15014
|
} else {
|
|
14946
|
-
|
|
15015
|
+
log28.info("tmux installation skipped or incomplete");
|
|
14947
15016
|
}
|
|
14948
15017
|
} catch (error) {
|
|
14949
15018
|
const message = error instanceof Error ? error.message : String(error);
|
|
14950
|
-
|
|
15019
|
+
log28.error("failed to install tmux", { error: message });
|
|
14951
15020
|
}
|
|
14952
15021
|
} else {
|
|
14953
|
-
|
|
15022
|
+
log28.info("run `ragent doctor --fix` to install missing dependencies interactively");
|
|
14954
15023
|
}
|
|
14955
15024
|
}
|
|
14956
15025
|
}
|
|
@@ -14962,30 +15031,30 @@ function registerDoctorCommand(program2) {
|
|
|
14962
15031
|
}
|
|
14963
15032
|
|
|
14964
15033
|
// src/commands/update.ts
|
|
14965
|
-
var
|
|
15034
|
+
var log29 = createLogger("update");
|
|
14966
15035
|
function registerUpdateCommand(program2) {
|
|
14967
15036
|
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
15037
|
printCommandArt("Update", "checking npm registry for newer ragent-cli versions");
|
|
14969
15038
|
const latestVersion = await checkForUpdate({ force: true });
|
|
14970
15039
|
if (!latestVersion) {
|
|
14971
|
-
|
|
15040
|
+
log29.info("you are up to date", { version: CURRENT_VERSION });
|
|
14972
15041
|
return;
|
|
14973
15042
|
}
|
|
14974
|
-
|
|
15043
|
+
log29.info("update available", { current: CURRENT_VERSION, latest: latestVersion });
|
|
14975
15044
|
if (opts.check) return;
|
|
14976
15045
|
await installLatestCliFromNpm();
|
|
14977
|
-
|
|
15046
|
+
log29.info("updated", { version: latestVersion });
|
|
14978
15047
|
const backend = getConfiguredServiceBackend();
|
|
14979
15048
|
if (backend && opts.restart) {
|
|
14980
|
-
|
|
15049
|
+
log29.info("restarting service", { backend });
|
|
14981
15050
|
try {
|
|
14982
15051
|
await restartService();
|
|
14983
15052
|
} catch (err) {
|
|
14984
|
-
|
|
14985
|
-
|
|
15053
|
+
log29.error("failed to restart service", { error: err instanceof Error ? err.message : String(err) });
|
|
15054
|
+
log29.info("please restart manually: ragent service restart");
|
|
14986
15055
|
}
|
|
14987
15056
|
} else if (!backend) {
|
|
14988
|
-
|
|
15057
|
+
log29.info("no service backend detected. If ragent is running manually, restart it to use the new version");
|
|
14989
15058
|
}
|
|
14990
15059
|
});
|
|
14991
15060
|
}
|
|
@@ -15009,7 +15078,7 @@ function registerSessionsCommand(program2) {
|
|
|
15009
15078
|
}
|
|
15010
15079
|
|
|
15011
15080
|
// src/commands/service.ts
|
|
15012
|
-
var
|
|
15081
|
+
var log30 = createLogger("service-cmd");
|
|
15013
15082
|
function registerServiceCommand(program2) {
|
|
15014
15083
|
const service = program2.command("service").description("Manage background connector service");
|
|
15015
15084
|
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 +15089,7 @@ function registerServiceCommand(program2) {
|
|
|
15020
15089
|
});
|
|
15021
15090
|
} catch (error) {
|
|
15022
15091
|
const message = error instanceof Error ? error.message : String(error);
|
|
15023
|
-
|
|
15092
|
+
log30.error("service install failed", { error: message });
|
|
15024
15093
|
process.exit(1);
|
|
15025
15094
|
}
|
|
15026
15095
|
});
|
|
@@ -15029,7 +15098,7 @@ function registerServiceCommand(program2) {
|
|
|
15029
15098
|
await startService();
|
|
15030
15099
|
} catch (error) {
|
|
15031
15100
|
const message = error instanceof Error ? error.message : String(error);
|
|
15032
|
-
|
|
15101
|
+
log30.error("service start failed", { error: message });
|
|
15033
15102
|
process.exit(1);
|
|
15034
15103
|
}
|
|
15035
15104
|
});
|
|
@@ -15038,7 +15107,7 @@ function registerServiceCommand(program2) {
|
|
|
15038
15107
|
await stopService();
|
|
15039
15108
|
} catch (error) {
|
|
15040
15109
|
const message = error instanceof Error ? error.message : String(error);
|
|
15041
|
-
|
|
15110
|
+
log30.error("service stop failed", { error: message });
|
|
15042
15111
|
process.exit(1);
|
|
15043
15112
|
}
|
|
15044
15113
|
});
|
|
@@ -15047,7 +15116,7 @@ function registerServiceCommand(program2) {
|
|
|
15047
15116
|
await restartService();
|
|
15048
15117
|
} catch (error) {
|
|
15049
15118
|
const message = error instanceof Error ? error.message : String(error);
|
|
15050
|
-
|
|
15119
|
+
log30.error("service restart failed", { error: message });
|
|
15051
15120
|
process.exit(1);
|
|
15052
15121
|
}
|
|
15053
15122
|
});
|
|
@@ -15056,7 +15125,7 @@ function registerServiceCommand(program2) {
|
|
|
15056
15125
|
await printServiceStatus();
|
|
15057
15126
|
} catch (error) {
|
|
15058
15127
|
const message = error instanceof Error ? error.message : String(error);
|
|
15059
|
-
|
|
15128
|
+
log30.error("service status failed", { error: message });
|
|
15060
15129
|
process.exit(1);
|
|
15061
15130
|
}
|
|
15062
15131
|
});
|
|
@@ -15065,7 +15134,7 @@ function registerServiceCommand(program2) {
|
|
|
15065
15134
|
await printServiceLogs(opts);
|
|
15066
15135
|
} catch (error) {
|
|
15067
15136
|
const message = error instanceof Error ? error.message : String(error);
|
|
15068
|
-
|
|
15137
|
+
log30.error("service logs failed", { error: message });
|
|
15069
15138
|
process.exit(1);
|
|
15070
15139
|
}
|
|
15071
15140
|
});
|
|
@@ -15074,7 +15143,7 @@ function registerServiceCommand(program2) {
|
|
|
15074
15143
|
await uninstallService();
|
|
15075
15144
|
} catch (error) {
|
|
15076
15145
|
const message = error instanceof Error ? error.message : String(error);
|
|
15077
|
-
|
|
15146
|
+
log30.error("service uninstall failed", { error: message });
|
|
15078
15147
|
process.exit(1);
|
|
15079
15148
|
}
|
|
15080
15149
|
});
|
|
@@ -15082,7 +15151,7 @@ function registerServiceCommand(program2) {
|
|
|
15082
15151
|
|
|
15083
15152
|
// src/commands/uninstall.ts
|
|
15084
15153
|
var fs8 = __toESM(require("fs"));
|
|
15085
|
-
var
|
|
15154
|
+
var log31 = createLogger("uninstall");
|
|
15086
15155
|
async function uninstallAgent(opts) {
|
|
15087
15156
|
const config = loadConfig();
|
|
15088
15157
|
const hostName = config.hostName || config.hostId || "this machine";
|
|
@@ -15092,12 +15161,12 @@ async function uninstallAgent(opts) {
|
|
|
15092
15161
|
false
|
|
15093
15162
|
);
|
|
15094
15163
|
if (!confirmed) {
|
|
15095
|
-
|
|
15164
|
+
log31.info("uninstall cancelled");
|
|
15096
15165
|
return;
|
|
15097
15166
|
}
|
|
15098
15167
|
}
|
|
15099
15168
|
if (config.portal && config.agentToken) {
|
|
15100
|
-
|
|
15169
|
+
log31.info("deregistering from server");
|
|
15101
15170
|
try {
|
|
15102
15171
|
const token = await refreshTokenIfNeeded({
|
|
15103
15172
|
portal: config.portal,
|
|
@@ -15105,24 +15174,24 @@ async function uninstallAgent(opts) {
|
|
|
15105
15174
|
});
|
|
15106
15175
|
const ok = await deregisterHost({ portal: config.portal, agentToken: token });
|
|
15107
15176
|
if (ok) {
|
|
15108
|
-
|
|
15177
|
+
log31.info("host removed from server");
|
|
15109
15178
|
}
|
|
15110
15179
|
} catch {
|
|
15111
|
-
|
|
15180
|
+
log31.warn("could not reach server \u2014 host may need manual removal from dashboard");
|
|
15112
15181
|
}
|
|
15113
15182
|
}
|
|
15114
|
-
|
|
15183
|
+
log31.info("stopping and removing service");
|
|
15115
15184
|
await uninstallService().catch(() => void 0);
|
|
15116
15185
|
if (fs8.existsSync(CONFIG_DIR)) {
|
|
15117
15186
|
fs8.rmSync(CONFIG_DIR, { recursive: true, force: true });
|
|
15118
|
-
|
|
15187
|
+
log31.info("removed config directory", { path: CONFIG_DIR });
|
|
15119
15188
|
}
|
|
15120
15189
|
try {
|
|
15121
15190
|
await execAsync(`npm unlink -g ${PACKAGE_NAME}`, { timeout: 15e3 });
|
|
15122
|
-
|
|
15191
|
+
log31.info("unlinked global package", { package: PACKAGE_NAME });
|
|
15123
15192
|
} catch {
|
|
15124
15193
|
}
|
|
15125
|
-
|
|
15194
|
+
log31.info("uninstall complete. rAgent has been removed from this machine");
|
|
15126
15195
|
}
|
|
15127
15196
|
function registerUninstallCommand(parent) {
|
|
15128
15197
|
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 +15200,7 @@ function registerUninstallCommand(parent) {
|
|
|
15131
15200
|
await uninstallAgent(opts);
|
|
15132
15201
|
} catch (error) {
|
|
15133
15202
|
const message = error instanceof Error ? error.message : String(error);
|
|
15134
|
-
|
|
15203
|
+
log31.error("uninstall failed", { error: message });
|
|
15135
15204
|
process.exit(1);
|
|
15136
15205
|
}
|
|
15137
15206
|
});
|
|
@@ -15190,10 +15259,10 @@ function registerDiscoverCommand(program2) {
|
|
|
15190
15259
|
}
|
|
15191
15260
|
|
|
15192
15261
|
// src/index.ts
|
|
15193
|
-
var
|
|
15262
|
+
var log32 = createLogger("cli");
|
|
15194
15263
|
process.on("unhandledRejection", (reason) => {
|
|
15195
15264
|
const message = reason instanceof Error ? reason.stack || reason.message : String(reason);
|
|
15196
|
-
|
|
15265
|
+
log32.error("unhandled promise rejection", { error: message });
|
|
15197
15266
|
});
|
|
15198
15267
|
import_commander.program.name("ragent").description("Connect machines to rAgent Live").version(CURRENT_VERSION);
|
|
15199
15268
|
registerConnectCommand(import_commander.program);
|