aline-ai 0.5.12__py3-none-any.whl → 0.6.0__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.
realign/db/schema.py CHANGED
@@ -62,9 +62,23 @@ Schema V15: Agents and contexts tables (replaces terminal.json and load.json).
62
62
  Schema V16: Remove FK constraints from agent_context_sessions/events.
63
63
  - Context may reference sessions/events not yet imported to DB
64
64
  - Recreate M2M tables without FK constraints on session_id/event_id
65
+
66
+ Schema V17: Rename creator_id/creator_name to uid/user_name.
67
+ - sessions.creator_id -> uid, sessions.creator_name -> user_name
68
+ - turns.creator_id -> uid, turns.creator_name -> user_name
69
+ - events.creator_id -> uid, events.creator_name -> user_name
70
+ - agents.creator_id -> uid, agents.creator_name -> user_name
71
+ - Update indexes accordingly
72
+
73
+ Schema V18: UID refactor - created_by/shared_by with users table.
74
+ - New users table: uid -> user_name mapping
75
+ - sessions/events: uid -> created_by, drop user_name, add shared_by
76
+ - turns: drop uid and user_name (inherit from session)
77
+ - agents: uid -> created_by, drop user_name
78
+ - Update indexes accordingly
65
79
  """
66
80
 
67
- SCHEMA_VERSION = 16
81
+ SCHEMA_VERSION = 18
68
82
 
69
83
  FTS_EVENTS_SCRIPTS = [
70
84
  # Full Text Search for Events
@@ -115,7 +129,7 @@ INIT_SCRIPTS = [
115
129
  metadata TEXT -- JSON metadata
116
130
  );
117
131
  """,
118
- # Sessions table (V2: decoupled from projects, V3: summary fields, V9: creator fields, V10: total_turns cache)
132
+ # Sessions table (V2: decoupled from projects, V3: summary fields, V18: created_by/shared_by, V10: total_turns cache)
119
133
  """
120
134
  CREATE TABLE IF NOT EXISTS sessions (
121
135
  id TEXT PRIMARY KEY, -- session ID (filename stem)
@@ -133,13 +147,13 @@ INIT_SCRIPTS = [
133
147
  summary_status TEXT DEFAULT 'idle', -- V7: 'idle' | 'processing' | 'completed' | 'failed'
134
148
  summary_locked_until TEXT, -- V7: lease/TTL to avoid stuck processing
135
149
  summary_error TEXT, -- V7: last error message if failed
136
- creator_name TEXT, -- V9: Username who created the session
137
- creator_id TEXT, -- V9: User UUID (based on MAC address)
150
+ created_by TEXT, -- V18: Creator UID (FK to users.uid)
151
+ shared_by TEXT, -- V18: Sharer UID (who imported this)
138
152
  total_turns INTEGER DEFAULT 0, -- V10: Cached total turn count (avoids reading files)
139
153
  total_turns_mtime REAL -- V12: File mtime when total_turns was cached (for validation)
140
154
  );
141
155
  """,
142
- # Turns table (corresponds to git commits, V9: creator fields)
156
+ # Turns table (corresponds to git commits, V18: uid/user_name removed)
143
157
  """
144
158
  CREATE TABLE IF NOT EXISTS turns (
145
159
  id TEXT PRIMARY KEY, -- UUID
@@ -158,8 +172,6 @@ INIT_SCRIPTS = [
158
172
  timestamp TEXT NOT NULL,
159
173
  created_at TEXT DEFAULT (datetime('now')),
160
174
  git_commit_hash TEXT, -- Linked git commit hash
161
- creator_name TEXT, -- V9: Username who created the turn
162
- creator_id TEXT, -- V9: User UUID
163
175
  UNIQUE(session_id, turn_number)
164
176
  );
165
177
  """,
@@ -209,12 +221,11 @@ INIT_SCRIPTS = [
209
221
  "CREATE INDEX IF NOT EXISTS idx_sessions_workspace ON sessions(workspace_path);",
210
222
  "CREATE INDEX IF NOT EXISTS idx_sessions_activity ON sessions(last_activity_at DESC);",
211
223
  "CREATE INDEX IF NOT EXISTS idx_sessions_type ON sessions(session_type);",
212
- "CREATE INDEX IF NOT EXISTS idx_sessions_creator ON sessions(creator_id);", # V9
224
+ "CREATE INDEX IF NOT EXISTS idx_sessions_created_by ON sessions(created_by);", # V18
213
225
  "CREATE INDEX IF NOT EXISTS idx_turns_session ON turns(session_id);",
214
226
  "CREATE INDEX IF NOT EXISTS idx_turns_timestamp ON turns(timestamp DESC);",
215
227
  "CREATE INDEX IF NOT EXISTS idx_turns_hash ON turns(content_hash);",
216
- "CREATE INDEX IF NOT EXISTS idx_turns_creator ON turns(creator_id);", # V9
217
- # Events table (V9: creator fields)
228
+ # Events table (V18: created_by/shared_by)
218
229
  """
219
230
  CREATE TABLE IF NOT EXISTS events (
220
231
  id TEXT PRIMARY KEY, -- UUID
@@ -233,8 +244,8 @@ INIT_SCRIPTS = [
233
244
  share_id TEXT, -- V14: Server share ID (for reuse)
234
245
  share_admin_token TEXT, -- V14: Server admin token (extend expiry)
235
246
  share_expiry_at TEXT, -- V14: Last known expiry timestamp
236
- creator_name TEXT, -- V9: Username who created the event
237
- creator_id TEXT -- V9: User UUID
247
+ created_by TEXT, -- V18: Creator UID (FK to users.uid)
248
+ shared_by TEXT -- V18: Sharer UID (who imported this)
238
249
  );
239
250
  """,
240
251
  # Event-Commit relationship (Many-to-Many)
@@ -256,7 +267,8 @@ INIT_SCRIPTS = [
256
267
  """,
257
268
  "CREATE INDEX IF NOT EXISTS idx_event_sessions_event ON event_sessions(event_id);",
258
269
  "CREATE INDEX IF NOT EXISTS idx_event_sessions_session ON event_sessions(session_id);",
259
- # Agents table (V15: replaces terminal.json)
270
+ "CREATE INDEX IF NOT EXISTS idx_events_created_by ON events(created_by);", # V18
271
+ # Agents table (V15: replaces terminal.json, V18: created_by)
260
272
  """
261
273
  CREATE TABLE IF NOT EXISTS agents (
262
274
  id TEXT PRIMARY KEY, -- terminal_id (UUID)
@@ -272,8 +284,7 @@ INIT_SCRIPTS = [
272
284
  source TEXT,
273
285
  created_at TEXT DEFAULT (datetime('now')),
274
286
  updated_at TEXT DEFAULT (datetime('now')),
275
- creator_name TEXT,
276
- creator_id TEXT
287
+ created_by TEXT -- V18: Creator UID (FK to users.uid)
277
288
  );
278
289
  """,
279
290
  "CREATE INDEX IF NOT EXISTS idx_agents_session ON agents(session_id);",
@@ -315,6 +326,15 @@ INIT_SCRIPTS = [
315
326
  """,
316
327
  "CREATE INDEX IF NOT EXISTS idx_agent_context_events_context ON agent_context_events(context_id);",
317
328
  "CREATE INDEX IF NOT EXISTS idx_agent_context_events_event ON agent_context_events(event_id);",
329
+ # Users table (V18: UID-to-user-info mapping)
330
+ """
331
+ CREATE TABLE IF NOT EXISTS users (
332
+ uid TEXT PRIMARY KEY,
333
+ user_name TEXT,
334
+ created_at TEXT DEFAULT (datetime('now')),
335
+ updated_at TEXT DEFAULT (datetime('now'))
336
+ );
337
+ """,
318
338
  *FTS_EVENTS_SCRIPTS,
319
339
  ]
320
340
 
@@ -570,6 +590,67 @@ MIGRATION_V15_TO_V16 = [
570
590
  "CREATE INDEX IF NOT EXISTS idx_agent_context_events_event ON agent_context_events(event_id);",
571
591
  ]
572
592
 
593
+ # V16 to V17: Rename creator_id/creator_name to uid/user_name
594
+ MIGRATION_V16_TO_V17 = [
595
+ # Sessions table: rename columns
596
+ "ALTER TABLE sessions RENAME COLUMN creator_id TO uid;",
597
+ "ALTER TABLE sessions RENAME COLUMN creator_name TO user_name;",
598
+ # Turns table: rename columns
599
+ "ALTER TABLE turns RENAME COLUMN creator_id TO uid;",
600
+ "ALTER TABLE turns RENAME COLUMN creator_name TO user_name;",
601
+ # Events table: rename columns
602
+ "ALTER TABLE events RENAME COLUMN creator_id TO uid;",
603
+ "ALTER TABLE events RENAME COLUMN creator_name TO user_name;",
604
+ # Agents table: rename columns
605
+ "ALTER TABLE agents RENAME COLUMN creator_id TO uid;",
606
+ "ALTER TABLE agents RENAME COLUMN creator_name TO user_name;",
607
+ # Update indexes: drop old, create new
608
+ "DROP INDEX IF EXISTS idx_sessions_creator;",
609
+ "DROP INDEX IF EXISTS idx_turns_creator;",
610
+ "DROP INDEX IF EXISTS idx_events_creator;",
611
+ "CREATE INDEX IF NOT EXISTS idx_sessions_uid ON sessions(uid);",
612
+ "CREATE INDEX IF NOT EXISTS idx_turns_uid ON turns(uid);",
613
+ "CREATE INDEX IF NOT EXISTS idx_events_uid ON events(uid);",
614
+ ]
615
+
616
+
617
+ # V17 to V18: uid/user_name → created_by/shared_by, users table, remove turns uid
618
+ MIGRATION_V17_TO_V18 = [
619
+ # 1. Create users table
620
+ """
621
+ CREATE TABLE IF NOT EXISTS users (
622
+ uid TEXT PRIMARY KEY,
623
+ user_name TEXT,
624
+ created_at TEXT DEFAULT (datetime('now')),
625
+ updated_at TEXT DEFAULT (datetime('now'))
626
+ );
627
+ """,
628
+ # 2. Extract user info from existing data into users table
629
+ "INSERT OR IGNORE INTO users (uid, user_name) SELECT DISTINCT uid, user_name FROM sessions WHERE uid IS NOT NULL AND uid != '';",
630
+ "INSERT OR IGNORE INTO users (uid, user_name) SELECT DISTINCT uid, user_name FROM events WHERE uid IS NOT NULL AND uid != '' AND uid NOT IN (SELECT uid FROM users);",
631
+ "INSERT OR IGNORE INTO users (uid, user_name) SELECT DISTINCT uid, user_name FROM turns WHERE uid IS NOT NULL AND uid != '' AND uid NOT IN (SELECT uid FROM users);",
632
+ "INSERT OR IGNORE INTO users (uid, user_name) SELECT DISTINCT uid, user_name FROM agents WHERE uid IS NOT NULL AND uid != '' AND uid NOT IN (SELECT uid FROM users);",
633
+ # 3. Sessions: uid → created_by, drop user_name, add shared_by
634
+ "DROP INDEX IF EXISTS idx_sessions_uid;",
635
+ "ALTER TABLE sessions RENAME COLUMN uid TO created_by;",
636
+ "ALTER TABLE sessions DROP COLUMN user_name;",
637
+ "ALTER TABLE sessions ADD COLUMN shared_by TEXT;",
638
+ "CREATE INDEX IF NOT EXISTS idx_sessions_created_by ON sessions(created_by);",
639
+ # 4. Events: uid → created_by, drop user_name, add shared_by
640
+ "DROP INDEX IF EXISTS idx_events_uid;",
641
+ "ALTER TABLE events RENAME COLUMN uid TO created_by;",
642
+ "ALTER TABLE events DROP COLUMN user_name;",
643
+ "ALTER TABLE events ADD COLUMN shared_by TEXT;",
644
+ "CREATE INDEX IF NOT EXISTS idx_events_created_by ON events(created_by);",
645
+ # 5. Turns: drop uid and user_name
646
+ "DROP INDEX IF EXISTS idx_turns_uid;",
647
+ "ALTER TABLE turns DROP COLUMN uid;",
648
+ "ALTER TABLE turns DROP COLUMN user_name;",
649
+ # 6. Agents: uid → created_by, drop user_name (no shared_by needed)
650
+ "ALTER TABLE agents RENAME COLUMN uid TO created_by;",
651
+ "ALTER TABLE agents DROP COLUMN user_name;",
652
+ ]
653
+
573
654
 
574
655
  def get_migration_scripts(from_version: int, to_version: int) -> list:
575
656
  """Get migration scripts for upgrading between versions."""
@@ -629,4 +710,10 @@ def get_migration_scripts(from_version: int, to_version: int) -> list:
629
710
  if from_version == 15:
630
711
  scripts.extend(MIGRATION_V15_TO_V16)
631
712
 
713
+ if from_version < 17 and to_version >= 17:
714
+ scripts.extend(MIGRATION_V16_TO_V17)
715
+
716
+ if from_version < 18 and to_version >= 18:
717
+ scripts.extend(MIGRATION_V17_TO_V18)
718
+
632
719
  return scripts
realign/db/sqlite_db.py CHANGED
@@ -22,6 +22,7 @@ from .base import (
22
22
  LockRecord,
23
23
  AgentRecord,
24
24
  AgentContextRecord,
25
+ UserRecord,
25
26
  )
26
27
  from .schema import (
27
28
  INIT_SCRIPTS,
@@ -303,7 +304,7 @@ class SQLiteDatabase(DatabaseInterface):
303
304
  if row:
304
305
  return self._row_to_session(row)
305
306
 
306
- # Get user identity from config (V9)
307
+ # Get user identity from config
307
308
  from ..config import ReAlignConfig
308
309
 
309
310
  config = ReAlignConfig.load()
@@ -316,8 +317,8 @@ class SQLiteDatabase(DatabaseInterface):
316
317
  INSERT INTO sessions (
317
318
  id, session_file_path, session_type, workspace_path,
318
319
  started_at, last_activity_at, created_at, updated_at, metadata,
319
- creator_name, creator_id
320
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
320
+ created_by
321
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
321
322
  """,
322
323
  (
323
324
  session_id,
@@ -329,12 +330,18 @@ class SQLiteDatabase(DatabaseInterface):
329
330
  now,
330
331
  now,
331
332
  metadata_json,
332
- config.user_name,
333
- config.user_id,
333
+ config.uid,
334
334
  ),
335
335
  )
336
336
  conn.commit()
337
337
 
338
+ # Upsert current user to users table
339
+ if config.uid:
340
+ try:
341
+ self.upsert_user(config.uid, config.user_name)
342
+ except Exception:
343
+ pass
344
+
338
345
  return SessionRecord(
339
346
  id=session_id,
340
347
  session_file_path=session_file_path,
@@ -345,8 +352,7 @@ class SQLiteDatabase(DatabaseInterface):
345
352
  updated_at=now,
346
353
  metadata=metadata or {},
347
354
  workspace_path=workspace_path,
348
- creator_name=config.user_name,
349
- creator_id=config.user_id,
355
+ created_by=config.uid,
350
356
  )
351
357
 
352
358
  def update_session_activity(self, session_id: str, last_activity_at: datetime) -> None:
@@ -659,15 +665,15 @@ class SQLiteDatabase(DatabaseInterface):
659
665
  # Older schema without temp_title column.
660
666
  pass
661
667
 
662
- # Insert turn record (V9: includes creator fields)
668
+ # Insert turn record (V18: no user identity fields)
663
669
  conn.execute(
664
670
  """
665
671
  INSERT OR REPLACE INTO turns (
666
672
  id, session_id, turn_number, user_message, assistant_summary,
667
673
  turn_status, llm_title, temp_title, llm_description, model_name,
668
674
  if_last_task, satisfaction, content_hash, timestamp,
669
- created_at, git_commit_hash, creator_name, creator_id
670
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
675
+ created_at, git_commit_hash
676
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
671
677
  """,
672
678
  (
673
679
  turn.id,
@@ -686,8 +692,6 @@ class SQLiteDatabase(DatabaseInterface):
686
692
  turn.timestamp,
687
693
  turn.created_at,
688
694
  turn.git_commit_hash,
689
- turn.creator_name,
690
- turn.creator_id,
691
695
  ),
692
696
  )
693
697
 
@@ -1495,13 +1499,13 @@ class SQLiteDatabase(DatabaseInterface):
1495
1499
  conn = self._get_connection()
1496
1500
  try:
1497
1501
  for event in events:
1498
- # Upsert Event (V9: includes creator fields)
1502
+ # Upsert Event (V18: created_by/shared_by)
1499
1503
  conn.execute(
1500
1504
  """
1501
1505
  INSERT INTO events (
1502
1506
  id, title, description, event_type, status,
1503
1507
  start_timestamp, end_timestamp, created_at, updated_at, metadata,
1504
- creator_name, creator_id
1508
+ created_by, shared_by
1505
1509
  ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
1506
1510
  ON CONFLICT(id) DO UPDATE SET
1507
1511
  title=excluded.title,
@@ -1512,8 +1516,8 @@ class SQLiteDatabase(DatabaseInterface):
1512
1516
  end_timestamp=excluded.end_timestamp,
1513
1517
  updated_at=excluded.updated_at,
1514
1518
  metadata=excluded.metadata,
1515
- creator_name=excluded.creator_name,
1516
- creator_id=excluded.creator_id
1519
+ created_by=excluded.created_by,
1520
+ shared_by=excluded.shared_by
1517
1521
  """,
1518
1522
  (
1519
1523
  event.id,
@@ -1526,8 +1530,8 @@ class SQLiteDatabase(DatabaseInterface):
1526
1530
  event.created_at,
1527
1531
  event.updated_at,
1528
1532
  json.dumps(event.metadata),
1529
- event.creator_name,
1530
- event.creator_id,
1533
+ event.created_by,
1534
+ event.shared_by,
1531
1535
  ),
1532
1536
  )
1533
1537
 
@@ -2063,11 +2067,11 @@ class SQLiteDatabase(DatabaseInterface):
2063
2067
  from ..config import ReAlignConfig
2064
2068
 
2065
2069
  config = ReAlignConfig.load()
2066
- creator_name = config.user_name
2067
- creator_id = config.user_id
2070
+ created_by = config.uid
2071
+ user_name_for_upsert = config.user_name
2068
2072
  except Exception:
2069
- creator_name = None
2070
- creator_id = None
2073
+ created_by = None
2074
+ user_name_for_upsert = None
2071
2075
 
2072
2076
  now = datetime.now()
2073
2077
  cursor.execute(
@@ -2075,8 +2079,8 @@ class SQLiteDatabase(DatabaseInterface):
2075
2079
  INSERT INTO agents (
2076
2080
  id, provider, session_type, session_id, context_id,
2077
2081
  transcript_path, cwd, project_dir, status, attention, source,
2078
- created_at, updated_at, creator_name, creator_id
2079
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2082
+ created_at, updated_at, created_by
2083
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2080
2084
  """,
2081
2085
  (
2082
2086
  agent_id,
@@ -2092,12 +2096,18 @@ class SQLiteDatabase(DatabaseInterface):
2092
2096
  source,
2093
2097
  now,
2094
2098
  now,
2095
- creator_name,
2096
- creator_id,
2099
+ created_by,
2097
2100
  ),
2098
2101
  )
2099
2102
  conn.commit()
2100
2103
 
2104
+ # Upsert current user to users table
2105
+ if created_by:
2106
+ try:
2107
+ self.upsert_user(created_by, user_name_for_upsert)
2108
+ except Exception:
2109
+ pass
2110
+
2101
2111
  return AgentRecord(
2102
2112
  id=agent_id,
2103
2113
  provider=provider,
@@ -2112,8 +2122,7 @@ class SQLiteDatabase(DatabaseInterface):
2112
2122
  source=source,
2113
2123
  created_at=now,
2114
2124
  updated_at=now,
2115
- creator_name=creator_name,
2116
- creator_id=creator_id,
2125
+ created_by=created_by,
2117
2126
  )
2118
2127
 
2119
2128
  def update_agent(
@@ -2642,12 +2651,15 @@ class SQLiteDatabase(DatabaseInterface):
2642
2651
  except (IndexError, KeyError):
2643
2652
  pass
2644
2653
 
2645
- # V9: creator fields
2646
- creator_name = None
2647
- creator_id = None
2654
+ # V18: user identity fields
2655
+ created_by = None
2656
+ shared_by = None
2657
+ try:
2658
+ created_by = row["created_by"]
2659
+ except (IndexError, KeyError):
2660
+ pass
2648
2661
  try:
2649
- creator_name = row["creator_name"]
2650
- creator_id = row["creator_id"]
2662
+ shared_by = row["shared_by"]
2651
2663
  except (IndexError, KeyError):
2652
2664
  pass
2653
2665
 
@@ -2681,21 +2693,13 @@ class SQLiteDatabase(DatabaseInterface):
2681
2693
  summary_status=summary_status,
2682
2694
  summary_locked_until=summary_locked_until,
2683
2695
  summary_error=summary_error,
2684
- creator_name=creator_name,
2685
- creator_id=creator_id,
2696
+ created_by=created_by,
2697
+ shared_by=shared_by,
2686
2698
  total_turns=total_turns,
2687
2699
  total_turns_mtime=total_turns_mtime,
2688
2700
  )
2689
2701
 
2690
2702
  def _row_to_turn(self, row: sqlite3.Row) -> TurnRecord:
2691
- # V9: creator fields (optional for backward compatibility)
2692
- creator_name = None
2693
- creator_id = None
2694
- try:
2695
- creator_name = row["creator_name"]
2696
- creator_id = row["creator_id"]
2697
- except (IndexError, KeyError):
2698
- pass
2699
2703
  temp_title = None
2700
2704
  try:
2701
2705
  temp_title = row["temp_title"]
@@ -2718,8 +2722,6 @@ class SQLiteDatabase(DatabaseInterface):
2718
2722
  timestamp=self._parse_datetime(row["timestamp"]),
2719
2723
  created_at=self._parse_datetime(row["created_at"]),
2720
2724
  git_commit_hash=row["git_commit_hash"],
2721
- creator_name=creator_name,
2722
- creator_id=creator_id,
2723
2725
  temp_title=temp_title,
2724
2726
  )
2725
2727
 
@@ -2793,12 +2795,15 @@ class SQLiteDatabase(DatabaseInterface):
2793
2795
  share_admin_token = None
2794
2796
  share_expiry_at = None
2795
2797
 
2796
- # V9: creator fields
2797
- creator_name = None
2798
- creator_id = None
2798
+ # V18: user identity fields
2799
+ created_by = None
2800
+ shared_by = None
2801
+ try:
2802
+ created_by = row["created_by"]
2803
+ except KeyError:
2804
+ pass
2799
2805
  try:
2800
- creator_name = row["creator_name"]
2801
- creator_id = row["creator_id"]
2806
+ shared_by = row["shared_by"]
2802
2807
  except KeyError:
2803
2808
  pass
2804
2809
 
@@ -2820,17 +2825,16 @@ class SQLiteDatabase(DatabaseInterface):
2820
2825
  share_id=share_id,
2821
2826
  share_admin_token=share_admin_token,
2822
2827
  share_expiry_at=share_expiry_at,
2823
- creator_name=creator_name,
2824
- creator_id=creator_id,
2828
+ created_by=created_by,
2829
+ shared_by=shared_by,
2825
2830
  )
2826
2831
 
2827
2832
  def _row_to_agent(self, row: sqlite3.Row) -> AgentRecord:
2828
2833
  """Convert a database row to an AgentRecord."""
2829
- creator_name = None
2830
- creator_id = None
2834
+ # V18: user identity field
2835
+ created_by = None
2831
2836
  try:
2832
- creator_name = row["creator_name"]
2833
- creator_id = row["creator_id"]
2837
+ created_by = row["created_by"]
2834
2838
  except (IndexError, KeyError):
2835
2839
  pass
2836
2840
 
@@ -2848,8 +2852,7 @@ class SQLiteDatabase(DatabaseInterface):
2848
2852
  source=row["source"],
2849
2853
  created_at=self._parse_datetime(row["created_at"]),
2850
2854
  updated_at=self._parse_datetime(row["updated_at"]),
2851
- creator_name=creator_name,
2852
- creator_id=creator_id,
2855
+ created_by=created_by,
2853
2856
  )
2854
2857
 
2855
2858
  def _row_to_agent_context(self, row: sqlite3.Row) -> AgentContextRecord:
@@ -2872,3 +2875,50 @@ class SQLiteDatabase(DatabaseInterface):
2872
2875
  session_ids=None, # Populated separately
2873
2876
  event_ids=None, # Populated separately
2874
2877
  )
2878
+
2879
+ # -------------------------------------------------------------------------
2880
+ # Users table methods (Schema V18)
2881
+ # -------------------------------------------------------------------------
2882
+
2883
+ def upsert_user(self, uid: str, user_name: Optional[str] = None) -> None:
2884
+ """Insert or update a user in the users table."""
2885
+ if not uid:
2886
+ return
2887
+ conn = self._get_connection()
2888
+ try:
2889
+ conn.execute(
2890
+ """
2891
+ INSERT INTO users (uid, user_name, created_at, updated_at)
2892
+ VALUES (?, ?, datetime('now'), datetime('now'))
2893
+ ON CONFLICT(uid) DO UPDATE SET
2894
+ user_name = COALESCE(excluded.user_name, users.user_name),
2895
+ updated_at = datetime('now')
2896
+ """,
2897
+ (uid, user_name),
2898
+ )
2899
+ conn.commit()
2900
+ except sqlite3.OperationalError:
2901
+ # Older schema without users table
2902
+ try:
2903
+ conn.rollback()
2904
+ except Exception:
2905
+ pass
2906
+
2907
+ def get_user(self, uid: str) -> Optional[UserRecord]:
2908
+ """Get a user by UID from the users table."""
2909
+ if not uid:
2910
+ return None
2911
+ conn = self._get_connection()
2912
+ try:
2913
+ cursor = conn.execute("SELECT * FROM users WHERE uid = ?", (uid,))
2914
+ row = cursor.fetchone()
2915
+ if row:
2916
+ return UserRecord(
2917
+ uid=row["uid"],
2918
+ user_name=row["user_name"],
2919
+ created_at=self._parse_datetime(row["created_at"]),
2920
+ updated_at=self._parse_datetime(row["updated_at"]),
2921
+ )
2922
+ except sqlite3.OperationalError:
2923
+ pass
2924
+ return None
realign/watcher_core.py CHANGED
@@ -1509,8 +1509,6 @@ class DialogueWatcher:
1509
1509
  timestamp=processing_created_at,
1510
1510
  created_at=processing_created_at,
1511
1511
  git_commit_hash=None,
1512
- creator_name=config.user_name,
1513
- creator_id=config.user_id,
1514
1512
  )
1515
1513
  try:
1516
1514
  db.create_turn(processing_turn, content="")
@@ -1593,9 +1591,7 @@ class DialogueWatcher:
1593
1591
  content_hash=turn_hash,
1594
1592
  timestamp=datetime.now(),
1595
1593
  created_at=datetime.now(),
1596
- git_commit_hash=None, # No git integration
1597
- creator_name=config.user_name,
1598
- creator_id=config.user_id,
1594
+ git_commit_hash=None,
1599
1595
  )
1600
1596
  db.create_turn(
1601
1597
  new_turn,
@@ -1628,8 +1624,6 @@ class DialogueWatcher:
1628
1624
  timestamp=datetime.now(),
1629
1625
  created_at=processing_created_at,
1630
1626
  git_commit_hash=None,
1631
- creator_name=config.user_name,
1632
- creator_id=config.user_id,
1633
1627
  )
1634
1628
  try:
1635
1629
  db.create_turn(
@@ -1882,8 +1876,6 @@ class DialogueWatcher:
1882
1876
  timestamp=now,
1883
1877
  created_at=now,
1884
1878
  git_commit_hash=None,
1885
- creator_name=config.user_name,
1886
- creator_id=config.user_id,
1887
1879
  )
1888
1880
  db.create_turn(temp_turn, content=turn_content or "", skip_session_summary=True)
1889
1881
  except Exception as e:
realign/watcher_daemon.py CHANGED
@@ -48,6 +48,17 @@ async def run_daemon():
48
48
  """Run the watcher daemon."""
49
49
  watcher = None
50
50
 
51
+ # Check login status before starting
52
+ try:
53
+ from .auth import is_logged_in
54
+ except ImportError:
55
+ from realign.auth import is_logged_in
56
+
57
+ if not is_logged_in():
58
+ logger.error("Not logged in. Watcher daemon requires authentication.")
59
+ print("[Watcher Daemon] Error: Not logged in. Run 'aline login' first.", file=sys.stderr)
60
+ sys.exit(1)
61
+
51
62
  # Shutdown handler
52
63
  def handle_shutdown(signum, frame):
53
64
  """Handle shutdown signals gracefully."""
realign/worker_daemon.py CHANGED
@@ -49,6 +49,17 @@ async def run_daemon() -> None:
49
49
  worker = None
50
50
  db = None
51
51
 
52
+ # Check login status before starting
53
+ try:
54
+ from .auth import is_logged_in
55
+ except ImportError:
56
+ from realign.auth import is_logged_in
57
+
58
+ if not is_logged_in():
59
+ logger.error("Not logged in. Worker daemon requires authentication.")
60
+ print("[Worker Daemon] Error: Not logged in. Run 'aline login' first.", file=sys.stderr)
61
+ sys.exit(1)
62
+
52
63
  def handle_shutdown(signum, frame):
53
64
  logger.info(f"Received signal {signum}, shutting down...")
54
65
  try: