aline-ai 0.5.4__py3-none-any.whl → 0.5.5__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.4.dist-info → aline_ai-0.5.5.dist-info}/METADATA +1 -1
- aline_ai-0.5.5.dist-info/RECORD +93 -0
- realign/__init__.py +1 -1
- realign/adapters/antigravity.py +28 -20
- realign/adapters/base.py +46 -50
- realign/adapters/claude.py +14 -14
- realign/adapters/codex.py +7 -7
- realign/adapters/gemini.py +11 -11
- realign/adapters/registry.py +14 -10
- realign/claude_detector.py +2 -2
- realign/claude_hooks/__init__.py +3 -3
- realign/claude_hooks/permission_request_hook_installer.py +31 -32
- realign/claude_hooks/stop_hook.py +4 -1
- realign/claude_hooks/stop_hook_installer.py +30 -31
- realign/cli.py +7 -0
- realign/codex_detector.py +11 -11
- realign/commands/add.py +88 -65
- realign/commands/config.py +3 -12
- realign/commands/context.py +3 -1
- realign/commands/export_shares.py +86 -127
- realign/commands/import_shares.py +145 -155
- realign/commands/init.py +166 -30
- realign/commands/restore.py +18 -6
- realign/commands/search.py +14 -42
- realign/commands/upgrade.py +155 -11
- realign/commands/watcher.py +98 -219
- realign/commands/worker.py +29 -6
- realign/config.py +25 -20
- realign/context.py +1 -3
- realign/dashboard/app.py +4 -4
- realign/dashboard/screens/create_event.py +3 -1
- realign/dashboard/screens/event_detail.py +14 -6
- realign/dashboard/screens/session_detail.py +3 -1
- realign/dashboard/screens/share_import.py +7 -3
- realign/dashboard/tmux_manager.py +54 -9
- realign/dashboard/widgets/config_panel.py +85 -1
- realign/dashboard/widgets/events_table.py +3 -1
- realign/dashboard/widgets/header.py +1 -0
- realign/dashboard/widgets/search_panel.py +37 -27
- realign/dashboard/widgets/sessions_table.py +24 -15
- realign/dashboard/widgets/terminal_panel.py +66 -22
- realign/dashboard/widgets/watcher_panel.py +6 -2
- realign/dashboard/widgets/worker_panel.py +10 -1
- realign/db/__init__.py +1 -1
- realign/db/base.py +5 -15
- realign/db/locks.py +0 -1
- realign/db/migration.py +82 -76
- realign/db/schema.py +2 -6
- realign/db/sqlite_db.py +23 -41
- realign/events/__init__.py +0 -1
- realign/events/event_summarizer.py +27 -15
- realign/events/session_summarizer.py +29 -15
- realign/file_lock.py +1 -0
- realign/hooks.py +150 -60
- realign/logging_config.py +12 -15
- realign/mcp_server.py +30 -51
- realign/mcp_watcher.py +0 -1
- realign/models/event.py +29 -20
- realign/prompts/__init__.py +7 -7
- realign/prompts/presets.py +15 -11
- realign/redactor.py +99 -59
- realign/triggers/__init__.py +9 -9
- realign/triggers/antigravity_trigger.py +30 -28
- realign/triggers/base.py +4 -3
- realign/triggers/claude_trigger.py +104 -85
- realign/triggers/codex_trigger.py +15 -5
- realign/triggers/gemini_trigger.py +57 -47
- realign/triggers/next_turn_trigger.py +3 -1
- realign/triggers/registry.py +6 -2
- realign/triggers/turn_status.py +3 -1
- realign/watcher_core.py +306 -131
- realign/watcher_daemon.py +8 -8
- realign/worker_core.py +3 -1
- realign/worker_daemon.py +3 -1
- aline_ai-0.5.4.dist-info/RECORD +0 -93
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.5.dist-info}/WHEEL +0 -0
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.5.dist-info}/entry_points.txt +0 -0
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.5.dist-info}/licenses/LICENSE +0 -0
- {aline_ai-0.5.4.dist-info → aline_ai-0.5.5.dist-info}/top_level.txt +0 -0
|
@@ -115,7 +115,9 @@ class SearchPanel(Static):
|
|
|
115
115
|
session_id = result.get("session_id") or ""
|
|
116
116
|
turn_id = result.get("turn_id") or ""
|
|
117
117
|
if session_id:
|
|
118
|
-
self.app.push_screen(
|
|
118
|
+
self.app.push_screen(
|
|
119
|
+
SessionDetailScreen(session_id, initial_turn_id=turn_id or None)
|
|
120
|
+
)
|
|
119
121
|
return
|
|
120
122
|
|
|
121
123
|
def on_input_submitted(self, event: Input.Submitted) -> None:
|
|
@@ -156,30 +158,32 @@ class SearchPanel(Static):
|
|
|
156
158
|
|
|
157
159
|
# Search based on type
|
|
158
160
|
if self._search_type in ("all", "event"):
|
|
159
|
-
events = db.search_events(
|
|
160
|
-
self._query, limit=20, use_regex=True, ignore_case=True
|
|
161
|
-
)
|
|
161
|
+
events = db.search_events(self._query, limit=20, use_regex=True, ignore_case=True)
|
|
162
162
|
for event in events:
|
|
163
|
-
self._results.append(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
163
|
+
self._results.append(
|
|
164
|
+
{
|
|
165
|
+
"type": "Event",
|
|
166
|
+
"id": self._shorten_id(event.id),
|
|
167
|
+
"full_id": event.id,
|
|
168
|
+
"title": (event.title or "(no title)")[:60],
|
|
169
|
+
"context": event.event_type or "-",
|
|
170
|
+
}
|
|
171
|
+
)
|
|
170
172
|
|
|
171
173
|
if self._search_type in ("all", "session"):
|
|
172
174
|
sessions = db.search_sessions(
|
|
173
175
|
self._query, limit=20, use_regex=True, ignore_case=True
|
|
174
176
|
)
|
|
175
177
|
for session in sessions:
|
|
176
|
-
self._results.append(
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
178
|
+
self._results.append(
|
|
179
|
+
{
|
|
180
|
+
"type": "Session",
|
|
181
|
+
"id": self._shorten_id(session.id),
|
|
182
|
+
"full_id": session.id,
|
|
183
|
+
"title": (session.session_title or "(no title)")[:60],
|
|
184
|
+
"context": session.session_type or "-",
|
|
185
|
+
}
|
|
186
|
+
)
|
|
183
187
|
|
|
184
188
|
if self._search_type in ("all", "turn"):
|
|
185
189
|
turns = db.search_conversations(
|
|
@@ -188,14 +192,18 @@ class SearchPanel(Static):
|
|
|
188
192
|
for turn in turns:
|
|
189
193
|
session_id = turn.get("session_id", "")
|
|
190
194
|
turn_id = turn.get("turn_id", "")
|
|
191
|
-
self._results.append(
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
195
|
+
self._results.append(
|
|
196
|
+
{
|
|
197
|
+
"type": "Turn",
|
|
198
|
+
"id": self._shorten_id(turn_id),
|
|
199
|
+
"turn_id": turn_id,
|
|
200
|
+
"session_id": session_id,
|
|
201
|
+
"title": (turn.get("title") or turn.get("summary") or "(no title)")[
|
|
202
|
+
:60
|
|
203
|
+
],
|
|
204
|
+
"context": f"{self._shorten_id(session_id)} • Turn #{turn.get('turn_number', '-')}",
|
|
205
|
+
}
|
|
206
|
+
)
|
|
199
207
|
|
|
200
208
|
self._update_display()
|
|
201
209
|
|
|
@@ -232,7 +240,9 @@ class SearchPanel(Static):
|
|
|
232
240
|
|
|
233
241
|
summary_text = f"Found {len(self._results)} results for '{self._query}'"
|
|
234
242
|
if self._search_type == "all":
|
|
235
|
-
summary_text +=
|
|
243
|
+
summary_text += (
|
|
244
|
+
f" (Events: {event_count}, Sessions: {session_count}, Turns: {turn_count})"
|
|
245
|
+
)
|
|
236
246
|
|
|
237
247
|
summary.update(f"[dim]{summary_text}[/dim]")
|
|
238
248
|
else:
|
|
@@ -461,14 +461,16 @@ class SessionsTable(Container, can_focus=True):
|
|
|
461
461
|
total_sessions = int(row[0]) if row else 0
|
|
462
462
|
|
|
463
463
|
# Get stats
|
|
464
|
-
stats_row = conn.execute(
|
|
464
|
+
stats_row = conn.execute(
|
|
465
|
+
"""
|
|
465
466
|
SELECT
|
|
466
467
|
COUNT(*) AS total,
|
|
467
468
|
COUNT(CASE WHEN session_type = 'claude' THEN 1 END) AS claude,
|
|
468
469
|
COUNT(CASE WHEN session_type = 'codex' THEN 1 END) AS codex,
|
|
469
470
|
COUNT(CASE WHEN session_type = 'gemini' THEN 1 END) AS gemini
|
|
470
471
|
FROM sessions
|
|
471
|
-
"""
|
|
472
|
+
"""
|
|
473
|
+
).fetchone()
|
|
472
474
|
|
|
473
475
|
stats = {
|
|
474
476
|
"total": stats_row[0] if stats_row else 0,
|
|
@@ -479,7 +481,8 @@ class SessionsTable(Container, can_focus=True):
|
|
|
479
481
|
|
|
480
482
|
# Get paginated sessions
|
|
481
483
|
offset = (int(page) - 1) * int(rows_per_page)
|
|
482
|
-
rows = conn.execute(
|
|
484
|
+
rows = conn.execute(
|
|
485
|
+
"""
|
|
483
486
|
SELECT
|
|
484
487
|
s.id,
|
|
485
488
|
s.session_type,
|
|
@@ -490,7 +493,9 @@ class SessionsTable(Container, can_focus=True):
|
|
|
490
493
|
FROM sessions s
|
|
491
494
|
ORDER BY s.last_activity_at DESC
|
|
492
495
|
LIMIT ? OFFSET ?
|
|
493
|
-
""",
|
|
496
|
+
""",
|
|
497
|
+
(int(rows_per_page), int(offset)),
|
|
498
|
+
).fetchall()
|
|
494
499
|
|
|
495
500
|
for i, row in enumerate(rows):
|
|
496
501
|
session_id = row[0]
|
|
@@ -517,16 +522,18 @@ class SessionsTable(Container, can_focus=True):
|
|
|
517
522
|
except Exception:
|
|
518
523
|
activity_str = last_activity
|
|
519
524
|
|
|
520
|
-
sessions.append(
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
525
|
+
sessions.append(
|
|
526
|
+
{
|
|
527
|
+
"index": offset + i + 1,
|
|
528
|
+
"id": session_id,
|
|
529
|
+
"short_id": self._shorten_session_id(session_id),
|
|
530
|
+
"source": source,
|
|
531
|
+
"project": project,
|
|
532
|
+
"turns": turn_count,
|
|
533
|
+
"title": title,
|
|
534
|
+
"last_activity": activity_str,
|
|
535
|
+
}
|
|
536
|
+
)
|
|
530
537
|
except Exception:
|
|
531
538
|
total_sessions = 0
|
|
532
539
|
stats = {}
|
|
@@ -553,7 +560,9 @@ class SessionsTable(Container, can_focus=True):
|
|
|
553
560
|
selected_session_id: Optional[str] = None
|
|
554
561
|
try:
|
|
555
562
|
if table.row_count > 0:
|
|
556
|
-
selected_session_id = str(
|
|
563
|
+
selected_session_id = str(
|
|
564
|
+
table.coordinate_to_cell_key(table.cursor_coordinate)[0].value
|
|
565
|
+
)
|
|
557
566
|
except Exception:
|
|
558
567
|
selected_session_id = None
|
|
559
568
|
if self.wrap_mode != self._last_wrap_mode:
|
|
@@ -61,8 +61,7 @@ class _SignalFileWatcher:
|
|
|
61
61
|
try:
|
|
62
62
|
if PERMISSION_SIGNAL_DIR.exists():
|
|
63
63
|
self._seen_files = {
|
|
64
|
-
f.name for f in PERMISSION_SIGNAL_DIR.iterdir()
|
|
65
|
-
if f.suffix == ".signal"
|
|
64
|
+
f.name for f in PERMISSION_SIGNAL_DIR.iterdir() if f.suffix == ".signal"
|
|
66
65
|
}
|
|
67
66
|
except Exception:
|
|
68
67
|
self._seen_files = set()
|
|
@@ -89,8 +88,7 @@ class _SignalFileWatcher:
|
|
|
89
88
|
|
|
90
89
|
# Check for new signal files
|
|
91
90
|
current_files = {
|
|
92
|
-
f.name for f in PERMISSION_SIGNAL_DIR.iterdir()
|
|
93
|
-
if f.suffix == ".signal"
|
|
91
|
+
f.name for f in PERMISSION_SIGNAL_DIR.iterdir() if f.suffix == ".signal"
|
|
94
92
|
}
|
|
95
93
|
new_files = current_files - self._seen_files
|
|
96
94
|
|
|
@@ -115,7 +113,7 @@ class _SignalFileWatcher:
|
|
|
115
113
|
files = sorted(
|
|
116
114
|
PERMISSION_SIGNAL_DIR.glob("*.signal"),
|
|
117
115
|
key=lambda f: f.stat().st_mtime,
|
|
118
|
-
reverse=True
|
|
116
|
+
reverse=True,
|
|
119
117
|
)
|
|
120
118
|
# Keep only the 10 most recent
|
|
121
119
|
for f in files[10:]:
|
|
@@ -132,6 +130,7 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
132
130
|
|
|
133
131
|
class PermissionRequestDetected(Message):
|
|
134
132
|
"""Posted when a new permission request signal file is detected."""
|
|
133
|
+
|
|
135
134
|
pass
|
|
136
135
|
|
|
137
136
|
DEFAULT_CSS = """
|
|
@@ -295,14 +294,32 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
295
294
|
def compose(self) -> ComposeResult:
|
|
296
295
|
controls_enabled = self.supported()
|
|
297
296
|
with Horizontal(classes="summary"):
|
|
298
|
-
yield Button(
|
|
299
|
-
|
|
297
|
+
yield Button(
|
|
298
|
+
"+ Claude",
|
|
299
|
+
id="new-cc",
|
|
300
|
+
variant="primary",
|
|
301
|
+
disabled=not controls_enabled,
|
|
302
|
+
)
|
|
303
|
+
yield Button(
|
|
304
|
+
"+ Codex",
|
|
305
|
+
id="new-codex",
|
|
306
|
+
variant="primary",
|
|
307
|
+
disabled=not controls_enabled,
|
|
308
|
+
)
|
|
309
|
+
yield Button(
|
|
310
|
+
"+ Opencode",
|
|
311
|
+
id="new-opencode",
|
|
312
|
+
variant="primary",
|
|
313
|
+
disabled=not controls_enabled,
|
|
314
|
+
)
|
|
300
315
|
yield Button("+ zsh", id="new-zsh", variant="primary", disabled=not controls_enabled)
|
|
301
316
|
yield Button("↻", id="refresh")
|
|
302
317
|
yield Static("", id="status", classes="status")
|
|
303
318
|
with Vertical(id="terminals", classes="list"):
|
|
304
319
|
if controls_enabled:
|
|
305
|
-
yield Static(
|
|
320
|
+
yield Static(
|
|
321
|
+
"No terminals yet. Click 'New cc' / 'New codex' to open the right pane."
|
|
322
|
+
)
|
|
306
323
|
else:
|
|
307
324
|
yield Static(self._support_message())
|
|
308
325
|
|
|
@@ -385,10 +402,16 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
385
402
|
for w in windows:
|
|
386
403
|
if not self._is_claude_window(w) or not w.context_id:
|
|
387
404
|
continue
|
|
388
|
-
session_ids, session_count, event_count = self._get_loaded_context_info(
|
|
405
|
+
session_ids, session_count, event_count = self._get_loaded_context_info(
|
|
406
|
+
w.context_id
|
|
407
|
+
)
|
|
389
408
|
if not session_ids and session_count == 0 and event_count == 0:
|
|
390
409
|
continue
|
|
391
|
-
context_info_by_context_id[w.context_id] = (
|
|
410
|
+
context_info_by_context_id[w.context_id] = (
|
|
411
|
+
session_ids,
|
|
412
|
+
session_count,
|
|
413
|
+
event_count,
|
|
414
|
+
)
|
|
392
415
|
all_context_session_ids.update(session_ids)
|
|
393
416
|
|
|
394
417
|
if all_context_session_ids:
|
|
@@ -536,12 +559,7 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
536
559
|
)
|
|
537
560
|
)
|
|
538
561
|
|
|
539
|
-
if (
|
|
540
|
-
w.active
|
|
541
|
-
and self._is_claude_window(w)
|
|
542
|
-
and w.context_id
|
|
543
|
-
and expanded
|
|
544
|
-
):
|
|
562
|
+
if w.active and self._is_claude_window(w) and w.context_id and expanded:
|
|
545
563
|
ctx = VerticalScroll(id=f"ctx-{safe}", classes="context-sessions")
|
|
546
564
|
await container.mount(ctx)
|
|
547
565
|
if loaded_ids:
|
|
@@ -600,7 +618,9 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
600
618
|
loaded_count = raw_sessions + raw_events
|
|
601
619
|
detail_line = f"{detail_line} | loaded context: {loaded_count}"
|
|
602
620
|
else:
|
|
603
|
-
detail_line =
|
|
621
|
+
detail_line = (
|
|
622
|
+
f"{detail_line} · {self._format_context_summary(raw_sessions, raw_events)}"
|
|
623
|
+
)
|
|
604
624
|
details.append(detail_line, style="dim not bold")
|
|
605
625
|
return details
|
|
606
626
|
|
|
@@ -631,7 +651,7 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
631
651
|
# Use osascript to invoke macOS native folder picker
|
|
632
652
|
default_path_escaped = default_path.replace('"', '\\"')
|
|
633
653
|
prompt_escaped = prompt.replace('"', '\\"')
|
|
634
|
-
script = f
|
|
654
|
+
script = f"""
|
|
635
655
|
set defaultFolder to POSIX file "{default_path_escaped}" as alias
|
|
636
656
|
try
|
|
637
657
|
set selectedFolder to choose folder with prompt "{prompt_escaped}" default location defaultFolder
|
|
@@ -639,7 +659,7 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
639
659
|
on error
|
|
640
660
|
return ""
|
|
641
661
|
end try
|
|
642
|
-
|
|
662
|
+
"""
|
|
643
663
|
try:
|
|
644
664
|
proc = await asyncio.get_event_loop().run_in_executor(
|
|
645
665
|
None,
|
|
@@ -716,7 +736,9 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
716
736
|
ok_global_permission = ensure_permission_request_hook_installed(quiet=True)
|
|
717
737
|
|
|
718
738
|
project_root = Path(workspace)
|
|
719
|
-
ok_project_stop = install_stop_hook(
|
|
739
|
+
ok_project_stop = install_stop_hook(
|
|
740
|
+
get_stop_settings_path(project_root), quiet=True
|
|
741
|
+
)
|
|
720
742
|
ok_project_submit = install_user_prompt_submit_hook(
|
|
721
743
|
get_submit_settings_path(project_root), quiet=True
|
|
722
744
|
)
|
|
@@ -725,8 +747,12 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
725
747
|
)
|
|
726
748
|
|
|
727
749
|
all_hooks_ok = (
|
|
728
|
-
ok_global_stop
|
|
729
|
-
and
|
|
750
|
+
ok_global_stop
|
|
751
|
+
and ok_global_submit
|
|
752
|
+
and ok_global_permission
|
|
753
|
+
and ok_project_stop
|
|
754
|
+
and ok_project_submit
|
|
755
|
+
and ok_project_permission
|
|
730
756
|
)
|
|
731
757
|
if not all_hooks_ok:
|
|
732
758
|
self.app.notify(
|
|
@@ -766,6 +792,24 @@ class TerminalPanel(Container, can_focus=True):
|
|
|
766
792
|
await self.refresh_data()
|
|
767
793
|
return
|
|
768
794
|
|
|
795
|
+
if button_id == "new-opencode":
|
|
796
|
+
workspace = await self._select_workspace("Select workspace for Opencode")
|
|
797
|
+
if not workspace:
|
|
798
|
+
return
|
|
799
|
+
|
|
800
|
+
command = self._command_in_directory(
|
|
801
|
+
tmux_manager.zsh_run_and_keep_open("opencode"), workspace
|
|
802
|
+
)
|
|
803
|
+
created = tmux_manager.create_inner_window("opencode", command)
|
|
804
|
+
if not created:
|
|
805
|
+
self.app.notify(
|
|
806
|
+
"Failed to open opencode terminal",
|
|
807
|
+
title="Terminal",
|
|
808
|
+
severity="error",
|
|
809
|
+
)
|
|
810
|
+
await self.refresh_data()
|
|
811
|
+
return
|
|
812
|
+
|
|
769
813
|
if button_id == "new-zsh":
|
|
770
814
|
created = tmux_manager.create_inner_window("zsh", "zsh")
|
|
771
815
|
if not created:
|
|
@@ -389,7 +389,9 @@ class WatcherPanel(Container, can_focus=True):
|
|
|
389
389
|
except Exception:
|
|
390
390
|
return []
|
|
391
391
|
|
|
392
|
-
def _collect_recent_sessions_page(
|
|
392
|
+
def _collect_recent_sessions_page(
|
|
393
|
+
self, *, page: int, rows_per_page: int
|
|
394
|
+
) -> tuple[list[dict], int]:
|
|
393
395
|
"""Collect one page of recent sessions from the database."""
|
|
394
396
|
try:
|
|
395
397
|
from ...db import get_database
|
|
@@ -477,7 +479,9 @@ class WatcherPanel(Container, can_focus=True):
|
|
|
477
479
|
if exists:
|
|
478
480
|
paths_lines.append(f" [green]●[/green] {name}: {path}")
|
|
479
481
|
else:
|
|
480
|
-
paths_lines.append(
|
|
482
|
+
paths_lines.append(
|
|
483
|
+
f" [yellow]○[/yellow] {name}: {path} [dim](not found)[/dim]"
|
|
484
|
+
)
|
|
481
485
|
else:
|
|
482
486
|
paths_lines.append(f" [dim]○ {name}: {path} (disabled)[/dim]")
|
|
483
487
|
paths_widget.update("\n".join(paths_lines))
|
|
@@ -483,7 +483,16 @@ class WorkerPanel(Container, can_focus=True):
|
|
|
483
483
|
("Failed", failed, "red"),
|
|
484
484
|
]:
|
|
485
485
|
bar_width = int((count / total) * max_width) if total > 0 else 0
|
|
486
|
-
bar =
|
|
486
|
+
bar = (
|
|
487
|
+
"["
|
|
488
|
+
+ color
|
|
489
|
+
+ "]"
|
|
490
|
+
+ ("█" * bar_width)
|
|
491
|
+
+ "[/"
|
|
492
|
+
+ color
|
|
493
|
+
+ "]"
|
|
494
|
+
+ ("░" * (max_width - bar_width))
|
|
495
|
+
)
|
|
487
496
|
lines.append(f" {label:<12} {bar} {count}")
|
|
488
497
|
|
|
489
498
|
return "\n".join(lines)
|
realign/db/__init__.py
CHANGED
|
@@ -20,7 +20,7 @@ def get_database(
|
|
|
20
20
|
blocking CLI commands under worker/watcher write load.
|
|
21
21
|
"""
|
|
22
22
|
global _DB_INSTANCE
|
|
23
|
-
|
|
23
|
+
|
|
24
24
|
# Resolution order:
|
|
25
25
|
# 1) Env override (tests/ops): REALIGN_SQLITE_DB_PATH or REALIGN_DB_PATH (legacy)
|
|
26
26
|
# 2) Config: ~/.aline/config.yaml (sqlite_db_path)
|
realign/db/base.py
CHANGED
|
@@ -121,9 +121,7 @@ class DatabaseInterface(ABC):
|
|
|
121
121
|
pass
|
|
122
122
|
|
|
123
123
|
@abstractmethod
|
|
124
|
-
def get_or_create_project(
|
|
125
|
-
self, path: Path, name: Optional[str] = None
|
|
126
|
-
) -> ProjectRecord:
|
|
124
|
+
def get_or_create_project(self, path: Path, name: Optional[str] = None) -> ProjectRecord:
|
|
127
125
|
"""Get existing project or create new one."""
|
|
128
126
|
pass
|
|
129
127
|
|
|
@@ -141,9 +139,7 @@ class DatabaseInterface(ABC):
|
|
|
141
139
|
pass
|
|
142
140
|
|
|
143
141
|
@abstractmethod
|
|
144
|
-
def update_session_activity(
|
|
145
|
-
self, session_id: str, last_activity_at: datetime
|
|
146
|
-
) -> None:
|
|
142
|
+
def update_session_activity(self, session_id: str, last_activity_at: datetime) -> None:
|
|
147
143
|
"""Update last activity timestamp for a session."""
|
|
148
144
|
pass
|
|
149
145
|
|
|
@@ -212,16 +208,12 @@ class DatabaseInterface(ABC):
|
|
|
212
208
|
pass
|
|
213
209
|
|
|
214
210
|
@abstractmethod
|
|
215
|
-
def get_turn_by_hash(
|
|
216
|
-
self, session_id: str, content_hash: str
|
|
217
|
-
) -> Optional[TurnRecord]:
|
|
211
|
+
def get_turn_by_hash(self, session_id: str, content_hash: str) -> Optional[TurnRecord]:
|
|
218
212
|
"""Check if a turn with this content hash already exists in the session."""
|
|
219
213
|
pass
|
|
220
214
|
|
|
221
215
|
@abstractmethod
|
|
222
|
-
def get_turn_by_number(
|
|
223
|
-
self, session_id: str, turn_number: int
|
|
224
|
-
) -> Optional[TurnRecord]:
|
|
216
|
+
def get_turn_by_number(self, session_id: str, turn_number: int) -> Optional[TurnRecord]:
|
|
225
217
|
"""Get a turn by session_id and turn_number."""
|
|
226
218
|
pass
|
|
227
219
|
|
|
@@ -379,9 +371,7 @@ class DatabaseInterface(ABC):
|
|
|
379
371
|
pass
|
|
380
372
|
|
|
381
373
|
@abstractmethod
|
|
382
|
-
def try_acquire_lock(
|
|
383
|
-
self, lock_key: str, *, owner: str, ttl_seconds: float
|
|
384
|
-
) -> bool:
|
|
374
|
+
def try_acquire_lock(self, lock_key: str, *, owner: str, ttl_seconds: float) -> bool:
|
|
385
375
|
"""Try to acquire a cross-process lease lock."""
|
|
386
376
|
pass
|
|
387
377
|
|
realign/db/locks.py
CHANGED