aline-ai 0.7.2__py3-none-any.whl → 0.7.4__py3-none-any.whl

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.
@@ -14,6 +14,7 @@ import shutil
14
14
  import stat
15
15
  import subprocess
16
16
  import sys
17
+ import threading
17
18
  import time
18
19
  import traceback
19
20
  import uuid
@@ -47,6 +48,10 @@ OPT_ATTENTION = "@aline_attention"
47
48
  OPT_CREATED_AT = "@aline_created_at"
48
49
  OPT_NO_TRACK = "@aline_no_track"
49
50
 
51
+ # Default outer layout preferences (tmux mode).
52
+ # Note: tmux "pixels" are terminal cell columns; Textual has no notion of pixels.
53
+ DEFAULT_DASHBOARD_PANE_WIDTH_COLS = 45
54
+
50
55
 
51
56
  @dataclass(frozen=True)
52
57
  class InnerWindow:
@@ -62,6 +67,9 @@ class InnerWindow:
62
67
  attention: str | None = None # "permission_request", "stop", or None
63
68
  created_at: float | None = None # Unix timestamp when window was created
64
69
  no_track: bool = False # Whether tracking is disabled for this terminal
70
+ pane_pid: int | None = None # PID of the initial process in the pane
71
+ pane_current_command: str | None = None # Foreground process in the pane
72
+ pane_tty: str | None = None # Controlling TTY for processes in the pane
65
73
 
66
74
 
67
75
  def tmux_available() -> bool:
@@ -93,26 +101,31 @@ def tmux_version() -> tuple[int, int] | None:
93
101
  return int(match.group(1)), int(match.group(2))
94
102
 
95
103
 
96
- def _run_tmux(args: Sequence[str], *, capture: bool = False) -> subprocess.CompletedProcess[str]:
97
- return subprocess.run(
98
- ["tmux", *args],
99
- text=True,
100
- capture_output=capture,
101
- check=False,
102
- )
104
+ def _run_tmux(
105
+ args: Sequence[str], *, capture: bool = False, timeout_s: float | None = None
106
+ ) -> subprocess.CompletedProcess[str]:
107
+ kwargs: dict[str, object] = {
108
+ "text": True,
109
+ "capture_output": capture,
110
+ "check": False,
111
+ }
112
+ # Keep keyword list minimal for test fakes that don't accept extra kwargs.
113
+ if timeout_s is not None:
114
+ kwargs["timeout"] = timeout_s
115
+ return subprocess.run(["tmux", *args], **kwargs) # type: ignore[arg-type]
103
116
 
104
117
 
105
118
  def _run_outer_tmux(
106
- args: Sequence[str], *, capture: bool = False
119
+ args: Sequence[str], *, capture: bool = False, timeout_s: float | None = None
107
120
  ) -> subprocess.CompletedProcess[str]:
108
121
  """Run tmux commands against the dedicated outer server socket."""
109
- return _run_tmux(["-L", OUTER_SOCKET, *args], capture=capture)
122
+ return _run_tmux(["-L", OUTER_SOCKET, *args], capture=capture, timeout_s=timeout_s)
110
123
 
111
124
 
112
125
  def _run_inner_tmux(
113
- args: Sequence[str], *, capture: bool = False
126
+ args: Sequence[str], *, capture: bool = False, timeout_s: float | None = None
114
127
  ) -> subprocess.CompletedProcess[str]:
115
- return _run_tmux(["-L", INNER_SOCKET, *args], capture=capture)
128
+ return _run_tmux(["-L", INNER_SOCKET, *args], capture=capture, timeout_s=timeout_s)
116
129
 
117
130
 
118
131
  def _python_dashboard_command() -> str:
@@ -196,16 +209,17 @@ def _session_id_from_transcript_path(transcript_path: str | None) -> str | None:
196
209
  def _load_terminal_state_from_db() -> dict[str, dict[str, str]]:
197
210
  """Load terminal state from database (best-effort)."""
198
211
  import time as _time
212
+
199
213
  t0 = _time.time()
200
214
  try:
201
215
  from ..db import get_database
202
216
 
203
217
  t1 = _time.time()
204
218
  db = get_database(read_only=True)
205
- logger.info(f"[PERF] _load_terminal_state_from_db get_database: {_time.time() - t1:.3f}s")
219
+ logger.debug(f"[PERF] _load_terminal_state_from_db get_database: {_time.time() - t1:.3f}s")
206
220
  t2 = _time.time()
207
221
  agents = db.list_agents(status="active", limit=100)
208
- logger.info(f"[PERF] _load_terminal_state_from_db list_agents: {_time.time() - t2:.3f}s")
222
+ logger.debug(f"[PERF] _load_terminal_state_from_db list_agents: {_time.time() - t2:.3f}s")
209
223
 
210
224
  out: dict[str, dict[str, str]] = {}
211
225
  for agent in agents:
@@ -255,6 +269,12 @@ def _load_terminal_state_from_json() -> dict[str, dict[str, str]]:
255
269
  return {}
256
270
 
257
271
 
272
+ _TERMINAL_STATE_CACHE_LOCK = threading.Lock()
273
+ _TERMINAL_STATE_CACHE: dict[str, dict[str, str]] | None = None
274
+ _TERMINAL_STATE_CACHE_AT: float = 0.0
275
+ _TERMINAL_STATE_CACHE_TTL_S: float = 1.5
276
+
277
+
258
278
  def _load_terminal_state() -> dict[str, dict[str, str]]:
259
279
  """Load terminal state.
260
280
 
@@ -264,6 +284,13 @@ def _load_terminal_state() -> dict[str, dict[str, str]]:
264
284
 
265
285
  Merges both sources, with DB taking precedence.
266
286
  """
287
+ global _TERMINAL_STATE_CACHE, _TERMINAL_STATE_CACHE_AT
288
+ now = time.monotonic()
289
+ with _TERMINAL_STATE_CACHE_LOCK:
290
+ cache = _TERMINAL_STATE_CACHE
291
+ if cache is not None and (now - _TERMINAL_STATE_CACHE_AT) <= _TERMINAL_STATE_CACHE_TTL_S:
292
+ return dict(cache)
293
+
267
294
  # Phase 1: Load from database
268
295
  db_state = _load_terminal_state_from_db()
269
296
 
@@ -274,6 +301,10 @@ def _load_terminal_state() -> dict[str, dict[str, str]]:
274
301
  result = dict(json_state)
275
302
  result.update(db_state)
276
303
 
304
+ with _TERMINAL_STATE_CACHE_LOCK:
305
+ _TERMINAL_STATE_CACHE = dict(result)
306
+ _TERMINAL_STATE_CACHE_AT = time.monotonic()
307
+
277
308
  return result
278
309
 
279
310
 
@@ -470,6 +501,7 @@ def bootstrap_dashboard_into_tmux() -> None:
470
501
 
471
502
  # Enable mouse for the managed session only.
472
503
  _run_outer_tmux(["set-option", "-t", OUTER_SESSION, "mouse", "on"])
504
+ _disable_outer_border_resize()
473
505
 
474
506
  # Disable status bar for cleaner UI (Aline sessions only).
475
507
  _run_outer_tmux(["set-option", "-t", OUTER_SESSION, "status", "off"])
@@ -505,6 +537,9 @@ def bootstrap_dashboard_into_tmux() -> None:
505
537
  )
506
538
  _run_outer_tmux(["select-window", "-t", f"{OUTER_SESSION}:{OUTER_WINDOW}"])
507
539
 
540
+ # Best-effort: enforce a stable dashboard pane width if the terminal pane already exists.
541
+ _set_outer_dashboard_pane_width(DEFAULT_DASHBOARD_PANE_WIDTH_COLS)
542
+
508
543
  # Sanity-check before exec'ing into tmux attach. If this fails, fall back to non-tmux mode.
509
544
  ready = _run_outer_tmux(["has-session", "-t", OUTER_SESSION], capture=True)
510
545
  if ready.returncode != 0:
@@ -690,6 +725,7 @@ def ensure_right_pane(width_percent: int = 50) -> bool:
690
725
  )
691
726
  panes = _parse_lines(panes_out)
692
727
  if len(panes) >= 2:
728
+ _set_outer_dashboard_pane_width(DEFAULT_DASHBOARD_PANE_WIDTH_COLS)
693
729
  return True
694
730
 
695
731
  # Split from the dashboard pane to keep it on the left.
@@ -704,20 +740,156 @@ def ensure_right_pane(width_percent: int = 50) -> bool:
704
740
  f"{OUTER_SESSION}:{OUTER_WINDOW}.0",
705
741
  "-d",
706
742
  attach_cmd,
707
- ]
743
+ ],
744
+ capture=True,
708
745
  )
709
- return split.returncode == 0
746
+ if split.returncode != 0:
747
+ detail = (split.stderr or split.stdout or "").strip()
748
+ if detail:
749
+ logger.warning(f"ensure_right_pane split-window failed: {detail}")
750
+ if split.returncode == 0:
751
+ _set_outer_dashboard_pane_width(DEFAULT_DASHBOARD_PANE_WIDTH_COLS)
752
+ return True
753
+ return False
754
+
755
+
756
+ def ensure_right_pane_ready(width_percent: int = 50) -> bool:
757
+ """Ensure the right pane exists and is attached to the inner tmux session."""
758
+ try:
759
+ ok = ensure_right_pane(width_percent)
760
+ except TypeError:
761
+ # Tests and some callers monkeypatch ensure_right_pane() as a no-arg lambda.
762
+ ok = ensure_right_pane()
763
+ if not ok:
764
+ return False
765
+
766
+ # Best-effort: enforce a stable dashboard pane width whenever we touch the outer layout.
767
+ _set_outer_dashboard_pane_width(DEFAULT_DASHBOARD_PANE_WIDTH_COLS)
768
+
769
+ attach_cmd = shlex.join(["tmux", "-L", INNER_SOCKET, "attach", "-t", INNER_SESSION])
770
+
771
+ # If the right pane exists but isn't running `tmux attach`, it may look "blank" or stale.
772
+ try:
773
+ proc = _run_outer_tmux(
774
+ [
775
+ "display-message",
776
+ "-p",
777
+ "-t",
778
+ f"{OUTER_SESSION}:{OUTER_WINDOW}.1",
779
+ "#{pane_current_command}",
780
+ ],
781
+ capture=True,
782
+ )
783
+ current_cmd = (proc.stdout or "").strip()
784
+ except Exception:
785
+ current_cmd = ""
786
+
787
+ if current_cmd and current_cmd != "tmux":
788
+ respawn = _run_outer_tmux(
789
+ [
790
+ "respawn-pane",
791
+ "-k",
792
+ "-t",
793
+ f"{OUTER_SESSION}:{OUTER_WINDOW}.1",
794
+ attach_cmd,
795
+ ],
796
+ capture=True,
797
+ )
798
+ if respawn.returncode != 0:
799
+ detail = (respawn.stderr or respawn.stdout or "").strip()
800
+ if detail:
801
+ logger.warning(f"ensure_right_pane_ready respawn failed: {detail}")
802
+ return False
803
+
804
+ return True
805
+
806
+
807
+ def _disable_outer_border_resize() -> None:
808
+ """Disable mouse drag-to-resize on pane borders (outer dashboard tmux server only)."""
809
+ try:
810
+ # tmux enables border resizing via MouseDrag1Border. Unbind it on our dedicated server
811
+ # to avoid accidental resizing when selecting text near the pane divider.
812
+ for key in ("MouseDrag1Border", "MouseDown1Border", "MouseDragEnd1Border", "MouseUp1Border"):
813
+ _run_outer_tmux(["unbind-key", "-T", "root", key], capture=True)
814
+ except Exception:
815
+ return
816
+
817
+
818
+ def _set_outer_dashboard_pane_width(width_cols: int) -> None:
819
+ """Best-effort: size the left dashboard pane to a fixed width (in terminal columns)."""
820
+ try:
821
+ w = int(width_cols)
822
+ if w <= 0:
823
+ return
824
+ except Exception:
825
+ return
826
+
827
+ try:
828
+ # Avoid assuming pane indexes are 0/1. Determine the leftmost pane by geometry.
829
+ panes_out = (
830
+ _run_outer_tmux(
831
+ [
832
+ "list-panes",
833
+ "-t",
834
+ f"{OUTER_SESSION}:{OUTER_WINDOW}",
835
+ "-F",
836
+ "#{pane_id}\t#{pane_left}\t#{pane_index}",
837
+ ],
838
+ capture=True,
839
+ timeout_s=0.2,
840
+ ).stdout
841
+ or ""
842
+ )
843
+ panes: list[tuple[int, int, str]] = []
844
+ for line in _parse_lines(panes_out):
845
+ parts = line.split("\t")
846
+ if len(parts) < 2:
847
+ continue
848
+ pane_id = (parts[0] or "").strip()
849
+ if not pane_id:
850
+ continue
851
+ try:
852
+ pane_left = int((parts[1] or "0").strip())
853
+ except Exception:
854
+ continue
855
+ pane_index = 0
856
+ if len(parts) > 2 and (parts[2] or "").strip():
857
+ try:
858
+ pane_index = int(parts[2].strip())
859
+ except Exception:
860
+ pane_index = 0
861
+ panes.append((pane_left, pane_index, pane_id))
862
+
863
+ # Only enforce widths when there are two panes (dashboard + terminal).
864
+ if len(panes) < 2:
865
+ return
866
+
867
+ panes.sort(key=lambda t: (t[0], t[1]))
868
+ leftmost_pane_id = panes[0][2]
869
+ _run_outer_tmux(
870
+ ["resize-pane", "-t", leftmost_pane_id, "-x", str(w)],
871
+ capture=True,
872
+ timeout_s=0.2,
873
+ )
874
+ except Exception:
875
+ return
876
+
877
+
878
+ def enforce_outer_dashboard_pane_width(width_cols: int = DEFAULT_DASHBOARD_PANE_WIDTH_COLS) -> None:
879
+ """Best-effort: enforce the fixed dashboard pane width for the outer tmux layout."""
880
+ _set_outer_dashboard_pane_width(width_cols)
710
881
 
711
882
 
712
883
  def list_inner_windows() -> list[InnerWindow]:
713
884
  import time as _time
885
+
714
886
  t0 = _time.time()
715
887
  if not ensure_inner_session():
716
888
  return []
717
- logger.info(f"[PERF] list_inner_windows ensure_inner_session: {_time.time() - t0:.3f}s")
889
+ logger.debug(f"[PERF] list_inner_windows ensure_inner_session: {_time.time() - t0:.3f}s")
718
890
  t1 = _time.time()
719
891
  state = _load_terminal_state()
720
- logger.info(f"[PERF] list_inner_windows _load_terminal_state: {_time.time() - t1:.3f}s")
892
+ logger.debug(f"[PERF] list_inner_windows _load_terminal_state: {_time.time() - t1:.3f}s")
721
893
  out = (
722
894
  _run_inner_tmux(
723
895
  [
@@ -743,7 +915,7 @@ def list_inner_windows() -> list[InnerWindow]:
743
915
  + OPT_CREATED_AT
744
916
  + "}\t#{"
745
917
  + OPT_NO_TRACK
746
- + "}",
918
+ + "}\t#{pane_pid}\t#{pane_current_command}\t#{pane_tty}",
747
919
  ],
748
920
  capture=True,
749
921
  ).stdout
@@ -773,6 +945,15 @@ def list_inner_windows() -> list[InnerWindow]:
773
945
  pass
774
946
  no_track_str = parts[11] if len(parts) > 11 and parts[11] else None
775
947
  no_track = no_track_str == "1"
948
+ pane_pid_str = parts[12] if len(parts) > 12 and parts[12] else None
949
+ pane_pid: int | None = None
950
+ if pane_pid_str:
951
+ try:
952
+ pane_pid = int(pane_pid_str)
953
+ except ValueError:
954
+ pass
955
+ pane_current_command = parts[13] if len(parts) > 13 and parts[13] else None
956
+ pane_tty = parts[14] if len(parts) > 14 and parts[14] else None
776
957
 
777
958
  if terminal_id:
778
959
  persisted = state.get(terminal_id) or {}
@@ -831,6 +1012,9 @@ def list_inner_windows() -> list[InnerWindow]:
831
1012
  attention=attention,
832
1013
  created_at=created_at,
833
1014
  no_track=no_track,
1015
+ pane_pid=pane_pid,
1016
+ pane_current_command=pane_current_command,
1017
+ pane_tty=pane_tty,
834
1018
  )
835
1019
  )
836
1020
  # Sort by creation time (newest first). Windows without created_at go to the bottom.
@@ -838,8 +1022,36 @@ def list_inner_windows() -> list[InnerWindow]:
838
1022
  return windows
839
1023
 
840
1024
 
1025
+ def list_outer_panes(*, timeout_s: float = 0.2) -> list[str]:
1026
+ """List panes in the outer dashboard window (best-effort).
1027
+
1028
+ Intended for lightweight watchdog checks; returns tab-delimited lines in the same
1029
+ format as `collect_tmux_debug_state()["outer_panes"]["stdout"]`.
1030
+ """
1031
+ if not tmux_available():
1032
+ return []
1033
+ try:
1034
+ proc = _run_outer_tmux(
1035
+ [
1036
+ "list-panes",
1037
+ "-t",
1038
+ f"{OUTER_SESSION}:{OUTER_WINDOW}",
1039
+ "-F",
1040
+ "#{pane_index}\t#{pane_active}\t#{pane_current_command}\t#{pane_pid}\t#{pane_tty}",
1041
+ ],
1042
+ capture=True,
1043
+ timeout_s=timeout_s,
1044
+ )
1045
+ except Exception:
1046
+ return []
1047
+ if proc.returncode != 0:
1048
+ return []
1049
+ return [ln for ln in (proc.stdout or "").splitlines() if ln.strip()]
1050
+
1051
+
841
1052
  def set_inner_window_options(window_id: str, options: dict[str, str]) -> bool:
842
1053
  import time as _time
1054
+
843
1055
  if not ensure_inner_session():
844
1056
  return False
845
1057
  ok = True
@@ -848,7 +1060,7 @@ def set_inner_window_options(window_id: str, options: dict[str, str]) -> bool:
848
1060
  # Important: these are per-window (not session-wide) to avoid cross-tab clobbering.
849
1061
  if _run_inner_tmux(["set-option", "-w", "-t", window_id, key, value]).returncode != 0:
850
1062
  ok = False
851
- logger.info(f"[PERF] set_inner_window_options {key}: {_time.time() - t0:.3f}s")
1063
+ logger.debug(f"[PERF] set_inner_window_options {key}: {_time.time() - t0:.3f}s")
852
1064
  return ok
853
1065
 
854
1066
 
@@ -868,15 +1080,17 @@ def create_inner_window(
868
1080
  no_track: bool = False,
869
1081
  ) -> InnerWindow | None:
870
1082
  import time as _time
1083
+
871
1084
  t0 = _time.time()
872
- logger.info(f"[PERF] create_inner_window START")
873
- if not ensure_right_pane():
1085
+ logger.debug("[PERF] create_inner_window START")
1086
+ if not ensure_right_pane_ready():
1087
+ logger.warning("create_inner_window: right pane unavailable")
874
1088
  return None
875
- logger.info(f"[PERF] create_inner_window ensure_right_pane: {_time.time() - t0:.3f}s")
1089
+ logger.debug(f"[PERF] create_inner_window ensure_right_pane: {_time.time() - t0:.3f}s")
876
1090
 
877
1091
  t1 = _time.time()
878
1092
  existing = list_inner_windows()
879
- logger.info(f"[PERF] create_inner_window list_inner_windows: {_time.time() - t1:.3f}s")
1093
+ logger.debug(f"[PERF] create_inner_window list_inner_windows: {_time.time() - t1:.3f}s")
880
1094
  name = _unique_name((w.window_name for w in existing), base_name)
881
1095
 
882
1096
  # Record creation time before creating the window
@@ -897,8 +1111,11 @@ def create_inner_window(
897
1111
  ],
898
1112
  capture=True,
899
1113
  )
900
- logger.info(f"[PERF] create_inner_window new-window: {_time.time() - t2:.3f}s")
1114
+ logger.debug(f"[PERF] create_inner_window new-window: {_time.time() - t2:.3f}s")
901
1115
  if proc.returncode != 0:
1116
+ detail = (proc.stderr or proc.stdout or "").strip()
1117
+ if detail:
1118
+ logger.warning(f"create_inner_window new-window failed: {detail}")
902
1119
  return None
903
1120
 
904
1121
  created = _parse_lines(proc.stdout or "")
@@ -923,7 +1140,7 @@ def create_inner_window(
923
1140
  opts.setdefault(OPT_NO_TRACK, "")
924
1141
  t3 = _time.time()
925
1142
  set_inner_window_options(window_id, opts)
926
- logger.info(f"[PERF] create_inner_window set_options: {_time.time() - t3:.3f}s")
1143
+ logger.debug(f"[PERF] create_inner_window set_options: {_time.time() - t3:.3f}s")
927
1144
 
928
1145
  _run_inner_tmux(["select-window", "-t", window_id])
929
1146
 
@@ -939,19 +1156,26 @@ def create_inner_window(
939
1156
 
940
1157
 
941
1158
  def select_inner_window(window_id: str) -> bool:
942
- if not ensure_right_pane():
1159
+ if not ensure_right_pane_ready():
1160
+ return False
1161
+ proc = _run_inner_tmux(["select-window", "-t", window_id], capture=True)
1162
+ if proc.returncode != 0:
1163
+ detail = (proc.stderr or proc.stdout or "").strip()
1164
+ if detail:
1165
+ logger.warning(f"select_inner_window failed ({window_id}): {detail}")
943
1166
  return False
944
- return _run_inner_tmux(["select-window", "-t", window_id]).returncode == 0
1167
+ return True
945
1168
 
946
1169
 
947
1170
  def focus_right_pane() -> bool:
948
1171
  """Focus the right pane (terminal area) in the outer tmux layout."""
949
- return (
950
- _run_outer_tmux(
951
- ["select-pane", "-t", f"{OUTER_SESSION}:{OUTER_WINDOW}.1"]
952
- ).returncode
953
- == 0
954
- )
1172
+ proc = _run_outer_tmux(["select-pane", "-t", f"{OUTER_SESSION}:{OUTER_WINDOW}.1"], capture=True)
1173
+ if proc.returncode != 0:
1174
+ detail = (proc.stderr or proc.stdout or "").strip()
1175
+ if detail:
1176
+ logger.warning(f"focus_right_pane failed: {detail}")
1177
+ return False
1178
+ return True
955
1179
 
956
1180
 
957
1181
  def clear_attention(window_id: str) -> bool:
@@ -991,3 +1215,94 @@ def get_active_context_id(*, allowed_providers: set[str] | None = None) -> str |
991
1215
 
992
1216
  context_id = (active.context_id or "").strip()
993
1217
  return context_id or None
1218
+
1219
+
1220
+ def collect_tmux_debug_state() -> dict[str, object]:
1221
+ """Best-effort snapshot of tmux state for diagnosing blank/stuck panes.
1222
+
1223
+ This must be non-intrusive: it should not create sessions or change state.
1224
+ """
1225
+
1226
+ def _cap(proc: subprocess.CompletedProcess[str] | None) -> dict[str, object]:
1227
+ if proc is None:
1228
+ return {}
1229
+
1230
+ def _trim(s: str | None) -> str:
1231
+ text = (s or "").strip()
1232
+ if len(text) > 4000:
1233
+ return text[:4000] + "…(truncated)"
1234
+ return text
1235
+
1236
+ return {
1237
+ "rc": int(getattr(proc, "returncode", -1)),
1238
+ "stdout": _trim(getattr(proc, "stdout", "")),
1239
+ "stderr": _trim(getattr(proc, "stderr", "")),
1240
+ }
1241
+
1242
+ state: dict[str, object] = {
1243
+ "tmux_available": tmux_available(),
1244
+ "in_tmux": in_tmux(),
1245
+ "managed_env": managed_env_enabled(),
1246
+ "outer_socket": OUTER_SOCKET,
1247
+ "inner_socket": INNER_SOCKET,
1248
+ "outer_session": OUTER_SESSION,
1249
+ "outer_window": OUTER_WINDOW,
1250
+ "inner_session": INNER_SESSION,
1251
+ }
1252
+
1253
+ if not tmux_available():
1254
+ return state
1255
+
1256
+ try:
1257
+ state["outer_has_session"] = _cap(
1258
+ _run_outer_tmux(["has-session", "-t", OUTER_SESSION], capture=True, timeout_s=0.5)
1259
+ )
1260
+ state["outer_panes"] = _cap(
1261
+ _run_outer_tmux(
1262
+ [
1263
+ "list-panes",
1264
+ "-t",
1265
+ f"{OUTER_SESSION}:{OUTER_WINDOW}",
1266
+ "-F",
1267
+ "#{pane_index}\t#{pane_active}\t#{pane_current_command}\t#{pane_pid}\t#{pane_tty}",
1268
+ ],
1269
+ capture=True,
1270
+ timeout_s=0.5,
1271
+ )
1272
+ )
1273
+ except Exception:
1274
+ pass
1275
+
1276
+ try:
1277
+ state["inner_has_session"] = _cap(
1278
+ _run_inner_tmux(["has-session", "-t", INNER_SESSION], capture=True, timeout_s=0.5)
1279
+ )
1280
+ state["inner_windows"] = _cap(
1281
+ _run_inner_tmux(
1282
+ [
1283
+ "list-windows",
1284
+ "-t",
1285
+ INNER_SESSION,
1286
+ "-F",
1287
+ "#{window_id}\t#{window_index}\t#{window_name}\t#{window_active}\t#{"
1288
+ + OPT_TERMINAL_ID
1289
+ + "}\t#{"
1290
+ + OPT_PROVIDER
1291
+ + "}\t#{"
1292
+ + OPT_SESSION_TYPE
1293
+ + "}\t#{"
1294
+ + OPT_SESSION_ID
1295
+ + "}\t#{"
1296
+ + OPT_CONTEXT_ID
1297
+ + "}\t#{"
1298
+ + OPT_ATTENTION
1299
+ + "}",
1300
+ ],
1301
+ capture=True,
1302
+ timeout_s=0.5,
1303
+ )
1304
+ )
1305
+ except Exception:
1306
+ pass
1307
+
1308
+ return state