aline-ai 0.6.2__py3-none-any.whl → 0.6.3__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.2.dist-info → aline_ai-0.6.3.dist-info}/METADATA +1 -1
- {aline_ai-0.6.2.dist-info → aline_ai-0.6.3.dist-info}/RECORD +28 -30
- realign/__init__.py +1 -1
- realign/adapters/__init__.py +0 -3
- realign/cli.py +0 -1
- realign/commands/export_shares.py +154 -226
- realign/commands/watcher.py +28 -79
- realign/config.py +1 -47
- realign/dashboard/app.py +2 -8
- realign/dashboard/screens/event_detail.py +0 -3
- realign/dashboard/screens/session_detail.py +0 -1
- realign/dashboard/widgets/config_panel.py +109 -249
- realign/dashboard/widgets/events_table.py +71 -128
- realign/dashboard/widgets/sessions_table.py +76 -135
- realign/dashboard/widgets/watcher_panel.py +0 -2
- realign/db/sqlite_db.py +1 -2
- realign/events/event_summarizer.py +76 -35
- realign/events/session_summarizer.py +73 -32
- realign/hooks.py +383 -574
- realign/llm_client.py +201 -520
- realign/triggers/__init__.py +0 -2
- realign/triggers/next_turn_trigger.py +4 -5
- realign/triggers/registry.py +1 -4
- realign/watcher_core.py +3 -35
- realign/adapters/antigravity.py +0 -159
- realign/triggers/antigravity_trigger.py +0 -140
- {aline_ai-0.6.2.dist-info → aline_ai-0.6.3.dist-info}/WHEEL +0 -0
- {aline_ai-0.6.2.dist-info → aline_ai-0.6.3.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.6.2.dist-info → aline_ai-0.6.3.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.6.2.dist-info → aline_ai-0.6.3.dist-info}/top_level.txt +0 -0
|
@@ -80,23 +80,16 @@ class SessionsTable(Container):
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
SessionsTable .table-container {
|
|
83
|
-
height:
|
|
83
|
+
height: 1fr;
|
|
84
84
|
overflow-x: auto;
|
|
85
|
-
overflow-y:
|
|
85
|
+
overflow-y: auto;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
SessionsTable DataTable {
|
|
89
89
|
height: auto;
|
|
90
90
|
max-height: 100%;
|
|
91
91
|
overflow-x: auto;
|
|
92
|
-
overflow-y:
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
SessionsTable .pagination-info {
|
|
96
|
-
height: 1;
|
|
97
|
-
margin-top: 1;
|
|
98
|
-
color: $text-muted;
|
|
99
|
-
text-align: center;
|
|
92
|
+
overflow-y: auto;
|
|
100
93
|
}
|
|
101
94
|
|
|
102
95
|
SessionsTable .stats-info {
|
|
@@ -107,9 +100,9 @@ class SessionsTable(Container):
|
|
|
107
100
|
"""
|
|
108
101
|
|
|
109
102
|
# Reactive properties
|
|
110
|
-
current_page: reactive[int] = reactive(1)
|
|
111
|
-
rows_per_page: reactive[int] = reactive(10)
|
|
112
103
|
wrap_mode: reactive[bool] = reactive(False)
|
|
104
|
+
# Maximum sessions to load (for scrollable list)
|
|
105
|
+
MAX_SESSIONS: int = 500
|
|
113
106
|
|
|
114
107
|
def __init__(self) -> None:
|
|
115
108
|
super().__init__()
|
|
@@ -123,8 +116,9 @@ class SessionsTable(Container):
|
|
|
123
116
|
self._refresh_worker: Optional[Worker] = None
|
|
124
117
|
self._share_export_worker: Optional[Worker] = None
|
|
125
118
|
self._refresh_timer = None
|
|
126
|
-
self.
|
|
127
|
-
self.
|
|
119
|
+
self._is_refreshing: bool = False
|
|
120
|
+
self._pending_refresh: bool = False
|
|
121
|
+
self._saved_cursor_session_id: Optional[str] = None
|
|
128
122
|
|
|
129
123
|
def compose(self) -> ComposeResult:
|
|
130
124
|
"""Compose the sessions table layout."""
|
|
@@ -144,7 +138,6 @@ class SessionsTable(Container):
|
|
|
144
138
|
yield Static(id="section-header", classes="section-header")
|
|
145
139
|
with Container(classes="table-container"):
|
|
146
140
|
yield SessionsListTable(id="sessions-table")
|
|
147
|
-
yield Static(id="pagination-info", classes="pagination-info")
|
|
148
141
|
yield Static(id="stats-info", classes="stats-info")
|
|
149
142
|
|
|
150
143
|
def on_mount(self) -> None:
|
|
@@ -155,22 +148,19 @@ class SessionsTable(Container):
|
|
|
155
148
|
table.owner = self
|
|
156
149
|
|
|
157
150
|
self._setup_table_columns(table)
|
|
158
|
-
|
|
159
|
-
# Calculate initial rows per page
|
|
160
|
-
self._calculate_rows_per_page()
|
|
161
151
|
logger.debug("SessionsTable.on_mount() completed")
|
|
162
152
|
except Exception as e:
|
|
163
153
|
logger.error(f"SessionsTable.on_mount() failed: {e}\n{traceback.format_exc()}")
|
|
164
154
|
raise
|
|
165
155
|
|
|
166
156
|
def on_resize(self) -> None:
|
|
167
|
-
"""Handle window resize
|
|
168
|
-
|
|
157
|
+
"""Handle window resize."""
|
|
158
|
+
pass # No longer need to recalculate pagination
|
|
169
159
|
|
|
170
160
|
def on_show(self) -> None:
|
|
171
|
-
"""
|
|
161
|
+
"""Refresh data when the tab becomes visible."""
|
|
172
162
|
if self._refresh_timer is None:
|
|
173
|
-
self._refresh_timer = self.set_interval(
|
|
163
|
+
self._refresh_timer = self.set_interval(60.0, self._on_refresh_timer)
|
|
174
164
|
else:
|
|
175
165
|
try:
|
|
176
166
|
self._refresh_timer.resume()
|
|
@@ -188,9 +178,7 @@ class SessionsTable(Container):
|
|
|
188
178
|
pass
|
|
189
179
|
|
|
190
180
|
def _on_became_visible(self) -> None:
|
|
191
|
-
self._sync_to_available_height()
|
|
192
181
|
self._load_sessions()
|
|
193
|
-
self._update_display()
|
|
194
182
|
try:
|
|
195
183
|
self.query_one("#sessions-table", DataTable).focus()
|
|
196
184
|
except Exception:
|
|
@@ -229,19 +217,6 @@ class SessionsTable(Container):
|
|
|
229
217
|
table.add_column("Last Activity", key="last_activity", width=12)
|
|
230
218
|
table.cursor_type = "row"
|
|
231
219
|
|
|
232
|
-
def _sync_to_available_height(self) -> None:
|
|
233
|
-
"""Recalculate rows per page and reload if the page size changed."""
|
|
234
|
-
old_rows_per_page = self.rows_per_page
|
|
235
|
-
self._calculate_rows_per_page()
|
|
236
|
-
|
|
237
|
-
if self.rows_per_page != old_rows_per_page:
|
|
238
|
-
total_pages = self._get_total_pages()
|
|
239
|
-
if self.current_page > total_pages:
|
|
240
|
-
self.current_page = total_pages
|
|
241
|
-
self._load_sessions()
|
|
242
|
-
|
|
243
|
-
self._update_display()
|
|
244
|
-
|
|
245
220
|
def get_selected_session_ids(self) -> list[str]:
|
|
246
221
|
return sorted(self._selected_session_ids)
|
|
247
222
|
|
|
@@ -423,47 +398,6 @@ class SessionsTable(Container):
|
|
|
423
398
|
self._start_share_export()
|
|
424
399
|
return
|
|
425
400
|
|
|
426
|
-
def _calculate_rows_per_page(self) -> None:
|
|
427
|
-
"""Calculate rows per page based on available height."""
|
|
428
|
-
try:
|
|
429
|
-
panel_height = self.size.height
|
|
430
|
-
|
|
431
|
-
# Calculate exact space needed:
|
|
432
|
-
# - Summary section: ~2 lines content + 2 border + 2 padding + 1 margin = 7
|
|
433
|
-
# - Section header: 1 line + 1 margin = 2
|
|
434
|
-
# - Table header row: 1
|
|
435
|
-
# - Pagination info: 1 line + 1 margin = 2
|
|
436
|
-
# - Panel padding: 2 (top + bottom)
|
|
437
|
-
# - Extra buffer: 3
|
|
438
|
-
|
|
439
|
-
fixed_height = 7 + 2 + 1 + 2 + 2 + 3 # = 17
|
|
440
|
-
|
|
441
|
-
available_for_rows = panel_height - fixed_height
|
|
442
|
-
rows = max(available_for_rows, 3) # At least 3 rows
|
|
443
|
-
|
|
444
|
-
self.rows_per_page = rows
|
|
445
|
-
except Exception:
|
|
446
|
-
self.rows_per_page = 10
|
|
447
|
-
|
|
448
|
-
def _get_total_pages(self) -> int:
|
|
449
|
-
"""Calculate total pages."""
|
|
450
|
-
if self._total_sessions == 0:
|
|
451
|
-
return 1
|
|
452
|
-
return (self._total_sessions + self.rows_per_page - 1) // self.rows_per_page
|
|
453
|
-
|
|
454
|
-
def action_next_page(self) -> None:
|
|
455
|
-
"""Go to next page."""
|
|
456
|
-
total_pages = self._get_total_pages()
|
|
457
|
-
if self.current_page < total_pages:
|
|
458
|
-
self.current_page += 1
|
|
459
|
-
self._load_sessions()
|
|
460
|
-
|
|
461
|
-
def action_prev_page(self) -> None:
|
|
462
|
-
"""Go to previous page."""
|
|
463
|
-
if self.current_page > 1:
|
|
464
|
-
self.current_page -= 1
|
|
465
|
-
self._load_sessions()
|
|
466
|
-
|
|
467
401
|
def _on_refresh_timer(self) -> None:
|
|
468
402
|
self.refresh_data(force=False)
|
|
469
403
|
|
|
@@ -472,26 +406,37 @@ class SessionsTable(Container):
|
|
|
472
406
|
if not self.display:
|
|
473
407
|
return
|
|
474
408
|
|
|
475
|
-
snapshot = (int(self.current_page), int(self.rows_per_page))
|
|
476
409
|
if self._refresh_worker is not None and self._refresh_worker.state in (
|
|
477
410
|
WorkerState.PENDING,
|
|
478
411
|
WorkerState.RUNNING,
|
|
479
412
|
):
|
|
480
413
|
if force:
|
|
481
|
-
self.
|
|
414
|
+
self._pending_refresh = True
|
|
482
415
|
return
|
|
483
416
|
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
self._pending_refresh_snapshot = None
|
|
417
|
+
# Save current cursor position before refresh
|
|
418
|
+
self._save_cursor_position()
|
|
487
419
|
|
|
488
420
|
def work() -> dict:
|
|
489
|
-
return self.
|
|
421
|
+
return self._collect_all_sessions()
|
|
490
422
|
|
|
491
|
-
self.
|
|
492
|
-
self.
|
|
423
|
+
self._is_refreshing = True
|
|
424
|
+
self._pending_refresh = False
|
|
493
425
|
self._refresh_worker = self.run_worker(work, thread=True, exit_on_error=False)
|
|
494
426
|
|
|
427
|
+
def _save_cursor_position(self) -> None:
|
|
428
|
+
"""Save the current cursor session ID to restore after refresh."""
|
|
429
|
+
try:
|
|
430
|
+
table = self.query_one("#sessions-table", DataTable)
|
|
431
|
+
if table.row_count > 0:
|
|
432
|
+
self._saved_cursor_session_id = str(
|
|
433
|
+
table.coordinate_to_cell_key(table.cursor_coordinate)[0].value
|
|
434
|
+
)
|
|
435
|
+
else:
|
|
436
|
+
self._saved_cursor_session_id = None
|
|
437
|
+
except Exception:
|
|
438
|
+
self._saved_cursor_session_id = None
|
|
439
|
+
|
|
495
440
|
def _load_sessions(self) -> None:
|
|
496
441
|
"""Compatibility hook (tests stub this); default triggers async refresh."""
|
|
497
442
|
if not self.is_mounted:
|
|
@@ -513,35 +458,34 @@ class SessionsTable(Container):
|
|
|
513
458
|
|
|
514
459
|
if event.state == WorkerState.ERROR:
|
|
515
460
|
result = {
|
|
516
|
-
"snapshot": self._active_refresh_snapshot,
|
|
517
461
|
"total_sessions": 0,
|
|
518
462
|
"stats": {},
|
|
519
463
|
"sessions": [],
|
|
520
464
|
}
|
|
465
|
+
self._is_refreshing = False
|
|
521
466
|
elif event.state != WorkerState.SUCCESS:
|
|
522
467
|
return
|
|
523
468
|
else:
|
|
524
469
|
result = self._refresh_worker.result or {}
|
|
470
|
+
self._is_refreshing = False
|
|
525
471
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
self._sessions_by_id = {}
|
|
542
|
-
self._update_display()
|
|
472
|
+
try:
|
|
473
|
+
self._total_sessions = int(result.get("total_sessions") or 0)
|
|
474
|
+
except Exception:
|
|
475
|
+
self._total_sessions = 0
|
|
476
|
+
try:
|
|
477
|
+
self._stats = dict(result.get("stats") or {})
|
|
478
|
+
except Exception:
|
|
479
|
+
self._stats = {}
|
|
480
|
+
try:
|
|
481
|
+
self._sessions = list(result.get("sessions") or [])
|
|
482
|
+
self._sessions_by_id = {s["id"]: s for s in self._sessions}
|
|
483
|
+
except Exception:
|
|
484
|
+
self._sessions = []
|
|
485
|
+
self._sessions_by_id = {}
|
|
486
|
+
self._update_display()
|
|
543
487
|
|
|
544
|
-
if self.
|
|
488
|
+
if self._pending_refresh:
|
|
545
489
|
self.refresh_data()
|
|
546
490
|
|
|
547
491
|
def _start_share_export(self) -> None:
|
|
@@ -772,8 +716,8 @@ class SessionsTable(Container):
|
|
|
772
716
|
suffix = " (copied to clipboard)" if copied else ""
|
|
773
717
|
self.app.notify(f"Share link created{suffix}", title="Share", timeout=4)
|
|
774
718
|
|
|
775
|
-
def
|
|
776
|
-
"""Collect sessions + stats
|
|
719
|
+
def _collect_all_sessions(self) -> dict:
|
|
720
|
+
"""Collect all sessions + stats (background thread)."""
|
|
777
721
|
total_sessions: int = 0
|
|
778
722
|
stats: dict = {}
|
|
779
723
|
sessions: list[dict] = []
|
|
@@ -810,9 +754,7 @@ class SessionsTable(Container):
|
|
|
810
754
|
"gemini": stats_row[3] if stats_row else 0,
|
|
811
755
|
}
|
|
812
756
|
|
|
813
|
-
# Get
|
|
814
|
-
# Use cached total_turns instead of subquery for performance
|
|
815
|
-
offset = (int(page) - 1) * int(rows_per_page)
|
|
757
|
+
# Get all sessions (up to MAX_SESSIONS)
|
|
816
758
|
try:
|
|
817
759
|
rows = conn.execute(
|
|
818
760
|
"""
|
|
@@ -827,9 +769,9 @@ class SessionsTable(Container):
|
|
|
827
769
|
s.shared_by
|
|
828
770
|
FROM sessions s
|
|
829
771
|
ORDER BY s.last_activity_at DESC
|
|
830
|
-
LIMIT ?
|
|
772
|
+
LIMIT ?
|
|
831
773
|
""",
|
|
832
|
-
(
|
|
774
|
+
(self.MAX_SESSIONS,),
|
|
833
775
|
).fetchall()
|
|
834
776
|
has_new_columns = True
|
|
835
777
|
except Exception:
|
|
@@ -845,9 +787,9 @@ class SessionsTable(Container):
|
|
|
845
787
|
s.total_turns
|
|
846
788
|
FROM sessions s
|
|
847
789
|
ORDER BY s.last_activity_at DESC
|
|
848
|
-
LIMIT ?
|
|
790
|
+
LIMIT ?
|
|
849
791
|
""",
|
|
850
|
-
(
|
|
792
|
+
(self.MAX_SESSIONS,),
|
|
851
793
|
).fetchall()
|
|
852
794
|
has_new_columns = False
|
|
853
795
|
|
|
@@ -869,7 +811,6 @@ class SessionsTable(Container):
|
|
|
869
811
|
"claude": "Claude",
|
|
870
812
|
"codex": "Codex",
|
|
871
813
|
"gemini": "Gemini",
|
|
872
|
-
"antigravity": "Antigravity",
|
|
873
814
|
}
|
|
874
815
|
source = source_map.get(session_type, session_type)
|
|
875
816
|
project = Path(workspace).name if workspace else "-"
|
|
@@ -904,7 +845,7 @@ class SessionsTable(Container):
|
|
|
904
845
|
|
|
905
846
|
sessions.append(
|
|
906
847
|
{
|
|
907
|
-
"index":
|
|
848
|
+
"index": i + 1,
|
|
908
849
|
"id": session_id,
|
|
909
850
|
"short_id": self._shorten_session_id(session_id),
|
|
910
851
|
"source": source,
|
|
@@ -917,13 +858,12 @@ class SessionsTable(Container):
|
|
|
917
858
|
}
|
|
918
859
|
)
|
|
919
860
|
except Exception as e:
|
|
920
|
-
logger.error(f"
|
|
861
|
+
logger.error(f"_collect_all_sessions failed: {e}\n{traceback.format_exc()}")
|
|
921
862
|
total_sessions = 0
|
|
922
863
|
stats = {}
|
|
923
864
|
sessions = []
|
|
924
865
|
|
|
925
866
|
return {
|
|
926
|
-
"snapshot": (int(page), int(rows_per_page)),
|
|
927
867
|
"total_sessions": total_sessions,
|
|
928
868
|
"stats": stats,
|
|
929
869
|
"sessions": sessions,
|
|
@@ -939,18 +879,23 @@ class SessionsTable(Container):
|
|
|
939
879
|
|
|
940
880
|
# Update table
|
|
941
881
|
table = self.query_one("#sessions-table", DataTable)
|
|
942
|
-
|
|
943
|
-
try
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
882
|
+
|
|
883
|
+
# Use saved cursor position if available, otherwise try to get current
|
|
884
|
+
restore_session_id = self._saved_cursor_session_id
|
|
885
|
+
if restore_session_id is None:
|
|
886
|
+
try:
|
|
887
|
+
if table.row_count > 0:
|
|
888
|
+
restore_session_id = str(
|
|
889
|
+
table.coordinate_to_cell_key(table.cursor_coordinate)[0].value
|
|
890
|
+
)
|
|
891
|
+
except Exception:
|
|
892
|
+
restore_session_id = None
|
|
893
|
+
|
|
950
894
|
table.clear()
|
|
951
895
|
|
|
952
|
-
# Always enable
|
|
896
|
+
# Always enable scrollbars
|
|
953
897
|
table.styles.overflow_x = "auto"
|
|
898
|
+
table.styles.overflow_y = "auto"
|
|
954
899
|
table.show_horizontal_scrollbar = True
|
|
955
900
|
|
|
956
901
|
for session in self._sessions:
|
|
@@ -979,10 +924,10 @@ class SessionsTable(Container):
|
|
|
979
924
|
)
|
|
980
925
|
|
|
981
926
|
if table.row_count > 0:
|
|
982
|
-
if
|
|
927
|
+
if restore_session_id:
|
|
983
928
|
try:
|
|
984
929
|
table.cursor_coordinate = (
|
|
985
|
-
table.get_row_index(
|
|
930
|
+
table.get_row_index(restore_session_id),
|
|
986
931
|
0,
|
|
987
932
|
)
|
|
988
933
|
except Exception:
|
|
@@ -990,12 +935,8 @@ class SessionsTable(Container):
|
|
|
990
935
|
else:
|
|
991
936
|
table.cursor_coordinate = (0, 0)
|
|
992
937
|
|
|
993
|
-
#
|
|
994
|
-
|
|
995
|
-
pagination_widget = self.query_one("#pagination-info", Static)
|
|
996
|
-
pagination_widget.update(
|
|
997
|
-
f"[dim]Page {self.current_page}/{total_pages} ({self._total_sessions} total) │ (p) prev (n) next[/dim]"
|
|
998
|
-
)
|
|
938
|
+
# Clear saved cursor position after restore
|
|
939
|
+
self._saved_cursor_session_id = None
|
|
999
940
|
|
|
1000
941
|
def _shorten_session_id(self, session_id: str) -> str:
|
|
1001
942
|
"""Shorten a session ID for display."""
|
|
@@ -318,7 +318,6 @@ class WatcherPanel(Container, can_focus=True):
|
|
|
318
318
|
("Claude", "~/.claude/projects/", config.auto_detect_claude),
|
|
319
319
|
("Codex", "~/.codex/sessions/", config.auto_detect_codex),
|
|
320
320
|
("Gemini", "~/.gemini/tmp/", config.auto_detect_gemini),
|
|
321
|
-
("Antigravity", "~/.gemini/antigravity/brain/", config.auto_detect_antigravity),
|
|
322
321
|
]
|
|
323
322
|
except Exception:
|
|
324
323
|
monitored_paths = []
|
|
@@ -426,7 +425,6 @@ class WatcherPanel(Container, can_focus=True):
|
|
|
426
425
|
"claude": "Claude",
|
|
427
426
|
"codex": "Codex",
|
|
428
427
|
"gemini": "Gemini",
|
|
429
|
-
"antigravity": "Antigravity",
|
|
430
428
|
}
|
|
431
429
|
source = source_map.get(session_type, session_type)
|
|
432
430
|
project = Path(workspace).name if workspace else "-"
|
realign/db/sqlite_db.py
CHANGED
|
@@ -999,8 +999,7 @@ class SQLiteDatabase(DatabaseInterface):
|
|
|
999
999
|
# - If the caller requested regeneration (`skip_dedup=True`), we must allow requeue.
|
|
1000
1000
|
# - If the DB is missing the corresponding turn row (historical bug / manual DB edits),
|
|
1001
1001
|
# requeue so the system can self-heal instead of getting stuck in a "done but missing" state.
|
|
1002
|
-
|
|
1003
|
-
requeue_done = bool(skip_dedup) or (session_type or "").lower() in ("antigravity",)
|
|
1002
|
+
requeue_done = bool(skip_dedup)
|
|
1004
1003
|
try:
|
|
1005
1004
|
conn = self._get_connection()
|
|
1006
1005
|
row = conn.execute(
|
|
@@ -9,7 +9,7 @@ from ..db.sqlite_db import SQLiteDatabase
|
|
|
9
9
|
from ..db.base import SessionRecord
|
|
10
10
|
from ..db.locks import lease_lock, lock_key_for_event_summary, make_lock_owner
|
|
11
11
|
from .debouncer import Debouncer
|
|
12
|
-
from ..llm_client import
|
|
12
|
+
from ..llm_client import extract_json, call_llm_cloud
|
|
13
13
|
|
|
14
14
|
logger = logging.getLogger(__name__)
|
|
15
15
|
|
|
@@ -201,41 +201,82 @@ def _generate_event_summary_llm(sessions: List[SessionRecord]) -> Tuple[str, str
|
|
|
201
201
|
}
|
|
202
202
|
)
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
user_prompt = json.dumps(
|
|
207
|
-
{
|
|
208
|
-
"total_sessions": len(sessions),
|
|
209
|
-
"sessions": sessions_data,
|
|
210
|
-
},
|
|
211
|
-
ensure_ascii=False,
|
|
212
|
-
indent=2,
|
|
213
|
-
)
|
|
214
|
-
|
|
204
|
+
# Try cloud provider first if user is logged in
|
|
215
205
|
try:
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
206
|
+
from ..auth import is_logged_in
|
|
207
|
+
|
|
208
|
+
if is_logged_in():
|
|
209
|
+
logger.debug("Attempting cloud LLM for event summary")
|
|
210
|
+
# Load user custom prompt if available
|
|
211
|
+
custom_prompt = None
|
|
212
|
+
user_prompt_path = Path.home() / ".aline" / "prompts" / "event_summary.md"
|
|
213
|
+
try:
|
|
214
|
+
if user_prompt_path.exists():
|
|
215
|
+
custom_prompt = user_prompt_path.read_text(encoding="utf-8").strip()
|
|
216
|
+
except Exception:
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
_, result = call_llm_cloud(
|
|
220
|
+
task="event_summary",
|
|
221
|
+
payload={"sessions": sessions_data},
|
|
222
|
+
custom_prompt=custom_prompt,
|
|
223
|
+
silent=True,
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if result:
|
|
227
|
+
title = result.get("event_title", "Untitled Event")[:100]
|
|
228
|
+
description = result.get("event_description", "")
|
|
229
|
+
logger.info(f"Cloud LLM event summary success: title={title[:50]}...")
|
|
230
|
+
return title, description
|
|
231
|
+
else:
|
|
232
|
+
# Cloud LLM failed, use fallback (local fallback disabled)
|
|
233
|
+
logger.warning("Cloud LLM event summary failed, using fallback")
|
|
234
|
+
return _fallback_event_summary(sessions)
|
|
235
|
+
except ImportError:
|
|
236
|
+
logger.debug("Auth module not available, skipping cloud LLM")
|
|
237
|
+
|
|
238
|
+
# User not logged in, use fallback (local fallback disabled)
|
|
239
|
+
logger.warning("Not logged in, cannot use cloud LLM for event summary")
|
|
240
|
+
return _fallback_event_summary(sessions)
|
|
241
|
+
|
|
242
|
+
# =========================================================================
|
|
243
|
+
# LOCAL LLM FALLBACK DISABLED - Code kept for reference
|
|
244
|
+
# =========================================================================
|
|
245
|
+
# system_prompt = _get_event_summary_prompt()
|
|
246
|
+
#
|
|
247
|
+
# user_prompt = json.dumps(
|
|
248
|
+
# {
|
|
249
|
+
# "total_sessions": len(sessions),
|
|
250
|
+
# "sessions": sessions_data,
|
|
251
|
+
# },
|
|
252
|
+
# ensure_ascii=False,
|
|
253
|
+
# indent=2,
|
|
254
|
+
# )
|
|
255
|
+
#
|
|
256
|
+
# try:
|
|
257
|
+
# # Use unified LLM client
|
|
258
|
+
# _, response = call_llm(
|
|
259
|
+
# system_prompt=system_prompt,
|
|
260
|
+
# user_prompt=user_prompt,
|
|
261
|
+
# provider="auto", # Try Claude first, fallback to OpenAI
|
|
262
|
+
# max_tokens=500,
|
|
263
|
+
# purpose="event_summary",
|
|
264
|
+
# )
|
|
265
|
+
#
|
|
266
|
+
# if not response:
|
|
267
|
+
# logger.warning("LLM returned empty response, using fallback")
|
|
268
|
+
# return _fallback_event_summary(sessions)
|
|
269
|
+
#
|
|
270
|
+
# result = extract_json(response)
|
|
271
|
+
#
|
|
272
|
+
# title = result.get("event_title", "Untitled Event")[:100]
|
|
273
|
+
# description = result.get("event_description", "")
|
|
274
|
+
#
|
|
275
|
+
# return title, description
|
|
276
|
+
#
|
|
277
|
+
# except Exception as e:
|
|
278
|
+
# logger.warning(f"LLM event summary failed, using fallback: {e}")
|
|
279
|
+
# return _fallback_event_summary(sessions)
|
|
239
280
|
|
|
240
281
|
|
|
241
282
|
def _fallback_event_summary(sessions: List[SessionRecord]) -> Tuple[str, str]:
|
|
@@ -10,7 +10,7 @@ from typing import List, Tuple, Optional
|
|
|
10
10
|
from ..db.sqlite_db import SQLiteDatabase
|
|
11
11
|
from ..db.base import TurnRecord
|
|
12
12
|
from ..db.locks import lease_lock, lock_key_for_session_summary, make_lock_owner
|
|
13
|
-
from ..llm_client import
|
|
13
|
+
from ..llm_client import extract_json, call_llm_cloud
|
|
14
14
|
|
|
15
15
|
logger = logging.getLogger(__name__)
|
|
16
16
|
|
|
@@ -267,41 +267,82 @@ def _generate_session_summary_llm(turns: List[TurnRecord]) -> Tuple[str, str]:
|
|
|
267
267
|
}
|
|
268
268
|
)
|
|
269
269
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
user_prompt = json.dumps(
|
|
273
|
-
{
|
|
274
|
-
"total_turns": len(turns),
|
|
275
|
-
"turns": turns_data,
|
|
276
|
-
},
|
|
277
|
-
ensure_ascii=False,
|
|
278
|
-
indent=2,
|
|
279
|
-
)
|
|
280
|
-
|
|
270
|
+
# Try cloud provider first if user is logged in
|
|
281
271
|
try:
|
|
282
|
-
|
|
283
|
-
_, response = call_llm(
|
|
284
|
-
system_prompt=system_prompt,
|
|
285
|
-
user_prompt=user_prompt,
|
|
286
|
-
provider="auto", # Try Claude first, fallback to OpenAI
|
|
287
|
-
max_tokens=500,
|
|
288
|
-
purpose="session_summary",
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
if not response:
|
|
292
|
-
logger.warning("LLM returned empty response, using fallback")
|
|
293
|
-
return _fallback_summary(turns)
|
|
294
|
-
|
|
295
|
-
result = extract_json(response)
|
|
272
|
+
from ..auth import is_logged_in
|
|
296
273
|
|
|
297
|
-
|
|
298
|
-
|
|
274
|
+
if is_logged_in():
|
|
275
|
+
logger.debug("Attempting cloud LLM for session summary")
|
|
276
|
+
# Load user custom prompt if available
|
|
277
|
+
custom_prompt = None
|
|
278
|
+
user_prompt_path = Path.home() / ".aline" / "prompts" / "session_summary.md"
|
|
279
|
+
try:
|
|
280
|
+
if user_prompt_path.exists():
|
|
281
|
+
custom_prompt = user_prompt_path.read_text(encoding="utf-8").strip()
|
|
282
|
+
except Exception:
|
|
283
|
+
pass
|
|
299
284
|
|
|
300
|
-
|
|
285
|
+
_, result = call_llm_cloud(
|
|
286
|
+
task="session_summary",
|
|
287
|
+
payload={"turns": turns_data},
|
|
288
|
+
custom_prompt=custom_prompt,
|
|
289
|
+
silent=True,
|
|
290
|
+
)
|
|
301
291
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
292
|
+
if result:
|
|
293
|
+
title = result.get("session_title", "Untitled Session")[:80]
|
|
294
|
+
summary = result.get("session_summary", "")
|
|
295
|
+
logger.info(f"Cloud LLM session summary success: title={title[:50]}...")
|
|
296
|
+
return title, summary
|
|
297
|
+
else:
|
|
298
|
+
# Cloud LLM failed, use fallback (local fallback disabled)
|
|
299
|
+
logger.warning("Cloud LLM session summary failed, using fallback")
|
|
300
|
+
return _fallback_summary(turns)
|
|
301
|
+
except ImportError:
|
|
302
|
+
logger.debug("Auth module not available, skipping cloud LLM")
|
|
303
|
+
|
|
304
|
+
# User not logged in, use fallback (local fallback disabled)
|
|
305
|
+
logger.warning("Not logged in, cannot use cloud LLM for session summary")
|
|
306
|
+
return _fallback_summary(turns)
|
|
307
|
+
|
|
308
|
+
# =========================================================================
|
|
309
|
+
# LOCAL LLM FALLBACK DISABLED - Code kept for reference
|
|
310
|
+
# =========================================================================
|
|
311
|
+
# system_prompt = _get_session_summary_prompt()
|
|
312
|
+
#
|
|
313
|
+
# user_prompt = json.dumps(
|
|
314
|
+
# {
|
|
315
|
+
# "total_turns": len(turns),
|
|
316
|
+
# "turns": turns_data,
|
|
317
|
+
# },
|
|
318
|
+
# ensure_ascii=False,
|
|
319
|
+
# indent=2,
|
|
320
|
+
# )
|
|
321
|
+
#
|
|
322
|
+
# try:
|
|
323
|
+
# # Use unified LLM client
|
|
324
|
+
# _, response = call_llm(
|
|
325
|
+
# system_prompt=system_prompt,
|
|
326
|
+
# user_prompt=user_prompt,
|
|
327
|
+
# provider="auto", # Try Claude first, fallback to OpenAI
|
|
328
|
+
# max_tokens=500,
|
|
329
|
+
# purpose="session_summary",
|
|
330
|
+
# )
|
|
331
|
+
#
|
|
332
|
+
# if not response:
|
|
333
|
+
# logger.warning("LLM returned empty response, using fallback")
|
|
334
|
+
# return _fallback_summary(turns)
|
|
335
|
+
#
|
|
336
|
+
# result = extract_json(response)
|
|
337
|
+
#
|
|
338
|
+
# title = result.get("session_title", "Untitled Session")[:80]
|
|
339
|
+
# summary = result.get("session_summary", "")
|
|
340
|
+
#
|
|
341
|
+
# return title, summary
|
|
342
|
+
#
|
|
343
|
+
# except Exception as e:
|
|
344
|
+
# logger.warning(f"LLM session summary failed, using fallback: {e}")
|
|
345
|
+
# return _fallback_summary(turns)
|
|
305
346
|
|
|
306
347
|
|
|
307
348
|
def _fallback_summary(turns: List[TurnRecord]) -> Tuple[str, str]:
|