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 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.13",
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
- const t0 = performance.now();
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
- console.error("[PROFILE-BARE] Error:", err);
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 log15 = createLogger("auth");
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
- log15.info("attempting session recovery with machine credential");
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
- log15.info("session recovered via machine credential");
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
- log15.info("token expiring \u2014 refreshing", { minutesRemaining: Math.round(remainingSeconds / 60) });
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
- log15.warn("token refresh failed", { error: errorData.error || response.status });
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
- log15.warn("machine credential recovery failed", { error: mcMessage });
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
- log15.info("token refreshed successfully");
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
- log15.warn("token refresh error", { error: message });
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
- log15.warn("token expired \u2014 could not deregister from server. Host may remain visible in dashboard until manually removed");
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 log16 = createLogger("session-streamer");
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
- constructor(sendFn, onStreamStopped) {
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
- log16.info("pty-attach resumed after WS queue drained", { sessionId: stream.sessionId });
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
- log16.warn("pty-attach paused under WS backpressure", {
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
- log16.info("cleaned up stale stream directories from previous run");
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
- log16.info("cancelled pending stop (re-mounted)", { sessionId });
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
- log16.warn("stream limit reached; rejecting", {
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
- log16.warn("failed to write input", { sessionId, error: message });
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
- log16.warn("resize failed", { sessionId, error: message });
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
- log16.info("stopped streaming", { sessionId });
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
- log16.info("stopped streaming", { sessionId: id });
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 reconnected ("Retry stream"). Does NOT restart pipe-pane.
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 send = (data) => {
11132
- if (targetViewerId) {
11133
- this.sendFn(sessionId, data, targetViewerId);
11134
- } else {
11135
- this.sendFn(sessionId, data);
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
- try {
11140
- let alternateScreen = false;
11141
- try {
11142
- const altFlag = (0, import_node_child_process2.execFileSync)(
11143
- "tmux",
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
- const scrollback = (0, import_node_child_process2.execFileSync)(
11152
- "tmux",
11153
- ["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
11154
- { env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
11155
- );
11156
- if (scrollback && scrollback.length > 0) {
11157
- send(scrollback.replace(/\n/g, "\r\n"));
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
- send("\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
11162
- try {
11163
- const captureArgs = alternateScreen ? ["capture-pane", "-a", "-t", paneTarget, "-p", "-e"] : ["capture-pane", "-t", paneTarget, "-p", "-e"];
11164
- const initial = (0, import_node_child_process2.execFileSync)(
11165
- "tmux",
11166
- captureArgs,
11167
- { env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
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 (initial) {
11170
- send(stripAlternateScreenModeSwitches(initial.replace(/\n/g, "\r\n").replace(/\r\n$/, "")));
11248
+ if (converted) {
11249
+ send(converted);
11250
+ return converted.split("\r\n").length;
11171
11251
  }
11172
- } catch {
11173
11252
  }
11174
- try {
11175
- const cursorInfo = (0, import_node_child_process2.execFileSync)(
11176
- "tmux",
11177
- ["display-message", "-t", paneTarget, "-p", "#{cursor_x} #{cursor_y}"],
11178
- { env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
11179
- ).trim();
11180
- const parts = cursorInfo.split(" ");
11181
- if (parts.length === 2) {
11182
- const x = parseInt(parts[0], 10);
11183
- const y = parseInt(parts[1], 10);
11184
- if (!isNaN(x) && !isNaN(y)) {
11185
- send(`\x1B[${y + 1};${x + 1}H`);
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
- stream.initializing = false;
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
- log16.warn("failed pipe-pane", { sessionId, error: message });
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
- log16.info("session stream ended", { sessionId });
11356
+ log17.info("session stream ended", { sessionId });
11281
11357
  this.onStreamStopped?.(sessionId);
11282
11358
  }
11283
11359
  });
11284
- let alternateScreen = false;
11285
- try {
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
- try {
11308
- const captureArgs = alternateScreen ? ["capture-pane", "-a", "-t", paneTarget, "-p", "-e"] : ["capture-pane", "-t", paneTarget, "-p", "-e"];
11309
- const initial = (0, import_node_child_process2.execFileSync)(
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
- log16.info("started streaming (tmux)", { sessionId, pane: paneTarget });
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
- log16.warn("failed to start stream", { sessionId, error: message });
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
- log16.info("session stream ended", { sessionId });
11431
+ log17.info("session stream ended", { sessionId });
11388
11432
  this.onStreamStopped?.(sessionId);
11389
11433
  }
11390
11434
  });
11391
- log16.info("started streaming (screen)", { sessionId, screen: sessionName });
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
- log16.warn("failed to start screen stream", { sessionId, error: message });
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
- log16.info("session stream ended", { sessionId });
11485
+ log17.info("session stream ended", { sessionId });
11442
11486
  this.onStreamStopped?.(sessionId);
11443
11487
  }
11444
11488
  });
11445
- log16.info("started streaming (zellij)", { sessionId, zellij: sessionName });
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
- log16.warn("failed to start zellij stream", { sessionId, error: message });
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
- log16.warn("process does not exist (no /proc entry)", { pid });
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
- log16.warn("process is a zombie", { pid });
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
- log16.info("session stream ended", { sessionId });
11585
+ log17.info("session stream ended", { sessionId });
11542
11586
  this.onStreamStopped?.(sessionId);
11543
11587
  });
11544
- log16.info("started streaming (process trace)", { sessionId, pid });
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
- log16.warn("failed to start process stream", { sessionId, error: message });
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 log17 = createLogger("crypto-channel");
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
- log17.warn("decrypt failed", { seq, encBytes: enc.length, error: message });
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 log18 = createLogger("pty");
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(data) {
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
- log18.warn("invalid tmux session name", { sessionName });
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
- log18.warn("failed to send input", { sessionId, error: message });
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
- log18.warn("invalid tmux session name", { sessionName });
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
- log18.warn("failed to resize tmux pane", { sessionId, error: message });
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 log19 = createLogger("shell");
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
- log19.warn("resize failed", { error: message });
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
- log19.warn("shell exited; restarting shell process");
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 log20 = createLogger("connection");
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
- log20.warn("rxSeq fell back to _legacy bucket \u2014 portal sent empty viewerId", { typeofViewerId: typeof viewerId });
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
- log20.error("failed to send JSON ping", { error: err.message });
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
- log20.warn("no pong received within 10s; closing stale connection");
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
- log20.info("replaying buffered output chunks", {
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 log21 = createLogger("service");
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
- log21.info("installed systemd user service", { path: SERVICE_FILE });
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
- log21.info("service already running", { pid: existingPid });
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
- log21.info("started fallback background service", { pid: child.pid });
12434
- log21.info("logs", { path: FALLBACK_LOG_FILE });
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
- log21.info("service is not running");
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
- log21.info("stopped fallback background service", { pid });
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
- log21.info("systemd user manager unavailable; using fallback pidfile backend");
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
- log21.info("started service via systemd");
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
- log21.info("stopped service via systemd");
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
- log21.info("stopped stale agent process", { pid });
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
- log21.info("restarted service via systemd");
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
- log21.info("systemd service is not installed or has no status");
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
- log21.info("fallback service running", { pid });
12547
- log21.info("logs", { path: FALLBACK_LOG_FILE });
12596
+ log22.info("fallback service running", { pid });
12597
+ log22.info("logs", { path: FALLBACK_LOG_FILE });
12548
12598
  return;
12549
12599
  }
12550
- log21.info("service is not running");
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
- log21.info("no log file found", { path: FALLBACK_LOG_FILE });
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
- log21.info("service uninstalled");
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 log22 = createLogger("control");
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
- log22.warn("native-stream ops produced but no transport sink installed", {
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
- log22.warn("control message missing HMAC signature");
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
- log22.warn("control message has malformed HMAC signature");
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
- log22.warn("rejecting control action \u2014 HMAC verification failed", { action });
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
- log22.info("closed remote session", { sessionId });
13228
+ log23.info("closed remote session", { sessionId });
13179
13229
  } catch (error) {
13180
13230
  const message = error instanceof Error ? error.message : String(error);
13181
- log22.warn("failed to close session", { sessionId, error: message });
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
- log22.warn("kill-process: could not parse PID", { sessionId });
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
- log22.warn("failed to send SIGTERM", { pid: targetPid, error: message });
13258
+ log23.warn("failed to send SIGTERM", { pid: targetPid, error: message });
13209
13259
  }
13210
13260
  }
13211
13261
  }
13212
- log22.info("sent SIGTERM to processes", { signaled, sessionId });
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
- log22.warn("failed to send SIGKILL", { pid: targetPid, error: message });
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
- log22.warn("failed to kill session", { sessionId, error: message });
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
- log22.info("killed detached tmux sessions", { count: killed });
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
- log22.warn("rejecting attachment upload \u2014 invalid size", { sessionId, fileName });
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
- log22.warn("rejecting attachment upload \u2014 invalid base64", { sessionId, fileName });
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
- log22.warn("rejecting attachment upload \u2014 decoded size out of bounds", {
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
- log22.info("attachment uploaded", { sessionId, path: writtenPath, bytes: content.length });
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(data, sessionId) {
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
- log22.warn("tmux resize failed", { sessionId, error: message });
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
- log22.info("updating ragent CLI", { current: CURRENT_VERSION });
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
- log22.error("failed to update ragent CLI", { error: message });
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
- log22.warn("ragent CLI updated, but no service backend is configured; restart the connector manually to use the new version");
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
- log22.info("ragent CLI updated; restarting connector service", { backend });
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
- log22.warn("rejecting provision request \u2014 malformed payload");
13448
+ log23.warn("rejecting provision request \u2014 malformed payload");
13397
13449
  return;
13398
13450
  }
13399
- log22.info("provision request", { name: provReq.manifest.name, provisionId: provReq.provisionId });
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
- log22.info("could not enable markdown", { sessionId, reason: result.reason });
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
- log22.warn("start-agent: no command provided, ignoring");
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
- log22.warn("start-agent: invalid session name, ignoring");
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
- log22.warn("start-agent: rejected dangerous command (no approval enforcer)", {
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
- log22.info("start-agent: dangerous command \u2014 awaiting approval", {
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
- log22.warn("start-agent: dangerous command not covered by approval policy, rejecting", {
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
- log22.warn("start-agent: dangerous command denied or timed out", {
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
- log22.info("start-agent: dangerous command approved", {
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
- log22.warn("start-agent: rejected unsafe workingDir", { workingDir: rawWorkingDir });
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
- log22.info("start-agent: launched native-stream", {
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
- log22.info(
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
- log22.info("started agent session", { sessionName, command: cmd });
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
- log22.error("failed to start agent session", { sessionName, error: message });
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
- log22.warn("display-message failed; status block target may not bind", {
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 && !viewerAlreadyAttached) {
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 log23 = createLogger("approval");
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
- log23.info("approval pending", {
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
- log23.info("approval timeout", {
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
- log23.info("approval pending (direct)", {
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
- log23.info("approval timeout (direct)", {
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
- log23.warn("received response for unknown approval request", { requestId });
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
- log23.info("approval rejected (grant binding mismatch)", {
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
- log23.info("approval rejected (invalid grant)", {
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
- log23.info("approval approved", {
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
- log23.info("approval denied", {
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
- log23.info("approval stopped", {
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
- log23.info("approval cancelled (session torn down)", {
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 log24 = createLogger("agent");
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
- log24.info("secret redaction disabled");
14315
+ log25.info("secret redaction disabled");
14265
14316
  }
14266
- log24.info("connector started", { hostName: options.hostName, hostId: options.hostId });
14267
- log24.info("portal", { portal: options.portal });
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
- log24.info("found existing tmux sessions", { count: tmuxCount });
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
- log24.info("connector connected to relay");
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
- log24.warn("rejecting encrypted input frame without seq (#310)");
14643
+ log25.warn("rejecting encrypted input frame without seq (#310)");
14575
14644
  } else if (!rxSeq.accept(inSeq)) {
14576
- log24.warn("rejecting replayed/out-of-order input", {
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
- log24.warn("failed to decrypt input \u2014 ignoring");
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
- log24.warn("rejecting plaintext input frame while encrypted channel is active");
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
- log24.warn("rejecting resize frame \u2014 HMAC verification failed");
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
- log24.warn("rejecting encrypted control frame without seq");
14684
+ log25.warn("rejecting encrypted control frame without seq");
14616
14685
  return;
14617
14686
  }
14618
14687
  if (!rxSeq.accept(inSeq)) {
14619
- log24.warn("rejecting replayed/out-of-order control", {
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
- log24.warn("failed to decrypt control \u2014 ignoring");
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
- log24.warn("failed to parse encrypted control payload");
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
- log24.warn("rejecting plaintext attachment upload while encrypted channel is active");
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
- log24.error("websocket error", { error: error.message });
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
- log24.info("relay disconnected; output will be buffered until reconnect", fields);
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
- log24.info("recovered from auth failure via machine credential; reconnecting");
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
- log24.error("auth recovery failed", { error: mcError.message });
14762
+ log25.error("auth recovery failed", { error: mcError.message });
14694
14763
  } else {
14695
14764
  const mcMsg = mcError instanceof Error ? mcError.message : String(mcError);
14696
- log24.error("machine credential recovery failed", { error: mcMsg });
14765
+ log25.error("machine credential recovery failed", { error: mcMsg });
14697
14766
  }
14698
14767
  }
14699
14768
  }
14700
- log24.error("auth error", { error: error.message });
14701
- log24.error(
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
- log24.error("relay connect failed", { error: message });
14777
+ log25.error("relay connect failed", { error: message });
14709
14778
  }
14710
14779
  if (!dispatcher.shouldRun) break;
14711
14780
  if (dispatcher.reconnectRequested) {
14712
- log24.info("reconnecting to relay");
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
- log24.info("disconnected; reconnecting", { seconds: Math.round(jitteredDelay / 1e3) });
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 log25 = createLogger("connect");
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
- log25.info("machine connected", { hostName, hostId });
14850
- log25.info("machine name (override with --name <friendly-name>)", { hostName });
14851
- log25.info("config saved", { path: CONFIG_FILE });
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
- log25.info("service mode enabled");
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
- log25.error("connect failed", { error: message });
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 log26 = createLogger("run");
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
- log26.error("run failed", { error: message });
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 log27 = createLogger("doctor");
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
- log27.info("Linux is required for the connector");
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
- log27.info("tmux install suggestion", { command: recipe.command });
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
- log27.info("tmux installation complete");
15013
+ log28.info("tmux installation complete");
14945
15014
  } else {
14946
- log27.info("tmux installation skipped or incomplete");
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
- log27.error("failed to install tmux", { error: message });
15019
+ log28.error("failed to install tmux", { error: message });
14951
15020
  }
14952
15021
  } else {
14953
- log27.info("run `ragent doctor --fix` to install missing dependencies interactively");
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 log28 = createLogger("update");
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
- log28.info("you are up to date", { version: CURRENT_VERSION });
15040
+ log29.info("you are up to date", { version: CURRENT_VERSION });
14972
15041
  return;
14973
15042
  }
14974
- log28.info("update available", { current: CURRENT_VERSION, latest: latestVersion });
15043
+ log29.info("update available", { current: CURRENT_VERSION, latest: latestVersion });
14975
15044
  if (opts.check) return;
14976
15045
  await installLatestCliFromNpm();
14977
- log28.info("updated", { version: latestVersion });
15046
+ log29.info("updated", { version: latestVersion });
14978
15047
  const backend = getConfiguredServiceBackend();
14979
15048
  if (backend && opts.restart) {
14980
- log28.info("restarting service", { backend });
15049
+ log29.info("restarting service", { backend });
14981
15050
  try {
14982
15051
  await restartService();
14983
15052
  } catch (err) {
14984
- log28.error("failed to restart service", { error: err instanceof Error ? err.message : String(err) });
14985
- log28.info("please restart manually: ragent service restart");
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
- log28.info("no service backend detected. If ragent is running manually, restart it to use the new version");
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 log29 = createLogger("service-cmd");
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
- log29.error("service install failed", { error: message });
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
- log29.error("service start failed", { error: message });
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
- log29.error("service stop failed", { error: message });
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
- log29.error("service restart failed", { error: message });
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
- log29.error("service status failed", { error: message });
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
- log29.error("service logs failed", { error: message });
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
- log29.error("service uninstall failed", { error: message });
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 log30 = createLogger("uninstall");
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
- log30.info("uninstall cancelled");
15164
+ log31.info("uninstall cancelled");
15096
15165
  return;
15097
15166
  }
15098
15167
  }
15099
15168
  if (config.portal && config.agentToken) {
15100
- log30.info("deregistering from server");
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
- log30.info("host removed from server");
15177
+ log31.info("host removed from server");
15109
15178
  }
15110
15179
  } catch {
15111
- log30.warn("could not reach server \u2014 host may need manual removal from dashboard");
15180
+ log31.warn("could not reach server \u2014 host may need manual removal from dashboard");
15112
15181
  }
15113
15182
  }
15114
- log30.info("stopping and removing service");
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
- log30.info("removed config directory", { path: CONFIG_DIR });
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
- log30.info("unlinked global package", { package: PACKAGE_NAME });
15191
+ log31.info("unlinked global package", { package: PACKAGE_NAME });
15123
15192
  } catch {
15124
15193
  }
15125
- log30.info("uninstall complete. rAgent has been removed from this machine");
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
- log30.error("uninstall failed", { error: message });
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 log31 = createLogger("cli");
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
- log31.error("unhandled promise rejection", { error: message });
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);