aline-ai 0.6.0__py3-none-any.whl → 0.6.2__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.6.0.dist-info → aline_ai-0.6.2.dist-info}/METADATA +1 -1
- {aline_ai-0.6.0.dist-info → aline_ai-0.6.2.dist-info}/RECORD +25 -20
- realign/__init__.py +1 -1
- realign/auth.py +21 -0
- realign/claude_hooks/stop_hook.py +35 -0
- realign/claude_hooks/user_prompt_submit_hook.py +5 -0
- realign/cli.py +76 -34
- realign/commands/auth.py +9 -0
- realign/dashboard/app.py +69 -6
- realign/dashboard/backends/__init__.py +6 -0
- realign/dashboard/backends/iterm2.py +599 -0
- realign/dashboard/backends/kitty.py +372 -0
- realign/dashboard/layout.py +320 -0
- realign/dashboard/screens/create_agent.py +41 -4
- realign/dashboard/terminal_backend.py +110 -0
- realign/dashboard/tmux_manager.py +17 -0
- realign/dashboard/widgets/terminal_panel.py +587 -110
- realign/db/sqlite_db.py +18 -0
- realign/events/session_summarizer.py +17 -2
- realign/watcher_core.py +56 -22
- realign/worker_core.py +2 -0
- {aline_ai-0.6.0.dist-info → aline_ai-0.6.2.dist-info}/WHEEL +0 -0
- {aline_ai-0.6.0.dist-info → aline_ai-0.6.2.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.6.0.dist-info → aline_ai-0.6.2.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.6.0.dist-info → aline_ai-0.6.2.dist-info}/top_level.txt +0 -0
realign/db/sqlite_db.py
CHANGED
|
@@ -442,6 +442,21 @@ class SQLiteDatabase(DatabaseInterface):
|
|
|
442
442
|
)
|
|
443
443
|
conn.commit()
|
|
444
444
|
|
|
445
|
+
def update_session_metadata_flag(self, session_id: str, key: str, value: Any) -> None:
|
|
446
|
+
"""Update a single key in session metadata JSON."""
|
|
447
|
+
conn = self._get_connection()
|
|
448
|
+
row = conn.execute(
|
|
449
|
+
"SELECT metadata FROM sessions WHERE id = ?", (session_id,)
|
|
450
|
+
).fetchone()
|
|
451
|
+
if row:
|
|
452
|
+
meta = json.loads(row[0] or "{}")
|
|
453
|
+
meta[key] = value
|
|
454
|
+
conn.execute(
|
|
455
|
+
"UPDATE sessions SET metadata = ?, updated_at = datetime('now') WHERE id = ?",
|
|
456
|
+
(json.dumps(meta), session_id),
|
|
457
|
+
)
|
|
458
|
+
conn.commit()
|
|
459
|
+
|
|
445
460
|
def backfill_session_total_turns(self) -> int:
|
|
446
461
|
"""Backfill total_turns for all sessions from turns table (V10 migration).
|
|
447
462
|
|
|
@@ -957,6 +972,7 @@ class SQLiteDatabase(DatabaseInterface):
|
|
|
957
972
|
skip_session_summary: bool = False,
|
|
958
973
|
expected_turns: Optional[int] = None,
|
|
959
974
|
skip_dedup: bool = False,
|
|
975
|
+
no_track: bool = False,
|
|
960
976
|
) -> str:
|
|
961
977
|
session_id = session_file_path.stem
|
|
962
978
|
dedupe_key = f"turn:{session_id}:{int(turn_number)}"
|
|
@@ -973,6 +989,8 @@ class SQLiteDatabase(DatabaseInterface):
|
|
|
973
989
|
payload["expected_turns"] = int(expected_turns)
|
|
974
990
|
if skip_dedup:
|
|
975
991
|
payload["skip_dedup"] = True
|
|
992
|
+
if no_track:
|
|
993
|
+
payload["no_track"] = True
|
|
976
994
|
|
|
977
995
|
# For append-only session formats (Claude/Codex/Gemini), a turn is immutable once completed.
|
|
978
996
|
# Avoid re-running already-done turn jobs on repeated enqueue attempts.
|
|
@@ -97,9 +97,24 @@ def update_session_summary_now(db: SQLiteDatabase, session_id: str) -> bool:
|
|
|
97
97
|
pass
|
|
98
98
|
return True
|
|
99
99
|
|
|
100
|
+
# Check session metadata for no_track mode
|
|
101
|
+
is_no_track = False
|
|
100
102
|
try:
|
|
101
|
-
|
|
102
|
-
|
|
103
|
+
session = db.get_session_by_id(session_id)
|
|
104
|
+
if session:
|
|
105
|
+
session_meta = getattr(session, "metadata", None) or {}
|
|
106
|
+
is_no_track = bool(session_meta.get("no_track", False))
|
|
107
|
+
except Exception:
|
|
108
|
+
pass
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
# Skip LLM call for no-track mode
|
|
112
|
+
if is_no_track:
|
|
113
|
+
title, summary = "No Track", "No Track"
|
|
114
|
+
logger.info(f"No-track mode: skipping LLM for session summary {session_id}")
|
|
115
|
+
else:
|
|
116
|
+
# Generate title and summary using LLM
|
|
117
|
+
title, summary = _generate_session_summary_llm(turns)
|
|
103
118
|
|
|
104
119
|
# Update database
|
|
105
120
|
db.update_session_summary(session_id, title, summary)
|
realign/watcher_core.py
CHANGED
|
@@ -368,6 +368,7 @@ class DialogueWatcher:
|
|
|
368
368
|
session_id = signal_data.get("session_id", "")
|
|
369
369
|
project_dir = signal_data.get("project_dir", "")
|
|
370
370
|
transcript_path = signal_data.get("transcript_path", "")
|
|
371
|
+
no_track = bool(signal_data.get("no_track", False))
|
|
371
372
|
|
|
372
373
|
logger.info(f"Stop signal received for session {session_id}")
|
|
373
374
|
print(f"[Watcher] Stop signal received for {session_id}", file=sys.stderr)
|
|
@@ -402,6 +403,7 @@ class DialogueWatcher:
|
|
|
402
403
|
workspace_path=project_path,
|
|
403
404
|
turn_number=target_turn,
|
|
404
405
|
session_type=self._detect_session_type(session_file),
|
|
406
|
+
no_track=no_track,
|
|
405
407
|
)
|
|
406
408
|
except Exception as e:
|
|
407
409
|
logger.warning(
|
|
@@ -462,6 +464,7 @@ class DialogueWatcher:
|
|
|
462
464
|
prompt = str(signal_data.get("prompt") or "")
|
|
463
465
|
transcript_path = str(signal_data.get("transcript_path") or "")
|
|
464
466
|
project_dir = str(signal_data.get("project_dir") or "")
|
|
467
|
+
no_track = bool(signal_data.get("no_track", False))
|
|
465
468
|
|
|
466
469
|
session_file = None
|
|
467
470
|
if transcript_path and Path(transcript_path).exists():
|
|
@@ -488,6 +491,7 @@ class DialogueWatcher:
|
|
|
488
491
|
session_id,
|
|
489
492
|
prompt,
|
|
490
493
|
project_dir,
|
|
494
|
+
no_track,
|
|
491
495
|
)
|
|
492
496
|
|
|
493
497
|
signal_file.unlink(missing_ok=True)
|
|
@@ -1318,6 +1322,7 @@ class DialogueWatcher:
|
|
|
1318
1322
|
debug_callback: Optional[Callable[[Dict[str, Any]], None]] = None,
|
|
1319
1323
|
skip_dedup: bool = False,
|
|
1320
1324
|
skip_session_summary: bool = False,
|
|
1325
|
+
no_track: bool = False,
|
|
1321
1326
|
) -> bool:
|
|
1322
1327
|
"""
|
|
1323
1328
|
Execute commit with DB-backed lease locking to prevent cross-process races.
|
|
@@ -1365,6 +1370,7 @@ class DialogueWatcher:
|
|
|
1365
1370
|
debug_callback=debug_callback,
|
|
1366
1371
|
skip_dedup=skip_dedup,
|
|
1367
1372
|
skip_session_summary=skip_session_summary,
|
|
1373
|
+
no_track=no_track,
|
|
1368
1374
|
)
|
|
1369
1375
|
except Exception as e:
|
|
1370
1376
|
print(f"[Watcher] Commit error: {e}", file=sys.stderr)
|
|
@@ -1381,6 +1387,7 @@ class DialogueWatcher:
|
|
|
1381
1387
|
debug_callback: Optional[Callable[[Dict[str, Any]], None]] = None,
|
|
1382
1388
|
skip_dedup: bool = False,
|
|
1383
1389
|
skip_session_summary: bool = False,
|
|
1390
|
+
no_track: bool = False,
|
|
1384
1391
|
) -> bool:
|
|
1385
1392
|
"""
|
|
1386
1393
|
Perform the actual commit operation to SQLite database.
|
|
@@ -1443,7 +1450,7 @@ class DialogueWatcher:
|
|
|
1443
1450
|
file_created = datetime.fromtimestamp(
|
|
1444
1451
|
getattr(file_stat, "st_birthtime", file_stat.st_ctime)
|
|
1445
1452
|
)
|
|
1446
|
-
db.get_or_create_session(
|
|
1453
|
+
session = db.get_or_create_session(
|
|
1447
1454
|
session_id=session_id,
|
|
1448
1455
|
session_file_path=session_file,
|
|
1449
1456
|
session_type=self._detect_session_type(session_file),
|
|
@@ -1451,6 +1458,19 @@ class DialogueWatcher:
|
|
|
1451
1458
|
workspace_path=str(project_path) if project_path else None,
|
|
1452
1459
|
)
|
|
1453
1460
|
|
|
1461
|
+
# Check no_track from parameter or existing session metadata (polling path)
|
|
1462
|
+
is_no_track = no_track
|
|
1463
|
+
if not is_no_track and session:
|
|
1464
|
+
session_meta = getattr(session, "metadata", None) or {}
|
|
1465
|
+
is_no_track = bool(session_meta.get("no_track", False))
|
|
1466
|
+
|
|
1467
|
+
# Store no_track flag in session metadata if applicable
|
|
1468
|
+
if is_no_track:
|
|
1469
|
+
try:
|
|
1470
|
+
db.update_session_metadata_flag(session_id, "no_track", True)
|
|
1471
|
+
except Exception:
|
|
1472
|
+
pass
|
|
1473
|
+
|
|
1454
1474
|
takeover_attempt = False
|
|
1455
1475
|
existing_turn = db.get_turn_by_number(session_id, turn_number)
|
|
1456
1476
|
if existing_turn and not skip_dedup:
|
|
@@ -1517,14 +1537,19 @@ class DialogueWatcher:
|
|
|
1517
1537
|
logger.debug(f"Failed to write processing placeholder: {e}")
|
|
1518
1538
|
|
|
1519
1539
|
try:
|
|
1520
|
-
#
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
turn_number
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1540
|
+
# Skip LLM call for no-track mode
|
|
1541
|
+
if is_no_track:
|
|
1542
|
+
llm_result = ("No Track", None, "No Track", "no", "fine")
|
|
1543
|
+
logger.info(f"No-track mode: skipping LLM for {session_id} turn {turn_number}")
|
|
1544
|
+
else:
|
|
1545
|
+
# Generate LLM summary with fallback for errors
|
|
1546
|
+
llm_result = self._generate_llm_summary(
|
|
1547
|
+
session_file,
|
|
1548
|
+
turn_number=turn_number,
|
|
1549
|
+
turn_content=turn_content,
|
|
1550
|
+
user_message=user_message,
|
|
1551
|
+
debug_callback=debug_callback,
|
|
1552
|
+
)
|
|
1528
1553
|
|
|
1529
1554
|
if not llm_result:
|
|
1530
1555
|
# LLM summary failed, use error marker to continue commit
|
|
@@ -1774,6 +1799,7 @@ class DialogueWatcher:
|
|
|
1774
1799
|
session_id: str,
|
|
1775
1800
|
prompt: str,
|
|
1776
1801
|
project_dir: str,
|
|
1802
|
+
no_track: bool = False,
|
|
1777
1803
|
) -> None:
|
|
1778
1804
|
"""Generate and store a temporary turn title for a newly submitted user prompt."""
|
|
1779
1805
|
try:
|
|
@@ -1802,20 +1828,28 @@ class DialogueWatcher:
|
|
|
1802
1828
|
if not user_message:
|
|
1803
1829
|
user_message = str(group.get("user_message") or "")
|
|
1804
1830
|
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1831
|
+
# Skip LLM call for no-track mode
|
|
1832
|
+
if no_track:
|
|
1833
|
+
title = "No Track"
|
|
1834
|
+
model_name = None
|
|
1835
|
+
description = "No Track"
|
|
1836
|
+
if_last_task = "no"
|
|
1837
|
+
satisfaction = "fine"
|
|
1838
|
+
else:
|
|
1839
|
+
turn_content = self._extract_turn_content_by_number(session_file, turn_number)
|
|
1840
|
+
result = self._generate_llm_summary(
|
|
1841
|
+
session_file,
|
|
1842
|
+
turn_number=turn_number,
|
|
1843
|
+
turn_content=turn_content,
|
|
1844
|
+
user_message=user_message or None,
|
|
1845
|
+
session_id=session_id or session_file.stem,
|
|
1846
|
+
)
|
|
1847
|
+
if not result:
|
|
1848
|
+
return
|
|
1815
1849
|
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1850
|
+
title, model_name, description, if_last_task, satisfaction = result
|
|
1851
|
+
if not title:
|
|
1852
|
+
return
|
|
1819
1853
|
|
|
1820
1854
|
from .db import get_database
|
|
1821
1855
|
from .db.base import TurnRecord
|
realign/worker_core.py
CHANGED
|
@@ -186,6 +186,7 @@ class AlineWorker:
|
|
|
186
186
|
expected_turns_raw = payload.get("expected_turns")
|
|
187
187
|
expected_turns = int(expected_turns_raw) if expected_turns_raw is not None else None
|
|
188
188
|
skip_dedup = bool(payload.get("skip_dedup") or False)
|
|
189
|
+
no_track = bool(payload.get("no_track") or False)
|
|
189
190
|
|
|
190
191
|
if not session_id or turn_number <= 0 or not session_file_path:
|
|
191
192
|
raise ValueError(f"Invalid turn_summary payload: {payload}")
|
|
@@ -212,6 +213,7 @@ class AlineWorker:
|
|
|
212
213
|
quiet=True,
|
|
213
214
|
skip_session_summary=skip_session_summary,
|
|
214
215
|
skip_dedup=skip_dedup,
|
|
216
|
+
no_track=no_track,
|
|
215
217
|
)
|
|
216
218
|
|
|
217
219
|
if created:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|