ragent-cli 1.11.17 → 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 +213 -19
- 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
|
|
@@ -11285,6 +11398,7 @@ var SessionStreamer = class {
|
|
|
11285
11398
|
if (stream.stopped) continue;
|
|
11286
11399
|
if (stream.streamType === "tmux-pipe") {
|
|
11287
11400
|
this.drainBuffer(stream.sessionId);
|
|
11401
|
+
stream.needsResync = true;
|
|
11288
11402
|
this.resyncStream(stream.sessionId);
|
|
11289
11403
|
} else {
|
|
11290
11404
|
for (const chunk of this.drainBuffer(stream.sessionId)) {
|
|
@@ -11329,6 +11443,13 @@ var SessionStreamer = class {
|
|
|
11329
11443
|
stream.initBuffer = [];
|
|
11330
11444
|
return;
|
|
11331
11445
|
}
|
|
11446
|
+
if (!this.isReadyFn()) {
|
|
11447
|
+
stream.needsResync = true;
|
|
11448
|
+
stream.initializing = false;
|
|
11449
|
+
stream.initBuffer = [];
|
|
11450
|
+
return;
|
|
11451
|
+
}
|
|
11452
|
+
stream.needsResync = false;
|
|
11332
11453
|
const send = (data) => {
|
|
11333
11454
|
if (targetViewerId) {
|
|
11334
11455
|
this.sendFn(sessionId, data, targetViewerId);
|
|
@@ -11384,6 +11505,57 @@ var SessionStreamer = class {
|
|
|
11384
11505
|
return false;
|
|
11385
11506
|
}
|
|
11386
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
|
+
}
|
|
11387
11559
|
/**
|
|
11388
11560
|
* Capture and send the visible pane content. Converts bare \n to \r\n for
|
|
11389
11561
|
* xterm.js and strips the trailing \r\n to prevent an extra scroll that
|
|
@@ -11435,7 +11607,7 @@ var SessionStreamer = class {
|
|
|
11435
11607
|
// ---------------------------------------------------------------------------
|
|
11436
11608
|
// tmux streaming (pipe-pane with cursor sync)
|
|
11437
11609
|
// ---------------------------------------------------------------------------
|
|
11438
|
-
startTmuxStream(sessionId, viewerKey = "") {
|
|
11610
|
+
startTmuxStream(sessionId, viewerKey = "", opts) {
|
|
11439
11611
|
const paneTarget = parsePaneTarget(sessionId);
|
|
11440
11612
|
if (!paneTarget) return false;
|
|
11441
11613
|
try {
|
|
@@ -11485,10 +11657,19 @@ var SessionStreamer = class {
|
|
|
11485
11657
|
log17.warn("failed pipe-pane", { sessionId, error: message });
|
|
11486
11658
|
return false;
|
|
11487
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;
|
|
11488
11668
|
const catProc = (0, import_node_child_process2.spawn)("cat", [fifoPath], { env: cleanEnv, stdio: ["ignore", "pipe", "ignore"] });
|
|
11489
11669
|
stream.catProc = catProc;
|
|
11490
11670
|
catProc.stdout.on("data", (chunk) => {
|
|
11491
11671
|
if (stream.stopped) return;
|
|
11672
|
+
stream.bytesReceived = (stream.bytesReceived ?? 0) + chunk.length;
|
|
11492
11673
|
const data = stream.utf8Decoder.write(chunk);
|
|
11493
11674
|
if (!data) return;
|
|
11494
11675
|
if (stream.initializing) {
|
|
@@ -11517,17 +11698,19 @@ var SessionStreamer = class {
|
|
|
11517
11698
|
this.sendResetFn?.(sessionId, "full");
|
|
11518
11699
|
const alternateScreen = this.detectAlternateScreen(paneTarget, cleanEnv);
|
|
11519
11700
|
stream.alternateScreen = alternateScreen;
|
|
11520
|
-
|
|
11521
|
-
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
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 {
|
|
11529
11713
|
}
|
|
11530
|
-
} catch {
|
|
11531
11714
|
}
|
|
11532
11715
|
this.sendFn(sessionId, "\x1B[?1049l\x1B[0m\x1B[2J\x1B[H");
|
|
11533
11716
|
const sendBroadcast = (data) => this.sendFn(sessionId, data);
|
|
@@ -14544,7 +14727,11 @@ async function runAgent(rawOptions) {
|
|
|
14544
14727
|
},
|
|
14545
14728
|
() => {
|
|
14546
14729
|
transcriptWatcher.resyncMarkdownAfterDrop();
|
|
14547
|
-
}
|
|
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()
|
|
14548
14735
|
);
|
|
14549
14736
|
const conn = new ConnectionManager(sessionStreamer, outputBuffer);
|
|
14550
14737
|
const sendOutput = (chunk) => {
|
|
@@ -14620,8 +14807,11 @@ async function runAgent(rawOptions) {
|
|
|
14620
14807
|
sendToGroup(conn.activeSocket, conn.activeGroups.privateGroup, payload);
|
|
14621
14808
|
}
|
|
14622
14809
|
},
|
|
14623
|
-
// V2: send full snapshot
|
|
14624
|
-
|
|
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) => {
|
|
14625
14815
|
if (!conn.isReady()) return;
|
|
14626
14816
|
const payload = {
|
|
14627
14817
|
type: "markdown",
|
|
@@ -14629,7 +14819,8 @@ async function runAgent(rawOptions) {
|
|
|
14629
14819
|
schemaVersion: 2,
|
|
14630
14820
|
sessionId,
|
|
14631
14821
|
seq,
|
|
14632
|
-
snapshot
|
|
14822
|
+
snapshot,
|
|
14823
|
+
...reset ? { reset: true } : {}
|
|
14633
14824
|
};
|
|
14634
14825
|
if (conn.sessionKey) {
|
|
14635
14826
|
const contentStr = JSON.stringify(payload);
|
|
@@ -14724,6 +14915,7 @@ async function runAgent(rawOptions) {
|
|
|
14724
14915
|
await new Promise((resolve) => {
|
|
14725
14916
|
const ws = new import_ws5.default(negotiated.url, "json.webpubsub.azure.v1");
|
|
14726
14917
|
conn.setConnection(ws, groups, negotiated.sessionKey ?? null);
|
|
14918
|
+
let connectedAt = 0;
|
|
14727
14919
|
if (negotiated.sessionKey) {
|
|
14728
14920
|
const enforcer = new ApprovalEnforcer({
|
|
14729
14921
|
secret: negotiated.sessionKey,
|
|
@@ -14744,6 +14936,7 @@ async function runAgent(rawOptions) {
|
|
|
14744
14936
|
}
|
|
14745
14937
|
ws.on("open", async () => {
|
|
14746
14938
|
log25.info("connector connected to relay");
|
|
14939
|
+
connectedAt = Date.now();
|
|
14747
14940
|
conn.resetReconnectDelay();
|
|
14748
14941
|
ws.send(JSON.stringify({ type: "joinGroup", group: groups.privateGroup }));
|
|
14749
14942
|
ws.send(JSON.stringify({ type: "joinGroup", group: groups.registryGroup }));
|
|
@@ -14910,6 +15103,7 @@ async function runAgent(rawOptions) {
|
|
|
14910
15103
|
const fields = {};
|
|
14911
15104
|
if (code) fields.code = code;
|
|
14912
15105
|
if (reason.length > 0) fields.reason = reason.toString();
|
|
15106
|
+
if (connectedAt > 0) fields.uptimeMs = Date.now() - connectedAt;
|
|
14913
15107
|
log25.info("relay disconnected; output will be buffered until reconnect", fields);
|
|
14914
15108
|
conn.cleanup();
|
|
14915
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",
|