ragent-cli 1.11.7 → 1.11.9

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.7",
34
+ version: "1.11.9",
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: {
@@ -2934,6 +2934,24 @@ var ALT_SCREEN_MODE_RE = /\x1b\[\?(?=[0-9;]*(?:47|1047|1048|1049)(?:;|[hl]))[0-9
2934
2934
  function stripAlternateScreenModeSwitches(data) {
2935
2935
  return data.replace(ALT_SCREEN_MODE_RE, "");
2936
2936
  }
2937
+ var AlternateScreenModeStripper = class {
2938
+ pending = "";
2939
+ write(data) {
2940
+ const combined = this.pending + data;
2941
+ this.pending = "";
2942
+ const match = combined.match(/\x1b(?:\[(?:\?(?:[0-9;]*)?)?)?$/);
2943
+ if (!match || match.index === void 0) {
2944
+ return stripAlternateScreenModeSwitches(combined);
2945
+ }
2946
+ this.pending = combined.slice(match.index);
2947
+ return stripAlternateScreenModeSwitches(combined.slice(0, match.index));
2948
+ }
2949
+ end() {
2950
+ const pending = this.pending;
2951
+ this.pending = "";
2952
+ return stripAlternateScreenModeSwitches(pending);
2953
+ }
2954
+ };
2937
2955
  function parsePaneTarget(sessionId) {
2938
2956
  if (!sessionId.startsWith("tmux:")) return null;
2939
2957
  const rest = sessionId.slice("tmux:".length);
@@ -3172,6 +3190,11 @@ var SessionStreamer = class {
3172
3190
  isStreaming(sessionId) {
3173
3191
  return this.active.has(sessionId);
3174
3192
  }
3193
+ hasViewer(sessionId, viewerId) {
3194
+ const viewerKey = viewerId?.trim() || "";
3195
+ if (!viewerKey) return false;
3196
+ return this.active.get(sessionId)?.viewerIds.has(viewerKey) ?? false;
3197
+ }
3175
3198
  /**
3176
3199
  * Stop all active streams immediately (no debounce).
3177
3200
  */
@@ -3253,11 +3276,19 @@ var SessionStreamer = class {
3253
3276
  * Used when a viewer missed the initial burst (e.g. joinGroup race)
3254
3277
  * or reconnected ("Retry stream"). Does NOT restart pipe-pane.
3255
3278
  */
3256
- resyncStream(sessionId) {
3279
+ resyncStream(sessionId, viewerId) {
3257
3280
  const stream = this.active.get(sessionId);
3258
3281
  if (!stream || stream.stopped || stream.streamType !== "tmux-pipe") return false;
3259
3282
  const { paneTarget, cleanEnv } = stream;
3260
3283
  if (!paneTarget || !cleanEnv) return false;
3284
+ const targetViewerId = viewerId?.trim() || void 0;
3285
+ const send = (data) => {
3286
+ if (targetViewerId) {
3287
+ this.sendFn(sessionId, data, targetViewerId);
3288
+ } else {
3289
+ this.sendFn(sessionId, data);
3290
+ }
3291
+ };
3261
3292
  stream.initializing = true;
3262
3293
  try {
3263
3294
  let alternateScreen = false;
@@ -3277,11 +3308,11 @@ var SessionStreamer = class {
3277
3308
  { env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
3278
3309
  );
3279
3310
  if (scrollback && scrollback.length > 0) {
3280
- this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
3311
+ send(scrollback.replace(/\n/g, "\r\n"));
3281
3312
  }
3282
3313
  } catch {
3283
3314
  }
3284
- this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
3315
+ send("\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
3285
3316
  try {
3286
3317
  const captureArgs = alternateScreen ? ["capture-pane", "-a", "-t", paneTarget, "-p", "-e"] : ["capture-pane", "-t", paneTarget, "-p", "-e"];
3287
3318
  const initial = (0, import_node_child_process2.execFileSync)(
@@ -3290,7 +3321,7 @@ var SessionStreamer = class {
3290
3321
  { env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
3291
3322
  );
3292
3323
  if (initial) {
3293
- this.sendFn(sessionId, stripAlternateScreenModeSwitches(initial.replace(/\n/g, "\r\n").replace(/\r\n$/, "")));
3324
+ send(stripAlternateScreenModeSwitches(initial.replace(/\n/g, "\r\n").replace(/\r\n$/, "")));
3294
3325
  }
3295
3326
  } catch {
3296
3327
  }
@@ -3305,7 +3336,7 @@ var SessionStreamer = class {
3305
3336
  const x = parseInt(parts[0], 10);
3306
3337
  const y = parseInt(parts[1], 10);
3307
3338
  if (!isNaN(x) && !isNaN(y)) {
3308
- this.sendFn(sessionId, `\x1B[${y + 1};${x + 1}H`);
3339
+ send(`\x1B[${y + 1};${x + 1}H`);
3309
3340
  }
3310
3341
  }
3311
3342
  } catch {
@@ -3359,7 +3390,8 @@ var SessionStreamer = class {
3359
3390
  cleanEnv,
3360
3391
  ptyProc: null,
3361
3392
  straceProc: null,
3362
- utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
3393
+ utf8Decoder: new import_node_string_decoder.StringDecoder("utf8"),
3394
+ altScreenStripper: new AlternateScreenModeStripper()
3363
3395
  };
3364
3396
  this.active.set(sessionId, stream);
3365
3397
  try {
@@ -3383,13 +3415,19 @@ var SessionStreamer = class {
3383
3415
  if (stream.initializing) {
3384
3416
  stream.initBuffer.push(data);
3385
3417
  } else {
3386
- this.sendFn(sessionId, stripAlternateScreenModeSwitches(data));
3418
+ const filtered = stream.altScreenStripper.write(data);
3419
+ if (filtered) this.sendFn(sessionId, filtered);
3387
3420
  }
3388
3421
  });
3389
3422
  catProc.on("exit", () => {
3390
3423
  if (!stream.stopped) {
3391
3424
  const remaining = stream.utf8Decoder.end();
3392
- if (remaining) this.sendFn(sessionId, stripAlternateScreenModeSwitches(remaining));
3425
+ if (remaining) {
3426
+ const filtered = stream.altScreenStripper.write(remaining);
3427
+ if (filtered) this.sendFn(sessionId, filtered);
3428
+ }
3429
+ const pending = stream.altScreenStripper.end();
3430
+ if (pending) this.sendFn(sessionId, pending);
3393
3431
  this.cleanupStream(stream);
3394
3432
  this.active.delete(sessionId);
3395
3433
  this.pendingStops.delete(sessionId);
@@ -3485,7 +3523,8 @@ var SessionStreamer = class {
3485
3523
  initBuffer: [],
3486
3524
  ptyProc: proc,
3487
3525
  straceProc: null,
3488
- utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
3526
+ utf8Decoder: new import_node_string_decoder.StringDecoder("utf8"),
3527
+ altScreenStripper: new AlternateScreenModeStripper()
3489
3528
  };
3490
3529
  this.active.set(sessionId, stream);
3491
3530
  proc.onData((data) => {
@@ -3538,7 +3577,8 @@ var SessionStreamer = class {
3538
3577
  initBuffer: [],
3539
3578
  ptyProc: proc,
3540
3579
  straceProc: null,
3541
- utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
3580
+ utf8Decoder: new import_node_string_decoder.StringDecoder("utf8"),
3581
+ altScreenStripper: new AlternateScreenModeStripper()
3542
3582
  };
3543
3583
  this.active.set(sessionId, stream);
3544
3584
  proc.onData((data) => {
@@ -3603,7 +3643,8 @@ var SessionStreamer = class {
3603
3643
  initBuffer: [],
3604
3644
  ptyProc: null,
3605
3645
  straceProc,
3606
- utf8Decoder: new import_node_string_decoder.StringDecoder("utf8")
3646
+ utf8Decoder: new import_node_string_decoder.StringDecoder("utf8"),
3647
+ altScreenStripper: new AlternateScreenModeStripper()
3607
3648
  };
3608
3649
  this.active.set(sessionId, stream);
3609
3650
  const writeRegex = /^write\(([12]),\s*"((?:[^"\\]|\\.)*)"/;
@@ -4691,6 +4732,26 @@ function requestStopSelfService() {
4691
4732
  }
4692
4733
  }
4693
4734
  }
4735
+ function requestRestartSelfService() {
4736
+ const backend = getConfiguredServiceBackend();
4737
+ if (backend === "systemd") {
4738
+ const child = (0, import_child_process3.spawn)("systemctl", ["--user", "restart", SERVICE_NAME], {
4739
+ detached: true,
4740
+ stdio: "ignore"
4741
+ });
4742
+ child.unref();
4743
+ return;
4744
+ }
4745
+ if (backend === "pidfile") {
4746
+ const child = (0, import_child_process3.spawn)(process.execPath, [__filename, "service", "restart"], {
4747
+ detached: true,
4748
+ stdio: "ignore",
4749
+ cwd: os7.homedir(),
4750
+ env: process.env
4751
+ });
4752
+ child.unref();
4753
+ }
4754
+ }
4694
4755
 
4695
4756
  // src/provisioner.ts
4696
4757
  var import_child_process4 = require("child_process");
@@ -4994,6 +5055,14 @@ function isSafeWorkingDir(raw) {
4994
5055
  return true;
4995
5056
  }
4996
5057
 
5058
+ // src/updater.ts
5059
+ async function installLatestCliFromNpm() {
5060
+ await execAsync(`npm install -g ${shellQuote(`${PACKAGE_NAME}@latest`)}`, {
5061
+ timeout: 5 * 60 * 1e3,
5062
+ maxBuffer: 10 * 1024 * 1024
5063
+ });
5064
+ }
5065
+
4997
5066
  // src/control-dispatcher.ts
4998
5067
  var log13 = createLogger("control");
4999
5068
  var TMUX_RESYNC_DEBOUNCE_MS = 150;
@@ -5104,6 +5173,7 @@ var ControlDispatcher = class {
5104
5173
  "stop-agent",
5105
5174
  "restart-agent",
5106
5175
  "restart-shell",
5176
+ "update-agent",
5107
5177
  "stop-session",
5108
5178
  "stop-detached",
5109
5179
  "disconnect",
@@ -5131,6 +5201,9 @@ var ControlDispatcher = class {
5131
5201
  this.connection.activeSocket.close();
5132
5202
  }
5133
5203
  return;
5204
+ case "update-agent":
5205
+ await this.updateAgentFromNpm();
5206
+ return;
5134
5207
  case "stop-agent":
5135
5208
  this.shouldRun = false;
5136
5209
  this.streamer.stopAll();
@@ -5329,6 +5402,24 @@ var ControlDispatcher = class {
5329
5402
  this.streamer.resize(sessionId, cols, rows);
5330
5403
  }
5331
5404
  }
5405
+ /** Update the installed connector package, then restart when a service backend exists. */
5406
+ async updateAgentFromNpm() {
5407
+ log13.info("updating ragent CLI", { current: CURRENT_VERSION });
5408
+ try {
5409
+ await installLatestCliFromNpm();
5410
+ } catch (error) {
5411
+ const message = error instanceof Error ? error.message : String(error);
5412
+ log13.error("failed to update ragent CLI", { error: message });
5413
+ return;
5414
+ }
5415
+ const backend = getConfiguredServiceBackend();
5416
+ if (!backend) {
5417
+ log13.warn("ragent CLI updated, but no service backend is configured; restart the connector manually to use the new version");
5418
+ return;
5419
+ }
5420
+ log13.info("ragent CLI updated; restarting connector service", { backend });
5421
+ requestRestartSelfService();
5422
+ }
5332
5423
  /** Handle provision request from dashboard. */
5333
5424
  async handleProvision(payload) {
5334
5425
  const provReq = validateProvisionRequest(payload);
@@ -5508,13 +5599,15 @@ var ControlDispatcher = class {
5508
5599
  if (!sessionId) return;
5509
5600
  const ws = this.connection.activeSocket;
5510
5601
  const group = this.connection.activeGroups.privateGroup;
5602
+ const viewerKey = viewerId?.trim() || "";
5511
5603
  if (sessionId.startsWith("tmux:") || sessionId.startsWith("screen:") || sessionId.startsWith("zellij:") || sessionId.startsWith("process:")) {
5512
5604
  const alreadyStreaming = this.streamer.isStreaming(sessionId);
5605
+ const viewerAlreadyAttached = alreadyStreaming && this.streamer.hasViewer(sessionId, viewerKey);
5513
5606
  const started = this.streamer.startStream(sessionId, viewerId ?? void 0);
5514
5607
  if (ws && ws.readyState === import_ws4.default.OPEN && group) {
5515
5608
  if (started) {
5516
- if (alreadyStreaming) {
5517
- this.streamer.resyncStream(sessionId);
5609
+ if (alreadyStreaming && !viewerAlreadyAttached) {
5610
+ this.streamer.resyncStream(sessionId, viewerKey);
5518
5611
  }
5519
5612
  const dims = this.queryPaneDimensions(sessionId);
5520
5613
  sendToGroup(ws, group, { type: "stream-started", sessionId, ...dims });
@@ -5983,7 +6076,7 @@ async function runAgent(rawOptions) {
5983
6076
  const outputBuffer = new OutputBuffer();
5984
6077
  const ptySessionId = `pty:${options.hostId}`;
5985
6078
  const sessionStreamer = new SessionStreamer(
5986
- (sessionId, data) => {
6079
+ (sessionId, data, viewerId) => {
5987
6080
  conn.requestInventorySync();
5988
6081
  if (!conn.isReady()) {
5989
6082
  sessionStreamer.bufferOutput(sessionId, data);
@@ -5991,11 +6084,12 @@ async function runAgent(rawOptions) {
5991
6084
  }
5992
6085
  const redactedData = redactStreamingChunk(sessionId, data);
5993
6086
  for (const chunk of splitTransportChunks(redactedData)) {
6087
+ const outputPayload = viewerId ? { type: "output", sessionId, viewerId } : { type: "output", sessionId };
5994
6088
  if (conn.sessionKey) {
5995
6089
  const { enc, iv, seq } = encryptPayload(chunk, conn.sessionKey, conn.txSeq.next());
5996
- sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { type: "output", enc, iv, seq, sessionId });
6090
+ sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { ...outputPayload, enc, iv, seq });
5997
6091
  } else {
5998
- sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { type: "output", data: chunk, sessionId });
6092
+ sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, { ...outputPayload, data: chunk });
5999
6093
  }
6000
6094
  }
6001
6095
  },
@@ -6625,10 +6719,7 @@ function registerUpdateCommand(program2) {
6625
6719
  }
6626
6720
  log19.info("update available", { current: CURRENT_VERSION, latest: latestVersion });
6627
6721
  if (opts.check) return;
6628
- await execAsync(`npm install -g ${shellQuote(PACKAGE_NAME)}`, {
6629
- timeout: 5 * 60 * 1e3,
6630
- maxBuffer: 10 * 1024 * 1024
6631
- });
6722
+ await installLatestCliFromNpm();
6632
6723
  log19.info("updated", { version: latestVersion });
6633
6724
  const backend = getConfiguredServiceBackend();
6634
6725
  if (backend && opts.restart) {
package/dist/sbom.json CHANGED
@@ -1166,8 +1166,8 @@
1166
1166
  {
1167
1167
  "type": "library",
1168
1168
  "name": "ragent-cli",
1169
- "version": "1.11.7",
1170
- "bom-ref": "ragent-live|ragent-cli@1.11.7",
1169
+ "version": "1.11.9",
1170
+ "bom-ref": "ragent-live|ragent-cli@1.11.9",
1171
1171
  "author": "Intellimetrics",
1172
1172
  "description": "CLI agent for rAgent Live — browser-first terminal control plane for AI coding agents",
1173
1173
  "licenses": [
@@ -1178,7 +1178,7 @@
1178
1178
  }
1179
1179
  }
1180
1180
  ],
1181
- "purl": "pkg:npm/ragent-cli@1.11.7",
1181
+ "purl": "pkg:npm/ragent-cli@1.11.9",
1182
1182
  "externalReferences": [
1183
1183
  {
1184
1184
  "url": "https://github.com/chadlindell/ragent-live/issues",
@@ -1303,7 +1303,7 @@
1303
1303
  "ragent-live|@emnapi/wasi-threads@1.2.1",
1304
1304
  "ragent-live|@pkgjs/parseargs@0.11.0",
1305
1305
  "ragent-live|@tybys/wasm-util@0.10.1",
1306
- "ragent-live|ragent-cli@1.11.7"
1306
+ "ragent-live|ragent-cli@1.11.9"
1307
1307
  ]
1308
1308
  },
1309
1309
  {
@@ -1436,7 +1436,7 @@
1436
1436
  ]
1437
1437
  },
1438
1438
  {
1439
- "ref": "ragent-live|ragent-cli@1.11.7",
1439
+ "ref": "ragent-live|ragent-cli@1.11.9",
1440
1440
  "dependsOn": [
1441
1441
  "ragent-live|@azure/web-pubsub-client@1.0.4",
1442
1442
  "ragent-live|commander@14.0.3",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ragent-cli",
3
- "version": "1.11.7",
3
+ "version": "1.11.9",
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": {