ragent-cli 1.11.11 → 1.11.13

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.11",
34
+ version: "1.11.13",
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: {
@@ -1991,20 +1991,43 @@ function setBlock(turns, turnId, blockId, block) {
1991
1991
  };
1992
1992
  }
1993
1993
  var MAX_SNAPSHOT_TURNS = 1e3;
1994
+ var MAX_SNAPSHOT_SIZE_CHARS = 45e4;
1994
1995
  function trimSnapshot(snapshot, maxTurns = MAX_SNAPSHOT_TURNS) {
1995
- if (snapshot.turnOrder.length <= maxTurns) return snapshot;
1996
- const keptOrder = snapshot.turnOrder.slice(-maxTurns);
1997
- const keptTurns = {};
1998
- for (const id of keptOrder) {
1999
- if (snapshot.turns[id]) {
2000
- keptTurns[id] = snapshot.turns[id];
1996
+ let currentSnapshot = snapshot;
1997
+ if (currentSnapshot.turnOrder.length > maxTurns) {
1998
+ const keptOrder = currentSnapshot.turnOrder.slice(-maxTurns);
1999
+ const keptTurns = {};
2000
+ for (const id of keptOrder) {
2001
+ if (currentSnapshot.turns[id]) {
2002
+ keptTurns[id] = currentSnapshot.turns[id];
2003
+ }
2004
+ }
2005
+ currentSnapshot = {
2006
+ ...currentSnapshot,
2007
+ turnOrder: keptOrder,
2008
+ turns: keptTurns
2009
+ };
2010
+ }
2011
+ let serialized = JSON.stringify(currentSnapshot);
2012
+ if (serialized.length <= MAX_SNAPSHOT_SIZE_CHARS) {
2013
+ return currentSnapshot;
2014
+ }
2015
+ while (currentSnapshot.turnOrder.length > 1 && serialized.length > MAX_SNAPSHOT_SIZE_CHARS) {
2016
+ const keptOrder = currentSnapshot.turnOrder.slice(1);
2017
+ const keptTurns = {};
2018
+ for (const id of keptOrder) {
2019
+ if (currentSnapshot.turns[id]) {
2020
+ keptTurns[id] = currentSnapshot.turns[id];
2021
+ }
2001
2022
  }
2023
+ currentSnapshot = {
2024
+ ...currentSnapshot,
2025
+ turnOrder: keptOrder,
2026
+ turns: keptTurns
2027
+ };
2028
+ serialized = JSON.stringify(currentSnapshot);
2002
2029
  }
2003
- return {
2004
- ...snapshot,
2005
- turnOrder: keptOrder,
2006
- turns: keptTurns
2007
- };
2030
+ return currentSnapshot;
2008
2031
  }
2009
2032
 
2010
2033
  // src/native-stream-manager.ts
@@ -2541,6 +2564,8 @@ function releaseSessionSource(sessionId) {
2541
2564
 
2542
2565
  // src/transcript-watcher.ts
2543
2566
  var log7 = createLogger("transcript-watcher");
2567
+ var cachedTicksPerSecond = null;
2568
+ var transcriptDiscoveryCache = /* @__PURE__ */ new Map();
2544
2569
  var ClaudeCodeParser = class {
2545
2570
  name = "claude-code";
2546
2571
  /** Maps tool_use id → tool name for matching results back to tools. */
@@ -3054,6 +3079,13 @@ function discoverTranscriptForPid(pid, agentType) {
3054
3079
  }
3055
3080
  }
3056
3081
  function listChildPids(pid) {
3082
+ if (os3.platform() !== "darwin") {
3083
+ try {
3084
+ const content = fs2.readFileSync(`/proc/${pid}/task/${pid}/children`, "utf8");
3085
+ return content.trim().split(/\s+/).map((value) => parseInt(value, 10)).filter((value) => Number.isFinite(value) && value > 0);
3086
+ } catch {
3087
+ }
3088
+ }
3057
3089
  try {
3058
3090
  return (0, import_child_process2.execFileSync)("pgrep", ["-P", String(pid)], {
3059
3091
  encoding: "utf-8",
@@ -3064,6 +3096,16 @@ function listChildPids(pid) {
3064
3096
  }
3065
3097
  }
3066
3098
  function getProcessCommand(pid) {
3099
+ if (os3.platform() !== "darwin") {
3100
+ try {
3101
+ const raw = fs2.readFileSync(`/proc/${pid}/cmdline`, "utf8");
3102
+ const parts = raw.split("\0").filter(Boolean);
3103
+ if (parts.length > 0) {
3104
+ return parts.join(" ");
3105
+ }
3106
+ } catch {
3107
+ }
3108
+ }
3067
3109
  try {
3068
3110
  return (0, import_child_process2.execFileSync)("ps", ["-p", String(pid), "-o", "args="], {
3069
3111
  encoding: "utf-8",
@@ -3105,10 +3147,13 @@ function getProcessStartEpochMs(pid) {
3105
3147
  const bootLine = procStat.split("\n").find((line) => line.startsWith("btime "));
3106
3148
  const bootSeconds = Number(bootLine?.split(/\s+/)[1]);
3107
3149
  if (!Number.isFinite(bootSeconds) || bootSeconds <= 0) return null;
3108
- const ticksPerSecond = Number((0, import_child_process2.execFileSync)("getconf", ["CLK_TCK"], {
3109
- encoding: "utf-8",
3110
- timeout: 3e3
3111
- }).trim());
3150
+ if (cachedTicksPerSecond === null) {
3151
+ cachedTicksPerSecond = Number((0, import_child_process2.execFileSync)("getconf", ["CLK_TCK"], {
3152
+ encoding: "utf-8",
3153
+ timeout: 3e3
3154
+ }).trim());
3155
+ }
3156
+ const ticksPerSecond = cachedTicksPerSecond;
3112
3157
  if (!Number.isFinite(ticksPerSecond) || ticksPerSecond <= 0) return null;
3113
3158
  return (bootSeconds + startTicks / ticksPerSecond) * 1e3;
3114
3159
  } catch {
@@ -3254,29 +3299,66 @@ function discoverAntigravityTranscriptFromHome(pid, minMtimeMs) {
3254
3299
  const brainDir = path2.join(userHome, ".gemini", "antigravity-cli", "brain");
3255
3300
  if (!fs2.existsSync(brainDir)) return null;
3256
3301
  const matches = [];
3257
- const stack = [brainDir];
3258
- let visited = 0;
3259
- while (stack.length > 0 && visited < 3e3) {
3260
- const current = stack.pop();
3261
- if (!current) continue;
3262
- visited++;
3263
- let entries;
3264
- try {
3265
- entries = fs2.readdirSync(current, { withFileTypes: true });
3266
- } catch {
3267
- continue;
3268
- }
3269
- for (const entry of entries) {
3270
- const fullPath = path2.join(current, entry.name);
3271
- if (entry.isDirectory()) {
3272
- stack.push(fullPath);
3273
- } else if (entry.isFile() && entry.name === "transcript.jsonl") {
3274
- matches.push(fullPath);
3302
+ try {
3303
+ const folders = fs2.readdirSync(brainDir, { withFileTypes: true });
3304
+ for (const folder of folders) {
3305
+ if (folder.isDirectory()) {
3306
+ const transcriptPath = path2.join(brainDir, folder.name, ".system_generated", "logs", "transcript.jsonl");
3307
+ if (fs2.existsSync(transcriptPath)) {
3308
+ matches.push(transcriptPath);
3309
+ }
3275
3310
  }
3276
3311
  }
3312
+ } catch {
3277
3313
  }
3278
3314
  return getNewestPath(matches, { minMtimeMs });
3279
3315
  }
3316
+ function discoverAntigravityViaCwd(paneCwd, pid) {
3317
+ const userHome = resolveUserHome(pid);
3318
+ if (!userHome) return null;
3319
+ const historyFile = path2.join(userHome, ".gemini", "antigravity-cli", "history.jsonl");
3320
+ if (!fs2.existsSync(historyFile)) return null;
3321
+ let targetCwd = paneCwd;
3322
+ try {
3323
+ targetCwd = fs2.realpathSync(paneCwd);
3324
+ } catch {
3325
+ }
3326
+ try {
3327
+ const content = fs2.readFileSync(historyFile, "utf-8");
3328
+ const lines = content.split("\n").filter(Boolean).reverse();
3329
+ for (const line of lines) {
3330
+ try {
3331
+ const entry = JSON.parse(line);
3332
+ if (entry.workspace) {
3333
+ let entryCwd = entry.workspace;
3334
+ try {
3335
+ entryCwd = fs2.realpathSync(entry.workspace);
3336
+ } catch {
3337
+ }
3338
+ if (entryCwd === targetCwd && entry.conversationId) {
3339
+ const transcriptPath = path2.join(
3340
+ userHome,
3341
+ ".gemini",
3342
+ "antigravity-cli",
3343
+ "brain",
3344
+ entry.conversationId,
3345
+ ".system_generated",
3346
+ "logs",
3347
+ "transcript.jsonl"
3348
+ );
3349
+ if (fs2.existsSync(transcriptPath)) {
3350
+ return transcriptPath;
3351
+ }
3352
+ }
3353
+ }
3354
+ } catch {
3355
+ continue;
3356
+ }
3357
+ }
3358
+ } catch {
3359
+ }
3360
+ return null;
3361
+ }
3280
3362
  function discoverViaCwd(paneCwd, pid, minMtimeMs) {
3281
3363
  const userHome = resolveUserHome(pid);
3282
3364
  if (!userHome) return null;
@@ -3296,31 +3378,72 @@ function discoverViaCwd(paneCwd, pid, minMtimeMs) {
3296
3378
  return null;
3297
3379
  }
3298
3380
  }
3299
- function discoverTranscriptFile(sessionId, agentType) {
3381
+ function discoverTranscriptFile(sessionId, agentType, forceRefresh = false) {
3382
+ const now2 = Date.now();
3383
+ const cacheKey = `${sessionId}:${agentType || ""}`;
3384
+ if (!forceRefresh) {
3385
+ const cached = transcriptDiscoveryCache.get(cacheKey);
3386
+ if (cached && cached.expiresAt > now2) {
3387
+ return cached.value;
3388
+ }
3389
+ }
3390
+ const result = discoverTranscriptFileImpl(sessionId, agentType);
3391
+ const isProcessSession = sessionId.startsWith("process:");
3392
+ const ttl = result ? isProcessSession ? 3600 * 1e3 : 5e3 : 5e3;
3393
+ transcriptDiscoveryCache.set(cacheKey, {
3394
+ value: result,
3395
+ expiresAt: Date.now() + ttl
3396
+ });
3397
+ return result;
3398
+ }
3399
+ function discoverTranscriptFileImpl(sessionId, agentType) {
3300
3400
  const processPid = parseProcessPid(sessionId);
3301
3401
  if (processPid) {
3402
+ const t0 = performance.now();
3302
3403
  const procResult = discoverViaProc(processPid, agentType);
3303
- if (procResult) return procResult;
3404
+ if (procResult) {
3405
+ console.log(`[DISCOVER] discoverViaProc found file for PID ${processPid} in ${(performance.now() - t0).toFixed(2)} ms`);
3406
+ return procResult;
3407
+ }
3304
3408
  if (agentType === "Claude Code") {
3409
+ const t1 = performance.now();
3305
3410
  const startMs = getProcessStartEpochMs(processPid);
3306
3411
  const cwdResult = discoverViaProcessCwd(processPid, startMs ? startMs - 6e4 : void 0);
3412
+ console.log(`[DISCOVER] Claude Code discovery for PID ${processPid} took ${(performance.now() - t1).toFixed(2)} ms`);
3307
3413
  if (cwdResult) return cwdResult;
3308
3414
  }
3309
3415
  if (agentType === "Codex CLI") {
3416
+ const t1 = performance.now();
3310
3417
  const startMs = getProcessStartEpochMs(processPid);
3311
3418
  const command = getProcessCommand(processPid);
3312
3419
  if (commandMatchesAgent(command, agentType)) {
3313
3420
  const codexResult = discoverCodexTranscriptFromHome(processPid, startMs ? startMs - 6e4 : void 0);
3421
+ console.log(`[DISCOVER] Codex CLI discovery for PID ${processPid} took ${(performance.now() - t1).toFixed(2)} ms`);
3314
3422
  if (codexResult) return codexResult;
3315
3423
  }
3316
3424
  }
3317
3425
  if (agentType === "Gemini CLI") {
3426
+ const t1 = performance.now();
3318
3427
  const geminiResult = discoverGeminiTranscriptFromHome(processPid);
3428
+ console.log(`[DISCOVER] Gemini CLI discovery for PID ${processPid} took ${(performance.now() - t1).toFixed(2)} ms`);
3319
3429
  if (geminiResult) return geminiResult;
3320
3430
  }
3321
3431
  if (agentType === "Antigravity CLI") {
3432
+ const t1 = performance.now();
3322
3433
  const startMs = getProcessStartEpochMs(processPid);
3434
+ try {
3435
+ const processCwd = fs2.readlinkSync(`/proc/${processPid}/cwd`);
3436
+ if (processCwd) {
3437
+ const cwdResult = discoverAntigravityViaCwd(processCwd, processPid);
3438
+ if (cwdResult) {
3439
+ console.log(`[DISCOVER] Antigravity CLI discovery (CWD path) for PID ${processPid} took ${(performance.now() - t1).toFixed(2)} ms`);
3440
+ return cwdResult;
3441
+ }
3442
+ }
3443
+ } catch {
3444
+ }
3323
3445
  const antigravityResult = discoverAntigravityTranscriptFromHome(processPid, startMs ? startMs - 6e4 : void 0);
3446
+ console.log(`[DISCOVER] Antigravity CLI discovery (home path) for PID ${processPid} took ${(performance.now() - t1).toFixed(2)} ms`);
3324
3447
  if (antigravityResult) return antigravityResult;
3325
3448
  }
3326
3449
  return null;
@@ -3380,6 +3503,34 @@ function discoverTranscriptFile(sessionId, agentType) {
3380
3503
  if (agentType === "Gemini CLI") {
3381
3504
  return discoverGeminiTranscriptFromHome(panePid ?? void 0);
3382
3505
  }
3506
+ if (agentType === "Antigravity CLI") {
3507
+ const agentInfos = panePid ? findAgentProcessInfos(panePid, agentType) : [];
3508
+ for (const info of agentInfos) {
3509
+ if (info.cwd) {
3510
+ const cwdResult = discoverAntigravityViaCwd(info.cwd, info.pid);
3511
+ if (cwdResult) return cwdResult;
3512
+ }
3513
+ }
3514
+ try {
3515
+ const paneCwd = (0, import_child_process2.execFileSync)(
3516
+ "tmux",
3517
+ ["display-message", "-t", paneTarget, "-p", "#{pane_current_path}"],
3518
+ { env: cleanEnv, timeout: 5e3, encoding: "utf-8" }
3519
+ ).trim();
3520
+ if (paneCwd) {
3521
+ const cwdResult = discoverAntigravityViaCwd(paneCwd, panePid ?? void 0);
3522
+ if (cwdResult) return cwdResult;
3523
+ }
3524
+ } catch {
3525
+ }
3526
+ const newestForAgent = getNewestPath(
3527
+ agentInfos.map((info) => discoverAntigravityTranscriptFromHome(
3528
+ info.pid,
3529
+ info.startEpochMs ? info.startEpochMs - 6e4 : void 0
3530
+ )).filter((value) => Boolean(value))
3531
+ );
3532
+ if (newestForAgent) return newestForAgent;
3533
+ }
3383
3534
  return null;
3384
3535
  }
3385
3536
  return null;
@@ -3387,6 +3538,7 @@ function discoverTranscriptFile(sessionId, agentType) {
3387
3538
  var MAX_PARTIAL_BUFFER = 256 * 1024;
3388
3539
  var MAX_REPLAY_TURNS = 1e3;
3389
3540
  var POLL_INTERVAL_MS = 800;
3541
+ var DOC_READ_DEBOUNCE_MS = 200;
3390
3542
  var REDISCOVERY_INTERVAL_MS = 5e3;
3391
3543
  var ENABLE_RETRY_DELAYS_MS = [1e3, 2e3, 5e3];
3392
3544
  var TranscriptWatcher = class {
@@ -3403,6 +3555,7 @@ var TranscriptWatcher = class {
3403
3555
  snapshot;
3404
3556
  watcher = null;
3405
3557
  pollTimer = null;
3558
+ docReadDebounceTimer = null;
3406
3559
  stopped = false;
3407
3560
  subscriberCount = 0;
3408
3561
  documentFingerprint = "";
@@ -3457,6 +3610,10 @@ var TranscriptWatcher = class {
3457
3610
  clearInterval(this.pollTimer);
3458
3611
  this.pollTimer = null;
3459
3612
  }
3613
+ if (this.docReadDebounceTimer) {
3614
+ clearTimeout(this.docReadDebounceTimer);
3615
+ this.docReadDebounceTimer = null;
3616
+ }
3460
3617
  }
3461
3618
  /** Get accumulated turns for replay (up to MAX_REPLAY_TURNS). */
3462
3619
  getReplayTurns(fromSeq) {
@@ -3515,7 +3672,16 @@ var TranscriptWatcher = class {
3515
3672
  startWatching() {
3516
3673
  try {
3517
3674
  this.watcher = fs2.watch(this.filePath, () => {
3518
- if (!this.stopped) this.readNewData();
3675
+ if (this.stopped) return;
3676
+ if (this.parser.parseDocument) {
3677
+ if (this.docReadDebounceTimer) return;
3678
+ this.docReadDebounceTimer = setTimeout(() => {
3679
+ this.docReadDebounceTimer = null;
3680
+ if (!this.stopped) this.readNewData();
3681
+ }, DOC_READ_DEBOUNCE_MS);
3682
+ return;
3683
+ }
3684
+ this.readNewData();
3519
3685
  });
3520
3686
  this.watcher.on("error", () => {
3521
3687
  this.watcher?.close();
@@ -3570,16 +3736,20 @@ var TranscriptWatcher = class {
3570
3736
  }
3571
3737
  }
3572
3738
  readDocument(force) {
3573
- let content;
3574
3739
  let stat;
3575
3740
  try {
3576
3741
  stat = fs2.statSync(this.filePath);
3577
- content = fs2.readFileSync(this.filePath, "utf-8");
3578
3742
  } catch {
3579
3743
  return;
3580
3744
  }
3581
3745
  const fingerprint = `${stat.mtimeMs}:${stat.size}`;
3582
3746
  if (!force && fingerprint === this.documentFingerprint) return;
3747
+ let content;
3748
+ try {
3749
+ content = fs2.readFileSync(this.filePath, "utf-8");
3750
+ } catch {
3751
+ return;
3752
+ }
3583
3753
  this.documentFingerprint = fingerprint;
3584
3754
  const parsedTurns = this.parser.parseDocument?.(content) ?? [];
3585
3755
  this.seq++;
@@ -3695,7 +3865,7 @@ var TranscriptWatcherManager = class {
3695
3865
  const existing = this.active.get(sessionId);
3696
3866
  let replacingExisting = false;
3697
3867
  if (existing) {
3698
- const newPath = discoverTranscriptFile(sessionId, agentType);
3868
+ const newPath = discoverTranscriptFile(sessionId, agentType, true);
3699
3869
  const agentChanged = existing.agentType !== requestedAgentType;
3700
3870
  const pathChanged = Boolean(newPath && newPath !== existing.filePath);
3701
3871
  const existingPathGone = !fs2.existsSync(existing.filePath);
@@ -3720,7 +3890,7 @@ var TranscriptWatcherManager = class {
3720
3890
  this.cancelEnableRetry(sessionId);
3721
3891
  return { ok: false, reason: "unsupported_agent" };
3722
3892
  }
3723
- const filePath = discoverTranscriptFile(sessionId, agentType);
3893
+ const filePath = discoverTranscriptFile(sessionId, agentType, isRetry || replacingExisting);
3724
3894
  if (!filePath) {
3725
3895
  log7.info("no transcript file found", { sessionId });
3726
3896
  this.armEnableRetry(sessionId, agentType);
@@ -3846,7 +4016,7 @@ var TranscriptWatcherManager = class {
3846
4016
  this.stopRediscovery(sessionId);
3847
4017
  return;
3848
4018
  }
3849
- const newPath = discoverTranscriptFile(sessionId, agentType);
4019
+ const newPath = discoverTranscriptFile(sessionId, agentType, true);
3850
4020
  if (!newPath || newPath === session.filePath) return;
3851
4021
  log7.info("transcript file changed", { sessionId, newPath });
3852
4022
  session.watcher.stop();
@@ -9524,6 +9694,39 @@ function resolveRuntimeTranscriptSource(sessionId) {
9524
9694
  // src/sessions.ts
9525
9695
  var isMac = os5.platform() === "darwin";
9526
9696
  var MARKDOWN_AGENT_TYPES = /* @__PURE__ */ new Set(["Claude Code", "Codex CLI", "Gemini CLI", "Antigravity CLI"]);
9697
+ var isTmuxAvailable = null;
9698
+ var isScreenAvailable = null;
9699
+ var isZellijAvailable = null;
9700
+ async function checkTmuxAvailable() {
9701
+ if (isTmuxAvailable !== null) return isTmuxAvailable;
9702
+ try {
9703
+ await execAsync("tmux -V");
9704
+ isTmuxAvailable = true;
9705
+ } catch {
9706
+ isTmuxAvailable = false;
9707
+ }
9708
+ return isTmuxAvailable;
9709
+ }
9710
+ async function checkScreenAvailable() {
9711
+ if (isScreenAvailable !== null) return isScreenAvailable;
9712
+ try {
9713
+ await execAsync("screen -v");
9714
+ isScreenAvailable = true;
9715
+ } catch {
9716
+ isScreenAvailable = false;
9717
+ }
9718
+ return isScreenAvailable;
9719
+ }
9720
+ async function checkZellijAvailable() {
9721
+ if (isZellijAvailable !== null) return isZellijAvailable;
9722
+ try {
9723
+ await execAsync("zellij --version");
9724
+ isZellijAvailable = true;
9725
+ } catch {
9726
+ isZellijAvailable = false;
9727
+ }
9728
+ return isZellijAvailable;
9729
+ }
9527
9730
  function getProcessStartTime(pid) {
9528
9731
  if (isMac) return null;
9529
9732
  try {
@@ -9540,7 +9743,38 @@ function getProcessStartTime(pid) {
9540
9743
  function collectStartTimes(pids) {
9541
9744
  return pids.map((pid) => getProcessStartTime(pid) ?? 0);
9542
9745
  }
9543
- async function detectVscodeEnvironment(pid) {
9746
+ async function getProcessTable() {
9747
+ try {
9748
+ const raw = await execAsync("ps axo pid,ppid,tty,stat,comm,args");
9749
+ const rows = [];
9750
+ const lines = raw.split("\n");
9751
+ for (let i = 0; i < lines.length; i++) {
9752
+ const trimmed = lines[i].trim();
9753
+ if (!trimmed) continue;
9754
+ if (i === 0 && /^\s*PID\b/i.test(lines[i])) continue;
9755
+ const parts = trimmed.split(/\s+/);
9756
+ if (parts.length < 6) continue;
9757
+ const pid = Number(parts[0]);
9758
+ const ppid = Number(parts[1]);
9759
+ const tty = parts[2];
9760
+ const stat = parts[3];
9761
+ const comm = parts[4];
9762
+ const args = parts.slice(5).join(" ");
9763
+ rows.push({
9764
+ pid,
9765
+ ppid: Number.isFinite(ppid) ? ppid : 0,
9766
+ tty,
9767
+ stat,
9768
+ comm,
9769
+ args
9770
+ });
9771
+ }
9772
+ return rows;
9773
+ } catch {
9774
+ return [];
9775
+ }
9776
+ }
9777
+ async function detectVscodeEnvironment(pid, processByPid) {
9544
9778
  if (!isMac) {
9545
9779
  try {
9546
9780
  const environ = fs4.readFileSync(`/proc/${pid}/environ`, "utf8");
@@ -9550,17 +9784,36 @@ async function detectVscodeEnvironment(pid) {
9550
9784
  } catch {
9551
9785
  }
9552
9786
  }
9553
- try {
9554
- const raw = await execAsync(`ps -p ${pid} -o ppid=`);
9555
- const ppid = Number(raw.trim());
9556
- if (ppid > 1) {
9557
- const parentInfo = await execAsync(`ps -p ${ppid} -o comm=`);
9558
- const parentComm = parentInfo.trim().toLowerCase();
9559
- if (parentComm === "code" || parentComm === "code-server") {
9560
- return true;
9787
+ if (!processByPid || processByPid.size === 0) {
9788
+ try {
9789
+ const raw = await execAsync(`ps -p ${pid} -o ppid=`);
9790
+ const ppid = Number(raw.trim());
9791
+ if (ppid > 1) {
9792
+ const parentInfo = await execAsync(`ps -p ${ppid} -o comm=`);
9793
+ const parentComm = parentInfo.trim().toLowerCase();
9794
+ if (parentComm === "code" || parentComm === "code-server") {
9795
+ return true;
9796
+ }
9561
9797
  }
9798
+ } catch {
9562
9799
  }
9563
- } catch {
9800
+ return false;
9801
+ }
9802
+ let currentPid = pid;
9803
+ const visited = /* @__PURE__ */ new Set();
9804
+ while (currentPid > 1 && !visited.has(currentPid)) {
9805
+ visited.add(currentPid);
9806
+ const proc = processByPid.get(currentPid);
9807
+ if (!proc) break;
9808
+ const parentPid = proc.ppid;
9809
+ if (parentPid <= 1) break;
9810
+ const parentProc = processByPid.get(parentPid);
9811
+ if (!parentProc) break;
9812
+ const parentComm = parentProc.comm.toLowerCase();
9813
+ if (parentComm === "code" || parentComm === "code-server") {
9814
+ return true;
9815
+ }
9816
+ currentPid = parentPid;
9564
9817
  }
9565
9818
  return false;
9566
9819
  }
@@ -9619,12 +9872,14 @@ function classifyFromCommand(command) {
9619
9872
  }
9620
9873
  return { agentType, confidence: 0.7 };
9621
9874
  }
9622
- async function collectTmuxSessions() {
9623
- try {
9624
- await execAsync("tmux -V");
9625
- } catch {
9875
+ async function collectTmuxSessions(processByPid) {
9876
+ if (!await checkTmuxAvailable()) {
9626
9877
  return [];
9627
9878
  }
9879
+ if (!processByPid) {
9880
+ const list = await getProcessTable();
9881
+ processByPid = new Map(list.map((p) => [p.pid, p]));
9882
+ }
9628
9883
  try {
9629
9884
  const raw = await execAsync(
9630
9885
  "tmux list-panes -a -F '#{session_name}|#{window_index}|#{pane_index}|#{pane_current_command}|#{session_attached}|#{window_activity}|#{pane_pid}|#{window_layout}|#{pane_current_path}|#{window_name}|#{pane_title}|#{window_flags}|#{session_group}|#{session_grouped}'"
@@ -9658,11 +9913,14 @@ async function collectTmuxSessions() {
9658
9913
  const pid = Number(panePid);
9659
9914
  if (pid > 0) pids.push(pid);
9660
9915
  const classification = classifyAgent(command, { workingDir: paneCurrentPath });
9661
- const vscodeDetected = pid > 0 ? await detectVscodeEnvironment(pid) : false;
9916
+ const vscodeDetected = pid > 0 ? await detectVscodeEnvironment(pid, processByPid) : false;
9917
+ const dirName = paneCurrentPath ? paneCurrentPath.split("/").filter(Boolean).pop() : "";
9918
+ const baseName = `${sessionName}:${windowIndex}.${paneIndex}`;
9919
+ const finalName = dirName ? `${baseName} (${dirName})` : baseName;
9662
9920
  results.push({
9663
9921
  id,
9664
9922
  type: "tmux",
9665
- name: `${sessionName}:${windowIndex}.${paneIndex}`,
9923
+ name: finalName,
9666
9924
  status: Number(attachedCount) > 0 ? "active" : "detached",
9667
9925
  command,
9668
9926
  agentType: classification?.agentType,
@@ -9687,10 +9945,8 @@ async function collectTmuxSessions() {
9687
9945
  return [];
9688
9946
  }
9689
9947
  }
9690
- async function collectScreenSessions() {
9691
- try {
9692
- await execAsync("screen -v");
9693
- } catch {
9948
+ async function collectScreenSessions(childrenByPpid) {
9949
+ if (!await checkScreenAvailable()) {
9694
9950
  return [];
9695
9951
  }
9696
9952
  try {
@@ -9702,7 +9958,7 @@ async function collectScreenSessions() {
9702
9958
  if (!match) continue;
9703
9959
  const [, screenPid, sessionName, state] = match;
9704
9960
  const pid = Number(screenPid);
9705
- const childInfo = await getChildAgentInfo(pid);
9961
+ const childInfo = await getChildAgentInfo(pid, childrenByPpid);
9706
9962
  if (!childInfo) continue;
9707
9963
  const id = `screen:${sessionName}:${screenPid}`;
9708
9964
  const allPids = [pid, ...childInfo.childPids];
@@ -9733,10 +9989,8 @@ async function collectScreenSessions() {
9733
9989
  return [];
9734
9990
  }
9735
9991
  }
9736
- async function collectZellijSessions() {
9737
- try {
9738
- await execAsync("zellij --version");
9739
- } catch {
9992
+ async function collectZellijSessions(childrenByPpid, processList) {
9993
+ if (!await checkZellijAvailable()) {
9740
9994
  return [];
9741
9995
  }
9742
9996
  try {
@@ -9745,19 +9999,25 @@ async function collectZellijSessions() {
9745
9999
  for (const line of raw.split("\n")) {
9746
10000
  const sessionName = line.trim();
9747
10001
  if (!sessionName) continue;
9748
- let serverPids;
9749
- try {
9750
- const psOut = await execAsync(
9751
- `ps axo pid,args | grep 'zellij.*--session ${sessionName}' | grep -v grep`
9752
- );
9753
- serverPids = psOut.split("\n").map((l) => Number(l.trim().split(/\s+/)[0])).filter((p) => p > 0);
9754
- } catch {
9755
- serverPids = [];
10002
+ let serverPids = [];
10003
+ if (processList) {
10004
+ serverPids = processList.filter(
10005
+ (p) => p.args.includes("zellij") && p.args.includes(`--session ${sessionName}`)
10006
+ ).map((p) => p.pid);
10007
+ } else {
10008
+ try {
10009
+ const psOut = await execAsync(
10010
+ `ps axo pid,args | grep 'zellij.*--session ${sessionName}' | grep -v grep`
10011
+ );
10012
+ serverPids = psOut.split("\n").map((l) => Number(l.trim().split(/\s+/)[0])).filter((p) => p > 0);
10013
+ } catch {
10014
+ serverPids = [];
10015
+ }
9756
10016
  }
9757
10017
  const allChildPids = [];
9758
10018
  let foundAgent = null;
9759
10019
  for (const spid of serverPids) {
9760
- const childInfo = await getChildAgentInfo(spid);
10020
+ const childInfo = await getChildAgentInfo(spid, childrenByPpid);
9761
10021
  if (childInfo) {
9762
10022
  foundAgent = childInfo;
9763
10023
  allChildPids.push(...childInfo.childPids);
@@ -9809,7 +10069,7 @@ async function getProcessWorkingDir(pid) {
9809
10069
  }
9810
10070
  return null;
9811
10071
  }
9812
- const cwd = await execAsync(`readlink -f /proc/${pid}/cwd`);
10072
+ const cwd = await fs4.promises.readlink(`/proc/${pid}/cwd`);
9813
10073
  return cwd.trim() || null;
9814
10074
  } catch {
9815
10075
  return null;
@@ -9827,35 +10087,20 @@ function transcriptSourceForSession(agentType, sessionId) {
9827
10087
  if (!agentType) return void 0;
9828
10088
  return getTranscriptSourceForAgentType(agentType);
9829
10089
  }
9830
- async function collectBareAgentProcesses(excludePids) {
10090
+ async function collectBareAgentProcesses(excludePids, processList, processByPid) {
9831
10091
  try {
9832
- const raw = await execAsync("ps axo pid,ppid,tty,stat,comm,args");
10092
+ const tStart = performance.now();
10093
+ if (!processList || !processByPid) {
10094
+ const list = await getProcessTable();
10095
+ processList = list;
10096
+ processByPid = new Map(list.map((p) => [p.pid, p]));
10097
+ }
10098
+ const tInit = performance.now();
9833
10099
  const sessions = [];
9834
10100
  const seen = /* @__PURE__ */ new Set();
9835
10101
  const excluded = excludePids ?? /* @__PURE__ */ new Set();
9836
- const rows = [];
9837
- const lines = raw.split("\n");
9838
- for (let i = 0; i < lines.length; i++) {
9839
- const trimmed = lines[i].trim();
9840
- if (!trimmed) continue;
9841
- if (i === 0 && /^\s*PID\b/i.test(lines[i])) continue;
9842
- const parts = trimmed.split(/\s+/);
9843
- if (parts.length < 6) continue;
9844
- const pid = Number(parts[0]);
9845
- const ppid = Number(parts[1]);
9846
- const tty = parts[2];
9847
- const stat = parts[3];
9848
- const args = parts.slice(5).join(" ");
9849
- rows.push({
9850
- pid,
9851
- ppid: Number.isFinite(ppid) ? ppid : 0,
9852
- tty,
9853
- stat,
9854
- args
9855
- });
9856
- }
9857
- rows.sort((a, b) => a.pid - b.pid);
9858
- const parentByPid = new Map(rows.map((row) => [row.pid, row.ppid]));
10102
+ const parentByPid = new Map(processList.map((row) => [row.pid, row.ppid]));
10103
+ const tParentMap = performance.now();
9859
10104
  const hasTrackedAncestor = (pid, tracked) => {
9860
10105
  const visited = /* @__PURE__ */ new Set();
9861
10106
  let current = parentByPid.get(pid) ?? 0;
@@ -9866,24 +10111,60 @@ async function collectBareAgentProcesses(excludePids) {
9866
10111
  }
9867
10112
  return false;
9868
10113
  };
9869
- for (const row of rows) {
10114
+ const tFilterStart = performance.now();
10115
+ const candidates = processList.filter((row) => {
9870
10116
  const { pid, tty, stat, args } = row;
9871
- if (!hasControllingTty(tty)) continue;
9872
- if (stat.startsWith("Z")) continue;
9873
- if (excluded.has(pid) || hasTrackedAncestor(pid, excluded)) continue;
9874
- if (seen.has(pid) || hasTrackedAncestor(pid, seen)) continue;
10117
+ if (!hasControllingTty(tty)) return false;
10118
+ if (stat.startsWith("Z")) return false;
10119
+ if (excluded.has(pid) || hasTrackedAncestor(pid, excluded)) return false;
10120
+ const lowerArgs = args.toLowerCase();
10121
+ const hasKeyword = lowerArgs.includes("claude") || lowerArgs.includes("codex") || lowerArgs.includes("aider") || lowerArgs.includes("cursor") || lowerArgs.includes("windsurf") || lowerArgs.includes("gemini") || lowerArgs.includes("agy") || lowerArgs.includes("antigravity") || lowerArgs.includes("amazon") || lowerArgs.includes("copilot");
10122
+ if (!hasKeyword) return false;
10123
+ return true;
10124
+ });
10125
+ const tFilterEnd = performance.now();
10126
+ const tCwdsStart = performance.now();
10127
+ const cwds = await Promise.all(
10128
+ candidates.map(async (row) => {
10129
+ const cwd = await getProcessWorkingDir(row.pid);
10130
+ return { pid: row.pid, cwd };
10131
+ })
10132
+ );
10133
+ const cwdByPid = new Map(cwds.map((c) => [c.pid, c.cwd]));
10134
+ const tCwdsEnd = performance.now();
10135
+ let tLoopAncestor = 0;
10136
+ let tLoopClassify = 0;
10137
+ let tLoopVscode = 0;
10138
+ let tLoopMarkdown = 0;
10139
+ let tLoopStartTimes = 0;
10140
+ const tLoopStart = performance.now();
10141
+ for (const row of candidates) {
10142
+ const { pid, args } = row;
10143
+ const t0 = performance.now();
10144
+ const skip = seen.has(pid) || hasTrackedAncestor(pid, seen);
10145
+ tLoopAncestor += performance.now() - t0;
10146
+ if (skip) continue;
10147
+ const workingDir = cwdByPid.get(pid) || null;
10148
+ const t1 = performance.now();
9875
10149
  let classification = classifyAgent(args);
9876
10150
  if (!classification) {
9877
- const inferredWorkingDir = await getProcessWorkingDir(pid);
9878
- classification = classifyAgent(args, { workingDir: inferredWorkingDir ?? void 0 });
10151
+ classification = classifyAgent(args, { workingDir: workingDir ?? void 0 });
9879
10152
  }
10153
+ tLoopClassify += performance.now() - t1;
9880
10154
  if (!classification) continue;
9881
10155
  seen.add(pid);
9882
- const workingDir = await getProcessWorkingDir(pid);
9883
10156
  const dirName = workingDir ? workingDir.split("/").pop() : null;
9884
10157
  const displayName = dirName && dirName !== "/" && dirName !== "" ? `${classification.agentType} (${dirName})` : `${classification.agentType} (pid ${pid})`;
9885
- const vscodeDetected = await detectVscodeEnvironment(pid);
10158
+ const t2 = performance.now();
10159
+ const vscodeDetected = await detectVscodeEnvironment(pid, processByPid);
10160
+ tLoopVscode += performance.now() - t2;
9886
10161
  const id = `process:${pid}`;
10162
+ const t3 = performance.now();
10163
+ const mdAvail = markdownAvailableForSession(id, classification.agentType);
10164
+ tLoopMarkdown += performance.now() - t3;
10165
+ const t4 = performance.now();
10166
+ const startTimes = collectStartTimes([pid]);
10167
+ tLoopStartTimes += performance.now() - t4;
9887
10168
  sessions.push({
9888
10169
  id,
9889
10170
  type: "process",
@@ -9892,32 +10173,54 @@ async function collectBareAgentProcesses(excludePids) {
9892
10173
  command: args,
9893
10174
  agentType: classification.agentType,
9894
10175
  agentConfidence: classification.confidence,
9895
- markdownAvailable: markdownAvailableForSession(id, classification.agentType),
10176
+ markdownAvailable: mdAvail,
9896
10177
  transcriptSource: transcriptSourceForSession(classification.agentType, id),
9897
10178
  workingDir: workingDir || void 0,
9898
10179
  environment: vscodeDetected ? "vscode" : void 0,
9899
10180
  lastActivityAt: (/* @__PURE__ */ new Date()).toISOString(),
9900
10181
  pids: [pid],
9901
- startTimes: collectStartTimes([pid])
10182
+ startTimes
9902
10183
  });
9903
10184
  }
10185
+ const tLoopEnd = performance.now();
10186
+ console.log(`[PROFILE-BARE] Init took ${(tInit - tStart).toFixed(2)} ms`);
10187
+ console.log(`[PROFILE-BARE] Parent map took ${(tParentMap - tInit).toFixed(2)} ms`);
10188
+ console.log(`[PROFILE-BARE] Filtering took ${(tFilterEnd - tFilterStart).toFixed(2)} ms`);
10189
+ console.log(`[PROFILE-BARE] CWDs took ${(tCwdsEnd - tCwdsStart).toFixed(2)} ms`);
10190
+ console.log(`[PROFILE-BARE] Loop total took ${(tLoopEnd - tLoopStart).toFixed(2)} ms`);
10191
+ console.log(` [PROFILE-BARE-LOOP] Ancestor checks: ${tLoopAncestor.toFixed(2)} ms`);
10192
+ console.log(` [PROFILE-BARE-LOOP] Classify: ${tLoopClassify.toFixed(2)} ms`);
10193
+ console.log(` [PROFILE-BARE-LOOP] VS Code env: ${tLoopVscode.toFixed(2)} ms`);
10194
+ console.log(` [PROFILE-BARE-LOOP] Markdown check: ${tLoopMarkdown.toFixed(2)} ms`);
10195
+ console.log(` [PROFILE-BARE-LOOP] Start times: ${tLoopStartTimes.toFixed(2)} ms`);
9904
10196
  return sessions;
9905
- } catch {
10197
+ } catch (err) {
10198
+ console.error("[PROFILE-BARE] Error:", err);
9906
10199
  return [];
9907
10200
  }
9908
10201
  }
9909
10202
  async function collectSessionInventory(hostId, command) {
10203
+ const processList = await getProcessTable();
10204
+ const processByPid = /* @__PURE__ */ new Map();
10205
+ const childrenByPpid = /* @__PURE__ */ new Map();
10206
+ for (const p of processList) {
10207
+ processByPid.set(p.pid, p);
10208
+ if (!childrenByPpid.has(p.ppid)) {
10209
+ childrenByPpid.set(p.ppid, []);
10210
+ }
10211
+ childrenByPpid.get(p.ppid).push(p);
10212
+ }
9910
10213
  const [tmux, screen, zellij] = await Promise.all([
9911
- collectTmuxSessions(),
9912
- collectScreenSessions(),
9913
- collectZellijSessions()
10214
+ collectTmuxSessions(processByPid),
10215
+ collectScreenSessions(childrenByPpid),
10216
+ collectZellijSessions(childrenByPpid, processList)
9914
10217
  ]);
9915
10218
  const multiplexerPids = /* @__PURE__ */ new Set();
9916
10219
  for (const s of [...screen, ...zellij]) {
9917
10220
  if (s.pids) s.pids.forEach((p) => multiplexerPids.add(p));
9918
10221
  }
9919
10222
  const tmuxChildTasks = tmux.flatMap(
9920
- (s) => (s.pids ?? []).map(async (pid) => ({ session: s, pid, childInfo: await getChildAgentInfo(pid) }))
10223
+ (s) => (s.pids ?? []).map(async (pid) => ({ session: s, pid, childInfo: await getChildAgentInfo(pid, childrenByPpid) }))
9921
10224
  );
9922
10225
  const tmuxChildResults = await Promise.all(tmuxChildTasks);
9923
10226
  for (const { session: s, pid, childInfo } of tmuxChildResults) {
@@ -9932,7 +10235,7 @@ async function collectSessionInventory(hostId, command) {
9932
10235
  s.markdownAvailable = markdownAvailableForSession(s.id, s.agentType);
9933
10236
  s.transcriptSource = transcriptSourceForSession(s.agentType, s.id);
9934
10237
  }
9935
- const bare = await collectBareAgentProcesses(multiplexerPids);
10238
+ const bare = await collectBareAgentProcesses(multiplexerPids, processList, processByPid);
9936
10239
  const ptyClassification = classifyAgent(command);
9937
10240
  const ptySession = {
9938
10241
  id: `pty:${hostId}`,
@@ -9985,7 +10288,7 @@ function detectAgentType(command) {
9985
10288
  return "Windsurf";
9986
10289
  }
9987
10290
  if (binary === "gemini" || scriptArg === "gemini") return "Gemini CLI";
9988
- if (binary === "agy" || binary === "antigravity" || binary === "antigravity-cli") return "Antigravity CLI";
10291
+ if (binary === "agy" || scriptArg === "agy" || binary === "antigravity" || scriptArg === "antigravity" || binary === "antigravity-cli" || scriptArg === "antigravity-cli") return "Antigravity CLI";
9989
10292
  if (binary === "amazon-q" || binary === "amazon_q") return "Amazon Q";
9990
10293
  if (binary === "copilot" || scriptArg === "copilot") return "Copilot CLI";
9991
10294
  return void 0;
@@ -9999,42 +10302,62 @@ function sessionInventoryFingerprint(sessions) {
9999
10302
  return `${s.id}|${s.type}|${s.name}|${s.status}|${s.command || ""}|${s.runningCommand || ""}|${pidStr}|${markdownFlag}|${sourceFlag}`;
10000
10303
  }).join("\n");
10001
10304
  }
10002
- async function getChildAgentInfo(parentPid, visited = /* @__PURE__ */ new Set()) {
10305
+ async function getChildAgentInfo(parentPid, childrenByPpid, visited = /* @__PURE__ */ new Set()) {
10003
10306
  if (visited.has(parentPid)) return null;
10004
10307
  visited.add(parentPid);
10005
- try {
10006
- const pgrepOut = await execAsync(`pgrep -P ${parentPid}`);
10007
- const pidList = pgrepOut.split("\n").map((l) => l.trim()).filter(Boolean).map(Number).filter((p) => p > 0);
10008
- if (pidList.length === 0) return null;
10009
- const raw = await execAsync(`ps -p ${pidList.join(",")} -o pid,args`);
10010
- const descendantPids = new Set(pidList);
10011
- let foundAgent = null;
10012
- const lines = raw.split("\n");
10013
- for (let i = 0; i < lines.length; i++) {
10014
- const trimmed = lines[i].trim();
10015
- if (!trimmed) continue;
10016
- if (i === 0 && /^\s*PID\b/i.test(lines[i])) continue;
10017
- const spaceIdx = trimmed.indexOf(" ");
10018
- if (spaceIdx < 0) continue;
10019
- const pid = Number(trimmed.slice(0, spaceIdx));
10020
- const args = trimmed.slice(spaceIdx + 1).trim();
10021
- if (pid <= 0) continue;
10022
- const agentType = detectAgentType(args);
10023
- if (agentType && !foundAgent) {
10024
- foundAgent = { command: args, agentType };
10025
- }
10026
- const deeper = await getChildAgentInfo(pid, visited);
10027
- if (deeper) {
10028
- deeper.childPids.forEach((childPid) => descendantPids.add(childPid));
10029
- if (!foundAgent) {
10030
- foundAgent = { command: deeper.command, agentType: deeper.agentType };
10308
+ if (!childrenByPpid || childrenByPpid.size === 0) {
10309
+ try {
10310
+ const pgrepOut = await execAsync(`pgrep -P ${parentPid}`);
10311
+ const pidList = pgrepOut.split("\n").map((l) => l.trim()).filter(Boolean).map(Number).filter((p) => p > 0);
10312
+ if (pidList.length === 0) return null;
10313
+ const raw = await execAsync(`ps -p ${pidList.join(",")} -o pid,args`);
10314
+ const descendantPids2 = new Set(pidList);
10315
+ let foundAgent2 = null;
10316
+ const lines = raw.split("\n");
10317
+ for (let i = 0; i < lines.length; i++) {
10318
+ const trimmed = lines[i].trim();
10319
+ if (!trimmed) continue;
10320
+ if (i === 0 && /^\s*PID\b/i.test(lines[i])) continue;
10321
+ const spaceIdx = trimmed.indexOf(" ");
10322
+ if (spaceIdx < 0) continue;
10323
+ const pid = Number(trimmed.slice(0, spaceIdx));
10324
+ const args = trimmed.slice(spaceIdx + 1).trim();
10325
+ if (pid <= 0) continue;
10326
+ const agentType = detectAgentType(args);
10327
+ if (agentType && !foundAgent2) {
10328
+ foundAgent2 = { command: args, agentType };
10329
+ }
10330
+ const deeper = await getChildAgentInfo(pid, void 0, visited);
10331
+ if (deeper) {
10332
+ deeper.childPids.forEach((childPid) => descendantPids2.add(childPid));
10333
+ if (!foundAgent2) {
10334
+ foundAgent2 = { command: deeper.command, agentType: deeper.agentType };
10335
+ }
10031
10336
  }
10032
10337
  }
10338
+ return foundAgent2 ? { ...foundAgent2, childPids: [...descendantPids2] } : null;
10339
+ } catch {
10340
+ return null;
10033
10341
  }
10034
- return foundAgent ? { ...foundAgent, childPids: [...descendantPids] } : null;
10035
- } catch {
10036
- return null;
10037
10342
  }
10343
+ const children = childrenByPpid.get(parentPid) ?? [];
10344
+ if (children.length === 0) return null;
10345
+ const descendantPids = new Set(children.map((c) => c.pid));
10346
+ let foundAgent = null;
10347
+ for (const child of children) {
10348
+ const agentType = detectAgentType(child.args);
10349
+ if (agentType && !foundAgent) {
10350
+ foundAgent = { command: child.args, agentType };
10351
+ }
10352
+ const deeper = await getChildAgentInfo(child.pid, childrenByPpid, visited);
10353
+ if (deeper) {
10354
+ deeper.childPids.forEach((childPid) => descendantPids.add(childPid));
10355
+ if (!foundAgent) {
10356
+ foundAgent = { command: deeper.command, agentType: deeper.agentType };
10357
+ }
10358
+ }
10359
+ }
10360
+ return foundAgent ? { ...foundAgent, childPids: [...descendantPids] } : null;
10038
10361
  }
10039
10362
 
10040
10363
  // src/auth.ts
@@ -11667,16 +11990,16 @@ var InventoryManager = class {
11667
11990
  this.options = options;
11668
11991
  }
11669
11992
  /** Announce to the registry group via WebSocket. */
11670
- async announceToRegistry(ws, registryGroup, type = "heartbeat") {
11993
+ async announceToRegistry(ws, registryGroup, type = "heartbeat", sessions) {
11671
11994
  if (ws.readyState !== import_ws2.default.OPEN || !registryGroup) return;
11672
- const sessions = await collectSessionInventory(this.options.hostId, this.options.command);
11995
+ const finalSessions = sessions || await collectSessionInventory(this.options.hostId, this.options.command);
11673
11996
  const vitals = this.collectVitals();
11674
11997
  sendToGroup(ws, registryGroup, {
11675
11998
  type,
11676
11999
  hostId: this.options.hostId,
11677
12000
  hostName: this.options.hostName,
11678
12001
  environment: os7.hostname(),
11679
- sessions,
12002
+ sessions: finalSessions,
11680
12003
  vitals,
11681
12004
  agentVersion: CURRENT_VERSION,
11682
12005
  lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -11693,7 +12016,7 @@ var InventoryManager = class {
11693
12016
  const checkpointDue = Date.now() - this.lastHttpHeartbeatAt > HTTP_HEARTBEAT_MS;
11694
12017
  if (changed || force) {
11695
12018
  if (ws && ws.readyState === import_ws2.default.OPEN) {
11696
- await this.announceToRegistry(ws, groups.registryGroup, "inventory");
12019
+ await this.announceToRegistry(ws, groups.registryGroup, "inventory", sessions);
11697
12020
  }
11698
12021
  this.lastSentFingerprint = fingerprint;
11699
12022
  }
@@ -11819,10 +12142,36 @@ var ConnectionManager = class {
11819
12142
  resetReconnectDelay() {
11820
12143
  this.reconnectDelay = DEFAULT_RECONNECT_DELAY_MS;
11821
12144
  }
12145
+ /**
12146
+ * Clear all active connection timers to prevent leaks.
12147
+ */
12148
+ clearTimers() {
12149
+ if (this.wsPingTimer) {
12150
+ clearInterval(this.wsPingTimer);
12151
+ this.wsPingTimer = null;
12152
+ }
12153
+ if (this.wsPongTimeout) {
12154
+ clearTimeout(this.wsPongTimeout);
12155
+ this.wsPongTimeout = null;
12156
+ }
12157
+ if (this.wsHeartbeatTimer) {
12158
+ clearInterval(this.wsHeartbeatTimer);
12159
+ this.wsHeartbeatTimer = null;
12160
+ }
12161
+ if (this.inventorySyncTimer) {
12162
+ clearInterval(this.inventorySyncTimer);
12163
+ this.inventorySyncTimer = null;
12164
+ }
12165
+ if (this.inventorySyncKickTimer) {
12166
+ clearTimeout(this.inventorySyncKickTimer);
12167
+ this.inventorySyncKickTimer = null;
12168
+ }
12169
+ }
11822
12170
  /**
11823
12171
  * Start periodic timers (heartbeat, inventory sync, ping/pong).
11824
12172
  */
11825
12173
  startTimers(onWsHeartbeat, onInventorySync) {
12174
+ this.clearTimers();
11826
12175
  this.onInventorySync = onInventorySync;
11827
12176
  this.wsHeartbeatTimer = setInterval(async () => {
11828
12177
  if (!this.isReady()) return;
@@ -11833,7 +12182,19 @@ var ConnectionManager = class {
11833
12182
  }, INVENTORY_SYNC_MS);
11834
12183
  this.wsPingTimer = setInterval(() => {
11835
12184
  if (!this.activeSocket || this.activeSocket.readyState !== import_ws3.default.OPEN) return;
11836
- this.activeSocket.ping();
12185
+ if (this.wsPongTimeout) {
12186
+ clearTimeout(this.wsPongTimeout);
12187
+ this.wsPongTimeout = null;
12188
+ }
12189
+ try {
12190
+ this.activeSocket.send(JSON.stringify({ type: "ping" }));
12191
+ } catch (err) {
12192
+ log20.error("failed to send JSON ping", { error: err.message });
12193
+ }
12194
+ try {
12195
+ this.activeSocket.ping();
12196
+ } catch {
12197
+ }
11837
12198
  this.wsPongTimeout = setTimeout(() => {
11838
12199
  log20.warn("no pong received within 10s; closing stale connection");
11839
12200
  try {
@@ -11884,26 +12245,7 @@ var ConnectionManager = class {
11884
12245
  */
11885
12246
  cleanup(opts = {}) {
11886
12247
  if (opts.stopStreams) this.sessionStreamer.stopAll();
11887
- if (this.wsPingTimer) {
11888
- clearInterval(this.wsPingTimer);
11889
- this.wsPingTimer = null;
11890
- }
11891
- if (this.wsPongTimeout) {
11892
- clearTimeout(this.wsPongTimeout);
11893
- this.wsPongTimeout = null;
11894
- }
11895
- if (this.wsHeartbeatTimer) {
11896
- clearInterval(this.wsHeartbeatTimer);
11897
- this.wsHeartbeatTimer = null;
11898
- }
11899
- if (this.inventorySyncTimer) {
11900
- clearInterval(this.inventorySyncTimer);
11901
- this.inventorySyncTimer = null;
11902
- }
11903
- if (this.inventorySyncKickTimer) {
11904
- clearTimeout(this.inventorySyncKickTimer);
11905
- this.inventorySyncKickTimer = null;
11906
- }
12248
+ this.clearTimers();
11907
12249
  if (this.activeSocket) {
11908
12250
  this.activeSocket.removeAllListeners();
11909
12251
  try {
@@ -12965,7 +13307,6 @@ var ControlDispatcher = class {
12965
13307
  }
12966
13308
  /** Handle input routing to the correct target. */
12967
13309
  handleInput(data, sessionId) {
12968
- this.connection.requestInventorySync(800);
12969
13310
  if (!sessionId || sessionId.startsWith("pty:")) {
12970
13311
  this.shell.write(data);
12971
13312
  } else if (sessionId.startsWith("tmux:")) {
@@ -13936,7 +14277,6 @@ async function runAgent(rawOptions) {
13936
14277
  const ptySessionId = `pty:${options.hostId}`;
13937
14278
  const sessionStreamer = new SessionStreamer(
13938
14279
  (sessionId, data, viewerId) => {
13939
- conn.requestInventorySync();
13940
14280
  if (!conn.isReady()) {
13941
14281
  sessionStreamer.bufferOutput(sessionId, data);
13942
14282
  return;
@@ -13968,7 +14308,6 @@ async function runAgent(rawOptions) {
13968
14308
  );
13969
14309
  const conn = new ConnectionManager(sessionStreamer, outputBuffer);
13970
14310
  const sendOutput = (chunk) => {
13971
- conn.requestInventorySync();
13972
14311
  if (!conn.isReady()) {
13973
14312
  outputBuffer.push(chunk);
13974
14313
  return;
@@ -14220,6 +14559,10 @@ async function runAgent(rawOptions) {
14220
14559
  } catch {
14221
14560
  return;
14222
14561
  }
14562
+ if (msg.type === "pong") {
14563
+ conn.onPong();
14564
+ return;
14565
+ }
14223
14566
  if (msg.type === "message" && msg.group === groups.privateGroup) {
14224
14567
  const payload = msg.data || {};
14225
14568
  if (payload.type === "input") {
package/dist/sbom.json CHANGED
@@ -1299,8 +1299,8 @@
1299
1299
  {
1300
1300
  "type": "library",
1301
1301
  "name": "ragent-cli",
1302
- "version": "1.11.11",
1303
- "bom-ref": "ragent-live|ragent-cli@1.11.11",
1302
+ "version": "1.11.13",
1303
+ "bom-ref": "ragent-live|ragent-cli@1.11.13",
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.11",
1314
+ "purl": "pkg:npm/ragent-cli@1.11.13",
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.11"
1439
+ "ragent-live|ragent-cli@1.11.13"
1440
1440
  ]
1441
1441
  },
1442
1442
  {
@@ -1584,7 +1584,7 @@
1584
1584
  ]
1585
1585
  },
1586
1586
  {
1587
- "ref": "ragent-live|ragent-cli@1.11.11",
1587
+ "ref": "ragent-live|ragent-cli@1.11.13",
1588
1588
  "dependsOn": [
1589
1589
  "ragent-live|@azure/web-pubsub-client@1.0.4",
1590
1590
  "ragent-live|@openai/codex-sdk@0.130.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ragent-cli",
3
- "version": "1.11.11",
3
+ "version": "1.11.13",
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": {