ragent-cli 1.11.17 → 1.11.20

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.17",
34
+ version: "1.11.20",
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: {
@@ -90,7 +90,7 @@ var require_package = __commonJS({
90
90
  commander: "^14.0.3",
91
91
  figlet: "^1.9.3",
92
92
  "node-pty": "^1.1.0",
93
- ws: "^8.19.0"
93
+ ws: "^8.21.0"
94
94
  },
95
95
  devDependencies: {
96
96
  "@changesets/changelog-github": "^0.5.2",
@@ -4185,7 +4185,7 @@ var TranscriptWatcherManager = class {
4185
4185
  clearTranscriptState(sessionId) {
4186
4186
  this.sendSnapshotFn(sessionId, [], 0);
4187
4187
  if (this.sendV2SnapshotFn) {
4188
- this.sendV2SnapshotFn(sessionId, createEmptySnapshot(sessionId), 0);
4188
+ this.sendV2SnapshotFn(sessionId, createEmptySnapshot(sessionId), 0, true);
4189
4189
  }
4190
4190
  }
4191
4191
  /**
@@ -4271,7 +4271,7 @@ var TranscriptWatcherManager = class {
4271
4271
  }
4272
4272
  this.sendSnapshotFn(sessionId, [], 0);
4273
4273
  if (this.sendV2SnapshotFn) {
4274
- this.sendV2SnapshotFn(sessionId, createEmptySnapshot(sessionId), 0);
4274
+ this.sendV2SnapshotFn(sessionId, createEmptySnapshot(sessionId), 0, true);
4275
4275
  }
4276
4276
  newWatcher.replayFromStart();
4277
4277
  this.active.set(sessionId, { watcher: newWatcher, filePath: newPath, agentType: agentType ?? "" });
@@ -10857,6 +10857,9 @@ var import_node_string_decoder = require("string_decoder");
10857
10857
  var pty = __toESM(require("node-pty"));
10858
10858
  var log17 = createLogger("session-streamer");
10859
10859
  var BACKPRESSURE_RESUME_CHECK_MS = 100;
10860
+ var LIVENESS_CHECK_TICKS = 30;
10861
+ var DEAD_PIPE_STRIKES = 2;
10862
+ var REESTABLISH_BACKOFF_MS = 3e4;
10860
10863
  var STOP_DEBOUNCE_MS = 2e3;
10861
10864
  var MAX_CONCURRENT_STREAMS = 20;
10862
10865
  var MAX_SESSION_BUFFER_BYTES = 64 * 1024;
@@ -10979,11 +10982,19 @@ var SessionStreamer = class {
10979
10982
  lastSeenDroppedFrames = 0;
10980
10983
  /** Sessions whose viewers need a capture-pane resync after frame drops. */
10981
10984
  pendingDropResync = /* @__PURE__ */ new Set();
10982
- constructor(sendFn, onStreamStopped, sendResetFn, onFramesDropped) {
10985
+ /** Whether the relay socket is currently OPEN. Used to (a) gate resync delivery
10986
+ * so resync bytes aren't buffered-then-discarded under reconnect churn, and
10987
+ * (b) gate the liveness watchdog's re-establish so its capture burst lands. */
10988
+ isReadyFn;
10989
+ /** Tick counter for the resume timer; the pipe-liveness check runs every
10990
+ * LIVENESS_CHECK_TICKS ticks rather than on every 100ms tick. */
10991
+ livenessTick = 0;
10992
+ constructor(sendFn, onStreamStopped, sendResetFn, onFramesDropped, isReadyFn) {
10983
10993
  this.sendFn = sendFn;
10984
10994
  this.onStreamStopped = onStreamStopped;
10985
10995
  this.sendResetFn = sendResetFn;
10986
10996
  this.onFramesDropped = onFramesDropped;
10997
+ this.isReadyFn = isReadyFn ?? (() => true);
10987
10998
  this.cleanupStaleStreams();
10988
10999
  this.startBackpressureResumeTimer();
10989
11000
  }
@@ -11037,8 +11048,119 @@ var SessionStreamer = class {
11037
11048
  this.resyncStream(next.value);
11038
11049
  }
11039
11050
  }
11051
+ if (this.isReadyFn()) {
11052
+ for (const stream of this.active.values()) {
11053
+ if (stream.needsResync && !stream.stopped && stream.streamType === "tmux-pipe" && !stream.initializing) {
11054
+ stream.needsResync = false;
11055
+ this.resyncStream(stream.sessionId);
11056
+ break;
11057
+ }
11058
+ }
11059
+ }
11060
+ if (++this.livenessTick >= LIVENESS_CHECK_TICKS) {
11061
+ this.livenessTick = 0;
11062
+ if (this.isReadyFn()) this.checkPipeLiveness();
11063
+ }
11040
11064
  }, BACKPRESSURE_RESUME_CHECK_MS);
11041
11065
  }
11066
+ /**
11067
+ * Re-establish a tmux-pipe stream whose `pane_pid` indicates a program swap
11068
+ * (tmux-resurrect restore, pane reuse, exec) or whose pipe-pane is alive in
11069
+ * tmux's view (`pane_pipe=1`) but no longer feeds our FIFO.
11070
+ *
11071
+ * Why this exists: `pipe-pane` is set up exactly once at stream start. tmux
11072
+ * reports `pipe=1` afterwards even when it has stopped delivering bytes (e.g.
11073
+ * the pane was recreated by a restore racing the connector restart). The
11074
+ * `cat` reader blocks forever rather than exiting, so the normal exit cleanup
11075
+ * never fires, and `resyncStream` deliberately does NOT restart pipe-pane —
11076
+ * so without this the pane is frozen until the stream is torn down by hand.
11077
+ *
11078
+ * Costs one batched `tmux list-panes -a` fork for pids, plus one `capture-pane`
11079
+ * fork ONLY per currently-silent stream (flowing streams are provably alive and
11080
+ * skip the hash) — so ~one fork/tick on a healthy host.
11081
+ */
11082
+ checkPipeLiveness() {
11083
+ const tmuxStreams = [...this.active.values()].filter(
11084
+ (s) => s.streamType === "tmux-pipe" && !s.stopped && !s.initializing
11085
+ );
11086
+ if (tmuxStreams.length === 0) return;
11087
+ const panes = this.readAllPaneInfo();
11088
+ if (!panes) return;
11089
+ const now2 = Date.now();
11090
+ const dead = [];
11091
+ for (const stream of tmuxStreams) {
11092
+ const info = panes.get(stream.paneTarget);
11093
+ if (!info) continue;
11094
+ const backedOff = stream.lastReestablishAt !== void 0 && now2 - stream.lastReestablishAt < REESTABLISH_BACKOFF_MS;
11095
+ if (stream.panePid && info.panePid !== stream.panePid) {
11096
+ if (!backedOff) {
11097
+ dead.push({
11098
+ sessionId: stream.sessionId,
11099
+ reason: "pane program swapped",
11100
+ detail: { oldPid: stream.panePid, newPid: info.panePid }
11101
+ });
11102
+ }
11103
+ continue;
11104
+ }
11105
+ const bytesNow = stream.bytesReceived ?? 0;
11106
+ const fifoSilent = bytesNow === (stream.lastLivenessBytes ?? 0);
11107
+ if (!fifoSilent) {
11108
+ stream.deadPipeStrikes = 0;
11109
+ } else {
11110
+ const contentHash = this.paneContentHash(stream.paneTarget, stream.cleanEnv);
11111
+ if (contentHash !== null) {
11112
+ const paneMoved = stream.lastContentHash !== void 0 && contentHash !== stream.lastContentHash;
11113
+ if (paneMoved) {
11114
+ stream.deadPipeStrikes = (stream.deadPipeStrikes ?? 0) + 1;
11115
+ if (stream.deadPipeStrikes >= DEAD_PIPE_STRIKES && !backedOff) {
11116
+ dead.push({
11117
+ sessionId: stream.sessionId,
11118
+ reason: "pipe-pane silent while pane active",
11119
+ detail: { strikes: stream.deadPipeStrikes }
11120
+ });
11121
+ }
11122
+ } else {
11123
+ stream.deadPipeStrikes = 0;
11124
+ }
11125
+ stream.lastContentHash = contentHash;
11126
+ }
11127
+ }
11128
+ stream.lastLivenessBytes = bytesNow;
11129
+ }
11130
+ if (dead.length === 0) return;
11131
+ const [first, ...rest] = dead;
11132
+ log17.warn(`${first.reason}; re-establishing stream`, { sessionId: first.sessionId, ...first.detail });
11133
+ if (rest.length > 0) {
11134
+ log17.info("more dead streams queued for re-establish (one per tick)", { remaining: rest.length });
11135
+ }
11136
+ this.reestablishTmuxStream(first.sessionId);
11137
+ }
11138
+ /**
11139
+ * Tear down a tmux-pipe stream's pipe-pane/FIFO/cat and start a fresh one for
11140
+ * the same sessionId, preserving its viewer set. Sends a new stream-reset +
11141
+ * capture so the portal hard-repaints the pane. Uses a LEAN re-establish (no
11142
+ * 5000-line scrollback re-send) to keep the post-reconnect burst small.
11143
+ */
11144
+ reestablishTmuxStream(sessionId) {
11145
+ const old = this.active.get(sessionId);
11146
+ if (!old || old.streamType !== "tmux-pipe") return;
11147
+ const viewerIds = new Set(old.viewerIds);
11148
+ this.cleanupStream(old);
11149
+ this.active.delete(sessionId);
11150
+ this.disconnectBuffers.delete(sessionId);
11151
+ this.disconnectBufferBytes.delete(sessionId);
11152
+ const restarted = this.startTmuxStream(sessionId, "", { skipScrollback: true });
11153
+ if (restarted) {
11154
+ const fresh = this.active.get(sessionId);
11155
+ if (fresh) {
11156
+ for (const v of viewerIds) fresh.viewerIds.add(v);
11157
+ fresh.lastReestablishAt = Date.now();
11158
+ }
11159
+ } else {
11160
+ log17.warn("failed to re-establish tmux stream", { sessionId });
11161
+ this.onStreamStopped?.(sessionId);
11162
+ }
11163
+ }
11042
11164
  /**
11043
11165
  * Pause a PTY-attach stream when WS queue pressure is high. Only applies
11044
11166
  * to `pty-attach` streams — tmux-pipe (FIFO cat) and process-trace
@@ -11285,6 +11407,7 @@ var SessionStreamer = class {
11285
11407
  if (stream.stopped) continue;
11286
11408
  if (stream.streamType === "tmux-pipe") {
11287
11409
  this.drainBuffer(stream.sessionId);
11410
+ stream.needsResync = true;
11288
11411
  this.resyncStream(stream.sessionId);
11289
11412
  } else {
11290
11413
  for (const chunk of this.drainBuffer(stream.sessionId)) {
@@ -11329,6 +11452,13 @@ var SessionStreamer = class {
11329
11452
  stream.initBuffer = [];
11330
11453
  return;
11331
11454
  }
11455
+ if (!this.isReadyFn()) {
11456
+ stream.needsResync = true;
11457
+ stream.initializing = false;
11458
+ stream.initBuffer = [];
11459
+ return;
11460
+ }
11461
+ stream.needsResync = false;
11332
11462
  const send = (data) => {
11333
11463
  if (targetViewerId) {
11334
11464
  this.sendFn(sessionId, data, targetViewerId);
@@ -11339,7 +11469,7 @@ var SessionStreamer = class {
11339
11469
  try {
11340
11470
  this.sendResetFn?.(sessionId, withScrollback ? "full" : "repaint", targetViewerId);
11341
11471
  const alternateScreen = this.detectAlternateScreen(paneTarget, cleanEnv);
11342
- if (withScrollback) {
11472
+ if (withScrollback && !alternateScreen) {
11343
11473
  send("\x1B[3J");
11344
11474
  try {
11345
11475
  const scrollback = (0, import_node_child_process2.execFileSync)(
@@ -11384,15 +11514,91 @@ var SessionStreamer = class {
11384
11514
  return false;
11385
11515
  }
11386
11516
  }
11517
+ /** Cheap stable string hash (djb2) for content change-detection. */
11518
+ hashString(s) {
11519
+ let h = 5381;
11520
+ for (let i = 0; i < s.length; i++) h = Math.imul(h, 33) + s.charCodeAt(i) | 0;
11521
+ return (h >>> 0).toString(36) + ":" + s.length;
11522
+ }
11523
+ /**
11524
+ * Hash of a pane's visible content (`capture-pane -p`). This is the watchdog's
11525
+ * activity signal: unlike cursor/history it changes on ANY visible redraw,
11526
+ * including in-place TUI updates (spinners, token counters, status bars) that
11527
+ * a Claude/Gemini pane emits without moving the cursor. Null on tmux failure
11528
+ * (the watchdog then skips the activity check for that tick).
11529
+ */
11530
+ paneContentHash(paneTarget, cleanEnv) {
11531
+ try {
11532
+ const out = (0, import_node_child_process2.execFileSync)(
11533
+ "tmux",
11534
+ ["capture-pane", "-t", paneTarget, "-p"],
11535
+ { env: cleanEnv ?? this.tmuxEnv(), timeout: 5e3, encoding: "utf-8" }
11536
+ );
11537
+ return this.hashString(out);
11538
+ } catch {
11539
+ return null;
11540
+ }
11541
+ }
11542
+ /** Build a tmux-safe env (TMUX/TMUX_PANE stripped) for liveness queries. */
11543
+ tmuxEnv() {
11544
+ const env = { ...process.env };
11545
+ delete env.TMUX;
11546
+ delete env.TMUX_PANE;
11547
+ return env;
11548
+ }
11549
+ /** Pane pid + a cheap activity fingerprint (cursor + history) for one pane. */
11550
+ readPaneInfo(paneTarget, cleanEnv) {
11551
+ try {
11552
+ const out = (0, import_node_child_process2.execFileSync)(
11553
+ "tmux",
11554
+ ["display-message", "-t", paneTarget, "-p", "#{pane_pid}|#{history_size}:#{cursor_x}:#{cursor_y}:#{pane_in_mode}"],
11555
+ { env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
11556
+ ).trim();
11557
+ const sep2 = out.indexOf("|");
11558
+ if (sep2 < 0) return null;
11559
+ return { panePid: out.slice(0, sep2), fingerprint: out.slice(sep2 + 1) };
11560
+ } catch {
11561
+ return null;
11562
+ }
11563
+ }
11564
+ /**
11565
+ * One batched tmux call → pane pid + activity fingerprint for every pane,
11566
+ * keyed by `session:window.pane` (matching parsePaneTarget). Returns null on
11567
+ * tmux failure so the watchdog skips this tick rather than acting on no data.
11568
+ */
11569
+ readAllPaneInfo() {
11570
+ try {
11571
+ const out = (0, import_node_child_process2.execFileSync)(
11572
+ "tmux",
11573
+ [
11574
+ "list-panes",
11575
+ "-a",
11576
+ "-F",
11577
+ "#{session_name}:#{window_index}.#{pane_index}|#{pane_pid}|#{history_size}:#{cursor_x}:#{cursor_y}:#{pane_in_mode}"
11578
+ ],
11579
+ { env: this.tmuxEnv(), timeout: 5e3, encoding: "utf-8" }
11580
+ );
11581
+ const map = /* @__PURE__ */ new Map();
11582
+ for (const line of out.split("\n")) {
11583
+ if (!line) continue;
11584
+ const parts = line.split("|");
11585
+ if (parts.length < 3) continue;
11586
+ map.set(parts[0], { panePid: parts[1], fingerprint: parts[2] });
11587
+ }
11588
+ return map;
11589
+ } catch {
11590
+ return null;
11591
+ }
11592
+ }
11387
11593
  /**
11388
11594
  * Capture and send the visible pane content. Converts bare \n to \r\n for
11389
11595
  * xterm.js and strips the trailing \r\n to prevent an extra scroll that
11390
11596
  * would shift the viewport by 1 row. Returns the number of lines sent
11391
11597
  * (0 when the capture failed or was empty) for relative cursor sync.
11392
11598
  */
11393
- sendVisiblePane(send, paneTarget, cleanEnv, alternateScreen) {
11599
+ sendVisiblePane(send, paneTarget, cleanEnv, _alternateScreen) {
11394
11600
  try {
11395
- const captureArgs = alternateScreen ? ["capture-pane", "-a", "-t", paneTarget, "-p", "-e"] : ["capture-pane", "-t", paneTarget, "-p", "-e"];
11601
+ const captureArgs = ["capture-pane", "-t", paneTarget, "-p", "-e"];
11396
11602
  const initial = (0, import_node_child_process2.execFileSync)(
11397
11603
  "tmux",
11398
11604
  captureArgs,
@@ -11435,7 +11641,7 @@ var SessionStreamer = class {
11435
11641
  // ---------------------------------------------------------------------------
11436
11642
  // tmux streaming (pipe-pane with cursor sync)
11437
11643
  // ---------------------------------------------------------------------------
11438
- startTmuxStream(sessionId, viewerKey = "") {
11644
+ startTmuxStream(sessionId, viewerKey = "", opts) {
11439
11645
  const paneTarget = parsePaneTarget(sessionId);
11440
11646
  if (!paneTarget) return false;
11441
11647
  try {
@@ -11485,10 +11691,17 @@ var SessionStreamer = class {
11485
11691
  log17.warn("failed pipe-pane", { sessionId, error: message });
11486
11692
  return false;
11487
11693
  }
11694
+ const paneInfo = this.readPaneInfo(paneTarget, cleanEnv);
11695
+ if (paneInfo) stream.panePid = paneInfo.panePid;
11696
+ stream.lastContentHash = this.paneContentHash(paneTarget, cleanEnv) ?? void 0;
11697
+ stream.bytesReceived = 0;
11698
+ stream.lastLivenessBytes = 0;
11699
+ stream.deadPipeStrikes = 0;
11488
11700
  const catProc = (0, import_node_child_process2.spawn)("cat", [fifoPath], { env: cleanEnv, stdio: ["ignore", "pipe", "ignore"] });
11489
11701
  stream.catProc = catProc;
11490
11702
  catProc.stdout.on("data", (chunk) => {
11491
11703
  if (stream.stopped) return;
11704
+ stream.bytesReceived = (stream.bytesReceived ?? 0) + chunk.length;
11492
11705
  const data = stream.utf8Decoder.write(chunk);
11493
11706
  if (!data) return;
11494
11707
  if (stream.initializing) {
@@ -11517,17 +11730,19 @@ var SessionStreamer = class {
11517
11730
  this.sendResetFn?.(sessionId, "full");
11518
11731
  const alternateScreen = this.detectAlternateScreen(paneTarget, cleanEnv);
11519
11732
  stream.alternateScreen = alternateScreen;
11520
- this.sendFn(sessionId, "\x1B[3J");
11521
- try {
11522
- const scrollback = (0, import_node_child_process2.execFileSync)(
11523
- "tmux",
11524
- ["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
11525
- { env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
11526
- );
11527
- if (scrollback && scrollback.length > 0) {
11528
- this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
11733
+ if (!opts?.skipScrollback && !alternateScreen) {
11734
+ this.sendFn(sessionId, "\x1B[3J");
11735
+ try {
11736
+ const scrollback = (0, import_node_child_process2.execFileSync)(
11737
+ "tmux",
11738
+ ["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
11739
+ { env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
11740
+ );
11741
+ if (scrollback && scrollback.length > 0) {
11742
+ this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
11743
+ }
11744
+ } catch {
11529
11745
  }
11530
- } catch {
11531
11746
  }
11532
11747
  this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
11533
11748
  const sendBroadcast = (data) => this.sendFn(sessionId, data);
@@ -14544,7 +14759,11 @@ async function runAgent(rawOptions) {
14544
14759
  },
14545
14760
  () => {
14546
14761
  transcriptWatcher.resyncMarkdownAfterDrop();
14547
- }
14762
+ },
14763
+ // #556 follow-up: lets the streamer gate resync delivery + the pipe-liveness
14764
+ // watchdog on a confirmed-OPEN socket, so resync bytes aren't buffered then
14765
+ // discarded under reconnect churn and re-establish captures actually land.
14766
+ () => conn.isReady()
14548
14767
  );
14549
14768
  const conn = new ConnectionManager(sessionStreamer, outputBuffer);
14550
14769
  const sendOutput = (chunk) => {
@@ -14620,8 +14839,11 @@ async function runAgent(rawOptions) {
14620
14839
  sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, payload);
14621
14840
  }
14622
14841
  },
14623
- // V2: send full snapshot
14624
- (sessionId, snapshot, seq) => {
14842
+ // V2: send full snapshot. `reset` marks an explicit clear (new conversation
14843
+ // / transcript file rotation on pane reuse) so the portal's snapshot guard
14844
+ // lets an empty snapshot wipe stale content instead of rejecting it. See the
14845
+ // `reset` field on TranscriptSnapshotMessage (#538) and shouldApplyTranscriptSnapshot.
14846
+ (sessionId, snapshot, seq, reset) => {
14625
14847
  if (!conn.isReady()) return;
14626
14848
  const payload = {
14627
14849
  type: "markdown",
@@ -14629,7 +14851,8 @@ async function runAgent(rawOptions) {
14629
14851
  schemaVersion: 2,
14630
14852
  sessionId,
14631
14853
  seq,
14632
- snapshot
14854
+ snapshot,
14855
+ ...reset ? { reset: true } : {}
14633
14856
  };
14634
14857
  if (conn.sessionKey) {
14635
14858
  const contentStr = JSON.stringify(payload);
@@ -14724,6 +14947,7 @@ async function runAgent(rawOptions) {
14724
14947
  await new Promise((resolve) => {
14725
14948
  const ws = new import_ws5.default(negotiated.url, "json.webpubsub.azure.v1");
14726
14949
  conn.setConnection(ws, groups, negotiated.sessionKey ?? null);
14950
+ let connectedAt = 0;
14727
14951
  if (negotiated.sessionKey) {
14728
14952
  const enforcer = new ApprovalEnforcer({
14729
14953
  secret: negotiated.sessionKey,
@@ -14744,6 +14968,7 @@ async function runAgent(rawOptions) {
14744
14968
  }
14745
14969
  ws.on("open", async () => {
14746
14970
  log25.info("connector connected to relay");
14971
+ connectedAt = Date.now();
14747
14972
  conn.resetReconnectDelay();
14748
14973
  ws.send(JSON.stringify({ type: "joinGroup", group: groups.privateGroup }));
14749
14974
  ws.send(JSON.stringify({ type: "joinGroup", group: groups.registryGroup }));
@@ -14910,6 +15135,7 @@ async function runAgent(rawOptions) {
14910
15135
  const fields = {};
14911
15136
  if (code) fields.code = code;
14912
15137
  if (reason.length > 0) fields.reason = reason.toString();
15138
+ if (connectedAt > 0) fields.uptimeMs = Date.now() - connectedAt;
14913
15139
  log25.info("relay disconnected; output will be buffered until reconnect", fields);
14914
15140
  conn.cleanup();
14915
15141
  resolve();
package/dist/sbom.json CHANGED
@@ -1299,8 +1299,8 @@
1299
1299
  {
1300
1300
  "type": "library",
1301
1301
  "name": "ragent-cli",
1302
- "version": "1.11.17",
1303
- "bom-ref": "ragent-live|ragent-cli@1.11.17",
1302
+ "version": "1.11.20",
1303
+ "bom-ref": "ragent-live|ragent-cli@1.11.20",
1304
1304
  "author": "Intellimetrics",
1305
1305
  "description": "CLI agent for rAgent Live — browser-first terminal control plane for AI coding agents",
1306
1306
  "licenses": [
@@ -1311,7 +1311,7 @@
1311
1311
  }
1312
1312
  }
1313
1313
  ],
1314
- "purl": "pkg:npm/ragent-cli@1.11.17",
1314
+ "purl": "pkg:npm/ragent-cli@1.11.20",
1315
1315
  "externalReferences": [
1316
1316
  {
1317
1317
  "url": "https://github.com/chadlindell/ragent-live/issues",
@@ -1384,8 +1384,8 @@
1384
1384
  {
1385
1385
  "type": "library",
1386
1386
  "name": "ws",
1387
- "version": "8.20.0",
1388
- "bom-ref": "ragent-live|ws@8.20.0",
1387
+ "version": "8.21.0",
1388
+ "bom-ref": "ragent-live|ws@8.21.0",
1389
1389
  "author": "Einar Otto Stangvik",
1390
1390
  "description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
1391
1391
  "licenses": [
@@ -1396,10 +1396,10 @@
1396
1396
  }
1397
1397
  }
1398
1398
  ],
1399
- "purl": "pkg:npm/ws@8.20.0",
1399
+ "purl": "pkg:npm/ws@8.21.0",
1400
1400
  "externalReferences": [
1401
1401
  {
1402
- "url": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
1402
+ "url": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
1403
1403
  "type": "distribution",
1404
1404
  "comment": "as detected from npm-ls property \"resolved\""
1405
1405
  },
@@ -1436,7 +1436,7 @@
1436
1436
  "ragent-live|@emnapi/wasi-threads@1.2.1",
1437
1437
  "ragent-live|@pkgjs/parseargs@0.11.0",
1438
1438
  "ragent-live|@tybys/wasm-util@0.10.1",
1439
- "ragent-live|ragent-cli@1.11.17"
1439
+ "ragent-live|ragent-cli@1.11.20"
1440
1440
  ]
1441
1441
  },
1442
1442
  {
@@ -1469,7 +1469,7 @@
1469
1469
  "ragent-live|buffer@6.0.3",
1470
1470
  "ragent-live|events@3.3.0",
1471
1471
  "ragent-live|tslib@2.8.1",
1472
- "ragent-live|ws@8.20.0"
1472
+ "ragent-live|ws@8.21.0"
1473
1473
  ]
1474
1474
  },
1475
1475
  {
@@ -1584,21 +1584,21 @@
1584
1584
  ]
1585
1585
  },
1586
1586
  {
1587
- "ref": "ragent-live|ragent-cli@1.11.17",
1587
+ "ref": "ragent-live|ragent-cli@1.11.20",
1588
1588
  "dependsOn": [
1589
1589
  "ragent-live|@azure/web-pubsub-client@1.0.4",
1590
1590
  "ragent-live|@openai/codex-sdk@0.130.0",
1591
1591
  "ragent-live|commander@14.0.3",
1592
1592
  "ragent-live|figlet@1.11.0",
1593
1593
  "ragent-live|node-pty@1.1.0",
1594
- "ragent-live|ws@8.20.0"
1594
+ "ragent-live|ws@8.21.0"
1595
1595
  ]
1596
1596
  },
1597
1597
  {
1598
1598
  "ref": "ragent-live|tslib@2.8.1"
1599
1599
  },
1600
1600
  {
1601
- "ref": "ragent-live|ws@8.20.0"
1601
+ "ref": "ragent-live|ws@8.21.0"
1602
1602
  }
1603
1603
  ]
1604
1604
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ragent-cli",
3
- "version": "1.11.17",
3
+ "version": "1.11.20",
4
4
  "description": "CLI agent for rAgent Live — browser-first terminal control plane for AI coding agents",
5
5
  "main": "dist/index.js",
6
6
  "bin": {
@@ -59,7 +59,7 @@
59
59
  "commander": "^14.0.3",
60
60
  "figlet": "^1.9.3",
61
61
  "node-pty": "^1.1.0",
62
- "ws": "^8.19.0"
62
+ "ws": "^8.21.0"
63
63
  },
64
64
  "devDependencies": {
65
65
  "@changesets/changelog-github": "^0.5.2",