auvezy-terminal-remote 0.3.1 → 0.4.1

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/cli.js CHANGED
@@ -61,9 +61,11 @@ function ensureDefaultUserConfig(input) {
61
61
  const userCommands = Array.isArray(src.commands) && src.commands.length > 0 ? src.commands : null;
62
62
  const commandsLegacy = userCommands !== null && userCommands.some((c) => typeof c.group !== "string" || c.group.length === 0);
63
63
  const commands = userCommands === null || commandsLegacy ? DEFAULT_COMMANDS : userCommands;
64
- return { ...src, shortcuts, commands };
64
+ const useInputBarValue = typeof src.input?.useInputBar === "boolean" ? src.input.useInputBar : DEFAULT_INPUT.useInputBar;
65
+ const inputPrefs = { useInputBar: useInputBarValue };
66
+ return { ...src, shortcuts, commands, input: inputPrefs };
65
67
  }
66
- var SHORTCUT_GROUPS, DEFAULT_SHORTCUTS, COMMAND_GROUPS, DEFAULT_COMMANDS;
68
+ var SHORTCUT_GROUPS, DEFAULT_SHORTCUTS, COMMAND_GROUPS, DEFAULT_COMMANDS, DEFAULT_INPUT;
67
69
  var init_defaults = __esm({
68
70
  "shared/dist/defaults.js"() {
69
71
  "use strict";
@@ -253,6 +255,9 @@ var init_defaults = __esm({
253
255
  }
254
256
  ];
255
257
  DEFAULT_COMMANDS = COMMAND_GROUPS.flatMap((g) => g.items.map((c) => ({ ...c, group: g.id })));
258
+ DEFAULT_INPUT = {
259
+ useInputBar: true
260
+ };
256
261
  }
257
262
  });
258
263
 
@@ -818,7 +823,7 @@ var init_config_routes = __esm({
818
823
  });
819
824
 
820
825
  // backend/dist/constants.js
821
- var WS_FLUSH_INTERVAL_MS, WS_MAX_CHUNK_BYTES, WS_HIGH_WATERMARK_BYTES, FILE_LOCK_RETRIES, FILE_LOCK_RETRY_INTERVAL_MS, FILE_LOCK_STALE_MS, IP_MONITOR_INTERVAL_MS, IP_MONITOR_STABILITY_THRESHOLD, PTY_DEFAULT_COLS, PTY_DEFAULT_ROWS, PTY_TERM_NAME, SHUTDOWN_WS_FLUSH_DELAY_MS, SHUTDOWN_FORCE_EXIT_MS, DOUBLE_CTRL_C_WINDOW_MS, PORT_FINDER_MAX_ATTEMPTS, STOP_INSTANCE_GRACE_MS, STOP_INSTANCE_POLL_INTERVAL_MS, ATTACH_RECONNECT_DELAYS_MS;
826
+ var WS_FLUSH_INTERVAL_MS, WS_MAX_CHUNK_BYTES, WS_HIGH_WATERMARK_BYTES, FILE_LOCK_RETRIES, FILE_LOCK_RETRY_INTERVAL_MS, FILE_LOCK_STALE_MS, IP_MONITOR_INTERVAL_MS, IP_MONITOR_STABILITY_THRESHOLD, PTY_DEFAULT_COLS, PTY_DEFAULT_ROWS, PTY_TERM_NAME, DOUBLE_PULSE_DELAY_MS, SHUTDOWN_WS_FLUSH_DELAY_MS, SHUTDOWN_FORCE_EXIT_MS, DOUBLE_CTRL_C_WINDOW_MS, PORT_FINDER_MAX_ATTEMPTS, STOP_INSTANCE_GRACE_MS, STOP_INSTANCE_POLL_INTERVAL_MS, ATTACH_RECONNECT_DELAYS_MS;
822
827
  var init_constants2 = __esm({
823
828
  "backend/dist/constants.js"() {
824
829
  "use strict";
@@ -833,6 +838,7 @@ var init_constants2 = __esm({
833
838
  PTY_DEFAULT_COLS = 80;
834
839
  PTY_DEFAULT_ROWS = 24;
835
840
  PTY_TERM_NAME = "xterm-256color";
841
+ DOUBLE_PULSE_DELAY_MS = 50;
836
842
  SHUTDOWN_WS_FLUSH_DELAY_MS = 500;
837
843
  SHUTDOWN_FORCE_EXIT_MS = 2e3;
838
844
  DOUBLE_CTRL_C_WINDOW_MS = 500;
@@ -1437,7 +1443,9 @@ function detectDisplayIp(hostHint) {
1437
1443
  return hostHint;
1438
1444
  }
1439
1445
  const ifaces = networkInterfaces();
1440
- const privates = [];
1446
+ const tailscale = [];
1447
+ const lanReal = [];
1448
+ const lanVirtual = [];
1441
1449
  const linkLocals = [];
1442
1450
  for (const list of Object.values(ifaces)) {
1443
1451
  if (!list)
@@ -1448,14 +1456,20 @@ function detectDisplayIp(hostHint) {
1448
1456
  if (info.family !== "IPv4")
1449
1457
  continue;
1450
1458
  const ip = info.address;
1451
- if (isPrivateIp(ip)) {
1452
- privates.push(ip);
1459
+ if (isTailscaleIp(ip)) {
1460
+ tailscale.push(ip);
1461
+ } else if (isPrivateIp(ip)) {
1462
+ const first = Number(ip.split(".")[0]);
1463
+ if (first === 172)
1464
+ lanVirtual.push(ip);
1465
+ else
1466
+ lanReal.push(ip);
1453
1467
  } else if (isLinkLocal(ip)) {
1454
1468
  linkLocals.push(ip);
1455
1469
  }
1456
1470
  }
1457
1471
  }
1458
- return privates[0] ?? linkLocals[0] ?? "127.0.0.1";
1472
+ return tailscale[0] ?? lanReal[0] ?? lanVirtual[0] ?? linkLocals[0] ?? "127.0.0.1";
1459
1473
  }
1460
1474
  function isShareableIpv6(ip, info) {
1461
1475
  if (!ip.includes(":"))
@@ -1627,6 +1641,16 @@ var init_pty_manager = __esm({
1627
1641
  _exited = false;
1628
1642
  _cols = PTY_DEFAULT_COLS;
1629
1643
  _rows = PTY_DEFAULT_ROWS;
1644
+ /**
1645
+ * 当前是否处于 alt-screen(DECSET 1049 / 1047 / 47)。
1646
+ * 通过扫描 PTY 输出序列实时维护:
1647
+ * - vim/htop/tmux/less 等全屏 TUI 进入时切 true(它们自己整屏重画,对
1648
+ * resize 反应正常,不需要 double-pulse hack)
1649
+ * - claude/zsh prompt 等增量重画 TUI 始终为 false → 需要 double-pulse
1650
+ */
1651
+ _inAltScreen = false;
1652
+ /** double-pulse resize 的中间帧定时器 */
1653
+ _doublePulseTimer = null;
1630
1654
  get cols() {
1631
1655
  return this._cols;
1632
1656
  }
@@ -1662,6 +1686,7 @@ var init_pty_manager = __esm({
1662
1686
  env: { ...process.env, ...opts.env }
1663
1687
  });
1664
1688
  this.process.onData((data) => {
1689
+ this.scanAltScreenToggle(data);
1665
1690
  this.emit("data", data);
1666
1691
  });
1667
1692
  this.process.onExit(({ exitCode, signal }) => {
@@ -1699,6 +1724,18 @@ var init_pty_manager = __esm({
1699
1724
  * webapp resize → ws → PTY.resize → emit 'resize' → broadcast →
1700
1725
  * webapp 收到 terminal_resize → 触发 fit → 又算出同尺寸 → 再发 resize → ...
1701
1726
  * 同尺寸跳过让链路在第二步就断掉
1727
+ *
1728
+ * Double-pulse hack(仅 normal-screen / 增量重画 TUI):
1729
+ * Claude Code (Ink) / blessed / prompt-toolkit / readline REPL 等用相对
1730
+ * 坐标增量重画的程序,收到 SIGWINCH 后只对"宽度变窄"分支才会清屏 +
1731
+ * 重新 layout(Ink 源码:`if (currentWidth < lastTerminalWidth) clear()`)。
1732
+ * 宽度变宽时既不清前帧也不重排已渲染历史 → 视觉上"没响应"。
1733
+ *
1734
+ * workaround:先 resize(cols-1) 让 Ink 走 width-shrink 分支强制清屏,
1735
+ * 50ms 后再 resize(cols) 回到目标尺寸。代价是程序会多一帧重画。
1736
+ *
1737
+ * alt-screen 程序(vim/tmux/htop/less)自己会在 SIGWINCH 时整屏重画,
1738
+ * 不需要也不应该 double-pulse(多余 SIGWINCH 让它们闪一下)→ 短路。
1702
1739
  */
1703
1740
  resize(cols, rows) {
1704
1741
  if (!this.process || this._exited)
@@ -1707,8 +1744,30 @@ var init_pty_manager = __esm({
1707
1744
  logger.debug({ cols, rows }, "PTY resize \u8DF3\u8FC7\uFF08\u540C\u5C3A\u5BF8\uFF09");
1708
1745
  return;
1709
1746
  }
1710
- logger.info({ cols, rows, prevCols: this._cols, prevRows: this._rows }, "PTY resize \u6267\u884C");
1747
+ logger.info({ cols, rows, prevCols: this._cols, prevRows: this._rows, alt: this._inAltScreen }, "PTY resize \u6267\u884C");
1711
1748
  try {
1749
+ if (this._doublePulseTimer) {
1750
+ clearTimeout(this._doublePulseTimer);
1751
+ this._doublePulseTimer = null;
1752
+ }
1753
+ const shouldDoublePulse = !this._inAltScreen && cols > this._cols && cols > 2;
1754
+ if (shouldDoublePulse) {
1755
+ this.process.resize(cols - 1, rows);
1756
+ this._doublePulseTimer = setTimeout(() => {
1757
+ this._doublePulseTimer = null;
1758
+ if (!this.process || this._exited)
1759
+ return;
1760
+ try {
1761
+ this.process.resize(cols, rows);
1762
+ this._cols = cols;
1763
+ this._rows = rows;
1764
+ this.emit("resize", cols, rows);
1765
+ } catch (err) {
1766
+ logger.error({ err, cols, rows }, "PTY resize \u7B2C\u4E8C\u8109\u51B2\u5931\u8D25");
1767
+ }
1768
+ }, DOUBLE_PULSE_DELAY_MS);
1769
+ return;
1770
+ }
1712
1771
  this.process.resize(cols, rows);
1713
1772
  this._cols = cols;
1714
1773
  this._rows = rows;
@@ -1723,6 +1782,10 @@ var init_pty_manager = __esm({
1723
1782
  * 幂等:多次调用安全
1724
1783
  */
1725
1784
  destroy() {
1785
+ if (this._doublePulseTimer) {
1786
+ clearTimeout(this._doublePulseTimer);
1787
+ this._doublePulseTimer = null;
1788
+ }
1726
1789
  if (!this.process)
1727
1790
  return;
1728
1791
  try {
@@ -1733,6 +1796,32 @@ var init_pty_manager = __esm({
1733
1796
  }
1734
1797
  this.process = null;
1735
1798
  }
1799
+ /**
1800
+ * 当前是否处于 alt-screen(仅供 resize 决策用,不暴露给上层)
1801
+ */
1802
+ get inAltScreen() {
1803
+ return this._inAltScreen;
1804
+ }
1805
+ /**
1806
+ * 扫描 PTY 输出,识别 alt-screen 切换序列:
1807
+ * - DECSET 1049(最常用,xterm 标准 + 保存光标位置):进入 / 退出
1808
+ * - DECSET 1047(仅 alt buffer 切换):进入 / 退出
1809
+ * - DECSET 47(最老的 alt-buffer,无光标保存):进入 / 退出
1810
+ *
1811
+ * 不需要完整 ANSI 解析器——这三个序列形态固定,正则简单匹配即可。
1812
+ * 同一 chunk 内可能既有 enter 又有 exit(罕见,但可能),按顺序处理。
1813
+ */
1814
+ scanAltScreenToggle(data) {
1815
+ const re = /\x1b\[\?(1049|1047|47)([hl])/g;
1816
+ let m;
1817
+ while ((m = re.exec(data)) !== null) {
1818
+ const isEnter = m[2] === "h";
1819
+ if (this._inAltScreen !== isEnter) {
1820
+ this._inAltScreen = isEnter;
1821
+ logger.debug({ inAltScreen: isEnter }, "PTY alt-screen \u72B6\u6001\u5207\u6362");
1822
+ }
1823
+ }
1824
+ }
1736
1825
  };
1737
1826
  }
1738
1827
  });
@@ -2008,7 +2097,7 @@ function handleWsMessage(ws, raw, cb) {
2008
2097
  break;
2009
2098
  case "resize":
2010
2099
  if (typeof msg.cols === "number" && typeof msg.rows === "number") {
2011
- cb.onResize(msg.cols, msg.rows);
2100
+ cb.onResize(msg.cols, msg.rows, ws, msg.master === true);
2012
2101
  } else {
2013
2102
  logger.warn({ msg }, "resize \u7F3A cols/rows \u5B57\u6BB5\u6216\u7C7B\u578B\u9519\u8BEF");
2014
2103
  }
@@ -2018,6 +2107,13 @@ function handleWsMessage(ws, raw, cb) {
2018
2107
  ws.send(JSON.stringify({ type: "heartbeat", timestamp: Date.now() }));
2019
2108
  }
2020
2109
  break;
2110
+ case "client_log":
2111
+ if (typeof msg.message === "string" && typeof msg.level === "string") {
2112
+ const ts = typeof msg.ts === "number" ? new Date(msg.ts).toISOString().slice(11, 23) : "?";
2113
+ process.stderr.write(`[client ${ts} ${msg.level}] ${msg.message}
2114
+ `);
2115
+ }
2116
+ break;
2021
2117
  default: {
2022
2118
  const t = msg.type;
2023
2119
  logger.warn({ type: t }, "\u672A\u77E5 WS \u6D88\u606F\u7C7B\u578B\uFF0C\u5DF2\u5FFD\u7565");
@@ -2124,6 +2220,7 @@ var init_ansi_filter = __esm({
2124
2220
  });
2125
2221
 
2126
2222
  // backend/dist/session/session-controller.js
2223
+ import { WebSocket as WebSocket3 } from "ws";
2127
2224
  var SessionController;
2128
2225
  var init_session_controller = __esm({
2129
2226
  "backend/dist/session/session-controller.js"() {
@@ -2157,6 +2254,12 @@ var init_session_controller = __esm({
2157
2254
  wsBackpressureEvents = 0;
2158
2255
  /** 可选的 hook 接收器(阶段 3 启用) */
2159
2256
  hookReceiver = null;
2257
+ /**
2258
+ * PTY 尺寸主控连接:声明 master=true 的客户端 WebSocket 引用。
2259
+ * 仅它能改 PTY cols/rows;其他客户端的 resize 被忽略。断开时自动释放
2260
+ * (onDisconnect 处理)。null = 无主控,先到先得
2261
+ */
2262
+ masterClient = null;
2160
2263
  /** ANSI 过滤器(阶段 8 启用;null = 关闭过滤直接透传) */
2161
2264
  ansiFilter;
2162
2265
  /** 可选的 PushService(阶段 9 启用;用于 hook 触发时推送通知) */
@@ -2340,8 +2443,18 @@ var init_session_controller = __esm({
2340
2443
  onUserInput: (data) => {
2341
2444
  this.pty.write(data);
2342
2445
  },
2343
- onResize: (cols, rows) => {
2446
+ onResize: (cols, rows, source, master) => {
2344
2447
  const counts = this.ws.getClientCounts();
2448
+ if (master) {
2449
+ this.masterClient = source;
2450
+ logger.info({ cols, rows, type }, "PTY \u4E3B\u63A7\u5207\u6362\uFF1A\u5BA2\u6237\u7AEF\u58F0\u660E master");
2451
+ this.pty.resize(cols, rows);
2452
+ return;
2453
+ }
2454
+ if (this.masterClient && this.masterClient !== source) {
2455
+ logger.debug({ cols, rows }, "\u4E3B\u63A7\u88AB\u5176\u4ED6\u5BA2\u6237\u7AEF\u6301\u6709\uFF0C\u5FFD\u7565\u6B64 resize");
2456
+ return;
2457
+ }
2345
2458
  if (counts.webapp > 0 && type === "attach") {
2346
2459
  logger.debug({ type, cols, rows, counts }, "webapp \u5728\u7EBF\uFF0Cattach \u7684 resize \u88AB\u5FFD\u7565");
2347
2460
  return;
@@ -2363,6 +2476,10 @@ var init_session_controller = __esm({
2363
2476
  });
2364
2477
  this.ws.onDisconnect((counts) => {
2365
2478
  logger.debug(counts, "\u5BA2\u6237\u7AEF\u65AD\u5F00\u540E\u5269\u4F59\u7EDF\u8BA1");
2479
+ if (this.masterClient && this.masterClient.readyState !== WebSocket3.OPEN) {
2480
+ logger.info("PTY \u4E3B\u63A7\u8FDE\u63A5\u5DF2\u65AD\u5F00\uFF0C\u91CA\u653E\u4E3B\u63A7\u9501");
2481
+ this.masterClient = null;
2482
+ }
2366
2483
  if (counts.webapp === 0 && counts.attach > 0) {
2367
2484
  this.ws.broadcast({
2368
2485
  type: "terminal_resize",
@@ -4490,7 +4607,7 @@ var init_cli_stop = __esm({
4490
4607
  });
4491
4608
 
4492
4609
  // backend/dist/attach/attach-client.js
4493
- import WebSocket3 from "ws";
4610
+ import WebSocket4 from "ws";
4494
4611
  import { EventEmitter as EventEmitter4 } from "node:events";
4495
4612
  function normalizeAttachUrl(input) {
4496
4613
  let url;
@@ -4545,7 +4662,7 @@ var init_attach_client = __esm({
4545
4662
  if (this.destroyed)
4546
4663
  return;
4547
4664
  this.setStatus("connecting");
4548
- const ws = new WebSocket3(this.url);
4665
+ const ws = new WebSocket4(this.url);
4549
4666
  this.ws = ws;
4550
4667
  ws.on("open", () => {
4551
4668
  this.reconnectAttempt = 0;
@@ -4610,7 +4727,7 @@ var init_attach_client = __esm({
4610
4727
  }
4611
4728
  // ────────────────── 内部 ──────────────────
4612
4729
  send(msg) {
4613
- if (!this.ws || this.ws.readyState !== WebSocket3.OPEN)
4730
+ if (!this.ws || this.ws.readyState !== WebSocket4.OPEN)
4614
4731
  return;
4615
4732
  try {
4616
4733
  this.ws.send(JSON.stringify(msg));