termdock 1.4.98 → 1.4.99

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.
Files changed (69) hide show
  1. package/.desktop-dist/main.js +211 -34
  2. package/dist/client/assets/{ModelPreview-Drqv0OTg.js → ModelPreview-CJK3Nmg4.js} +1 -1
  3. package/dist/client/assets/{abnfDiagram-VRR7QNED-Cl8CT45X.js → abnfDiagram-VRR7QNED-C-QKkcSr.js} +1 -1
  4. package/dist/client/assets/{arc-2Sc638oe.js → arc-BpjFutHZ.js} +1 -1
  5. package/dist/client/assets/{architectureDiagram-ZJ3FMSHR-CNH9w9eN.js → architectureDiagram-ZJ3FMSHR-DeeuzGsk.js} +1 -1
  6. package/dist/client/assets/{blockDiagram-677ZJIJ3-ClvE_yP1.js → blockDiagram-677ZJIJ3-BkDiKR3-.js} +1 -1
  7. package/dist/client/assets/{c4Diagram-LMCZKHZV-CxIMU8l9.js → c4Diagram-LMCZKHZV-BX6424vk.js} +1 -1
  8. package/dist/client/assets/channel-BiaGwVDJ.js +1 -0
  9. package/dist/client/assets/{chunk-2Q5K7J3B-CdvXmc6Z.js → chunk-2Q5K7J3B-DwNMfRvw.js} +1 -1
  10. package/dist/client/assets/{chunk-32BRIVSS-B1dA4l5A.js → chunk-32BRIVSS-D5g3chKu.js} +1 -1
  11. package/dist/client/assets/{chunk-5VM5RSS4-CVk_pzpf.js → chunk-5VM5RSS4-B2rw8Ldu.js} +1 -1
  12. package/dist/client/assets/{chunk-EX3LRPZG-B46zhCa0.js → chunk-EX3LRPZG-j3CVdb45.js} +1 -1
  13. package/dist/client/assets/{chunk-JWPE2WC7-1wRPE3eO.js → chunk-JWPE2WC7-iHTR9CAX.js} +1 -1
  14. package/dist/client/assets/{chunk-MOJQB5TN-BzPrhNIS.js → chunk-MOJQB5TN-DVD1wXze.js} +1 -1
  15. package/dist/client/assets/{chunk-RYQCIY6F-D0sDGnBf.js → chunk-RYQCIY6F-zZRopnRd.js} +1 -1
  16. package/dist/client/assets/{chunk-V7JOEXUC-BRo0103H.js → chunk-V7JOEXUC-h4EZpc4k.js} +1 -1
  17. package/dist/client/assets/{chunk-VR4S4FIN-CW-wSkDE.js → chunk-VR4S4FIN-C9MZH2Oy.js} +1 -1
  18. package/dist/client/assets/{chunk-XXDRQBXY-BuWiQ0uq.js → chunk-XXDRQBXY-BNPDO5fQ.js} +1 -1
  19. package/dist/client/assets/classDiagram-OUVF2IWQ-DYljVZD_.js +1 -0
  20. package/dist/client/assets/classDiagram-v2-EOCWNBFH-DYljVZD_.js +1 -0
  21. package/dist/client/assets/{cose-bilkent-JH36ORCC-WzKVd03Q.js → cose-bilkent-JH36ORCC-DlqULumX.js} +1 -1
  22. package/dist/client/assets/{cynefin-VYW2F7L2-CfXYAqnq.js → cynefin-VYW2F7L2-D6pWZSwi.js} +1 -1
  23. package/dist/client/assets/{cynefinDiagram-TSTJHNR4-BnJGle_X.js → cynefinDiagram-TSTJHNR4-B6SI7XOe.js} +1 -1
  24. package/dist/client/assets/{dagre-VKFMJZFB-CDDlGCQy.js → dagre-VKFMJZFB-DXnKJ4XD.js} +1 -1
  25. package/dist/client/assets/{diagram-FQU43EPY-t8DNZS7P.js → diagram-FQU43EPY-B5NTAhw6.js} +1 -1
  26. package/dist/client/assets/{diagram-G47NLZAW-CZpQd91k.js → diagram-G47NLZAW-Dihaal0B.js} +1 -1
  27. package/dist/client/assets/{diagram-NH7WQ7WH-OET0EFlt.js → diagram-NH7WQ7WH-Be987DPJ.js} +1 -1
  28. package/dist/client/assets/{diagram-OA4YK3LP-COgju4NX.js → diagram-OA4YK3LP-CfgTeXAJ.js} +1 -1
  29. package/dist/client/assets/{diagram-WEI45ONY-6gNSoIek.js → diagram-WEI45ONY-BgrK8rP2.js} +1 -1
  30. package/dist/client/assets/{ebnfDiagram-CCIWWBDH-CTlIMc8W.js → ebnfDiagram-CCIWWBDH-BPsH1_QO.js} +1 -1
  31. package/dist/client/assets/{erDiagram-Q63AITRT-BDsEvZvq.js → erDiagram-Q63AITRT-F3s7Ytrc.js} +1 -1
  32. package/dist/client/assets/{flowDiagram-23GEKE2U-ggl4zaY9.js → flowDiagram-23GEKE2U-ByL-8svD.js} +1 -1
  33. package/dist/client/assets/{ganttDiagram-NO4QXBWP-D9Ca5oWS.js → ganttDiagram-NO4QXBWP-Dtv-fi06.js} +1 -1
  34. package/dist/client/assets/{gitGraphDiagram-IHSO6WYX-THPUH6Bg.js → gitGraphDiagram-IHSO6WYX-CeD48POl.js} +1 -1
  35. package/dist/client/assets/{index-BuMMW0wS.js → index-C82Uc6Vy.js} +4 -4
  36. package/dist/client/assets/{infoDiagram-FWYZ7A6U-CKF-vSTz.js → infoDiagram-FWYZ7A6U-BxvLe33p.js} +1 -1
  37. package/dist/client/assets/{ishikawaDiagram-FXEZZL3T-CBFlmgI3.js → ishikawaDiagram-FXEZZL3T-XPW1aoJK.js} +1 -1
  38. package/dist/client/assets/{journeyDiagram-5HDEW3XC-lbXIqX5a.js → journeyDiagram-5HDEW3XC-Bhiae04Z.js} +1 -1
  39. package/dist/client/assets/{kanban-definition-HUTT4EX6-BUuO_e54.js → kanban-definition-HUTT4EX6-D5iOY3Zc.js} +1 -1
  40. package/dist/client/assets/{linear-D_uoNzR8.js → linear-PCgLsFTe.js} +1 -1
  41. package/dist/client/assets/{mermaid.core-D4Yy5OxZ.js → mermaid.core-DWuc-xG5.js} +4 -4
  42. package/dist/client/assets/{mindmap-definition-LN4V7U3C-B0Ml-jaq.js → mindmap-definition-LN4V7U3C-C0EFK91v.js} +1 -1
  43. package/dist/client/assets/{pegDiagram-2B236MQR-CKF9cuqx.js → pegDiagram-2B236MQR-ClTz8aHa.js} +1 -1
  44. package/dist/client/assets/{pieDiagram-ENE6RG2P-DL3XisAD.js → pieDiagram-ENE6RG2P-BuTv1DGN.js} +1 -1
  45. package/dist/client/assets/{quadrantDiagram-ABIIQ3AL-DrsHXXqd.js → quadrantDiagram-ABIIQ3AL-FLZC-cqI.js} +1 -1
  46. package/dist/client/assets/{railroadDiagram-RFXS5EU6-DMAz8hLg.js → railroadDiagram-RFXS5EU6-DK68FlFh.js} +1 -1
  47. package/dist/client/assets/{requirementDiagram-TGXJPOKE-CCHretnQ.js → requirementDiagram-TGXJPOKE-B_701AJ1.js} +1 -1
  48. package/dist/client/assets/{sankeyDiagram-HTMAVEWB-Btb7ZNwU.js → sankeyDiagram-HTMAVEWB-Bk87L7JG.js} +1 -1
  49. package/dist/client/assets/{sequenceDiagram-DBY2YBRQ-DcZk1PdX.js → sequenceDiagram-DBY2YBRQ-Biy5tPTS.js} +1 -1
  50. package/dist/client/assets/{sizeCapture-X5ZJPWSS-vMYb_VZU.js → sizeCapture-X5ZJPWSS-__4zzUuh.js} +1 -1
  51. package/dist/client/assets/{stateDiagram-2N3HPSRC-CltexqJj.js → stateDiagram-2N3HPSRC-B_400cf9.js} +1 -1
  52. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-CK77br3V.js +1 -0
  53. package/dist/client/assets/{swimlanes-5IMT3BWC-DPnwlpXX.js → swimlanes-5IMT3BWC-CxWZz0n0.js} +2 -2
  54. package/dist/client/assets/swimlanesDiagram-G3AALYLV-DbYe8ax3.js +8 -0
  55. package/dist/client/assets/{timeline-definition-FHXFAJF6-l78vfKln.js → timeline-definition-FHXFAJF6-DSrIUp8H.js} +1 -1
  56. package/dist/client/assets/{vennDiagram-L72KCM5P-BggNEe_f.js → vennDiagram-L72KCM5P-CdwaUmpL.js} +1 -1
  57. package/dist/client/assets/{wardleyDiagram-EHGQE667-v04USz2e.js → wardleyDiagram-EHGQE667-DwxlF0Xk.js} +1 -1
  58. package/dist/client/assets/{xychartDiagram-FW5EYKEG-CInHBOpK.js → xychartDiagram-FW5EYKEG-CQNmEc_n.js} +1 -1
  59. package/dist/client/index.html +1 -1
  60. package/dist/client/sw.js +1 -1
  61. package/dist/server/agent/sessionSearchStore.js +38 -10
  62. package/dist/server/routes/terminal.js +206 -30
  63. package/package.json +3 -2
  64. package/runtime-manifest.json +2 -2
  65. package/dist/client/assets/channel-DPt818Jx.js +0 -1
  66. package/dist/client/assets/classDiagram-OUVF2IWQ-C2hZbTnO.js +0 -1
  67. package/dist/client/assets/classDiagram-v2-EOCWNBFH-C2hZbTnO.js +0 -1
  68. package/dist/client/assets/stateDiagram-v2-6OUMAXLB-BslbFNw3.js +0 -1
  69. package/dist/client/assets/swimlanesDiagram-G3AALYLV-B0V1XVQb.js +0 -8
@@ -43,6 +43,7 @@ const TERMDOCK_DIR = `${os.homedir()}/.termdock`;
43
43
  const automationStore = new AutomationStore(`${TERMDOCK_DIR}/automations.json`);
44
44
  const collaborationStore = new CollaborationStore(`${TERMDOCK_DIR}/collaboration-groups.json`);
45
45
  const sessionSearchStore = new SessionSearchStore(`${TERMDOCK_DIR}/session-search`);
46
+ let atomicJsonWriteSequence = 0;
46
47
  async function readJsonFileIfExists(filePath) {
47
48
  try {
48
49
  const raw = await fs.promises.readFile(filePath, 'utf-8');
@@ -56,7 +57,21 @@ async function readJsonFileIfExists(filePath) {
56
57
  }
57
58
  async function writeJsonFile(filePath, value) {
58
59
  await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
59
- await fs.promises.writeFile(filePath, JSON.stringify(value, null, 2), 'utf-8');
60
+ const temporaryPath = `${filePath}.${process.pid}.${++atomicJsonWriteSequence}.tmp`;
61
+ let handle = null;
62
+ try {
63
+ handle = await fs.promises.open(temporaryPath, 'w');
64
+ await handle.writeFile(JSON.stringify(value, null, 2), 'utf-8');
65
+ await handle.sync();
66
+ await handle.close();
67
+ handle = null;
68
+ await fs.promises.rename(temporaryPath, filePath);
69
+ }
70
+ catch (error) {
71
+ await handle?.close().catch(() => undefined);
72
+ await fs.promises.rm(temporaryPath, { force: true }).catch(() => undefined);
73
+ throw error;
74
+ }
60
75
  }
61
76
  // Termdock metadata constants used to populate tmux user options
62
77
  // (`@termdock-*`) so external tools (e.g. `termdock --tls`) can identify
@@ -636,6 +651,8 @@ const CLEANUP_INTERVAL = TERMINAL.cleanupInterval;
636
651
  const RECONNECT_SCROLLBACK = TERMINAL.reconnectScrollback;
637
652
  const TMUX_POLL_INTERVAL = parseInt(process.env.TMUX_POLL_INTERVAL || '500', 10);
638
653
  const ACTIVE_PROGRAM_POLL_INTERVAL = parseInt(process.env.TERMINAL_ACTIVE_PROGRAM_POLL_INTERVAL || '1200', 10);
654
+ const TMUX_LAYOUT_CACHE_TTL_MS = Math.max(TMUX_POLL_INTERVAL, 1000);
655
+ const PROCESS_SNAPSHOT_CACHE_TTL_MS = Math.max(ACTIVE_PROGRAM_POLL_INTERVAL, 2500);
639
656
  const FLOW_CONTROL_PAUSE_LEASE_MS = TERMINAL.flowControlPauseLeaseMs;
640
657
  const TMUX_DELIMITER = '\x1f';
641
658
  const TERMDOCK_TMUX_HISTORY_LIMIT = TMUX.historyLimit;
@@ -2954,6 +2971,51 @@ try {
2954
2971
  }
2955
2972
  }
2956
2973
  catch { /* plugin loading must never block startup */ }
2974
+ let processSnapshot = null;
2975
+ let processSnapshotPromise = null;
2976
+ function parseProcessSnapshot(stdout) {
2977
+ return stdout
2978
+ .split('\n')
2979
+ .map((line) => line.trim())
2980
+ .filter(Boolean)
2981
+ .map((line) => {
2982
+ // ps -o format: TTY PID PPID PGID TPGID STAT COMM ARGS
2983
+ const match = line.match(/^(\S+)\s+(\d+)\s+(\d+)\s+(-?\d+)\s+(-?\d+)\s+(\S+)\s+(\S+)\s+(.+)$/);
2984
+ if (!match)
2985
+ return null;
2986
+ return {
2987
+ tty: match[1] || '',
2988
+ pid: Number.parseInt(match[2] || '0', 10),
2989
+ ppid: Number.parseInt(match[3] || '0', 10),
2990
+ pgid: Number.parseInt(match[4] || '0', 10),
2991
+ tpgid: Number.parseInt(match[5] || '0', 10),
2992
+ stat: match[6] || '',
2993
+ comm: match[7] || '',
2994
+ args: match[8]?.trim() || '',
2995
+ };
2996
+ })
2997
+ .filter((row) => row !== null);
2998
+ }
2999
+ async function getProcessSnapshot() {
3000
+ const now = Date.now();
3001
+ if (processSnapshot && now - processSnapshot.fetchedAt < PROCESS_SNAPSHOT_CACHE_TTL_MS) {
3002
+ return processSnapshot.rows;
3003
+ }
3004
+ if (processSnapshotPromise)
3005
+ return processSnapshotPromise;
3006
+ processSnapshotPromise = execFileAsync('ps', [
3007
+ '-e',
3008
+ '-o',
3009
+ 'tty=,pid=,ppid=,pgid=,tpgid=,stat=,comm=,args=',
3010
+ ], { timeout: 3000, maxBuffer: 4 * 1024 * 1024 }).then(({ stdout }) => {
3011
+ const rows = parseProcessSnapshot(stdout);
3012
+ processSnapshot = { rows, fetchedAt: Date.now() };
3013
+ return rows;
3014
+ }).finally(() => {
3015
+ processSnapshotPromise = null;
3016
+ });
3017
+ return processSnapshotPromise;
3018
+ }
2957
3019
  async function resolveTmuxPaneProgram(pane) {
2958
3020
  // If pane command is a known shell, try to find a child foreground process
2959
3021
  const command = normalizeProgramName(pane.command);
@@ -2969,31 +3031,36 @@ async function resolveTmuxPaneProgram(pane) {
2969
3031
  return { command, source: 'tmux-pane', rawArgs: null };
2970
3032
  }
2971
3033
  try {
2972
- const psArgs = pane.tty
2973
- ? ['-t', pane.tty.replace(/^\/dev\//, ''), '-o', 'pid=,ppid=,pgid=,tpgid=,stat=,comm=,args=']
2974
- : ['-o', 'pid=,ppid=,pgid=,tpgid=,stat=,comm=,args='];
2975
- const { stdout } = await execFileAsync('ps', psArgs, { timeout: 3000, maxBuffer: 512 * 1024 });
2976
- const rows = stdout
2977
- .split('\n')
2978
- .map((line) => line.trim())
2979
- .filter(Boolean)
2980
- .map((line) => {
2981
- // ps -o format: PID PPID PGID TPGID STAT COMM ARGS
2982
- // COMM is a single token; ARGS is the rest of the line
2983
- const match = line.match(/^(\d+)\s+(\d+)\s+(-?\d+)\s+(-?\d+)\s+(\S+)\s+(\S+)\s+(.+)$/);
2984
- if (!match)
2985
- return null;
2986
- return {
2987
- pid: Number.parseInt(match[1] || '0', 10),
2988
- ppid: Number.parseInt(match[2] || '0', 10),
2989
- pgid: Number.parseInt(match[3] || '0', 10),
2990
- tpgid: Number.parseInt(match[4] || '0', 10),
2991
- stat: match[5] || '',
2992
- comm: match[6] || '',
2993
- args: match[7]?.trim() || '',
2994
- };
2995
- })
2996
- .filter((row) => row !== null);
3034
+ let rows;
3035
+ if (process.platform === 'linux' && pane.tty) {
3036
+ const paneTty = pane.tty.replace(/^\/dev\//, '');
3037
+ rows = (await getProcessSnapshot()).filter((row) => row.tty === paneTty);
3038
+ }
3039
+ else {
3040
+ const psArgs = pane.tty
3041
+ ? ['-t', pane.tty.replace(/^\/dev\//, ''), '-o', 'pid=,ppid=,pgid=,tpgid=,stat=,comm=,args=']
3042
+ : ['-o', 'pid=,ppid=,pgid=,tpgid=,stat=,comm=,args='];
3043
+ const { stdout } = await execFileAsync('ps', psArgs, { timeout: 3000, maxBuffer: 512 * 1024 });
3044
+ rows = stdout
3045
+ .split('\n')
3046
+ .map((line) => line.trim())
3047
+ .filter(Boolean)
3048
+ .map((line) => {
3049
+ const match = line.match(/^(\d+)\s+(\d+)\s+(-?\d+)\s+(-?\d+)\s+(\S+)\s+(\S+)\s+(.+)$/);
3050
+ if (!match)
3051
+ return null;
3052
+ return {
3053
+ pid: Number.parseInt(match[1] || '0', 10),
3054
+ ppid: Number.parseInt(match[2] || '0', 10),
3055
+ pgid: Number.parseInt(match[3] || '0', 10),
3056
+ tpgid: Number.parseInt(match[4] || '0', 10),
3057
+ stat: match[5] || '',
3058
+ comm: match[6] || '',
3059
+ args: match[7]?.trim() || '',
3060
+ };
3061
+ })
3062
+ .filter((row) => row !== null);
3063
+ }
2997
3064
  const selected = selectTmuxForegroundProgram({
2998
3065
  panePid: pane.pid,
2999
3066
  rows,
@@ -3463,6 +3530,115 @@ async function getTmuxLayout(sessionName) {
3463
3530
  inCopyMode: paneInMode === '1',
3464
3531
  };
3465
3532
  }
3533
+ let tmuxLayoutsSnapshot = null;
3534
+ let tmuxLayoutsSnapshotPromise = null;
3535
+ async function buildTmuxLayoutsSnapshot() {
3536
+ const [sessionsRaw, windowsRaw, panesRaw] = await Promise.all([
3537
+ runTmux([
3538
+ 'list-sessions',
3539
+ '-F',
3540
+ `#{session_id}${TMUX_DELIMITER}#{session_name}${TMUX_DELIMITER}#{window_id}${TMUX_DELIMITER}#{pane_id}${TMUX_DELIMITER}#{pane_in_mode}`,
3541
+ ]),
3542
+ runTmux([
3543
+ 'list-windows',
3544
+ '-a',
3545
+ '-F',
3546
+ `#{session_name}${TMUX_DELIMITER}#{window_id}${TMUX_DELIMITER}#{window_name}${TMUX_DELIMITER}#{window_index}${TMUX_DELIMITER}#{window_active}`,
3547
+ ]),
3548
+ runTmux([
3549
+ 'list-panes',
3550
+ '-a',
3551
+ '-F',
3552
+ `#{session_name}${TMUX_DELIMITER}#{window_id}${TMUX_DELIMITER}#{pane_id}${TMUX_DELIMITER}#{pane_index}${TMUX_DELIMITER}#{pane_active}${TMUX_DELIMITER}#{pane_width}${TMUX_DELIMITER}#{pane_height}${TMUX_DELIMITER}#{pane_top}${TMUX_DELIMITER}#{pane_left}${TMUX_DELIMITER}#{pane_current_command}${TMUX_DELIMITER}#{pane_pid}${TMUX_DELIMITER}#{pane_tty}${TMUX_DELIMITER}#{pane_title}${TMUX_DELIMITER}#{pane_current_path}`,
3553
+ ]),
3554
+ ]);
3555
+ const layouts = new Map();
3556
+ for (const line of sessionsRaw.trim().split('\n')) {
3557
+ if (!line)
3558
+ continue;
3559
+ const row = parseDelimitedRow(line, 5);
3560
+ if (!row)
3561
+ continue;
3562
+ const [sessionId, sessionName, activeWindowId, activePaneId, paneInMode] = row;
3563
+ layouts.set(sessionName, {
3564
+ sessionId,
3565
+ sessionName,
3566
+ windows: [],
3567
+ activeWindowId,
3568
+ activePaneId,
3569
+ inCopyMode: paneInMode === '1',
3570
+ });
3571
+ }
3572
+ const windowsById = new Map();
3573
+ for (const line of windowsRaw.trim().split('\n')) {
3574
+ if (!line)
3575
+ continue;
3576
+ const row = parseDelimitedRow(line, 5);
3577
+ if (!row)
3578
+ continue;
3579
+ const [sessionName, windowId, windowName, windowIndexRaw, windowActiveRaw] = row;
3580
+ const layout = layouts.get(sessionName);
3581
+ if (!layout)
3582
+ continue;
3583
+ const window = {
3584
+ id: windowId,
3585
+ name: windowName || '',
3586
+ index: parseInt(windowIndexRaw || '0', 10),
3587
+ active: windowActiveRaw === '1',
3588
+ panes: [],
3589
+ };
3590
+ layout.windows.push(window);
3591
+ windowsById.set(`${sessionName}\0${windowId}`, window);
3592
+ }
3593
+ for (const line of panesRaw.trim().split('\n')) {
3594
+ if (!line)
3595
+ continue;
3596
+ const row = parseDelimitedRow(line, 14);
3597
+ if (!row)
3598
+ continue;
3599
+ const [sessionName, windowId, paneId, paneIndexRaw, paneActiveRaw, widthRaw, heightRaw, topRaw, leftRaw, command, pidRaw, tty, title, currentPath] = row;
3600
+ const window = windowsById.get(`${sessionName}\0${windowId}`);
3601
+ if (!window)
3602
+ continue;
3603
+ window.panes.push({
3604
+ id: paneId,
3605
+ index: parseInt(paneIndexRaw || '0', 10),
3606
+ active: paneActiveRaw === '1',
3607
+ width: parseInt(widthRaw || '0', 10),
3608
+ height: parseInt(heightRaw || '0', 10),
3609
+ top: parseInt(topRaw || '0', 10),
3610
+ left: parseInt(leftRaw || '0', 10),
3611
+ command: command || '',
3612
+ pid: parseInt(pidRaw || '0', 10) || 0,
3613
+ tty: tty || '',
3614
+ title: title || '',
3615
+ currentPath: currentPath || '',
3616
+ });
3617
+ }
3618
+ return layouts;
3619
+ }
3620
+ async function getCachedTmuxLayout(sessionName) {
3621
+ if (tmuxLayoutsSnapshot && Date.now() - tmuxLayoutsSnapshot.fetchedAt < TMUX_LAYOUT_CACHE_TTL_MS) {
3622
+ const cached = tmuxLayoutsSnapshot.layouts.get(sessionName);
3623
+ if (cached)
3624
+ return cached;
3625
+ }
3626
+ if (!tmuxLayoutsSnapshotPromise) {
3627
+ tmuxLayoutsSnapshotPromise = buildTmuxLayoutsSnapshot().then((layouts) => {
3628
+ tmuxLayoutsSnapshot = { layouts, fetchedAt: Date.now() };
3629
+ return layouts;
3630
+ }).finally(() => {
3631
+ tmuxLayoutsSnapshotPromise = null;
3632
+ });
3633
+ }
3634
+ const layouts = await tmuxLayoutsSnapshotPromise;
3635
+ const layout = layouts.get(sessionName);
3636
+ if (layout)
3637
+ return layout;
3638
+ // A just-created session can race the global snapshot. Keep that rare path
3639
+ // correct without returning stale metadata for the new terminal.
3640
+ return getTmuxLayout(sessionName);
3641
+ }
3466
3642
  async function getRestoreHistory(sessionId, session) {
3467
3643
  // In tmux mode, scrollback belongs to tmux itself rather than the app layer,
3468
3644
  // so capture the active pane to rebuild the visible screen on refresh.
@@ -5650,7 +5826,7 @@ router.get('/:sessionId/stream', async (req, res) => {
5650
5826
  session.activeProgram = ap;
5651
5827
  }
5652
5828
  else if (session.mode === 'tmux' && session.tmuxSessionName) {
5653
- const layout = await getTmuxLayout(session.tmuxSessionName);
5829
+ const layout = await getCachedTmuxLayout(session.tmuxSessionName);
5654
5830
  const activePane = getActivePaneFromLayout(layout);
5655
5831
  const ap = activePane
5656
5832
  ? await resolveTmuxPaneProgram(activePane)
@@ -5727,7 +5903,7 @@ router.get('/:sessionId/stream', async (req, res) => {
5727
5903
  return;
5728
5904
  }
5729
5905
  try {
5730
- const layout = await getTmuxLayout(session.tmuxSessionName);
5906
+ const layout = await getCachedTmuxLayout(session.tmuxSessionName);
5731
5907
  // Resolve the active program — try ps-based detection for generic commands
5732
5908
  const activePane = getActivePaneFromLayout(layout);
5733
5909
  if (activePane) {
@@ -6336,7 +6512,7 @@ export function handleTerminalWebSocket(ws, sessionId, clientId, options = {}) {
6336
6512
  session.activeProgram = ap;
6337
6513
  }
6338
6514
  else if (session.mode === 'tmux' && session.tmuxSessionName) {
6339
- const layout = await getTmuxLayout(session.tmuxSessionName);
6515
+ const layout = await getCachedTmuxLayout(session.tmuxSessionName);
6340
6516
  const ap = getActiveProgramFromTmuxLayout(layout);
6341
6517
  if (ap)
6342
6518
  session.activeProgram = ap;
@@ -6419,7 +6595,7 @@ export function handleTerminalWebSocket(ws, sessionId, clientId, options = {}) {
6419
6595
  if (ws.readyState !== ws.OPEN)
6420
6596
  return;
6421
6597
  try {
6422
- const layout = await getTmuxLayout(session.tmuxSessionName);
6598
+ const layout = await getCachedTmuxLayout(session.tmuxSessionName);
6423
6599
  // Update active program — try ps-based detection for generic commands
6424
6600
  const activePane = getActivePaneFromLayout(layout);
6425
6601
  let ap = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termdock",
3
- "version": "1.4.98",
3
+ "version": "1.4.99",
4
4
  "description": "A complete web-based terminal application with modern UI",
5
5
  "type": "module",
6
6
  "main": ".desktop-dist/main.js",
@@ -24,7 +24,7 @@
24
24
  "dev:client": "vite",
25
25
  "dev:server": "cross-env PORT=9835 tsx watch src/server/entry.ts",
26
26
  "build": "npm run clean && npm run build:client && npm run build:server && npm run build:runtime-manifest",
27
- "build:desktop": "node scripts/prepare-desktop-tokens.mjs && tsc -p tsconfig.desktop.json",
27
+ "build:desktop": "node scripts/prepare-desktop-tokens.mjs && tsc -p tsconfig.desktop.json && esbuild desktop/preload.cts --bundle --platform=node --format=cjs --external:electron --outfile=.desktop-dist/preload.cjs",
28
28
  "desktop:dev": "npm run build && npm run build:desktop && electron-forge start",
29
29
  "desktop:prepare": "npm run build && npm run build:desktop && node scripts/prepare-desktop-runtime.mjs",
30
30
  "desktop:make": "node scripts/ensure-macos-appdmg.mjs && npm run desktop:prepare && electron-forge make",
@@ -111,6 +111,7 @@
111
111
  "concurrently": "^8.2.0",
112
112
  "cross-env": "^10.1.0",
113
113
  "electron": "^43.2.0",
114
+ "esbuild": "^0.27.7",
114
115
  "eslint": "^9.39.2",
115
116
  "jsdom": "^24.0.0",
116
117
  "postcss": "^8.4.0",
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
3
  "packageName": "termdock",
4
- "version": "1.4.98",
4
+ "version": "1.4.99",
5
5
  "runtimeProtocolVersion": 1,
6
6
  "minimumDesktopVersion": "1.4.46",
7
7
  "nodeMajor": 22,
8
8
  "dependencyHash": "sha256-emuXnf9w0kJF3ksMJVvdI0u8oLHKwZJ2/OzHBAXulZs=",
9
- "serverBundleHash": "sha256-exDTuohFamGGte9Col2+A4qvyulcp5DfV4U3bOD1gWo=",
9
+ "serverBundleHash": "sha256-HoYNZvRYLVItcMdTryy29alYQdle/LCxvw2bx4YzWfk=",
10
10
  "entrypoint": "dist/server/cli.js",
11
11
  "clientEntrypoint": "dist/client/index.html"
12
12
  }
@@ -1 +0,0 @@
1
- import{U as a,C as n}from"./mermaid.core-D4Yy5OxZ.js";const t=(r,o)=>a.lang.round(n.parse(r)[o]);export{t as c};
@@ -1 +0,0 @@
1
- import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-BRo0103H.js";import{b as i}from"./mermaid.core-D4Yy5OxZ.js";import"./chunk-5VM5RSS4-CVk_pzpf.js";import"./chunk-XXDRQBXY-BuWiQ0uq.js";import"./chunk-VR4S4FIN-CW-wSkDE.js";import"./chunk-32BRIVSS-B1dA4l5A.js";import"./purify.es-CC4Brkr_.js";import"./index-BuMMW0wS.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
@@ -1 +0,0 @@
1
- import{s as a,a as s,c as t,C as e}from"./chunk-V7JOEXUC-BRo0103H.js";import{b as i}from"./mermaid.core-D4Yy5OxZ.js";import"./chunk-5VM5RSS4-CVk_pzpf.js";import"./chunk-XXDRQBXY-BuWiQ0uq.js";import"./chunk-VR4S4FIN-CW-wSkDE.js";import"./chunk-32BRIVSS-B1dA4l5A.js";import"./purify.es-CC4Brkr_.js";import"./index-BuMMW0wS.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:t,get db(){return new e},renderer:s,styles:a,init:i(r=>{r.class||(r.class={}),r.class.arrowMarkerAbsolute=r.arrowMarkerAbsolute},"init")};export{g as diagram};
@@ -1 +0,0 @@
1
- import{b as r,a as e,s as a,S as i}from"./chunk-EX3LRPZG-B46zhCa0.js";import{b as s}from"./mermaid.core-D4Yy5OxZ.js";import"./chunk-XXDRQBXY-BuWiQ0uq.js";import"./chunk-VR4S4FIN-CW-wSkDE.js";import"./chunk-32BRIVSS-B1dA4l5A.js";import"./purify.es-CC4Brkr_.js";import"./index-BuMMW0wS.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var g={parser:a,get db(){return new i(2)},renderer:e,styles:r,init:s(t=>{t.state||(t.state={}),t.state.arrowMarkerAbsolute=t.arrowMarkerAbsolute},"init")};export{g as diagram};
@@ -1,8 +0,0 @@
1
- import{c as r,s as e}from"./flowDiagram-23GEKE2U-ggl4zaY9.js";import{b as o}from"./mermaid.core-D4Yy5OxZ.js";import"./chunk-5VM5RSS4-CVk_pzpf.js";import"./chunk-XXDRQBXY-BuWiQ0uq.js";import"./chunk-VR4S4FIN-CW-wSkDE.js";import"./chunk-32BRIVSS-B1dA4l5A.js";import"./purify.es-CC4Brkr_.js";import"./channel-DPt818Jx.js";import"./index-BuMMW0wS.js";import"./react-XDCjal2T.js";import"./dnd-BCFAPC0L.js";import"./terminal-C7X98Amf.js";var a=o(t=>`${e(t)}
2
- .swimlane.cluster rect {
3
- stroke: ${t.clusterBorder} !important;
4
- }
5
- [data-look="neo"].cluster rect {
6
- filter: none;
7
- }
8
- `,"getStyles"),m=a,v=r({defaultLayout:"swimlane",styles:m});export{v as diagram};