aline-ai 0.6.3__py3-none-any.whl → 0.6.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.
@@ -516,9 +516,21 @@ def ensure_inner_session() -> bool:
516
516
  return False
517
517
 
518
518
  if _run_inner_tmux(["has-session", "-t", INNER_SESSION]).returncode != 0:
519
- if _run_inner_tmux(["new-session", "-d", "-s", INNER_SESSION]).returncode != 0:
519
+ # Create a stable "home" window so user-created terminals can use names like "zsh"
520
+ # without always becoming "zsh-2".
521
+ if (
522
+ _run_inner_tmux(["new-session", "-d", "-s", INNER_SESSION, "-n", "home"]).returncode
523
+ != 0
524
+ ):
520
525
  return False
521
526
 
527
+ # Ensure the default/home window stays named "home" (tmux auto-rename would otherwise
528
+ # change it to "zsh"/"opencode" depending on the last foreground command).
529
+ try:
530
+ _ensure_inner_home_window()
531
+ except Exception:
532
+ pass
533
+
522
534
  # Dedicated inner server; safe to enable mouse globally there.
523
535
  _run_inner_tmux(["set-option", "-g", "mouse", "on"])
524
536
 
@@ -537,6 +549,101 @@ def ensure_inner_session() -> bool:
537
549
  return True
538
550
 
539
551
 
552
+ def _ensure_inner_home_window() -> None:
553
+ """Ensure the inner session has a reserved, non-renaming 'home' window (best-effort)."""
554
+ if _run_inner_tmux(["has-session", "-t", INNER_SESSION]).returncode != 0:
555
+ return
556
+
557
+ out = (
558
+ _run_inner_tmux(
559
+ [
560
+ "list-windows",
561
+ "-t",
562
+ INNER_SESSION,
563
+ "-F",
564
+ "#{window_id}\t#{window_index}\t#{window_name}\t#{"
565
+ + OPT_TERMINAL_ID
566
+ + "}\t#{"
567
+ + OPT_PROVIDER
568
+ + "}\t#{"
569
+ + OPT_SESSION_TYPE
570
+ + "}\t#{"
571
+ + OPT_CONTEXT_ID
572
+ + "}\t#{"
573
+ + OPT_CREATED_AT
574
+ + "}\t#{"
575
+ + OPT_NO_TRACK
576
+ + "}",
577
+ ],
578
+ capture=True,
579
+ ).stdout
580
+ or ""
581
+ )
582
+
583
+ candidates: list[tuple[str, int, str, str, str, str, str, str, str]] = []
584
+ for line in _parse_lines(out):
585
+ parts = (line.split("\t", 8) + [""] * 9)[:9]
586
+ window_id = parts[0]
587
+ try:
588
+ window_index = int(parts[1])
589
+ except Exception:
590
+ window_index = 9999
591
+ window_name = parts[2]
592
+ terminal_id = parts[3]
593
+ provider = parts[4]
594
+ session_type = parts[5]
595
+ context_id = parts[6]
596
+ created_at = parts[7]
597
+ no_track = parts[8]
598
+
599
+ # Pick an unmanaged window (the default one created by `new-session`) as "home".
600
+ unmanaged = (
601
+ not (terminal_id or "").strip()
602
+ and not (provider or "").strip()
603
+ and not (session_type or "").strip()
604
+ and not (context_id or "").strip()
605
+ and not (created_at or "").strip()
606
+ )
607
+ if unmanaged:
608
+ candidates.append(
609
+ (
610
+ window_id,
611
+ window_index,
612
+ window_name,
613
+ terminal_id,
614
+ provider,
615
+ session_type,
616
+ context_id,
617
+ created_at,
618
+ no_track,
619
+ )
620
+ )
621
+
622
+ if not candidates:
623
+ return
624
+
625
+ # Prefer the first window (index 0) if present.
626
+ candidates.sort(key=lambda t: t[1])
627
+ window_id = candidates[0][0]
628
+
629
+ # Rename to "home" and prevent tmux auto-renaming it based on foreground command.
630
+ _run_inner_tmux(["rename-window", "-t", window_id, "home"])
631
+ _run_inner_tmux(["set-option", "-w", "-t", window_id, "automatic-rename", "off"])
632
+ _run_inner_tmux(["set-option", "-w", "-t", window_id, "allow-rename", "off"])
633
+
634
+ # Mark as internal/no-track so UI can hide it.
635
+ try:
636
+ set_inner_window_options(
637
+ window_id,
638
+ {
639
+ OPT_NO_TRACK: "1",
640
+ OPT_CREATED_AT: str(time.time()),
641
+ },
642
+ )
643
+ except Exception:
644
+ pass
645
+
646
+
540
647
  def ensure_right_pane(width_percent: int = 50) -> bool:
541
648
  """Create the right-side pane (terminal area) if it doesn't exist.
542
649
 
@@ -701,6 +808,7 @@ def create_inner_window(
701
808
  terminal_id: str | None = None,
702
809
  provider: str | None = None,
703
810
  context_id: str | None = None,
811
+ no_track: bool = False,
704
812
  ) -> InnerWindow | None:
705
813
  if not ensure_right_pane():
706
814
  return None
@@ -744,6 +852,10 @@ def create_inner_window(
744
852
  opts.setdefault(OPT_SESSION_TYPE, "")
745
853
  opts.setdefault(OPT_SESSION_ID, "")
746
854
  opts.setdefault(OPT_TRANSCRIPT_PATH, "")
855
+ if no_track:
856
+ opts[OPT_NO_TRACK] = "1"
857
+ else:
858
+ opts.setdefault(OPT_NO_TRACK, "")
747
859
  set_inner_window_options(window_id, opts)
748
860
 
749
861
  _run_inner_tmux(["select-window", "-t", window_id])
@@ -784,6 +896,16 @@ def clear_attention(window_id: str) -> bool:
784
896
 
785
897
  def get_active_claude_context_id() -> str | None:
786
898
  """Return the active inner tmux window's Claude ALINE_CONTEXT_ID (if any)."""
899
+ return get_active_context_id(allowed_providers={"claude"})
900
+
901
+
902
+ def get_active_codex_context_id() -> str | None:
903
+ """Return the active inner tmux window's Codex ALINE_CONTEXT_ID (if any)."""
904
+ return get_active_context_id(allowed_providers={"codex"})
905
+
906
+
907
+ def get_active_context_id(*, allowed_providers: set[str] | None = None) -> str | None:
908
+ """Return the active inner tmux window's ALINE_CONTEXT_ID (optionally filtered by provider)."""
787
909
  try:
788
910
  windows = list_inner_windows()
789
911
  except Exception:
@@ -793,9 +915,12 @@ def get_active_claude_context_id() -> str | None:
793
915
  if active is None:
794
916
  return None
795
917
 
796
- is_claude = (active.provider == "claude") or (active.session_type == "claude")
797
- if not is_claude:
798
- return None
918
+ if allowed_providers is not None:
919
+ allowed = {str(p).strip() for p in allowed_providers if str(p).strip()}
920
+ provider = (active.provider or "").strip()
921
+ session_type = (active.session_type or "").strip()
922
+ if provider not in allowed and session_type not in allowed:
923
+ return None
799
924
 
800
925
  context_id = (active.context_id or "").strip()
801
926
  return context_id or None
@@ -89,6 +89,30 @@ class ConfigPanel(Static):
89
89
  height: auto;
90
90
  margin-top: 2;
91
91
  }
92
+
93
+ ConfigPanel .terminal-settings {
94
+ height: auto;
95
+ margin-top: 2;
96
+ }
97
+
98
+ ConfigPanel .terminal-settings .setting-row {
99
+ height: auto;
100
+ }
101
+
102
+ ConfigPanel .terminal-settings .setting-label {
103
+ width: auto;
104
+ }
105
+
106
+ ConfigPanel .terminal-settings RadioSet {
107
+ width: auto;
108
+ height: auto;
109
+ layout: horizontal;
110
+ }
111
+
112
+ ConfigPanel .terminal-settings RadioButton {
113
+ width: auto;
114
+ margin-right: 2;
115
+ }
92
116
  """
93
117
 
94
118
  def __init__(self) -> None:
@@ -97,6 +121,7 @@ class ConfigPanel(Static):
97
121
  self._syncing_radio: bool = False # Flag to prevent recursive radio updates
98
122
  self._login_in_progress: bool = False # Track login state
99
123
  self._refresh_timer = None # Timer for auto-refresh
124
+ self._auto_close_stale_enabled: bool = False # Track auto-close setting
100
125
 
101
126
  def compose(self) -> ComposeResult:
102
127
  """Compose the config panel layout."""
@@ -115,6 +140,15 @@ class ConfigPanel(Static):
115
140
  yield RadioButton("Enabled", id="border-resize-enabled", value=True)
116
141
  yield RadioButton("Disabled", id="border-resize-disabled")
117
142
 
143
+ # Terminal settings section
144
+ with Static(classes="terminal-settings"):
145
+ yield Static("[bold]Terminal Settings[/bold]", classes="section-title")
146
+ with Horizontal(classes="setting-row"):
147
+ yield Static("Auto-close stale terminals (24h):", classes="setting-label")
148
+ with RadioSet(id="auto-close-stale-radio"):
149
+ yield RadioButton("Enabled", id="auto-close-stale-enabled")
150
+ yield RadioButton("Disabled", id="auto-close-stale-disabled", value=True)
151
+
118
152
  # Tools section
119
153
  with Static(classes="tools-section"):
120
154
  yield Static("[bold]Tools[/bold]", classes="section-title")
@@ -129,6 +163,9 @@ class ConfigPanel(Static):
129
163
  # Query and set the actual tmux border resize state
130
164
  self._sync_border_resize_radio()
131
165
 
166
+ # Sync auto-close stale terminals setting from config
167
+ self._sync_auto_close_stale_radio()
168
+
132
169
  # Start timer to periodically refresh account status (every 5 seconds)
133
170
  self._refresh_timer = self.set_interval(5.0, self._update_account_status)
134
171
 
@@ -151,6 +188,9 @@ class ConfigPanel(Static):
151
188
  # Check which radio button is selected
152
189
  enabled = event.pressed.id == "border-resize-enabled"
153
190
  self._toggle_border_resize(enabled)
191
+ elif event.radio_set.id == "auto-close-stale-radio":
192
+ enabled = event.pressed.id == "auto-close-stale-enabled"
193
+ self._toggle_auto_close_stale(enabled)
154
194
 
155
195
  def _update_account_status(self) -> None:
156
196
  """Update the account status display."""
@@ -318,6 +358,40 @@ class ConfigPanel(Static):
318
358
  except Exception as e:
319
359
  self.app.notify(f"Error toggling border resize: {e}", title="Tmux", severity="error")
320
360
 
361
+ def _sync_auto_close_stale_radio(self) -> None:
362
+ """Sync radio buttons with config file setting."""
363
+ try:
364
+ config = ReAlignConfig.load()
365
+ is_enabled = config.auto_close_stale_terminals
366
+ self._auto_close_stale_enabled = is_enabled
367
+
368
+ # Update radio buttons without triggering the toggle action
369
+ self._syncing_radio = True
370
+ try:
371
+ if is_enabled:
372
+ radio = self.query_one("#auto-close-stale-enabled", RadioButton)
373
+ else:
374
+ radio = self.query_one("#auto-close-stale-disabled", RadioButton)
375
+ radio.value = True
376
+ finally:
377
+ self._syncing_radio = False
378
+ except Exception:
379
+ pass
380
+
381
+ def _toggle_auto_close_stale(self, enabled: bool) -> None:
382
+ """Enable or disable auto-close stale terminals setting."""
383
+ try:
384
+ config = ReAlignConfig.load()
385
+ config.auto_close_stale_terminals = enabled
386
+ config.save()
387
+ self._auto_close_stale_enabled = enabled
388
+ if enabled:
389
+ self.app.notify("Auto-close stale terminals enabled", title="Terminal")
390
+ else:
391
+ self.app.notify("Auto-close stale terminals disabled", title="Terminal")
392
+ except Exception as e:
393
+ self.app.notify(f"Error saving setting: {e}", title="Config", severity="error")
394
+
321
395
  def _handle_doctor(self) -> None:
322
396
  """Run aline doctor command in background."""
323
397
  self.app.notify("Running Aline Doctor...", title="Doctor")
@@ -17,7 +17,7 @@ from textual.binding import Binding
17
17
  from textual.containers import Container, Horizontal, Vertical
18
18
  from textual.reactive import reactive
19
19
  from textual.worker import Worker, WorkerState
20
- from textual.widgets import Button, DataTable, Static
20
+ from textual.widgets import Button, DataTable, Select, Static
21
21
 
22
22
  from ...logging_config import setup_logger
23
23
  from .openable_table import OpenableDataTable