ragent-cli 1.11.16 → 1.11.18
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 +248 -30
- package/dist/sbom.json +5 -5
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -31,7 +31,7 @@ var require_package = __commonJS({
|
|
|
31
31
|
"package.json"(exports2, module2) {
|
|
32
32
|
module2.exports = {
|
|
33
33
|
name: "ragent-cli",
|
|
34
|
-
version: "1.11.
|
|
34
|
+
version: "1.11.18",
|
|
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: {
|
|
@@ -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
|
-
|
|
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,110 @@ 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
|
+
* Batched into one `tmux list-panes -a` call so liveness costs one fork.
|
|
11079
|
+
*/
|
|
11080
|
+
checkPipeLiveness() {
|
|
11081
|
+
const tmuxStreams = [...this.active.values()].filter(
|
|
11082
|
+
(s) => s.streamType === "tmux-pipe" && !s.stopped && !s.initializing
|
|
11083
|
+
);
|
|
11084
|
+
if (tmuxStreams.length === 0) return;
|
|
11085
|
+
const panes = this.readAllPaneInfo();
|
|
11086
|
+
if (!panes) return;
|
|
11087
|
+
const now2 = Date.now();
|
|
11088
|
+
const dead = [];
|
|
11089
|
+
for (const stream of tmuxStreams) {
|
|
11090
|
+
const info = panes.get(stream.paneTarget);
|
|
11091
|
+
if (!info) continue;
|
|
11092
|
+
const backedOff = stream.lastReestablishAt !== void 0 && now2 - stream.lastReestablishAt < REESTABLISH_BACKOFF_MS;
|
|
11093
|
+
if (stream.panePid && info.panePid !== stream.panePid) {
|
|
11094
|
+
if (!backedOff) {
|
|
11095
|
+
dead.push({
|
|
11096
|
+
sessionId: stream.sessionId,
|
|
11097
|
+
reason: "pane program swapped",
|
|
11098
|
+
detail: { oldPid: stream.panePid, newPid: info.panePid }
|
|
11099
|
+
});
|
|
11100
|
+
}
|
|
11101
|
+
continue;
|
|
11102
|
+
}
|
|
11103
|
+
const bytesNow = stream.bytesReceived ?? 0;
|
|
11104
|
+
const paneMoved = info.fingerprint !== stream.lastLivenessFingerprint;
|
|
11105
|
+
const fifoSilent = bytesNow === (stream.lastLivenessBytes ?? 0);
|
|
11106
|
+
if (paneMoved && fifoSilent) {
|
|
11107
|
+
stream.deadPipeStrikes = (stream.deadPipeStrikes ?? 0) + 1;
|
|
11108
|
+
if (stream.deadPipeStrikes >= DEAD_PIPE_STRIKES && !backedOff) {
|
|
11109
|
+
dead.push({
|
|
11110
|
+
sessionId: stream.sessionId,
|
|
11111
|
+
reason: "pipe-pane silent while pane active",
|
|
11112
|
+
detail: { strikes: stream.deadPipeStrikes }
|
|
11113
|
+
});
|
|
11114
|
+
}
|
|
11115
|
+
} else {
|
|
11116
|
+
stream.deadPipeStrikes = 0;
|
|
11117
|
+
}
|
|
11118
|
+
stream.lastLivenessBytes = bytesNow;
|
|
11119
|
+
stream.lastLivenessFingerprint = info.fingerprint;
|
|
11120
|
+
}
|
|
11121
|
+
if (dead.length === 0) return;
|
|
11122
|
+
const [first, ...rest] = dead;
|
|
11123
|
+
log17.warn(`${first.reason}; re-establishing stream`, { sessionId: first.sessionId, ...first.detail });
|
|
11124
|
+
if (rest.length > 0) {
|
|
11125
|
+
log17.info("more dead streams queued for re-establish (one per tick)", { remaining: rest.length });
|
|
11126
|
+
}
|
|
11127
|
+
this.reestablishTmuxStream(first.sessionId);
|
|
11128
|
+
}
|
|
11129
|
+
/**
|
|
11130
|
+
* Tear down a tmux-pipe stream's pipe-pane/FIFO/cat and start a fresh one for
|
|
11131
|
+
* the same sessionId, preserving its viewer set. Sends a new stream-reset +
|
|
11132
|
+
* capture so the portal hard-repaints the pane. Uses a LEAN re-establish (no
|
|
11133
|
+
* 5000-line scrollback re-send) to keep the post-reconnect burst small.
|
|
11134
|
+
*/
|
|
11135
|
+
reestablishTmuxStream(sessionId) {
|
|
11136
|
+
const old = this.active.get(sessionId);
|
|
11137
|
+
if (!old || old.streamType !== "tmux-pipe") return;
|
|
11138
|
+
const viewerIds = new Set(old.viewerIds);
|
|
11139
|
+
this.cleanupStream(old);
|
|
11140
|
+
this.active.delete(sessionId);
|
|
11141
|
+
this.disconnectBuffers.delete(sessionId);
|
|
11142
|
+
this.disconnectBufferBytes.delete(sessionId);
|
|
11143
|
+
const restarted = this.startTmuxStream(sessionId, "", { skipScrollback: true });
|
|
11144
|
+
if (restarted) {
|
|
11145
|
+
const fresh = this.active.get(sessionId);
|
|
11146
|
+
if (fresh) {
|
|
11147
|
+
for (const v of viewerIds) fresh.viewerIds.add(v);
|
|
11148
|
+
fresh.lastReestablishAt = Date.now();
|
|
11149
|
+
}
|
|
11150
|
+
} else {
|
|
11151
|
+
log17.warn("failed to re-establish tmux stream", { sessionId });
|
|
11152
|
+
this.onStreamStopped?.(sessionId);
|
|
11153
|
+
}
|
|
11154
|
+
}
|
|
11042
11155
|
/**
|
|
11043
11156
|
* Pause a PTY-attach stream when WS queue pressure is high. Only applies
|
|
11044
11157
|
* to `pty-attach` streams — tmux-pipe (FIFO cat) and process-trace
|
|
@@ -11266,6 +11379,34 @@ var SessionStreamer = class {
|
|
|
11266
11379
|
this.disconnectBufferBytes.delete(sessionId);
|
|
11267
11380
|
return result;
|
|
11268
11381
|
}
|
|
11382
|
+
/**
|
|
11383
|
+
* #556: recover active streams after a relay reconnect.
|
|
11384
|
+
*
|
|
11385
|
+
* For tmux-pipe streams the disconnect-buffered deltas are stale AND
|
|
11386
|
+
* redundant: the portal requests a capture-pane resync on reconnect, and the
|
|
11387
|
+
* capture reflects everything tmux applied to the pane during the disconnect.
|
|
11388
|
+
* Replaying the buffered deltas instead (a) paints stale bytes over the fresh
|
|
11389
|
+
* capture → interleaved garble, and (b) re-floods the relay immediately after
|
|
11390
|
+
* a reconnect, helping trigger Azure's next close — the reconnect churn loop
|
|
11391
|
+
* behind the terminal artifacting. So we DISCARD the buffer and resync.
|
|
11392
|
+
*
|
|
11393
|
+
* pty-attach / process-trace streams have no capture-pane resync, so their
|
|
11394
|
+
* buffered output IS replayed via `replay`.
|
|
11395
|
+
*/
|
|
11396
|
+
recoverStreamsAfterReconnect(replay) {
|
|
11397
|
+
for (const stream of this.active.values()) {
|
|
11398
|
+
if (stream.stopped) continue;
|
|
11399
|
+
if (stream.streamType === "tmux-pipe") {
|
|
11400
|
+
this.drainBuffer(stream.sessionId);
|
|
11401
|
+
stream.needsResync = true;
|
|
11402
|
+
this.resyncStream(stream.sessionId);
|
|
11403
|
+
} else {
|
|
11404
|
+
for (const chunk of this.drainBuffer(stream.sessionId)) {
|
|
11405
|
+
replay(stream.sessionId, chunk);
|
|
11406
|
+
}
|
|
11407
|
+
}
|
|
11408
|
+
}
|
|
11409
|
+
}
|
|
11269
11410
|
/**
|
|
11270
11411
|
* Re-send capture-pane data for an already-active tmux stream.
|
|
11271
11412
|
* Used when a viewer missed the initial burst (e.g. joinGroup race),
|
|
@@ -11302,6 +11443,13 @@ var SessionStreamer = class {
|
|
|
11302
11443
|
stream.initBuffer = [];
|
|
11303
11444
|
return;
|
|
11304
11445
|
}
|
|
11446
|
+
if (!this.isReadyFn()) {
|
|
11447
|
+
stream.needsResync = true;
|
|
11448
|
+
stream.initializing = false;
|
|
11449
|
+
stream.initBuffer = [];
|
|
11450
|
+
return;
|
|
11451
|
+
}
|
|
11452
|
+
stream.needsResync = false;
|
|
11305
11453
|
const send = (data) => {
|
|
11306
11454
|
if (targetViewerId) {
|
|
11307
11455
|
this.sendFn(sessionId, data, targetViewerId);
|
|
@@ -11357,6 +11505,57 @@ var SessionStreamer = class {
|
|
|
11357
11505
|
return false;
|
|
11358
11506
|
}
|
|
11359
11507
|
}
|
|
11508
|
+
/** Build a tmux-safe env (TMUX/TMUX_PANE stripped) for liveness queries. */
|
|
11509
|
+
tmuxEnv() {
|
|
11510
|
+
const env = { ...process.env };
|
|
11511
|
+
delete env.TMUX;
|
|
11512
|
+
delete env.TMUX_PANE;
|
|
11513
|
+
return env;
|
|
11514
|
+
}
|
|
11515
|
+
/** Pane pid + a cheap activity fingerprint (cursor + history) for one pane. */
|
|
11516
|
+
readPaneInfo(paneTarget, cleanEnv) {
|
|
11517
|
+
try {
|
|
11518
|
+
const out = (0, import_node_child_process2.execFileSync)(
|
|
11519
|
+
"tmux",
|
|
11520
|
+
["display-message", "-t", paneTarget, "-p", "#{pane_pid}|#{history_size}:#{cursor_x}:#{cursor_y}:#{pane_in_mode}"],
|
|
11521
|
+
{ env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
|
|
11522
|
+
).trim();
|
|
11523
|
+
const sep2 = out.indexOf("|");
|
|
11524
|
+
if (sep2 < 0) return null;
|
|
11525
|
+
return { panePid: out.slice(0, sep2), fingerprint: out.slice(sep2 + 1) };
|
|
11526
|
+
} catch {
|
|
11527
|
+
return null;
|
|
11528
|
+
}
|
|
11529
|
+
}
|
|
11530
|
+
/**
|
|
11531
|
+
* One batched tmux call → pane pid + activity fingerprint for every pane,
|
|
11532
|
+
* keyed by `session:window.pane` (matching parsePaneTarget). Returns null on
|
|
11533
|
+
* tmux failure so the watchdog skips this tick rather than acting on no data.
|
|
11534
|
+
*/
|
|
11535
|
+
readAllPaneInfo() {
|
|
11536
|
+
try {
|
|
11537
|
+
const out = (0, import_node_child_process2.execFileSync)(
|
|
11538
|
+
"tmux",
|
|
11539
|
+
[
|
|
11540
|
+
"list-panes",
|
|
11541
|
+
"-a",
|
|
11542
|
+
"-F",
|
|
11543
|
+
"#{session_name}:#{window_index}.#{pane_index}|#{pane_pid}|#{history_size}:#{cursor_x}:#{cursor_y}:#{pane_in_mode}"
|
|
11544
|
+
],
|
|
11545
|
+
{ env: this.tmuxEnv(), timeout: 5e3, encoding: "utf-8" }
|
|
11546
|
+
);
|
|
11547
|
+
const map = /* @__PURE__ */ new Map();
|
|
11548
|
+
for (const line of out.split("\n")) {
|
|
11549
|
+
if (!line) continue;
|
|
11550
|
+
const parts = line.split("|");
|
|
11551
|
+
if (parts.length < 3) continue;
|
|
11552
|
+
map.set(parts[0], { panePid: parts[1], fingerprint: parts[2] });
|
|
11553
|
+
}
|
|
11554
|
+
return map;
|
|
11555
|
+
} catch {
|
|
11556
|
+
return null;
|
|
11557
|
+
}
|
|
11558
|
+
}
|
|
11360
11559
|
/**
|
|
11361
11560
|
* Capture and send the visible pane content. Converts bare \n to \r\n for
|
|
11362
11561
|
* xterm.js and strips the trailing \r\n to prevent an extra scroll that
|
|
@@ -11408,7 +11607,7 @@ var SessionStreamer = class {
|
|
|
11408
11607
|
// ---------------------------------------------------------------------------
|
|
11409
11608
|
// tmux streaming (pipe-pane with cursor sync)
|
|
11410
11609
|
// ---------------------------------------------------------------------------
|
|
11411
|
-
startTmuxStream(sessionId, viewerKey = "") {
|
|
11610
|
+
startTmuxStream(sessionId, viewerKey = "", opts) {
|
|
11412
11611
|
const paneTarget = parsePaneTarget(sessionId);
|
|
11413
11612
|
if (!paneTarget) return false;
|
|
11414
11613
|
try {
|
|
@@ -11458,10 +11657,19 @@ var SessionStreamer = class {
|
|
|
11458
11657
|
log17.warn("failed pipe-pane", { sessionId, error: message });
|
|
11459
11658
|
return false;
|
|
11460
11659
|
}
|
|
11660
|
+
const paneInfo = this.readPaneInfo(paneTarget, cleanEnv);
|
|
11661
|
+
if (paneInfo) {
|
|
11662
|
+
stream.panePid = paneInfo.panePid;
|
|
11663
|
+
stream.lastLivenessFingerprint = paneInfo.fingerprint;
|
|
11664
|
+
}
|
|
11665
|
+
stream.bytesReceived = 0;
|
|
11666
|
+
stream.lastLivenessBytes = 0;
|
|
11667
|
+
stream.deadPipeStrikes = 0;
|
|
11461
11668
|
const catProc = (0, import_node_child_process2.spawn)("cat", [fifoPath], { env: cleanEnv, stdio: ["ignore", "pipe", "ignore"] });
|
|
11462
11669
|
stream.catProc = catProc;
|
|
11463
11670
|
catProc.stdout.on("data", (chunk) => {
|
|
11464
11671
|
if (stream.stopped) return;
|
|
11672
|
+
stream.bytesReceived = (stream.bytesReceived ?? 0) + chunk.length;
|
|
11465
11673
|
const data = stream.utf8Decoder.write(chunk);
|
|
11466
11674
|
if (!data) return;
|
|
11467
11675
|
if (stream.initializing) {
|
|
@@ -11490,17 +11698,19 @@ var SessionStreamer = class {
|
|
|
11490
11698
|
this.sendResetFn?.(sessionId, "full");
|
|
11491
11699
|
const alternateScreen = this.detectAlternateScreen(paneTarget, cleanEnv);
|
|
11492
11700
|
stream.alternateScreen = alternateScreen;
|
|
11493
|
-
|
|
11494
|
-
|
|
11495
|
-
|
|
11496
|
-
|
|
11497
|
-
|
|
11498
|
-
|
|
11499
|
-
|
|
11500
|
-
|
|
11501
|
-
|
|
11701
|
+
if (!opts?.skipScrollback) {
|
|
11702
|
+
this.sendFn(sessionId, "\x1B[3J");
|
|
11703
|
+
try {
|
|
11704
|
+
const scrollback = (0, import_node_child_process2.execFileSync)(
|
|
11705
|
+
"tmux",
|
|
11706
|
+
["capture-pane", "-t", paneTarget, "-p", "-e", "-S", "-5000", "-E", "-1"],
|
|
11707
|
+
{ env: cleanEnv, timeout: 1e4, encoding: "utf-8" }
|
|
11708
|
+
);
|
|
11709
|
+
if (scrollback && scrollback.length > 0) {
|
|
11710
|
+
this.sendFn(sessionId, scrollback.replace(/\n/g, "\r\n"));
|
|
11711
|
+
}
|
|
11712
|
+
} catch {
|
|
11502
11713
|
}
|
|
11503
|
-
} catch {
|
|
11504
11714
|
}
|
|
11505
11715
|
this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
|
|
11506
11716
|
const sendBroadcast = (data) => this.sendFn(sessionId, data);
|
|
@@ -14517,7 +14727,11 @@ async function runAgent(rawOptions) {
|
|
|
14517
14727
|
},
|
|
14518
14728
|
() => {
|
|
14519
14729
|
transcriptWatcher.resyncMarkdownAfterDrop();
|
|
14520
|
-
}
|
|
14730
|
+
},
|
|
14731
|
+
// #556 follow-up: lets the streamer gate resync delivery + the pipe-liveness
|
|
14732
|
+
// watchdog on a confirmed-OPEN socket, so resync bytes aren't buffered then
|
|
14733
|
+
// discarded under reconnect churn and re-establish captures actually land.
|
|
14734
|
+
() => conn.isReady()
|
|
14521
14735
|
);
|
|
14522
14736
|
const conn = new ConnectionManager(sessionStreamer, outputBuffer);
|
|
14523
14737
|
const sendOutput = (chunk) => {
|
|
@@ -14593,8 +14807,11 @@ async function runAgent(rawOptions) {
|
|
|
14593
14807
|
sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, payload);
|
|
14594
14808
|
}
|
|
14595
14809
|
},
|
|
14596
|
-
// V2: send full snapshot
|
|
14597
|
-
|
|
14810
|
+
// V2: send full snapshot. `reset` marks an explicit clear (new conversation
|
|
14811
|
+
// / transcript file rotation on pane reuse) so the portal's snapshot guard
|
|
14812
|
+
// lets an empty snapshot wipe stale content instead of rejecting it. See the
|
|
14813
|
+
// `reset` field on TranscriptSnapshotMessage (#538) and shouldApplyTranscriptSnapshot.
|
|
14814
|
+
(sessionId, snapshot, seq, reset) => {
|
|
14598
14815
|
if (!conn.isReady()) return;
|
|
14599
14816
|
const payload = {
|
|
14600
14817
|
type: "markdown",
|
|
@@ -14602,7 +14819,8 @@ async function runAgent(rawOptions) {
|
|
|
14602
14819
|
schemaVersion: 2,
|
|
14603
14820
|
sessionId,
|
|
14604
14821
|
seq,
|
|
14605
|
-
snapshot
|
|
14822
|
+
snapshot,
|
|
14823
|
+
...reset ? { reset: true } : {}
|
|
14606
14824
|
};
|
|
14607
14825
|
if (conn.sessionKey) {
|
|
14608
14826
|
const contentStr = JSON.stringify(payload);
|
|
@@ -14697,6 +14915,7 @@ async function runAgent(rawOptions) {
|
|
|
14697
14915
|
await new Promise((resolve) => {
|
|
14698
14916
|
const ws = new import_ws5.default(negotiated.url, "json.webpubsub.azure.v1");
|
|
14699
14917
|
conn.setConnection(ws, groups, negotiated.sessionKey ?? null);
|
|
14918
|
+
let connectedAt = 0;
|
|
14700
14919
|
if (negotiated.sessionKey) {
|
|
14701
14920
|
const enforcer = new ApprovalEnforcer({
|
|
14702
14921
|
secret: negotiated.sessionKey,
|
|
@@ -14717,6 +14936,7 @@ async function runAgent(rawOptions) {
|
|
|
14717
14936
|
}
|
|
14718
14937
|
ws.on("open", async () => {
|
|
14719
14938
|
log25.info("connector connected to relay");
|
|
14939
|
+
connectedAt = Date.now();
|
|
14720
14940
|
conn.resetReconnectDelay();
|
|
14721
14941
|
ws.send(JSON.stringify({ type: "joinGroup", group: groups.privateGroup }));
|
|
14722
14942
|
ws.send(JSON.stringify({ type: "joinGroup", group: groups.registryGroup }));
|
|
@@ -14749,19 +14969,16 @@ async function runAgent(rawOptions) {
|
|
|
14749
14969
|
}
|
|
14750
14970
|
}
|
|
14751
14971
|
});
|
|
14752
|
-
|
|
14753
|
-
const
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
|
|
14758
|
-
|
|
14759
|
-
} else {
|
|
14760
|
-
sendToGroup(ws, groups.privateGroup, { type: "output", data: outputChunk, sessionId });
|
|
14761
|
-
}
|
|
14972
|
+
sessionStreamer.recoverStreamsAfterReconnect((sessionId, chunk) => {
|
|
14973
|
+
for (const outputChunk of splitTransportChunks(chunk)) {
|
|
14974
|
+
if (conn.sessionKey) {
|
|
14975
|
+
const { enc, iv, seq } = encryptPayload(outputChunk, conn.sessionKey, conn.txSeq.next());
|
|
14976
|
+
sendToGroup(ws, groups.privateGroup, { type: "output", enc, iv, seq, sessionId });
|
|
14977
|
+
} else {
|
|
14978
|
+
sendToGroup(ws, groups.privateGroup, { type: "output", data: outputChunk, sessionId });
|
|
14762
14979
|
}
|
|
14763
14980
|
}
|
|
14764
|
-
}
|
|
14981
|
+
});
|
|
14765
14982
|
await inventory.syncInventory(ws, groups, true);
|
|
14766
14983
|
conn.startTimers(
|
|
14767
14984
|
() => inventory.announceToRegistry(ws, groups.registryGroup, "heartbeat"),
|
|
@@ -14886,6 +15103,7 @@ async function runAgent(rawOptions) {
|
|
|
14886
15103
|
const fields = {};
|
|
14887
15104
|
if (code) fields.code = code;
|
|
14888
15105
|
if (reason.length > 0) fields.reason = reason.toString();
|
|
15106
|
+
if (connectedAt > 0) fields.uptimeMs = Date.now() - connectedAt;
|
|
14889
15107
|
log25.info("relay disconnected; output will be buffered until reconnect", fields);
|
|
14890
15108
|
conn.cleanup();
|
|
14891
15109
|
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.
|
|
1303
|
-
"bom-ref": "ragent-live|ragent-cli@1.11.
|
|
1302
|
+
"version": "1.11.18",
|
|
1303
|
+
"bom-ref": "ragent-live|ragent-cli@1.11.18",
|
|
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.
|
|
1314
|
+
"purl": "pkg:npm/ragent-cli@1.11.18",
|
|
1315
1315
|
"externalReferences": [
|
|
1316
1316
|
{
|
|
1317
1317
|
"url": "https://github.com/chadlindell/ragent-live/issues",
|
|
@@ -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.
|
|
1439
|
+
"ragent-live|ragent-cli@1.11.18"
|
|
1440
1440
|
]
|
|
1441
1441
|
},
|
|
1442
1442
|
{
|
|
@@ -1584,7 +1584,7 @@
|
|
|
1584
1584
|
]
|
|
1585
1585
|
},
|
|
1586
1586
|
{
|
|
1587
|
-
"ref": "ragent-live|ragent-cli@1.11.
|
|
1587
|
+
"ref": "ragent-live|ragent-cli@1.11.18",
|
|
1588
1588
|
"dependsOn": [
|
|
1589
1589
|
"ragent-live|@azure/web-pubsub-client@1.0.4",
|
|
1590
1590
|
"ragent-live|@openai/codex-sdk@0.130.0",
|