cli-remote-agent 1.0.8 → 1.0.11

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
@@ -59,6 +59,7 @@ var TMUX_LIST = "tmux:list";
59
59
  var TMUX_LIST_RESULT = "tmux:list:result";
60
60
  var TMUX_KILL = "tmux:kill";
61
61
  var TMUX_KILL_RESULT = "tmux:kill:result";
62
+ var TMUX_SCROLL = "tmux:scroll";
62
63
 
63
64
  // ../shared/src/constants.ts
64
65
  var HEARTBEAT_INTERVAL = 15e3;
@@ -372,6 +373,7 @@ function getLiveClaudeSessions() {
372
373
  out.push({
373
374
  pid: obj.pid,
374
375
  cwd: typeof obj.cwd === "string" ? obj.cwd : "",
376
+ sessionId: typeof obj.sessionId === "string" ? obj.sessionId : void 0,
375
377
  name: typeof obj.name === "string" ? obj.name : void 0,
376
378
  status: typeof obj.status === "string" ? obj.status : void 0,
377
379
  updatedAt: typeof obj.updatedAt === "number" ? obj.updatedAt : void 0
@@ -402,6 +404,15 @@ function isDescendantOf(pid, ancestor, parents) {
402
404
  }
403
405
  return false;
404
406
  }
407
+ async function findClaudeSessionForPtyPid(ptyPid) {
408
+ const live = getLiveClaudeSessions().filter((s) => s.sessionId);
409
+ if (live.length === 0) return null;
410
+ const parents = await getPidParents();
411
+ for (let i = live.length - 1; i >= 0; i--) {
412
+ if (isDescendantOf(live[i].pid, ptyPid, parents)) return live[i].sessionId;
413
+ }
414
+ return null;
415
+ }
405
416
 
406
417
  // src/tmux.ts
407
418
  var execFileAsync2 = (0, import_util2.promisify)(import_child_process3.execFile);
@@ -459,6 +470,41 @@ async function killTmuxSession(name) {
459
470
  return { ok: false, error: msg };
460
471
  }
461
472
  }
473
+ async function scrollTmux(name, direction) {
474
+ const bin = resolveTmuxPath();
475
+ if (!bin) return { inCopyMode: false };
476
+ const target = `=${name}`;
477
+ const run = (args) => execFileAsync2(bin, args, { timeout: 4e3 }).catch(() => {
478
+ });
479
+ const inMode = async () => {
480
+ try {
481
+ const { stdout } = await execFileAsync2(bin, ["display-message", "-t", target, "-p", "#{pane_in_mode}"], { timeout: 3e3 });
482
+ return stdout.trim() === "1";
483
+ } catch {
484
+ return false;
485
+ }
486
+ };
487
+ if (direction === "exit") {
488
+ if (await inMode()) await run(["send-keys", "-t", target, "-X", "cancel"]);
489
+ return { inCopyMode: false };
490
+ }
491
+ if (direction === "up") {
492
+ await run(["copy-mode", "-t", target]);
493
+ await run(["send-keys", "-t", target, "-X", "halfpage-up"]);
494
+ return { inCopyMode: true };
495
+ }
496
+ if (!await inMode()) return { inCopyMode: false };
497
+ await run(["send-keys", "-t", target, "-X", "halfpage-down"]);
498
+ try {
499
+ const { stdout } = await execFileAsync2(bin, ["display-message", "-t", target, "-p", "#{scroll_position}"], { timeout: 3e3 });
500
+ if ((parseInt(stdout.trim(), 10) || 0) === 0) {
501
+ await run(["send-keys", "-t", target, "-X", "cancel"]);
502
+ return { inCopyMode: false };
503
+ }
504
+ } catch {
505
+ }
506
+ return { inCopyMode: true };
507
+ }
462
508
  async function enrichWithPanesAndClaude(sessions2) {
463
509
  if (IS_WIN || sessions2.length === 0) return;
464
510
  try {
@@ -639,6 +685,9 @@ var PtySession = class {
639
685
  });
640
686
  }
641
687
  }
688
+ get pid() {
689
+ return this.pty.pid;
690
+ }
642
691
  write(data) {
643
692
  this.pty.write(data);
644
693
  }
@@ -704,6 +753,9 @@ function createTerminalSession(sessionId, cols, rows, shellPreference, cwd, onDa
704
753
  sessions.set(sessionId, session);
705
754
  logger.info({ sessionId, cols, rows }, "PTY session created");
706
755
  }
756
+ function getSessionPid(sessionId) {
757
+ return sessions.get(sessionId)?.pid;
758
+ }
707
759
  function writeToSession(sessionId, data) {
708
760
  sessions.get(sessionId)?.write(data);
709
761
  }
@@ -1614,9 +1666,12 @@ startLocalControl(LOCAL_CONTROL_PORT, (raw) => {
1614
1666
  socket.emit(CLAUDE_HOOK, payload);
1615
1667
  }
1616
1668
  });
1669
+ var sessionTmuxName = /* @__PURE__ */ new Map();
1670
+ var sessionInCopyMode = /* @__PURE__ */ new Map();
1617
1671
  socket.on(TERMINAL_OPEN, (payload) => {
1618
1672
  const { sessionId, cols, rows, tmux, launch } = payload;
1619
1673
  if (!sessionId) return;
1674
+ if (tmux) sessionTmuxName.set(sessionId, tmux);
1620
1675
  let launchSpec;
1621
1676
  if (tmux) {
1622
1677
  const spec = buildTmuxLaunch(tmux, launch);
@@ -1640,6 +1695,8 @@ socket.on(TERMINAL_OPEN, (payload) => {
1640
1695
  },
1641
1696
  (sid, exitCode) => {
1642
1697
  socket.emit(TERMINAL_EXIT, { sessionId: sid, exitCode });
1698
+ sessionTmuxName.delete(sid);
1699
+ sessionInCopyMode.delete(sid);
1643
1700
  },
1644
1701
  launchSpec
1645
1702
  );
@@ -1661,7 +1718,20 @@ socket.on(TMUX_KILL, async (payload) => {
1661
1718
  logger.info({ name, ok: result.ok, error: result.error }, "tmux kill requested");
1662
1719
  socket.emit(TMUX_KILL_RESULT, { requestId, name, ok: result.ok, error: result.error });
1663
1720
  });
1721
+ socket.on(TMUX_SCROLL, async (payload) => {
1722
+ const { sessionId, direction } = payload;
1723
+ const name = sessionId && sessionTmuxName.get(sessionId);
1724
+ if (!name) return;
1725
+ const { inCopyMode } = await scrollTmux(name, direction);
1726
+ sessionInCopyMode.set(sessionId, inCopyMode);
1727
+ });
1664
1728
  socket.on(TERMINAL_INPUT, (payload) => {
1729
+ const name = sessionTmuxName.get(payload.sessionId);
1730
+ if (name && sessionInCopyMode.get(payload.sessionId)) {
1731
+ sessionInCopyMode.set(payload.sessionId, false);
1732
+ scrollTmux(name, "exit").catch(() => {
1733
+ });
1734
+ }
1665
1735
  writeToSession(payload.sessionId, payload.data);
1666
1736
  });
1667
1737
  socket.on(TERMINAL_RESIZE, (payload) => {
@@ -1669,6 +1739,8 @@ socket.on(TERMINAL_RESIZE, (payload) => {
1669
1739
  });
1670
1740
  socket.on(TERMINAL_CLOSE, (payload) => {
1671
1741
  closeSession(payload.sessionId);
1742
+ sessionTmuxName.delete(payload.sessionId);
1743
+ sessionInCopyMode.delete(payload.sessionId);
1672
1744
  });
1673
1745
  socket.on(SESSION_SYNC, () => {
1674
1746
  const aliveIds = getAliveSessionIds();
@@ -1745,7 +1817,17 @@ socket.on(AGENT_EXEC, async (payload) => {
1745
1817
  });
1746
1818
  socket.on(CLAUDE_CONV_READ, async (payload) => {
1747
1819
  try {
1748
- const result = await readConversation(payload.projectPath, payload.afterLine || 0, payload.sessionId);
1820
+ let sessionId = payload.sessionId;
1821
+ if (!sessionId && payload.terminalSessionId) {
1822
+ const ptyPid = getSessionPid(payload.terminalSessionId);
1823
+ const resolved = ptyPid ? await findClaudeSessionForPtyPid(ptyPid) : null;
1824
+ if (!resolved) {
1825
+ socket.emit(CLAUDE_CONV_DATA, { sessionId: "", messages: [], totalLines: 0 });
1826
+ return;
1827
+ }
1828
+ sessionId = resolved;
1829
+ }
1830
+ const result = await readConversation(payload.projectPath, payload.afterLine || 0, sessionId);
1749
1831
  if (result) {
1750
1832
  socket.emit(CLAUDE_CONV_DATA, {
1751
1833
  sessionId: result.sessionId,