ragent-cli 1.11.11 → 1.11.12

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.12",
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;
@@ -3695,7 +3846,7 @@ var TranscriptWatcherManager = class {
3695
3846
  const existing = this.active.get(sessionId);
3696
3847
  let replacingExisting = false;
3697
3848
  if (existing) {
3698
- const newPath = discoverTranscriptFile(sessionId, agentType);
3849
+ const newPath = discoverTranscriptFile(sessionId, agentType, true);
3699
3850
  const agentChanged = existing.agentType !== requestedAgentType;
3700
3851
  const pathChanged = Boolean(newPath && newPath !== existing.filePath);
3701
3852
  const existingPathGone = !fs2.existsSync(existing.filePath);
@@ -3720,7 +3871,7 @@ var TranscriptWatcherManager = class {
3720
3871
  this.cancelEnableRetry(sessionId);
3721
3872
  return { ok: false, reason: "unsupported_agent" };
3722
3873
  }
3723
- const filePath = discoverTranscriptFile(sessionId, agentType);
3874
+ const filePath = discoverTranscriptFile(sessionId, agentType, isRetry || replacingExisting);
3724
3875
  if (!filePath) {
3725
3876
  log7.info("no transcript file found", { sessionId });
3726
3877
  this.armEnableRetry(sessionId, agentType);
@@ -3846,7 +3997,7 @@ var TranscriptWatcherManager = class {
3846
3997
  this.stopRediscovery(sessionId);
3847
3998
  return;
3848
3999
  }
3849
- const newPath = discoverTranscriptFile(sessionId, agentType);
4000
+ const newPath = discoverTranscriptFile(sessionId, agentType, true);
3850
4001
  if (!newPath || newPath === session.filePath) return;
3851
4002
  log7.info("transcript file changed", { sessionId, newPath });
3852
4003
  session.watcher.stop();
@@ -9524,6 +9675,39 @@ function resolveRuntimeTranscriptSource(sessionId) {
9524
9675
  // src/sessions.ts
9525
9676
  var isMac = os5.platform() === "darwin";
9526
9677
  var MARKDOWN_AGENT_TYPES = /* @__PURE__ */ new Set(["Claude Code", "Codex CLI", "Gemini CLI", "Antigravity CLI"]);
9678
+ var isTmuxAvailable = null;
9679
+ var isScreenAvailable = null;
9680
+ var isZellijAvailable = null;
9681
+ async function checkTmuxAvailable() {
9682
+ if (isTmuxAvailable !== null) return isTmuxAvailable;
9683
+ try {
9684
+ await execAsync("tmux -V");
9685
+ isTmuxAvailable = true;
9686
+ } catch {
9687
+ isTmuxAvailable = false;
9688
+ }
9689
+ return isTmuxAvailable;
9690
+ }
9691
+ async function checkScreenAvailable() {
9692
+ if (isScreenAvailable !== null) return isScreenAvailable;
9693
+ try {
9694
+ await execAsync("screen -v");
9695
+ isScreenAvailable = true;
9696
+ } catch {
9697
+ isScreenAvailable = false;
9698
+ }
9699
+ return isScreenAvailable;
9700
+ }
9701
+ async function checkZellijAvailable() {
9702
+ if (isZellijAvailable !== null) return isZellijAvailable;
9703
+ try {
9704
+ await execAsync("zellij --version");
9705
+ isZellijAvailable = true;
9706
+ } catch {
9707
+ isZellijAvailable = false;
9708
+ }
9709
+ return isZellijAvailable;
9710
+ }
9527
9711
  function getProcessStartTime(pid) {
9528
9712
  if (isMac) return null;
9529
9713
  try {
@@ -9540,7 +9724,38 @@ function getProcessStartTime(pid) {
9540
9724
  function collectStartTimes(pids) {
9541
9725
  return pids.map((pid) => getProcessStartTime(pid) ?? 0);
9542
9726
  }
9543
- async function detectVscodeEnvironment(pid) {
9727
+ async function getProcessTable() {
9728
+ try {
9729
+ const raw = await execAsync("ps axo pid,ppid,tty,stat,comm,args");
9730
+ const rows = [];
9731
+ const lines = raw.split("\n");
9732
+ for (let i = 0; i < lines.length; i++) {
9733
+ const trimmed = lines[i].trim();
9734
+ if (!trimmed) continue;
9735
+ if (i === 0 && /^\s*PID\b/i.test(lines[i])) continue;
9736
+ const parts = trimmed.split(/\s+/);
9737
+ if (parts.length < 6) continue;
9738
+ const pid = Number(parts[0]);
9739
+ const ppid = Number(parts[1]);
9740
+ const tty = parts[2];
9741
+ const stat = parts[3];
9742
+ const comm = parts[4];
9743
+ const args = parts.slice(5).join(" ");
9744
+ rows.push({
9745
+ pid,
9746
+ ppid: Number.isFinite(ppid) ? ppid : 0,
9747
+ tty,
9748
+ stat,
9749
+ comm,
9750
+ args
9751
+ });
9752
+ }
9753
+ return rows;
9754
+ } catch {
9755
+ return [];
9756
+ }
9757
+ }
9758
+ async function detectVscodeEnvironment(pid, processByPid) {
9544
9759
  if (!isMac) {
9545
9760
  try {
9546
9761
  const environ = fs4.readFileSync(`/proc/${pid}/environ`, "utf8");
@@ -9550,17 +9765,36 @@ async function detectVscodeEnvironment(pid) {
9550
9765
  } catch {
9551
9766
  }
9552
9767
  }
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;
9768
+ if (!processByPid || processByPid.size === 0) {
9769
+ try {
9770
+ const raw = await execAsync(`ps -p ${pid} -o ppid=`);
9771
+ const ppid = Number(raw.trim());
9772
+ if (ppid > 1) {
9773
+ const parentInfo = await execAsync(`ps -p ${ppid} -o comm=`);
9774
+ const parentComm = parentInfo.trim().toLowerCase();
9775
+ if (parentComm === "code" || parentComm === "code-server") {
9776
+ return true;
9777
+ }
9561
9778
  }
9779
+ } catch {
9562
9780
  }
9563
- } catch {
9781
+ return false;
9782
+ }
9783
+ let currentPid = pid;
9784
+ const visited = /* @__PURE__ */ new Set();
9785
+ while (currentPid > 1 && !visited.has(currentPid)) {
9786
+ visited.add(currentPid);
9787
+ const proc = processByPid.get(currentPid);
9788
+ if (!proc) break;
9789
+ const parentPid = proc.ppid;
9790
+ if (parentPid <= 1) break;
9791
+ const parentProc = processByPid.get(parentPid);
9792
+ if (!parentProc) break;
9793
+ const parentComm = parentProc.comm.toLowerCase();
9794
+ if (parentComm === "code" || parentComm === "code-server") {
9795
+ return true;
9796
+ }
9797
+ currentPid = parentPid;
9564
9798
  }
9565
9799
  return false;
9566
9800
  }
@@ -9619,12 +9853,14 @@ function classifyFromCommand(command) {
9619
9853
  }
9620
9854
  return { agentType, confidence: 0.7 };
9621
9855
  }
9622
- async function collectTmuxSessions() {
9623
- try {
9624
- await execAsync("tmux -V");
9625
- } catch {
9856
+ async function collectTmuxSessions(processByPid) {
9857
+ if (!await checkTmuxAvailable()) {
9626
9858
  return [];
9627
9859
  }
9860
+ if (!processByPid) {
9861
+ const list = await getProcessTable();
9862
+ processByPid = new Map(list.map((p) => [p.pid, p]));
9863
+ }
9628
9864
  try {
9629
9865
  const raw = await execAsync(
9630
9866
  "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 +9894,14 @@ async function collectTmuxSessions() {
9658
9894
  const pid = Number(panePid);
9659
9895
  if (pid > 0) pids.push(pid);
9660
9896
  const classification = classifyAgent(command, { workingDir: paneCurrentPath });
9661
- const vscodeDetected = pid > 0 ? await detectVscodeEnvironment(pid) : false;
9897
+ const vscodeDetected = pid > 0 ? await detectVscodeEnvironment(pid, processByPid) : false;
9898
+ const dirName = paneCurrentPath ? paneCurrentPath.split("/").filter(Boolean).pop() : "";
9899
+ const baseName = `${sessionName}:${windowIndex}.${paneIndex}`;
9900
+ const finalName = dirName ? `${baseName} (${dirName})` : baseName;
9662
9901
  results.push({
9663
9902
  id,
9664
9903
  type: "tmux",
9665
- name: `${sessionName}:${windowIndex}.${paneIndex}`,
9904
+ name: finalName,
9666
9905
  status: Number(attachedCount) > 0 ? "active" : "detached",
9667
9906
  command,
9668
9907
  agentType: classification?.agentType,
@@ -9687,10 +9926,8 @@ async function collectTmuxSessions() {
9687
9926
  return [];
9688
9927
  }
9689
9928
  }
9690
- async function collectScreenSessions() {
9691
- try {
9692
- await execAsync("screen -v");
9693
- } catch {
9929
+ async function collectScreenSessions(childrenByPpid) {
9930
+ if (!await checkScreenAvailable()) {
9694
9931
  return [];
9695
9932
  }
9696
9933
  try {
@@ -9702,7 +9939,7 @@ async function collectScreenSessions() {
9702
9939
  if (!match) continue;
9703
9940
  const [, screenPid, sessionName, state] = match;
9704
9941
  const pid = Number(screenPid);
9705
- const childInfo = await getChildAgentInfo(pid);
9942
+ const childInfo = await getChildAgentInfo(pid, childrenByPpid);
9706
9943
  if (!childInfo) continue;
9707
9944
  const id = `screen:${sessionName}:${screenPid}`;
9708
9945
  const allPids = [pid, ...childInfo.childPids];
@@ -9733,10 +9970,8 @@ async function collectScreenSessions() {
9733
9970
  return [];
9734
9971
  }
9735
9972
  }
9736
- async function collectZellijSessions() {
9737
- try {
9738
- await execAsync("zellij --version");
9739
- } catch {
9973
+ async function collectZellijSessions(childrenByPpid, processList) {
9974
+ if (!await checkZellijAvailable()) {
9740
9975
  return [];
9741
9976
  }
9742
9977
  try {
@@ -9745,19 +9980,25 @@ async function collectZellijSessions() {
9745
9980
  for (const line of raw.split("\n")) {
9746
9981
  const sessionName = line.trim();
9747
9982
  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 = [];
9983
+ let serverPids = [];
9984
+ if (processList) {
9985
+ serverPids = processList.filter(
9986
+ (p) => p.args.includes("zellij") && p.args.includes(`--session ${sessionName}`)
9987
+ ).map((p) => p.pid);
9988
+ } else {
9989
+ try {
9990
+ const psOut = await execAsync(
9991
+ `ps axo pid,args | grep 'zellij.*--session ${sessionName}' | grep -v grep`
9992
+ );
9993
+ serverPids = psOut.split("\n").map((l) => Number(l.trim().split(/\s+/)[0])).filter((p) => p > 0);
9994
+ } catch {
9995
+ serverPids = [];
9996
+ }
9756
9997
  }
9757
9998
  const allChildPids = [];
9758
9999
  let foundAgent = null;
9759
10000
  for (const spid of serverPids) {
9760
- const childInfo = await getChildAgentInfo(spid);
10001
+ const childInfo = await getChildAgentInfo(spid, childrenByPpid);
9761
10002
  if (childInfo) {
9762
10003
  foundAgent = childInfo;
9763
10004
  allChildPids.push(...childInfo.childPids);
@@ -9809,7 +10050,7 @@ async function getProcessWorkingDir(pid) {
9809
10050
  }
9810
10051
  return null;
9811
10052
  }
9812
- const cwd = await execAsync(`readlink -f /proc/${pid}/cwd`);
10053
+ const cwd = await fs4.promises.readlink(`/proc/${pid}/cwd`);
9813
10054
  return cwd.trim() || null;
9814
10055
  } catch {
9815
10056
  return null;
@@ -9827,35 +10068,20 @@ function transcriptSourceForSession(agentType, sessionId) {
9827
10068
  if (!agentType) return void 0;
9828
10069
  return getTranscriptSourceForAgentType(agentType);
9829
10070
  }
9830
- async function collectBareAgentProcesses(excludePids) {
10071
+ async function collectBareAgentProcesses(excludePids, processList, processByPid) {
9831
10072
  try {
9832
- const raw = await execAsync("ps axo pid,ppid,tty,stat,comm,args");
10073
+ const tStart = performance.now();
10074
+ if (!processList || !processByPid) {
10075
+ const list = await getProcessTable();
10076
+ processList = list;
10077
+ processByPid = new Map(list.map((p) => [p.pid, p]));
10078
+ }
10079
+ const tInit = performance.now();
9833
10080
  const sessions = [];
9834
10081
  const seen = /* @__PURE__ */ new Set();
9835
10082
  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]));
10083
+ const parentByPid = new Map(processList.map((row) => [row.pid, row.ppid]));
10084
+ const tParentMap = performance.now();
9859
10085
  const hasTrackedAncestor = (pid, tracked) => {
9860
10086
  const visited = /* @__PURE__ */ new Set();
9861
10087
  let current = parentByPid.get(pid) ?? 0;
@@ -9866,24 +10092,60 @@ async function collectBareAgentProcesses(excludePids) {
9866
10092
  }
9867
10093
  return false;
9868
10094
  };
9869
- for (const row of rows) {
10095
+ const tFilterStart = performance.now();
10096
+ const candidates = processList.filter((row) => {
9870
10097
  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;
10098
+ if (!hasControllingTty(tty)) return false;
10099
+ if (stat.startsWith("Z")) return false;
10100
+ if (excluded.has(pid) || hasTrackedAncestor(pid, excluded)) return false;
10101
+ const lowerArgs = args.toLowerCase();
10102
+ 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");
10103
+ if (!hasKeyword) return false;
10104
+ return true;
10105
+ });
10106
+ const tFilterEnd = performance.now();
10107
+ const tCwdsStart = performance.now();
10108
+ const cwds = await Promise.all(
10109
+ candidates.map(async (row) => {
10110
+ const cwd = await getProcessWorkingDir(row.pid);
10111
+ return { pid: row.pid, cwd };
10112
+ })
10113
+ );
10114
+ const cwdByPid = new Map(cwds.map((c) => [c.pid, c.cwd]));
10115
+ const tCwdsEnd = performance.now();
10116
+ let tLoopAncestor = 0;
10117
+ let tLoopClassify = 0;
10118
+ let tLoopVscode = 0;
10119
+ let tLoopMarkdown = 0;
10120
+ let tLoopStartTimes = 0;
10121
+ const tLoopStart = performance.now();
10122
+ for (const row of candidates) {
10123
+ const { pid, args } = row;
10124
+ const t0 = performance.now();
10125
+ const skip = seen.has(pid) || hasTrackedAncestor(pid, seen);
10126
+ tLoopAncestor += performance.now() - t0;
10127
+ if (skip) continue;
10128
+ const workingDir = cwdByPid.get(pid) || null;
10129
+ const t1 = performance.now();
9875
10130
  let classification = classifyAgent(args);
9876
10131
  if (!classification) {
9877
- const inferredWorkingDir = await getProcessWorkingDir(pid);
9878
- classification = classifyAgent(args, { workingDir: inferredWorkingDir ?? void 0 });
10132
+ classification = classifyAgent(args, { workingDir: workingDir ?? void 0 });
9879
10133
  }
10134
+ tLoopClassify += performance.now() - t1;
9880
10135
  if (!classification) continue;
9881
10136
  seen.add(pid);
9882
- const workingDir = await getProcessWorkingDir(pid);
9883
10137
  const dirName = workingDir ? workingDir.split("/").pop() : null;
9884
10138
  const displayName = dirName && dirName !== "/" && dirName !== "" ? `${classification.agentType} (${dirName})` : `${classification.agentType} (pid ${pid})`;
9885
- const vscodeDetected = await detectVscodeEnvironment(pid);
10139
+ const t2 = performance.now();
10140
+ const vscodeDetected = await detectVscodeEnvironment(pid, processByPid);
10141
+ tLoopVscode += performance.now() - t2;
9886
10142
  const id = `process:${pid}`;
10143
+ const t3 = performance.now();
10144
+ const mdAvail = markdownAvailableForSession(id, classification.agentType);
10145
+ tLoopMarkdown += performance.now() - t3;
10146
+ const t4 = performance.now();
10147
+ const startTimes = collectStartTimes([pid]);
10148
+ tLoopStartTimes += performance.now() - t4;
9887
10149
  sessions.push({
9888
10150
  id,
9889
10151
  type: "process",
@@ -9892,32 +10154,54 @@ async function collectBareAgentProcesses(excludePids) {
9892
10154
  command: args,
9893
10155
  agentType: classification.agentType,
9894
10156
  agentConfidence: classification.confidence,
9895
- markdownAvailable: markdownAvailableForSession(id, classification.agentType),
10157
+ markdownAvailable: mdAvail,
9896
10158
  transcriptSource: transcriptSourceForSession(classification.agentType, id),
9897
10159
  workingDir: workingDir || void 0,
9898
10160
  environment: vscodeDetected ? "vscode" : void 0,
9899
10161
  lastActivityAt: (/* @__PURE__ */ new Date()).toISOString(),
9900
10162
  pids: [pid],
9901
- startTimes: collectStartTimes([pid])
10163
+ startTimes
9902
10164
  });
9903
10165
  }
10166
+ const tLoopEnd = performance.now();
10167
+ console.log(`[PROFILE-BARE] Init took ${(tInit - tStart).toFixed(2)} ms`);
10168
+ console.log(`[PROFILE-BARE] Parent map took ${(tParentMap - tInit).toFixed(2)} ms`);
10169
+ console.log(`[PROFILE-BARE] Filtering took ${(tFilterEnd - tFilterStart).toFixed(2)} ms`);
10170
+ console.log(`[PROFILE-BARE] CWDs took ${(tCwdsEnd - tCwdsStart).toFixed(2)} ms`);
10171
+ console.log(`[PROFILE-BARE] Loop total took ${(tLoopEnd - tLoopStart).toFixed(2)} ms`);
10172
+ console.log(` [PROFILE-BARE-LOOP] Ancestor checks: ${tLoopAncestor.toFixed(2)} ms`);
10173
+ console.log(` [PROFILE-BARE-LOOP] Classify: ${tLoopClassify.toFixed(2)} ms`);
10174
+ console.log(` [PROFILE-BARE-LOOP] VS Code env: ${tLoopVscode.toFixed(2)} ms`);
10175
+ console.log(` [PROFILE-BARE-LOOP] Markdown check: ${tLoopMarkdown.toFixed(2)} ms`);
10176
+ console.log(` [PROFILE-BARE-LOOP] Start times: ${tLoopStartTimes.toFixed(2)} ms`);
9904
10177
  return sessions;
9905
- } catch {
10178
+ } catch (err) {
10179
+ console.error("[PROFILE-BARE] Error:", err);
9906
10180
  return [];
9907
10181
  }
9908
10182
  }
9909
10183
  async function collectSessionInventory(hostId, command) {
10184
+ const processList = await getProcessTable();
10185
+ const processByPid = /* @__PURE__ */ new Map();
10186
+ const childrenByPpid = /* @__PURE__ */ new Map();
10187
+ for (const p of processList) {
10188
+ processByPid.set(p.pid, p);
10189
+ if (!childrenByPpid.has(p.ppid)) {
10190
+ childrenByPpid.set(p.ppid, []);
10191
+ }
10192
+ childrenByPpid.get(p.ppid).push(p);
10193
+ }
9910
10194
  const [tmux, screen, zellij] = await Promise.all([
9911
- collectTmuxSessions(),
9912
- collectScreenSessions(),
9913
- collectZellijSessions()
10195
+ collectTmuxSessions(processByPid),
10196
+ collectScreenSessions(childrenByPpid),
10197
+ collectZellijSessions(childrenByPpid, processList)
9914
10198
  ]);
9915
10199
  const multiplexerPids = /* @__PURE__ */ new Set();
9916
10200
  for (const s of [...screen, ...zellij]) {
9917
10201
  if (s.pids) s.pids.forEach((p) => multiplexerPids.add(p));
9918
10202
  }
9919
10203
  const tmuxChildTasks = tmux.flatMap(
9920
- (s) => (s.pids ?? []).map(async (pid) => ({ session: s, pid, childInfo: await getChildAgentInfo(pid) }))
10204
+ (s) => (s.pids ?? []).map(async (pid) => ({ session: s, pid, childInfo: await getChildAgentInfo(pid, childrenByPpid) }))
9921
10205
  );
9922
10206
  const tmuxChildResults = await Promise.all(tmuxChildTasks);
9923
10207
  for (const { session: s, pid, childInfo } of tmuxChildResults) {
@@ -9932,7 +10216,7 @@ async function collectSessionInventory(hostId, command) {
9932
10216
  s.markdownAvailable = markdownAvailableForSession(s.id, s.agentType);
9933
10217
  s.transcriptSource = transcriptSourceForSession(s.agentType, s.id);
9934
10218
  }
9935
- const bare = await collectBareAgentProcesses(multiplexerPids);
10219
+ const bare = await collectBareAgentProcesses(multiplexerPids, processList, processByPid);
9936
10220
  const ptyClassification = classifyAgent(command);
9937
10221
  const ptySession = {
9938
10222
  id: `pty:${hostId}`,
@@ -9985,7 +10269,7 @@ function detectAgentType(command) {
9985
10269
  return "Windsurf";
9986
10270
  }
9987
10271
  if (binary === "gemini" || scriptArg === "gemini") return "Gemini CLI";
9988
- if (binary === "agy" || binary === "antigravity" || binary === "antigravity-cli") return "Antigravity CLI";
10272
+ if (binary === "agy" || scriptArg === "agy" || binary === "antigravity" || scriptArg === "antigravity" || binary === "antigravity-cli" || scriptArg === "antigravity-cli") return "Antigravity CLI";
9989
10273
  if (binary === "amazon-q" || binary === "amazon_q") return "Amazon Q";
9990
10274
  if (binary === "copilot" || scriptArg === "copilot") return "Copilot CLI";
9991
10275
  return void 0;
@@ -9999,42 +10283,62 @@ function sessionInventoryFingerprint(sessions) {
9999
10283
  return `${s.id}|${s.type}|${s.name}|${s.status}|${s.command || ""}|${s.runningCommand || ""}|${pidStr}|${markdownFlag}|${sourceFlag}`;
10000
10284
  }).join("\n");
10001
10285
  }
10002
- async function getChildAgentInfo(parentPid, visited = /* @__PURE__ */ new Set()) {
10286
+ async function getChildAgentInfo(parentPid, childrenByPpid, visited = /* @__PURE__ */ new Set()) {
10003
10287
  if (visited.has(parentPid)) return null;
10004
10288
  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 };
10289
+ if (!childrenByPpid || childrenByPpid.size === 0) {
10290
+ try {
10291
+ const pgrepOut = await execAsync(`pgrep -P ${parentPid}`);
10292
+ const pidList = pgrepOut.split("\n").map((l) => l.trim()).filter(Boolean).map(Number).filter((p) => p > 0);
10293
+ if (pidList.length === 0) return null;
10294
+ const raw = await execAsync(`ps -p ${pidList.join(",")} -o pid,args`);
10295
+ const descendantPids2 = new Set(pidList);
10296
+ let foundAgent2 = null;
10297
+ const lines = raw.split("\n");
10298
+ for (let i = 0; i < lines.length; i++) {
10299
+ const trimmed = lines[i].trim();
10300
+ if (!trimmed) continue;
10301
+ if (i === 0 && /^\s*PID\b/i.test(lines[i])) continue;
10302
+ const spaceIdx = trimmed.indexOf(" ");
10303
+ if (spaceIdx < 0) continue;
10304
+ const pid = Number(trimmed.slice(0, spaceIdx));
10305
+ const args = trimmed.slice(spaceIdx + 1).trim();
10306
+ if (pid <= 0) continue;
10307
+ const agentType = detectAgentType(args);
10308
+ if (agentType && !foundAgent2) {
10309
+ foundAgent2 = { command: args, agentType };
10310
+ }
10311
+ const deeper = await getChildAgentInfo(pid, void 0, visited);
10312
+ if (deeper) {
10313
+ deeper.childPids.forEach((childPid) => descendantPids2.add(childPid));
10314
+ if (!foundAgent2) {
10315
+ foundAgent2 = { command: deeper.command, agentType: deeper.agentType };
10316
+ }
10031
10317
  }
10032
10318
  }
10319
+ return foundAgent2 ? { ...foundAgent2, childPids: [...descendantPids2] } : null;
10320
+ } catch {
10321
+ return null;
10322
+ }
10323
+ }
10324
+ const children = childrenByPpid.get(parentPid) ?? [];
10325
+ if (children.length === 0) return null;
10326
+ const descendantPids = new Set(children.map((c) => c.pid));
10327
+ let foundAgent = null;
10328
+ for (const child of children) {
10329
+ const agentType = detectAgentType(child.args);
10330
+ if (agentType && !foundAgent) {
10331
+ foundAgent = { command: child.args, agentType };
10332
+ }
10333
+ const deeper = await getChildAgentInfo(child.pid, childrenByPpid, visited);
10334
+ if (deeper) {
10335
+ deeper.childPids.forEach((childPid) => descendantPids.add(childPid));
10336
+ if (!foundAgent) {
10337
+ foundAgent = { command: deeper.command, agentType: deeper.agentType };
10338
+ }
10033
10339
  }
10034
- return foundAgent ? { ...foundAgent, childPids: [...descendantPids] } : null;
10035
- } catch {
10036
- return null;
10037
10340
  }
10341
+ return foundAgent ? { ...foundAgent, childPids: [...descendantPids] } : null;
10038
10342
  }
10039
10343
 
10040
10344
  // src/auth.ts
@@ -11667,16 +11971,16 @@ var InventoryManager = class {
11667
11971
  this.options = options;
11668
11972
  }
11669
11973
  /** Announce to the registry group via WebSocket. */
11670
- async announceToRegistry(ws, registryGroup, type = "heartbeat") {
11974
+ async announceToRegistry(ws, registryGroup, type = "heartbeat", sessions) {
11671
11975
  if (ws.readyState !== import_ws2.default.OPEN || !registryGroup) return;
11672
- const sessions = await collectSessionInventory(this.options.hostId, this.options.command);
11976
+ const finalSessions = sessions || await collectSessionInventory(this.options.hostId, this.options.command);
11673
11977
  const vitals = this.collectVitals();
11674
11978
  sendToGroup(ws, registryGroup, {
11675
11979
  type,
11676
11980
  hostId: this.options.hostId,
11677
11981
  hostName: this.options.hostName,
11678
11982
  environment: os7.hostname(),
11679
- sessions,
11983
+ sessions: finalSessions,
11680
11984
  vitals,
11681
11985
  agentVersion: CURRENT_VERSION,
11682
11986
  lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -11693,7 +11997,7 @@ var InventoryManager = class {
11693
11997
  const checkpointDue = Date.now() - this.lastHttpHeartbeatAt > HTTP_HEARTBEAT_MS;
11694
11998
  if (changed || force) {
11695
11999
  if (ws && ws.readyState === import_ws2.default.OPEN) {
11696
- await this.announceToRegistry(ws, groups.registryGroup, "inventory");
12000
+ await this.announceToRegistry(ws, groups.registryGroup, "inventory", sessions);
11697
12001
  }
11698
12002
  this.lastSentFingerprint = fingerprint;
11699
12003
  }
@@ -11819,10 +12123,36 @@ var ConnectionManager = class {
11819
12123
  resetReconnectDelay() {
11820
12124
  this.reconnectDelay = DEFAULT_RECONNECT_DELAY_MS;
11821
12125
  }
12126
+ /**
12127
+ * Clear all active connection timers to prevent leaks.
12128
+ */
12129
+ clearTimers() {
12130
+ if (this.wsPingTimer) {
12131
+ clearInterval(this.wsPingTimer);
12132
+ this.wsPingTimer = null;
12133
+ }
12134
+ if (this.wsPongTimeout) {
12135
+ clearTimeout(this.wsPongTimeout);
12136
+ this.wsPongTimeout = null;
12137
+ }
12138
+ if (this.wsHeartbeatTimer) {
12139
+ clearInterval(this.wsHeartbeatTimer);
12140
+ this.wsHeartbeatTimer = null;
12141
+ }
12142
+ if (this.inventorySyncTimer) {
12143
+ clearInterval(this.inventorySyncTimer);
12144
+ this.inventorySyncTimer = null;
12145
+ }
12146
+ if (this.inventorySyncKickTimer) {
12147
+ clearTimeout(this.inventorySyncKickTimer);
12148
+ this.inventorySyncKickTimer = null;
12149
+ }
12150
+ }
11822
12151
  /**
11823
12152
  * Start periodic timers (heartbeat, inventory sync, ping/pong).
11824
12153
  */
11825
12154
  startTimers(onWsHeartbeat, onInventorySync) {
12155
+ this.clearTimers();
11826
12156
  this.onInventorySync = onInventorySync;
11827
12157
  this.wsHeartbeatTimer = setInterval(async () => {
11828
12158
  if (!this.isReady()) return;
@@ -11833,7 +12163,19 @@ var ConnectionManager = class {
11833
12163
  }, INVENTORY_SYNC_MS);
11834
12164
  this.wsPingTimer = setInterval(() => {
11835
12165
  if (!this.activeSocket || this.activeSocket.readyState !== import_ws3.default.OPEN) return;
11836
- this.activeSocket.ping();
12166
+ if (this.wsPongTimeout) {
12167
+ clearTimeout(this.wsPongTimeout);
12168
+ this.wsPongTimeout = null;
12169
+ }
12170
+ try {
12171
+ this.activeSocket.send(JSON.stringify({ type: "ping" }));
12172
+ } catch (err) {
12173
+ log20.error("failed to send JSON ping", { error: err.message });
12174
+ }
12175
+ try {
12176
+ this.activeSocket.ping();
12177
+ } catch {
12178
+ }
11837
12179
  this.wsPongTimeout = setTimeout(() => {
11838
12180
  log20.warn("no pong received within 10s; closing stale connection");
11839
12181
  try {
@@ -11884,26 +12226,7 @@ var ConnectionManager = class {
11884
12226
  */
11885
12227
  cleanup(opts = {}) {
11886
12228
  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
- }
12229
+ this.clearTimers();
11907
12230
  if (this.activeSocket) {
11908
12231
  this.activeSocket.removeAllListeners();
11909
12232
  try {
@@ -12965,7 +13288,6 @@ var ControlDispatcher = class {
12965
13288
  }
12966
13289
  /** Handle input routing to the correct target. */
12967
13290
  handleInput(data, sessionId) {
12968
- this.connection.requestInventorySync(800);
12969
13291
  if (!sessionId || sessionId.startsWith("pty:")) {
12970
13292
  this.shell.write(data);
12971
13293
  } else if (sessionId.startsWith("tmux:")) {
@@ -13936,7 +14258,6 @@ async function runAgent(rawOptions) {
13936
14258
  const ptySessionId = `pty:${options.hostId}`;
13937
14259
  const sessionStreamer = new SessionStreamer(
13938
14260
  (sessionId, data, viewerId) => {
13939
- conn.requestInventorySync();
13940
14261
  if (!conn.isReady()) {
13941
14262
  sessionStreamer.bufferOutput(sessionId, data);
13942
14263
  return;
@@ -13968,7 +14289,6 @@ async function runAgent(rawOptions) {
13968
14289
  );
13969
14290
  const conn = new ConnectionManager(sessionStreamer, outputBuffer);
13970
14291
  const sendOutput = (chunk) => {
13971
- conn.requestInventorySync();
13972
14292
  if (!conn.isReady()) {
13973
14293
  outputBuffer.push(chunk);
13974
14294
  return;
@@ -14220,6 +14540,10 @@ async function runAgent(rawOptions) {
14220
14540
  } catch {
14221
14541
  return;
14222
14542
  }
14543
+ if (msg.type === "pong") {
14544
+ conn.onPong();
14545
+ return;
14546
+ }
14223
14547
  if (msg.type === "message" && msg.group === groups.privateGroup) {
14224
14548
  const payload = msg.data || {};
14225
14549
  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.12",
1303
+ "bom-ref": "ragent-live|ragent-cli@1.11.12",
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.12",
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.12"
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.12",
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.12",
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": {