aline-ai 0.5.6__py3-none-any.whl → 0.5.8__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.
- {aline_ai-0.5.6.dist-info → aline_ai-0.5.8.dist-info}/METADATA +1 -1
- {aline_ai-0.5.6.dist-info → aline_ai-0.5.8.dist-info}/RECORD +14 -14
- realign/__init__.py +1 -1
- realign/adapters/claude.py +13 -7
- realign/cli.py +24 -6
- realign/commands/init.py +31 -5
- realign/commands/search.py +30 -0
- realign/dashboard/app.py +49 -25
- realign/dashboard/widgets/sessions_table.py +19 -7
- realign/dashboard/widgets/terminal_panel.py +24 -14
- {aline_ai-0.5.6.dist-info → aline_ai-0.5.8.dist-info}/WHEEL +0 -0
- {aline_ai-0.5.6.dist-info → aline_ai-0.5.8.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.5.6.dist-info → aline_ai-0.5.8.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.5.6.dist-info → aline_ai-0.5.8.dist-info}/top_level.txt +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
aline_ai-0.5.
|
|
2
|
-
realign/__init__.py,sha256=
|
|
1
|
+
aline_ai-0.5.8.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
|
|
2
|
+
realign/__init__.py,sha256=r_zeipazrpik0MPgEERMIzrPFCcbvLypS_erc6wytjc,1623
|
|
3
3
|
realign/claude_detector.py,sha256=ZLSJacMo6zzQclXByABKA70UNpstxqIv3fPGqdpA934,2792
|
|
4
|
-
realign/cli.py,sha256=
|
|
4
|
+
realign/cli.py,sha256=9VS3WbysZ78NRK5EvkJVg8s6Uh2TQjsGX1E9Pl81pHc,31234
|
|
5
5
|
realign/codex_detector.py,sha256=N9ulgMgvTzDfXE4s4vLd6OoS0hT7R6h2bDFFXWa-2hE,4183
|
|
6
6
|
realign/config.py,sha256=lIKZqeOwYc_gHo760lYYX6PnapuKrCWGqT5SA8-PbeA,12044
|
|
7
7
|
realign/context.py,sha256=S1YEUn5HWSDTerDDMsSsRV871IZxoaxDjPTPI2z6-Xs,9976
|
|
@@ -19,7 +19,7 @@ realign/worker_daemon.py,sha256=LpJbQDY0Z4AMtq0LmpxvFeQM4puuoGDRBayKRafvKhc,3574
|
|
|
19
19
|
realign/adapters/__init__.py,sha256=bpDm5aBxMdq4OA_beYahoUb4zfNaq3KOG6KghQJruRc,827
|
|
20
20
|
realign/adapters/antigravity.py,sha256=geaYxAEswpgsVtERqsQ1OwvPFsy5tRkyjx2yQ-Uq9nM,5461
|
|
21
21
|
realign/adapters/base.py,sha256=2IdAZKGjg5gPB3YLf_8r3V4XAdbK7fHpj06GjjsYEFY,7409
|
|
22
|
-
realign/adapters/claude.py,sha256=
|
|
22
|
+
realign/adapters/claude.py,sha256=ksTRwC5Z8AzUcB21LFjx6DETP08cv__fjgBzm-TeZdI,5444
|
|
23
23
|
realign/adapters/codex.py,sha256=5ex3zJ5Hpb_StV2CcBSHVhHleygZxzVJjYsWw8qK1Bc,2051
|
|
24
24
|
realign/adapters/gemini.py,sha256=NvtXQPWUtEY-DaAAMvLGvQW4FalTG-g0pD514HYnzF0,2540
|
|
25
25
|
realign/adapters/registry.py,sha256=yM6nf9nGTJ1vaK2Uixp-VacseK7PmxZkCdKedmWI8MA,3255
|
|
@@ -37,14 +37,14 @@ realign/commands/config.py,sha256=nYnu_h2pk7GODcrzrV04K51D-s7v06FlRXHJ0HJ-gvU,67
|
|
|
37
37
|
realign/commands/context.py,sha256=pM2KfZHVkB-ou4nBhFvKSwnYliLBzwN3zerLyBAbhfE,7095
|
|
38
38
|
realign/commands/export_shares.py,sha256=Djy1aO7MoU1_ewzn6CZ43oNhSEEonV3sTkSQbHgiaKI,135806
|
|
39
39
|
realign/commands/import_shares.py,sha256=ukX8huvLvEM5g0qEIoqrV1-imz1g-r0Jj2FqD-ojrIA,25297
|
|
40
|
-
realign/commands/init.py,sha256=
|
|
40
|
+
realign/commands/init.py,sha256=ef-q3Qz5D_0Eqld8qjtX26X2QrovBSYcva3uAjiJuwk,33015
|
|
41
41
|
realign/commands/restore.py,sha256=s2BxQZHxQw9r12NzRVsK20KlGafy5AIoSjWMo5PcnHY,11173
|
|
42
|
-
realign/commands/search.py,sha256=
|
|
42
|
+
realign/commands/search.py,sha256=QJrC0hln9sCDFxXbpo0nPGMHXrud18qA5QfRyD0z6fQ,25926
|
|
43
43
|
realign/commands/upgrade.py,sha256=L3PLOUIN5qAQTbkfoVtSsIbbzEezA_xjjk9F1GMVfjw,12781
|
|
44
44
|
realign/commands/watcher.py,sha256=fWL3kaRkqE03-NtFLaXlx93hJAQrAuNPSoYhOyQZfq8,136273
|
|
45
45
|
realign/commands/worker.py,sha256=K1DG1uZ--ebKwklHCyIFdN_axoLjL9Onx8Naq-DOZBs,23078
|
|
46
46
|
realign/dashboard/__init__.py,sha256=QZkHTsGityH8UkF8rmvA3xW7dMXNe0swEWr443qfgCM,128
|
|
47
|
-
realign/dashboard/app.py,sha256=
|
|
47
|
+
realign/dashboard/app.py,sha256=jyW6mqmItTy253CPSqInxctkWzkrGEikdy-ikuShQ14,13299
|
|
48
48
|
realign/dashboard/tmux_manager.py,sha256=DdCiumQ7YQZnje5VfOQ60585C0X6Va_AhBQi_zmhE0Y,24035
|
|
49
49
|
realign/dashboard/screens/__init__.py,sha256=US6sAmQs5VVkH2tFkH_z0WDT4H8cVhLL-JckfSR1yQY,446
|
|
50
50
|
realign/dashboard/screens/create_agent.py,sha256=ugEs3IHrT7FsbuMEwyrqY3eoylp_pbftw42_Fu07tF4,7419
|
|
@@ -60,8 +60,8 @@ realign/dashboard/widgets/events_table.py,sha256=OG9RjwU4c50-RUMmdhXzmIMnYrt6_mC
|
|
|
60
60
|
realign/dashboard/widgets/header.py,sha256=0HHCFXX7F3C6HII-WDwOJwWkJrajmKPWmdoMWyOkn9E,1587
|
|
61
61
|
realign/dashboard/widgets/openable_table.py,sha256=GeJPDEYp0kRHShqvmPMzAePpYXRZHUNqcWNnxqsqxjA,1963
|
|
62
62
|
realign/dashboard/widgets/search_panel.py,sha256=ZNJDfwDSxUFnCeltYQYsQsPJ6t4HDeNWpENoTOoBdVM,8951
|
|
63
|
-
realign/dashboard/widgets/sessions_table.py,sha256=
|
|
64
|
-
realign/dashboard/widgets/terminal_panel.py,sha256=
|
|
63
|
+
realign/dashboard/widgets/sessions_table.py,sha256=PohOkg-ESLBa-Sq0PdLPhV-YzVXOGpUo5ETs0MYO4u8,33415
|
|
64
|
+
realign/dashboard/widgets/terminal_panel.py,sha256=S4UUMlFaBWDKZB_MR0jhBNvdwdGvPuRNHNxweFGDfks,28751
|
|
65
65
|
realign/dashboard/widgets/watcher_panel.py,sha256=O_mdDacgc87xA-5KEfta53Ik_Xsk_B2OfwenMOTtGw8,19722
|
|
66
66
|
realign/dashboard/widgets/worker_panel.py,sha256=F_jKWABuCNmjQgeeuCr4KnFRKdY4CLTNcEXMYwsNaSk,18691
|
|
67
67
|
realign/db/__init__.py,sha256=-1d-Zc4IOUVokbdTXi3R-bIwlkFEPAz_qTHAdcsdp6g,1870
|
|
@@ -88,8 +88,8 @@ realign/triggers/next_turn_trigger.py,sha256=BpP0PWn4mU1MZd6mv89jWcjs8Jtv0zEWapW
|
|
|
88
88
|
realign/triggers/registry.py,sha256=cb-AVLbYB2pqwfWL3q1DQxLv4kOw7g7m-GshTdfFESc,3827
|
|
89
89
|
realign/triggers/turn_status.py,sha256=wAZEhXDAmDoX5F-ohWfSnZZ0eA6DAJ9svSPiSv_f6sg,6041
|
|
90
90
|
realign/triggers/turn_summary.py,sha256=f3hEUshgv9skJ9AbfWpoYs417lsv_HK2A_vpPjgryO4,4467
|
|
91
|
-
aline_ai-0.5.
|
|
92
|
-
aline_ai-0.5.
|
|
93
|
-
aline_ai-0.5.
|
|
94
|
-
aline_ai-0.5.
|
|
95
|
-
aline_ai-0.5.
|
|
91
|
+
aline_ai-0.5.8.dist-info/METADATA,sha256=S2cnGF2C84A28xQzAGz7nszWbBKErn4LazeX6KhAiBA,1597
|
|
92
|
+
aline_ai-0.5.8.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
93
|
+
aline_ai-0.5.8.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
|
|
94
|
+
aline_ai-0.5.8.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
|
|
95
|
+
aline_ai-0.5.8.dist-info/RECORD,,
|
realign/__init__.py
CHANGED
realign/adapters/claude.py
CHANGED
|
@@ -76,14 +76,13 @@ class ClaudeAdapter(SessionAdapter):
|
|
|
76
76
|
project_path_str = "/" + parent_name[1:].replace("-", "/")
|
|
77
77
|
project_path = Path(project_path_str)
|
|
78
78
|
|
|
79
|
-
#
|
|
80
|
-
# We relax this slightly: if it's a valid-looking path, we return it,
|
|
81
|
-
# but ideally we check existence.
|
|
79
|
+
# If path exists locally, return it immediately
|
|
82
80
|
if project_path.exists():
|
|
83
81
|
return project_path
|
|
84
82
|
|
|
85
83
|
# Fallback: read cwd from the session JSONL (more reliable when the encoded
|
|
86
84
|
# directory name is ambiguous due to '-' in real paths).
|
|
85
|
+
cwd_path: Optional[Path] = None
|
|
87
86
|
try:
|
|
88
87
|
with session_file.open("r", encoding="utf-8") as f:
|
|
89
88
|
for i, line in enumerate(f):
|
|
@@ -98,13 +97,20 @@ class ClaudeAdapter(SessionAdapter):
|
|
|
98
97
|
continue
|
|
99
98
|
cwd = obj.get("cwd")
|
|
100
99
|
if isinstance(cwd, str) and cwd.strip():
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
cwd_path = Path(cwd.strip())
|
|
101
|
+
# If cwd exists locally, prefer it
|
|
102
|
+
if cwd_path.exists():
|
|
103
|
+
return cwd_path
|
|
104
|
+
break # Found cwd, stop searching
|
|
104
105
|
except Exception:
|
|
105
106
|
pass
|
|
106
107
|
|
|
107
|
-
|
|
108
|
+
# Return the best available path even if it doesn't exist locally.
|
|
109
|
+
# This allows importing sessions from other machines (e.g., SWEBench).
|
|
110
|
+
# Prefer cwd from JSONL as it's more accurate than path decoding.
|
|
111
|
+
if cwd_path is not None:
|
|
112
|
+
return cwd_path
|
|
113
|
+
return project_path
|
|
108
114
|
|
|
109
115
|
except Exception:
|
|
110
116
|
return None
|
realign/cli.py
CHANGED
|
@@ -819,18 +819,36 @@ def version():
|
|
|
819
819
|
def dashboard(
|
|
820
820
|
ctx: typer.Context,
|
|
821
821
|
dev: bool = typer.Option(False, "--dev", help="Enable developer mode (shows Watcher and Worker tabs)"),
|
|
822
|
+
debug: bool = typer.Option(False, "--debug", help="Enable debug logging to ~/.aline/.logs/dashboard.log"),
|
|
822
823
|
):
|
|
823
824
|
"""Open the interactive TUI dashboard."""
|
|
825
|
+
import os
|
|
826
|
+
import traceback
|
|
827
|
+
|
|
828
|
+
# Set debug log level if requested
|
|
829
|
+
if debug:
|
|
830
|
+
os.environ["REALIGN_LOG_LEVEL"] = "DEBUG"
|
|
831
|
+
|
|
824
832
|
from .dashboard.tmux_manager import bootstrap_dashboard_into_tmux
|
|
833
|
+
from .logging_config import setup_logger
|
|
834
|
+
|
|
835
|
+
# Initialize logger before dashboard
|
|
836
|
+
logger = setup_logger("realign.dashboard", "dashboard.log")
|
|
837
|
+
logger.info(f"Dashboard command invoked (dev={dev}, debug={debug})")
|
|
825
838
|
|
|
826
|
-
|
|
839
|
+
try:
|
|
840
|
+
bootstrap_dashboard_into_tmux()
|
|
827
841
|
|
|
828
|
-
|
|
842
|
+
from .dashboard.app import AlineDashboard
|
|
829
843
|
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
844
|
+
# Use dev flag from this command or inherit from parent context
|
|
845
|
+
dev_mode = dev or (ctx.obj.get("dev", False) if ctx.obj else False)
|
|
846
|
+
dash = AlineDashboard(dev_mode=dev_mode)
|
|
847
|
+
dash.run()
|
|
848
|
+
except Exception as e:
|
|
849
|
+
logger.error(f"Dashboard crashed: {e}\n{traceback.format_exc()}")
|
|
850
|
+
# Re-raise so user sees the error
|
|
851
|
+
raise
|
|
834
852
|
|
|
835
853
|
|
|
836
854
|
# Restore command group
|
realign/commands/init.py
CHANGED
|
@@ -18,10 +18,14 @@ console = Console()
|
|
|
18
18
|
|
|
19
19
|
# tmux config template for Aline-managed dashboard sessions.
|
|
20
20
|
# Stored at ~/.aline/tmux/tmux.conf and sourced by the dashboard tmux bootstrap.
|
|
21
|
+
# Bump this version when the tmux config changes to trigger auto-update on `aline init`.
|
|
22
|
+
_TMUX_CONFIG_VERSION = 2
|
|
23
|
+
|
|
24
|
+
|
|
21
25
|
def _get_tmux_config() -> str:
|
|
22
26
|
"""Generate tmux config with Type-to-Exit bindings."""
|
|
23
|
-
conf =
|
|
24
|
-
#
|
|
27
|
+
conf = f"# Aline tmux config (v{_TMUX_CONFIG_VERSION})\n"
|
|
28
|
+
conf += r"""#
|
|
25
29
|
# Goal: make mouse selection copy to the system clipboard (macOS Terminal friendly).
|
|
26
30
|
# - Drag-select text with the mouse; when you release, it is copied to the clipboard.
|
|
27
31
|
# - Paste anywhere with Cmd+V.
|
|
@@ -448,24 +452,46 @@ def _initialize_prompts_directory() -> None:
|
|
|
448
452
|
file_path.write_text(content, encoding="utf-8")
|
|
449
453
|
|
|
450
454
|
|
|
455
|
+
def _get_tmux_config_version(content: str) -> int:
|
|
456
|
+
"""Extract version number from tmux config content. Returns 0 if not found."""
|
|
457
|
+
# Look for "# Aline tmux config (vN)" pattern
|
|
458
|
+
match = re.search(r"# Aline tmux config \(v(\d+)\)", content)
|
|
459
|
+
if match:
|
|
460
|
+
return int(match.group(1))
|
|
461
|
+
# Old configs without version marker are version 1
|
|
462
|
+
if "# Aline tmux config" in content:
|
|
463
|
+
return 1
|
|
464
|
+
return 0
|
|
465
|
+
|
|
466
|
+
|
|
451
467
|
def _initialize_tmux_config() -> Path:
|
|
452
|
-
"""Initialize ~/.aline/tmux/tmux.conf
|
|
468
|
+
"""Initialize ~/.aline/tmux/tmux.conf with auto-update on version change."""
|
|
453
469
|
tmux_conf_path = Path.home() / ".aline" / "tmux" / "tmux.conf"
|
|
454
470
|
tmux_conf_path.parent.mkdir(parents=True, exist_ok=True)
|
|
471
|
+
|
|
455
472
|
if not tmux_conf_path.exists():
|
|
456
473
|
tmux_conf_path.write_text(_get_tmux_config(), encoding="utf-8")
|
|
457
474
|
return tmux_conf_path
|
|
458
475
|
|
|
459
|
-
#
|
|
460
|
-
# tmux parses `#` as a comment delimiter, turning `bind ... # ...` into `bind ...` (invalid).
|
|
476
|
+
# Check existing config
|
|
461
477
|
try:
|
|
462
478
|
existing = tmux_conf_path.read_text(encoding="utf-8")
|
|
463
479
|
except Exception:
|
|
464
480
|
return tmux_conf_path
|
|
465
481
|
|
|
482
|
+
# Only manage Aline-generated configs
|
|
466
483
|
if "# Aline tmux config" not in existing:
|
|
467
484
|
return tmux_conf_path
|
|
468
485
|
|
|
486
|
+
# Check version and update if outdated
|
|
487
|
+
existing_version = _get_tmux_config_version(existing)
|
|
488
|
+
if existing_version < _TMUX_CONFIG_VERSION:
|
|
489
|
+
# Auto-update to latest config
|
|
490
|
+
tmux_conf_path.write_text(_get_tmux_config(), encoding="utf-8")
|
|
491
|
+
return tmux_conf_path
|
|
492
|
+
|
|
493
|
+
# Best-effort repair for older Aline-generated configs that used unquoted `#` keys.
|
|
494
|
+
# tmux parses `#` as a comment delimiter, turning `bind ... # ...` into `bind ...` (invalid).
|
|
469
495
|
repaired = existing
|
|
470
496
|
repaired = _TMUX_CONF_REPAIR_TILDE_KEY_RE.sub(r"\1\\~\2\\~", repaired)
|
|
471
497
|
repaired = _TMUX_CONF_REPAIR_KEY_NEEDS_QUOTE_RE.sub(r'\1"\2"\3"\2"', repaired)
|
realign/commands/search.py
CHANGED
|
@@ -528,6 +528,19 @@ def search_command(
|
|
|
528
528
|
|
|
529
529
|
console.print(f"[dim]Found {', '.join(summary_parts)}.[/dim]")
|
|
530
530
|
|
|
531
|
+
# Check if any result count hits the limit - suggest increasing limit
|
|
532
|
+
hit_limit = (
|
|
533
|
+
event_count == limit
|
|
534
|
+
or turn_count == limit
|
|
535
|
+
or session_count == limit
|
|
536
|
+
or (type == "content" and len(results.get("content", [])) == limit)
|
|
537
|
+
)
|
|
538
|
+
if hit_limit:
|
|
539
|
+
console.print(
|
|
540
|
+
f"[yellow]Results may be truncated (limit={limit}). "
|
|
541
|
+
f"Use --limit N to see more results.[/yellow]"
|
|
542
|
+
)
|
|
543
|
+
|
|
531
544
|
# === Original structured output for non-regex mode ===
|
|
532
545
|
else:
|
|
533
546
|
if type == "all":
|
|
@@ -624,6 +637,23 @@ def search_command(
|
|
|
624
637
|
"\n[dim]Tip: Use --verbose to see Markdown previews, or --no-regex for exact keyword match.[/dim]"
|
|
625
638
|
)
|
|
626
639
|
|
|
640
|
+
# Check if any result count hits the limit - suggest increasing limit
|
|
641
|
+
event_count = len(results.get("events", []))
|
|
642
|
+
turn_count = len(results.get("turns", []))
|
|
643
|
+
session_count = len(results.get("sessions", []))
|
|
644
|
+
content_count = len(results.get("content", []))
|
|
645
|
+
hit_limit = (
|
|
646
|
+
event_count == limit
|
|
647
|
+
or turn_count == limit
|
|
648
|
+
or session_count == limit
|
|
649
|
+
or content_count == limit
|
|
650
|
+
)
|
|
651
|
+
if hit_limit:
|
|
652
|
+
console.print(
|
|
653
|
+
f"[yellow]Results may be truncated (limit={limit}). "
|
|
654
|
+
f"Use --limit N to see more results.[/yellow]"
|
|
655
|
+
)
|
|
656
|
+
|
|
627
657
|
return 0
|
|
628
658
|
|
|
629
659
|
except Exception as e:
|
realign/dashboard/app.py
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
import subprocess
|
|
4
4
|
import sys
|
|
5
5
|
import time
|
|
6
|
+
import traceback
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
from textual.app import App, ComposeResult
|
|
9
10
|
from textual.binding import Binding
|
|
10
11
|
from textual.widgets import Footer, TabbedContent, TabPane
|
|
11
12
|
|
|
13
|
+
from ..logging_config import setup_logger
|
|
12
14
|
from .widgets import (
|
|
13
15
|
AlineHeader,
|
|
14
16
|
WatcherPanel,
|
|
@@ -20,6 +22,9 @@ from .widgets import (
|
|
|
20
22
|
TerminalPanel,
|
|
21
23
|
)
|
|
22
24
|
|
|
25
|
+
# Set up dashboard logger - logs to ~/.aline/.logs/dashboard.log
|
|
26
|
+
logger = setup_logger("realign.dashboard", "dashboard.log")
|
|
27
|
+
|
|
23
28
|
|
|
24
29
|
def _detect_system_dark_mode() -> bool:
|
|
25
30
|
"""Detect if the system is in dark mode.
|
|
@@ -77,28 +82,35 @@ class AlineDashboard(App):
|
|
|
77
82
|
"""
|
|
78
83
|
super().__init__()
|
|
79
84
|
self.dev_mode = dev_mode
|
|
85
|
+
logger.info(f"AlineDashboard initialized (dev_mode={dev_mode})")
|
|
80
86
|
|
|
81
87
|
def compose(self) -> ComposeResult:
|
|
82
88
|
"""Compose the dashboard layout."""
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
89
|
+
logger.debug("compose() started")
|
|
90
|
+
try:
|
|
91
|
+
yield AlineHeader()
|
|
92
|
+
tab_ids = self._tab_ids()
|
|
93
|
+
with TabbedContent(initial=tab_ids[0] if tab_ids else "terminal"):
|
|
94
|
+
with TabPane("Agents", id="terminal"):
|
|
95
|
+
yield TerminalPanel()
|
|
96
|
+
if self.dev_mode:
|
|
97
|
+
with TabPane("Watcher", id="watcher"):
|
|
98
|
+
yield WatcherPanel()
|
|
99
|
+
with TabPane("Worker", id="worker"):
|
|
100
|
+
yield WorkerPanel()
|
|
101
|
+
with TabPane("Contexts", id="sessions"):
|
|
102
|
+
yield SessionsTable()
|
|
103
|
+
with TabPane("Share", id="events"):
|
|
104
|
+
yield EventsTable()
|
|
105
|
+
with TabPane("Config", id="config"):
|
|
106
|
+
yield ConfigPanel()
|
|
107
|
+
with TabPane("Search", id="search"):
|
|
108
|
+
yield SearchPanel()
|
|
109
|
+
yield Footer()
|
|
110
|
+
logger.debug("compose() completed successfully")
|
|
111
|
+
except Exception as e:
|
|
112
|
+
logger.error(f"compose() failed: {e}\n{traceback.format_exc()}")
|
|
113
|
+
raise
|
|
102
114
|
|
|
103
115
|
def _tab_ids(self) -> list[str]:
|
|
104
116
|
if self.dev_mode:
|
|
@@ -107,10 +119,16 @@ class AlineDashboard(App):
|
|
|
107
119
|
|
|
108
120
|
def on_mount(self) -> None:
|
|
109
121
|
"""Apply theme based on system settings and watch for changes."""
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
122
|
+
logger.info("on_mount() started")
|
|
123
|
+
try:
|
|
124
|
+
self._sync_theme()
|
|
125
|
+
# Check for system theme changes every 2 seconds
|
|
126
|
+
self.set_interval(2, self._sync_theme)
|
|
127
|
+
self._quit_confirm_deadline: float | None = None
|
|
128
|
+
logger.info("on_mount() completed successfully")
|
|
129
|
+
except Exception as e:
|
|
130
|
+
logger.error(f"on_mount() failed: {e}\n{traceback.format_exc()}")
|
|
131
|
+
raise
|
|
114
132
|
|
|
115
133
|
def _sync_theme(self) -> None:
|
|
116
134
|
"""Sync app theme with system theme."""
|
|
@@ -341,8 +359,14 @@ class AlineDashboard(App):
|
|
|
341
359
|
|
|
342
360
|
def run_dashboard() -> None:
|
|
343
361
|
"""Run the Aline Dashboard."""
|
|
344
|
-
|
|
345
|
-
|
|
362
|
+
logger.info("Starting Aline Dashboard")
|
|
363
|
+
try:
|
|
364
|
+
app = AlineDashboard()
|
|
365
|
+
app.run()
|
|
366
|
+
logger.info("Aline Dashboard exited normally")
|
|
367
|
+
except Exception as e:
|
|
368
|
+
logger.error(f"Dashboard crashed: {e}\n{traceback.format_exc()}")
|
|
369
|
+
raise
|
|
346
370
|
|
|
347
371
|
|
|
348
372
|
if __name__ == "__main__":
|
|
@@ -6,6 +6,7 @@ import json
|
|
|
6
6
|
import os
|
|
7
7
|
import shutil
|
|
8
8
|
import subprocess
|
|
9
|
+
import traceback
|
|
9
10
|
from datetime import datetime
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
from typing import List, Optional, Set
|
|
@@ -18,8 +19,11 @@ from textual.reactive import reactive
|
|
|
18
19
|
from textual.worker import Worker, WorkerState
|
|
19
20
|
from textual.widgets import Button, DataTable, Static
|
|
20
21
|
|
|
22
|
+
from ...logging_config import setup_logger
|
|
21
23
|
from .openable_table import OpenableDataTable
|
|
22
24
|
|
|
25
|
+
logger = setup_logger("realign.dashboard.sessions", "dashboard.log")
|
|
26
|
+
|
|
23
27
|
|
|
24
28
|
class SessionsListTable(OpenableDataTable):
|
|
25
29
|
"""Sessions list table with multi-select behavior."""
|
|
@@ -145,13 +149,19 @@ class SessionsTable(Container):
|
|
|
145
149
|
|
|
146
150
|
def on_mount(self) -> None:
|
|
147
151
|
"""Set up the table on mount."""
|
|
148
|
-
|
|
149
|
-
|
|
152
|
+
logger.debug("SessionsTable.on_mount() started")
|
|
153
|
+
try:
|
|
154
|
+
table = self.query_one("#sessions-table", SessionsListTable)
|
|
155
|
+
table.owner = self
|
|
150
156
|
|
|
151
|
-
|
|
157
|
+
self._setup_table_columns(table)
|
|
152
158
|
|
|
153
|
-
|
|
154
|
-
|
|
159
|
+
# Calculate initial rows per page
|
|
160
|
+
self._calculate_rows_per_page()
|
|
161
|
+
logger.debug("SessionsTable.on_mount() completed")
|
|
162
|
+
except Exception as e:
|
|
163
|
+
logger.error(f"SessionsTable.on_mount() failed: {e}\n{traceback.format_exc()}")
|
|
164
|
+
raise
|
|
155
165
|
|
|
156
166
|
def on_resize(self) -> None:
|
|
157
167
|
"""Handle window resize to adjust rows per page."""
|
|
@@ -793,6 +803,7 @@ class SessionsTable(Container):
|
|
|
793
803
|
}
|
|
794
804
|
|
|
795
805
|
# Get paginated sessions
|
|
806
|
+
# Use cached total_turns instead of subquery for performance
|
|
796
807
|
offset = (int(page) - 1) * int(rows_per_page)
|
|
797
808
|
rows = conn.execute(
|
|
798
809
|
"""
|
|
@@ -802,7 +813,7 @@ class SessionsTable(Container):
|
|
|
802
813
|
s.workspace_path,
|
|
803
814
|
s.session_title,
|
|
804
815
|
s.last_activity_at,
|
|
805
|
-
|
|
816
|
+
s.total_turns
|
|
806
817
|
FROM sessions s
|
|
807
818
|
ORDER BY s.last_activity_at DESC
|
|
808
819
|
LIMIT ? OFFSET ?
|
|
@@ -847,7 +858,8 @@ class SessionsTable(Container):
|
|
|
847
858
|
"last_activity": activity_str,
|
|
848
859
|
}
|
|
849
860
|
)
|
|
850
|
-
except Exception:
|
|
861
|
+
except Exception as e:
|
|
862
|
+
logger.error(f"_collect_snapshot failed: {e}\n{traceback.format_exc()}")
|
|
851
863
|
total_sessions = 0
|
|
852
864
|
stats = {}
|
|
853
865
|
sessions = []
|
|
@@ -11,6 +11,7 @@ import asyncio
|
|
|
11
11
|
import os
|
|
12
12
|
import re
|
|
13
13
|
import shlex
|
|
14
|
+
import traceback
|
|
14
15
|
from pathlib import Path
|
|
15
16
|
from typing import Callable
|
|
16
17
|
|
|
@@ -21,6 +22,9 @@ from textual.widgets import Button, Static
|
|
|
21
22
|
from rich.text import Text
|
|
22
23
|
|
|
23
24
|
from .. import tmux_manager
|
|
25
|
+
from ...logging_config import setup_logger
|
|
26
|
+
|
|
27
|
+
logger = setup_logger("realign.dashboard.terminal", "dashboard.log")
|
|
24
28
|
|
|
25
29
|
|
|
26
30
|
# Signal directory for permission request notifications
|
|
@@ -291,21 +295,27 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
291
295
|
self._signal_watcher: _SignalFileWatcher | None = None
|
|
292
296
|
|
|
293
297
|
def compose(self) -> ComposeResult:
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
if controls_enabled:
|
|
304
|
-
yield Static(
|
|
305
|
-
"No terminals yet. Click 'Create' to open a new agent terminal."
|
|
298
|
+
logger.debug("TerminalPanel.compose() started")
|
|
299
|
+
try:
|
|
300
|
+
controls_enabled = self.supported()
|
|
301
|
+
with Horizontal(classes="summary"):
|
|
302
|
+
yield Button(
|
|
303
|
+
"+ Create",
|
|
304
|
+
id="new-agent",
|
|
305
|
+
variant="primary",
|
|
306
|
+
disabled=not controls_enabled,
|
|
306
307
|
)
|
|
307
|
-
|
|
308
|
-
|
|
308
|
+
with Vertical(id="terminals", classes="list"):
|
|
309
|
+
if controls_enabled:
|
|
310
|
+
yield Static(
|
|
311
|
+
"No terminals yet. Click 'Create' to open a new agent terminal."
|
|
312
|
+
)
|
|
313
|
+
else:
|
|
314
|
+
yield Static(self._support_message())
|
|
315
|
+
logger.debug("TerminalPanel.compose() completed")
|
|
316
|
+
except Exception as e:
|
|
317
|
+
logger.error(f"TerminalPanel.compose() failed: {e}\n{traceback.format_exc()}")
|
|
318
|
+
raise
|
|
309
319
|
|
|
310
320
|
def on_show(self) -> None:
|
|
311
321
|
# Don't `await refresh_data()` directly here: Textual may do an initial layout pass with
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|