aline-ai 0.6.5__py3-none-any.whl → 0.6.7__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.
Files changed (42) hide show
  1. {aline_ai-0.6.5.dist-info → aline_ai-0.6.7.dist-info}/METADATA +1 -1
  2. {aline_ai-0.6.5.dist-info → aline_ai-0.6.7.dist-info}/RECORD +41 -34
  3. realign/__init__.py +1 -1
  4. realign/agent_names.py +79 -0
  5. realign/claude_hooks/stop_hook.py +3 -0
  6. realign/claude_hooks/terminal_state.py +43 -1
  7. realign/claude_hooks/user_prompt_submit_hook.py +3 -0
  8. realign/cli.py +62 -0
  9. realign/codex_detector.py +18 -3
  10. realign/codex_home.py +65 -16
  11. realign/codex_terminal_linker.py +18 -7
  12. realign/commands/agent.py +109 -0
  13. realign/commands/doctor.py +74 -1
  14. realign/commands/export_shares.py +448 -0
  15. realign/commands/import_shares.py +203 -1
  16. realign/commands/search.py +58 -29
  17. realign/commands/sync_agent.py +347 -0
  18. realign/dashboard/app.py +9 -9
  19. realign/dashboard/clipboard.py +54 -0
  20. realign/dashboard/screens/__init__.py +4 -0
  21. realign/dashboard/screens/agent_detail.py +333 -0
  22. realign/dashboard/screens/create_agent_info.py +244 -0
  23. realign/dashboard/screens/event_detail.py +6 -27
  24. realign/dashboard/styles/dashboard.tcss +22 -28
  25. realign/dashboard/tmux_manager.py +36 -10
  26. realign/dashboard/widgets/__init__.py +2 -2
  27. realign/dashboard/widgets/agents_panel.py +1248 -0
  28. realign/dashboard/widgets/events_table.py +4 -27
  29. realign/dashboard/widgets/sessions_table.py +4 -27
  30. realign/db/base.py +69 -0
  31. realign/db/locks.py +4 -0
  32. realign/db/schema.py +111 -2
  33. realign/db/sqlite_db.py +360 -2
  34. realign/events/agent_summarizer.py +157 -0
  35. realign/events/session_summarizer.py +25 -0
  36. realign/watcher_core.py +193 -5
  37. realign/worker_core.py +59 -1
  38. realign/dashboard/widgets/terminal_panel.py +0 -1653
  39. {aline_ai-0.6.5.dist-info → aline_ai-0.6.7.dist-info}/WHEEL +0 -0
  40. {aline_ai-0.6.5.dist-info → aline_ai-0.6.7.dist-info}/entry_points.txt +0 -0
  41. {aline_ai-0.6.5.dist-info → aline_ai-0.6.7.dist-info}/licenses/LICENSE +0 -0
  42. {aline_ai-0.6.5.dist-info → aline_ai-0.6.7.dist-info}/top_level.txt +0 -0
@@ -3,9 +3,6 @@
3
3
  import contextlib
4
4
  import io
5
5
  import json
6
- import os
7
- import shutil
8
- import subprocess
9
6
  import traceback
10
7
  from datetime import datetime
11
8
  from typing import Optional, Set
@@ -20,6 +17,7 @@ from textual.worker import Worker, WorkerState
20
17
  from textual.widgets import Button, DataTable, Static
21
18
 
22
19
  from ...logging_config import setup_logger
20
+ from ..clipboard import copy_text
23
21
  from .openable_table import OpenableDataTable
24
22
 
25
23
  logger = setup_logger("realign.dashboard.events", "dashboard.log")
@@ -613,33 +611,12 @@ class EventsTable(Container):
613
611
 
614
612
  # Build copy text
615
613
  if slack_message:
616
- copy_text = str(slack_message) + "\n\n" + str(share_link)
614
+ text_to_copy = str(slack_message) + "\n\n" + str(share_link)
617
615
  else:
618
- copy_text = str(share_link)
616
+ text_to_copy = str(share_link)
619
617
 
620
618
  # Copy to clipboard
621
- copied = False
622
- if os.environ.get("TMUX") and shutil.which("pbcopy"):
623
- try:
624
- copied = (
625
- subprocess.run(
626
- ["pbcopy"],
627
- input=copy_text,
628
- text=True,
629
- capture_output=False,
630
- check=False,
631
- ).returncode
632
- == 0
633
- )
634
- except Exception:
635
- copied = False
636
-
637
- if not copied:
638
- try:
639
- self.app.copy_to_clipboard(copy_text)
640
- copied = True
641
- except Exception:
642
- copied = False
619
+ copied = copy_text(self.app, text_to_copy)
643
620
 
644
621
  suffix = " (copied to clipboard)" if copied else ""
645
622
  self.app.notify(f"Share link created{suffix}", title="Share", timeout=4)
@@ -3,9 +3,6 @@
3
3
  import contextlib
4
4
  import io
5
5
  import json
6
- import os
7
- import shutil
8
- import subprocess
9
6
  import traceback
10
7
  from datetime import datetime
11
8
  from pathlib import Path
@@ -20,6 +17,7 @@ from textual.worker import Worker, WorkerState
20
17
  from textual.widgets import Button, DataTable, Select, Static
21
18
 
22
19
  from ...logging_config import setup_logger
20
+ from ..clipboard import copy_text
23
21
  from .openable_table import OpenableDataTable
24
22
 
25
23
  logger = setup_logger("realign.dashboard.sessions", "dashboard.log")
@@ -685,33 +683,12 @@ class SessionsTable(Container):
685
683
 
686
684
  # Build copy text
687
685
  if slack_message:
688
- copy_text = str(slack_message) + "\n\n" + str(share_link)
686
+ text_to_copy = str(slack_message) + "\n\n" + str(share_link)
689
687
  else:
690
- copy_text = str(share_link)
688
+ text_to_copy = str(share_link)
691
689
 
692
690
  # Copy to clipboard
693
- copied = False
694
- if os.environ.get("TMUX") and shutil.which("pbcopy"):
695
- try:
696
- copied = (
697
- subprocess.run(
698
- ["pbcopy"],
699
- input=copy_text,
700
- text=True,
701
- capture_output=False,
702
- check=False,
703
- ).returncode
704
- == 0
705
- )
706
- except Exception:
707
- copied = False
708
-
709
- if not copied:
710
- try:
711
- self.app.copy_to_clipboard(copy_text)
712
- copied = True
713
- except Exception:
714
- copied = False
691
+ copied = copy_text(self.app, text_to_copy)
715
692
 
716
693
  suffix = " (copied to clipboard)" if copied else ""
717
694
  self.app.notify(f"Share link created{suffix}", title="Share", timeout=4)
realign/db/base.py CHANGED
@@ -57,6 +57,8 @@ class SessionRecord:
57
57
  total_turns: Optional[int] = None
58
58
  # V12: file mtime when total_turns was cached (for validation)
59
59
  total_turns_mtime: Optional[float] = None
60
+ # V19: agent association
61
+ agent_id: Optional[str] = None
60
62
 
61
63
 
62
64
  @dataclass
@@ -125,6 +127,39 @@ class AgentRecord:
125
127
  created_by: Optional[str] = None # Creator UID
126
128
 
127
129
 
130
+ @dataclass
131
+ class AgentInfoRecord:
132
+ """Agent profile/identity data (V20, V22: sync fields)."""
133
+
134
+ id: str
135
+ name: str
136
+ created_at: datetime
137
+ updated_at: datetime
138
+ description: Optional[str] = ""
139
+ visibility: str = "visible"
140
+ # V22: sync metadata
141
+ share_id: Optional[str] = None
142
+ share_url: Optional[str] = None
143
+ share_admin_token: Optional[str] = None
144
+ share_contributor_token: Optional[str] = None
145
+ share_expiry_at: Optional[str] = None
146
+ last_synced_at: Optional[str] = None
147
+ sync_version: int = 0
148
+
149
+
150
+ @dataclass
151
+ class WindowLinkRecord:
152
+ """Represents a terminal/session association (V23)."""
153
+
154
+ terminal_id: str
155
+ agent_id: Optional[str] = None
156
+ session_id: Optional[str] = None
157
+ provider: Optional[str] = None
158
+ source: Optional[str] = None
159
+ ts: Optional[float] = None
160
+ created_at: Optional[datetime] = None
161
+
162
+
128
163
  @dataclass
129
164
  class AgentContextRecord:
130
165
  """Represents a context entry (V15: replaces load.json)."""
@@ -214,6 +249,27 @@ class DatabaseInterface(ABC):
214
249
  """Backfill total_turns for all sessions from turns table (V10 migration)."""
215
250
  pass
216
251
 
252
+ @abstractmethod
253
+ def insert_window_link(
254
+ self,
255
+ *,
256
+ terminal_id: str,
257
+ agent_id: Optional[str],
258
+ session_id: Optional[str],
259
+ provider: Optional[str],
260
+ source: Optional[str],
261
+ ts: Optional[float] = None,
262
+ ) -> None:
263
+ """Insert a window link record (V23)."""
264
+ pass
265
+
266
+ @abstractmethod
267
+ def list_latest_window_links(
268
+ self, *, agent_id: Optional[str] = None, limit: int = 1000
269
+ ) -> List[WindowLinkRecord]:
270
+ """List latest window link per terminal (V23)."""
271
+ pass
272
+
217
273
  @abstractmethod
218
274
  def list_sessions(
219
275
  self, limit: int = 100, workspace_path: Optional[str] = None
@@ -250,6 +306,19 @@ class DatabaseInterface(ABC):
250
306
  """
251
307
  pass
252
308
 
309
+ @abstractmethod
310
+ def get_sessions_by_agent_id(self, agent_id: str, limit: int = 1000) -> List[SessionRecord]:
311
+ """Get all sessions linked to an agent.
312
+
313
+ Args:
314
+ agent_id: The agent_info ID
315
+ limit: Maximum number of sessions to return
316
+
317
+ Returns:
318
+ List of SessionRecord objects for this agent
319
+ """
320
+ pass
321
+
253
322
  @abstractmethod
254
323
  def get_turn_by_hash(self, session_id: str, content_hash: str) -> Optional[TurnRecord]:
255
324
  """Check if a turn with this content hash already exists in the session."""
realign/db/locks.py CHANGED
@@ -31,6 +31,10 @@ def lock_key_for_event_summary(event_id: str) -> str:
31
31
  return f"event_summary:{event_id}"
32
32
 
33
33
 
34
+ def lock_key_for_agent_description(agent_id: str) -> str:
35
+ return f"agent_description:{agent_id}"
36
+
37
+
34
38
  @contextmanager
35
39
  def lease_lock(
36
40
  db: DatabaseInterface,
realign/db/schema.py CHANGED
@@ -76,9 +76,18 @@ Schema V18: UID refactor - created_by/shared_by with users table.
76
76
  - turns: drop uid and user_name (inherit from session)
77
77
  - agents: uid -> created_by, drop user_name
78
78
  - Update indexes accordingly
79
+
80
+ Schema V19: Agent association for sessions.
81
+ - sessions.agent_id: Logical agent entity association
82
+
83
+ Schema V20: Agent identity/profile table.
84
+ - agent_info table: name, description for agent profiles
85
+
86
+ Schema V23: WindowLink mapping for terminal/session association.
87
+ - windowlink table: terminal_id/agent_id/session_id with timestamp
79
88
  """
80
89
 
81
- SCHEMA_VERSION = 18
90
+ SCHEMA_VERSION = 23
82
91
 
83
92
  FTS_EVENTS_SCRIPTS = [
84
93
  # Full Text Search for Events
@@ -150,7 +159,8 @@ INIT_SCRIPTS = [
150
159
  created_by TEXT, -- V18: Creator UID (FK to users.uid)
151
160
  shared_by TEXT, -- V18: Sharer UID (who imported this)
152
161
  total_turns INTEGER DEFAULT 0, -- V10: Cached total turn count (avoids reading files)
153
- total_turns_mtime REAL -- V12: File mtime when total_turns was cached (for validation)
162
+ total_turns_mtime REAL, -- V12: File mtime when total_turns was cached (for validation)
163
+ agent_id TEXT -- V19: Logical agent association
154
164
  );
155
165
  """,
156
166
  # Turns table (corresponds to git commits, V18: uid/user_name removed)
@@ -222,6 +232,7 @@ INIT_SCRIPTS = [
222
232
  "CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(last_activity_at DESC);",
223
233
  "CREATE INDEX IF NOT EXISTS idx_sessions_type ON sessions(session_type);",
224
234
  "CREATE INDEX IF NOT EXISTS idx_sessions_created_by ON sessions(created_by);", # V18
235
+ "CREATE INDEX IF NOT EXISTS idx_sessions_agent_id ON sessions(agent_id);", # V19
225
236
  "CREATE INDEX IF NOT EXISTS idx_turns_session ON turns(session_id);",
226
237
  "CREATE INDEX IF NOT EXISTS idx_turns_timestamp ON turns(timestamp DESC);",
227
238
  "CREATE INDEX IF NOT EXISTS idx_turns_hash ON turns(content_hash);",
@@ -335,6 +346,24 @@ INIT_SCRIPTS = [
335
346
  updated_at TEXT DEFAULT (datetime('now'))
336
347
  );
337
348
  """,
349
+ # Agent identity/profile table (V20, V22: sync columns)
350
+ """
351
+ CREATE TABLE IF NOT EXISTS agent_info (
352
+ id TEXT PRIMARY KEY,
353
+ name TEXT NOT NULL,
354
+ description TEXT DEFAULT '',
355
+ visibility TEXT NOT NULL DEFAULT 'visible',
356
+ share_id TEXT,
357
+ share_url TEXT,
358
+ share_admin_token TEXT,
359
+ share_contributor_token TEXT,
360
+ share_expiry_at TEXT,
361
+ last_synced_at TEXT,
362
+ sync_version INTEGER DEFAULT 0,
363
+ created_at TEXT DEFAULT (datetime('now')),
364
+ updated_at TEXT DEFAULT (datetime('now'))
365
+ );
366
+ """,
338
367
  *FTS_EVENTS_SCRIPTS,
339
368
  ]
340
369
 
@@ -386,6 +415,21 @@ MIGRATION_V1_TO_V2 = [
386
415
  """
387
416
  CREATE INDEX IF NOT EXISTS idx_sessions_type ON sessions(session_type);
388
417
  """,
418
+ # WindowLink table (V23)
419
+ """
420
+ CREATE TABLE IF NOT EXISTS windowlink (
421
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
422
+ terminal_id TEXT NOT NULL,
423
+ agent_id TEXT,
424
+ session_id TEXT,
425
+ provider TEXT,
426
+ source TEXT,
427
+ ts REAL,
428
+ created_at TEXT DEFAULT (datetime('now'))
429
+ );
430
+ """,
431
+ "CREATE INDEX IF NOT EXISTS idx_windowlink_terminal_ts ON windowlink(terminal_id, ts DESC);",
432
+ "CREATE INDEX IF NOT EXISTS idx_windowlink_agent_ts ON windowlink(agent_id, ts DESC);",
389
433
  ]
390
434
 
391
435
  # Migration scripts from V2 to V3
@@ -652,6 +696,56 @@ MIGRATION_V17_TO_V18 = [
652
696
  ]
653
697
 
654
698
 
699
+ MIGRATION_V18_TO_V19 = [
700
+ "ALTER TABLE sessions ADD COLUMN agent_id TEXT;",
701
+ "CREATE INDEX IF NOT EXISTS idx_sessions_agent_id ON sessions(agent_id);",
702
+ ]
703
+
704
+ MIGRATION_V19_TO_V20 = [
705
+ """
706
+ CREATE TABLE IF NOT EXISTS agent_info (
707
+ id TEXT PRIMARY KEY,
708
+ name TEXT NOT NULL,
709
+ description TEXT DEFAULT '',
710
+ visibility TEXT NOT NULL DEFAULT 'visible',
711
+ created_at TEXT DEFAULT (datetime('now')),
712
+ updated_at TEXT DEFAULT (datetime('now'))
713
+ );
714
+ """,
715
+ ]
716
+
717
+ MIGRATION_V20_TO_V21 = [
718
+ "ALTER TABLE agent_info ADD COLUMN visibility TEXT NOT NULL DEFAULT 'visible';",
719
+ ]
720
+
721
+ MIGRATION_V21_TO_V22 = [
722
+ "ALTER TABLE agent_info ADD COLUMN share_id TEXT;",
723
+ "ALTER TABLE agent_info ADD COLUMN share_url TEXT;",
724
+ "ALTER TABLE agent_info ADD COLUMN share_admin_token TEXT;",
725
+ "ALTER TABLE agent_info ADD COLUMN share_contributor_token TEXT;",
726
+ "ALTER TABLE agent_info ADD COLUMN share_expiry_at TEXT;",
727
+ "ALTER TABLE agent_info ADD COLUMN last_synced_at TEXT;",
728
+ "ALTER TABLE agent_info ADD COLUMN sync_version INTEGER DEFAULT 0;",
729
+ ]
730
+
731
+ MIGRATION_V22_TO_V23 = [
732
+ """
733
+ CREATE TABLE IF NOT EXISTS windowlink (
734
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
735
+ terminal_id TEXT NOT NULL,
736
+ agent_id TEXT,
737
+ session_id TEXT,
738
+ provider TEXT,
739
+ source TEXT,
740
+ ts REAL,
741
+ created_at TEXT DEFAULT (datetime('now'))
742
+ );
743
+ """,
744
+ "CREATE INDEX IF NOT EXISTS idx_windowlink_terminal_ts ON windowlink(terminal_id, ts DESC);",
745
+ "CREATE INDEX IF NOT EXISTS idx_windowlink_agent_ts ON windowlink(agent_id, ts DESC);",
746
+ ]
747
+
748
+
655
749
  def get_migration_scripts(from_version: int, to_version: int) -> list:
656
750
  """Get migration scripts for upgrading between versions."""
657
751
  scripts = []
@@ -716,4 +810,19 @@ def get_migration_scripts(from_version: int, to_version: int) -> list:
716
810
  if from_version < 18 and to_version >= 18:
717
811
  scripts.extend(MIGRATION_V17_TO_V18)
718
812
 
813
+ if from_version < 19 and to_version >= 19:
814
+ scripts.extend(MIGRATION_V18_TO_V19)
815
+
816
+ if from_version < 20 and to_version >= 20:
817
+ scripts.extend(MIGRATION_V19_TO_V20)
818
+
819
+ if from_version < 21 and to_version >= 21:
820
+ scripts.extend(MIGRATION_V20_TO_V21)
821
+
822
+ if from_version < 22 and to_version >= 22:
823
+ scripts.extend(MIGRATION_V21_TO_V22)
824
+
825
+ if from_version < 23 and to_version >= 23:
826
+ scripts.extend(MIGRATION_V22_TO_V23)
827
+
719
828
  return scripts