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 +249 -23
- package/dist/sbom.json +12 -12
- package/package.json +2 -2
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.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.
|
|
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
|
-
|
|
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,
|
|
11599
|
+
sendVisiblePane(send, paneTarget, cleanEnv, _alternateScreen) {
|
|
11394
11600
|
try {
|
|
11395
|
-
const captureArgs =
|
|
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
|
-
|
|
11521
|
-
|
|
11522
|
-
|
|
11523
|
-
|
|
11524
|
-
|
|
11525
|
-
|
|
11526
|
-
|
|
11527
|
-
|
|
11528
|
-
|
|
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
|
-
|
|
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.
|
|
1303
|
-
"bom-ref": "ragent-live|ragent-cli@1.11.
|
|
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.
|
|
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.
|
|
1388
|
-
"bom-ref": "ragent-live|ws@8.
|
|
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.
|
|
1399
|
+
"purl": "pkg:npm/ws@8.21.0",
|
|
1400
1400
|
"externalReferences": [
|
|
1401
1401
|
{
|
|
1402
|
-
"url": "https://registry.npmjs.org/ws/-/ws-8.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
62
|
+
"ws": "^8.21.0"
|
|
63
63
|
},
|
|
64
64
|
"devDependencies": {
|
|
65
65
|
"@changesets/changelog-github": "^0.5.2",
|