aline-ai 0.5.10__py3-none-any.whl → 0.5.12__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aline-ai
3
- Version: 0.5.10
3
+ Version: 0.5.12
4
4
  Summary: Shared AI memory; everyone knows everything in teams
5
5
  Author: Sharemind
6
6
  License: MIT
@@ -1,10 +1,10 @@
1
- aline_ai-0.5.10.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
- realign/__init__.py,sha256=cxZ-7zshfDXh7ZfRZqr7NrX86rO2tjfUbtVcF9Ls0BQ,1624
1
+ aline_ai-0.5.12.dist-info/licenses/LICENSE,sha256=H8wTqV5IF1oHw_HbBtS1PSDU8G_q81yblEIL_JfV8Vo,1077
2
+ realign/__init__.py,sha256=6cJg0KmEft8KkD6TMW5-Lib2T9w9w105TIzIZ8mfEao,1624
3
3
  realign/claude_detector.py,sha256=ZLSJacMo6zzQclXByABKA70UNpstxqIv3fPGqdpA934,2792
4
4
  realign/cli.py,sha256=9VS3WbysZ78NRK5EvkJVg8s6Uh2TQjsGX1E9Pl81pHc,31234
5
5
  realign/codex_detector.py,sha256=N9ulgMgvTzDfXE4s4vLd6OoS0hT7R6h2bDFFXWa-2hE,4183
6
6
  realign/config.py,sha256=lIKZqeOwYc_gHo760lYYX6PnapuKrCWGqT5SA8-PbeA,12044
7
- realign/context.py,sha256=ctQWoz883jXx_whObCoB58vhJjGAVUxld9sOZ371Tn8,13821
7
+ realign/context.py,sha256=8hzgNOg-7_eMW22wt7OM5H9IsmMveKXCv0epG7E0G7w,13917
8
8
  realign/file_lock.py,sha256=kLNm1Rra4TCrTMyPM5fwjVascq-CUz2Bzh9HHKtCKOE,3444
9
9
  realign/hooks.py,sha256=NR4LgWgkA6npW_B68I7OdCaZNWseYSP7ZbK4Sl5nnTo,74692
10
10
  realign/llm_client.py,sha256=KPfJScQvqse-Tm-VpqnZ6C5jvajPl2n4Ddz9sUp7WIY,24564
@@ -28,7 +28,7 @@ realign/claude_hooks/permission_request_hook.py,sha256=jMN7UtL6bMqHObUCP5A5ysvFr
28
28
  realign/claude_hooks/permission_request_hook_installer.py,sha256=_8Wr_L5MES7iGukJzcaj4bqR0BH8kFL44U_X4iKtw2Y,7791
29
29
  realign/claude_hooks/stop_hook.py,sha256=2nzF2aF1p5teMJ0eV0ALEHD1K-yVj5sSh7UE8xL54ZE,12025
30
30
  realign/claude_hooks/stop_hook_installer.py,sha256=uyqKOqpix7CQP64ERBvvh7viSPp_wx_JVGNAX18rKh0,7228
31
- realign/claude_hooks/terminal_state.py,sha256=tq-PLwPLoadP8m_QN3WlQvPP_wi-rLG881Z8-tINjxo,5224
31
+ realign/claude_hooks/terminal_state.py,sha256=i8B6b_2_9ttPEemp7SrGdFRJSa-vm5lc7YSTRTvAWNg,5397
32
32
  realign/claude_hooks/user_prompt_submit_hook.py,sha256=WD-UavhBTueN2TPfnZrnPC7DFYGEeptjUEF21EJn7Qo,10312
33
33
  realign/claude_hooks/user_prompt_submit_hook_installer.py,sha256=2xLF8yZcE7Iwib9gU-xCkA1NWxNH9Nc5CFKPYK7rtXw,5371
34
34
  realign/commands/__init__.py,sha256=sx_ck55oxaoiF4N3LugG0ZXwonUDxeEZ5uHbBKCC7K8,89
@@ -37,7 +37,7 @@ realign/commands/config.py,sha256=nYnu_h2pk7GODcrzrV04K51D-s7v06FlRXHJ0HJ-gvU,67
37
37
  realign/commands/context.py,sha256=pM2KfZHVkB-ou4nBhFvKSwnYliLBzwN3zerLyBAbhfE,7095
38
38
  realign/commands/export_shares.py,sha256=Djy1aO7MoU1_ewzn6CZ43oNhSEEonV3sTkSQbHgiaKI,135806
39
39
  realign/commands/import_shares.py,sha256=ukX8huvLvEM5g0qEIoqrV1-imz1g-r0Jj2FqD-ojrIA,25297
40
- realign/commands/init.py,sha256=ef-q3Qz5D_0Eqld8qjtX26X2QrovBSYcva3uAjiJuwk,33015
40
+ realign/commands/init.py,sha256=tQyOX7csS8t9Ils3FKla41pS8_0Kd7I1Y1rz58a83aU,33511
41
41
  realign/commands/restore.py,sha256=s2BxQZHxQw9r12NzRVsK20KlGafy5AIoSjWMo5PcnHY,11173
42
42
  realign/commands/search.py,sha256=QJrC0hln9sCDFxXbpo0nPGMHXrud18qA5QfRyD0z6fQ,25926
43
43
  realign/commands/upgrade.py,sha256=L3PLOUIN5qAQTbkfoVtSsIbbzEezA_xjjk9F1GMVfjw,12781
@@ -45,7 +45,7 @@ realign/commands/watcher.py,sha256=fWL3kaRkqE03-NtFLaXlx93hJAQrAuNPSoYhOyQZfq8,1
45
45
  realign/commands/worker.py,sha256=K1DG1uZ--ebKwklHCyIFdN_axoLjL9Onx8Naq-DOZBs,23078
46
46
  realign/dashboard/__init__.py,sha256=QZkHTsGityH8UkF8rmvA3xW7dMXNe0swEWr443qfgCM,128
47
47
  realign/dashboard/app.py,sha256=jyW6mqmItTy253CPSqInxctkWzkrGEikdy-ikuShQ14,13299
48
- realign/dashboard/tmux_manager.py,sha256=-8LEupS7e4PzwwQ5uwOYV-EzrXaprypQObw8OP1MgG4,26380
48
+ realign/dashboard/tmux_manager.py,sha256=Vt_30WNtDg7c_9SEh8xdDtBLJ8kNq6bGSPh5r3VXpg0,26276
49
49
  realign/dashboard/screens/__init__.py,sha256=US6sAmQs5VVkH2tFkH_z0WDT4H8cVhLL-JckfSR1yQY,446
50
50
  realign/dashboard/screens/create_agent.py,sha256=lpcT1zLq_p02codtHTE8KdbEzCEaNLnk1lqU3QLcXCg,10057
51
51
  realign/dashboard/screens/create_event.py,sha256=oiQY1zKpUYnQU-5fQLeuZH9BV5NClE5B5XZIVBYG5A8,5506
@@ -67,9 +67,9 @@ realign/dashboard/widgets/worker_panel.py,sha256=F_jKWABuCNmjQgeeuCr4KnFRKdY4CLT
67
67
  realign/db/__init__.py,sha256=65LsNdsq_rkwNC1eg1OAr3HC0ORXtelOh0I8MhNGr-g,3288
68
68
  realign/db/base.py,sha256=MIqu08uG8i5atjZ9uF-uc0Rx35ondxCtUPK92hMoHx4,13179
69
69
  realign/db/locks.py,sha256=yzCiPJZ4eOQX-Q4mXB6s76U2U7lXAzIBBy1t59w-AVU,1698
70
- realign/db/migrate_agents.py,sha256=RPSVDAM-mQMAyTar3-XGrVUQIoCrMprk9tGa-AoZL_A,9421
70
+ realign/db/migrate_agents.py,sha256=cDeVUzKW950dJ0lV74QObHuONqKwErSrXI5akU2vBmQ,9633
71
71
  realign/db/migration.py,sha256=af1QFEfIh_qX0pFyXzm5gWFVbQn0sKOUNLSJHlr__FU,13405
72
- realign/db/schema.py,sha256=S0BPY8V823ttPWk69W5PO6nu0DQDVHvYY0UObGI9ePo,23481
72
+ realign/db/schema.py,sha256=93dfMtw3LgkBMpiUlCQ0EscY9RFsuS8sEBDckH8lGws,25864
73
73
  realign/db/sqlite_db.py,sha256=sZXcvEaSu4C_MQ8pF20RUhwsPtBlNr6ANqf8suM5X8E,102660
74
74
  realign/events/__init__.py,sha256=IM-NxF4Zk2hYFD07k4WrfNRuuiC9ihGjf4GBpJhjd2E,35
75
75
  realign/events/debouncer.py,sha256=U3Q7dYpnMsAgWsW_E_IbSC4lrdEoi6H_SFLGLOAazs4,3062
@@ -89,8 +89,8 @@ realign/triggers/next_turn_trigger.py,sha256=BpP0PWn4mU1MZd6mv89jWcjs8Jtv0zEWapW
89
89
  realign/triggers/registry.py,sha256=cb-AVLbYB2pqwfWL3q1DQxLv4kOw7g7m-GshTdfFESc,3827
90
90
  realign/triggers/turn_status.py,sha256=wAZEhXDAmDoX5F-ohWfSnZZ0eA6DAJ9svSPiSv_f6sg,6041
91
91
  realign/triggers/turn_summary.py,sha256=f3hEUshgv9skJ9AbfWpoYs417lsv_HK2A_vpPjgryO4,4467
92
- aline_ai-0.5.10.dist-info/METADATA,sha256=efo6zCepaEc3fBPRcQAi6QL9k5Y-gUee8RfSQBxbRwE,1598
93
- aline_ai-0.5.10.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
94
- aline_ai-0.5.10.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
95
- aline_ai-0.5.10.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
96
- aline_ai-0.5.10.dist-info/RECORD,,
92
+ aline_ai-0.5.12.dist-info/METADATA,sha256=bMuapdLKzuZXb45akvIB6w7uhc5jXl31onpetDCEAxQ,1598
93
+ aline_ai-0.5.12.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
94
+ aline_ai-0.5.12.dist-info/entry_points.txt,sha256=TvYELpMoWsUTcQdMV8tBHxCbEf_LbK4sESqK3r8PM6Y,78
95
+ aline_ai-0.5.12.dist-info/top_level.txt,sha256=yIL3s2xv9nf1GwD5n71Aq_JEIV4AfzCIDNKBzewuRm4,8
96
+ aline_ai-0.5.12.dist-info/RECORD,,
realign/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  import hashlib
4
4
  from pathlib import Path
5
5
 
6
- __version__ = "0.5.10"
6
+ __version__ = "0.5.12"
7
7
 
8
8
 
9
9
  def get_realign_dir(project_root: Path) -> Path:
@@ -34,6 +34,16 @@ def _read_json(path: Path) -> dict[str, Any]:
34
34
  return {}
35
35
 
36
36
 
37
+ def _get_db():
38
+ """Get database connection (lazy import to avoid circular deps in hooks)."""
39
+ try:
40
+ from ..db import get_database
41
+
42
+ return get_database(read_only=False)
43
+ except Exception:
44
+ return None
45
+
46
+
37
47
  def _write_to_db(
38
48
  *,
39
49
  terminal_id: str,
@@ -52,11 +62,9 @@ def _write_to_db(
52
62
  Returns True if successful, False otherwise.
53
63
  """
54
64
  try:
55
- from ..db.sqlite_db import SQLiteDatabase
56
-
57
- db_path = Path.home() / ".aline" / "realign.db"
58
- db = SQLiteDatabase(str(db_path))
59
- db.initialize()
65
+ db = _get_db()
66
+ if not db:
67
+ return False
60
68
 
61
69
  # Check if agent exists
62
70
  existing = db.get_agent_by_id(terminal_id)
@@ -88,7 +96,7 @@ def _write_to_db(
88
96
  source=source if source else None,
89
97
  attention=attention,
90
98
  )
91
- db.close()
99
+ # Note: Don't close - get_database() returns a singleton
92
100
  return True
93
101
  except Exception:
94
102
  return False
realign/commands/init.py CHANGED
@@ -19,7 +19,7 @@ console = Console()
19
19
  # tmux config template for Aline-managed dashboard sessions.
20
20
  # Stored at ~/.aline/tmux/tmux.conf and sourced by the dashboard tmux bootstrap.
21
21
  # Bump this version when the tmux config changes to trigger auto-update on `aline init`.
22
- _TMUX_CONFIG_VERSION = 2
22
+ _TMUX_CONFIG_VERSION = 8
23
23
 
24
24
 
25
25
  def _get_tmux_config() -> str:
@@ -53,16 +53,25 @@ set -s escape-time 0
53
53
  # Better scrolling: enter copy-mode with -e so scrolling to bottom exits it.
54
54
  bind-key -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'copy-mode -e -t ='"
55
55
 
56
- # macOS clipboard (pbcopy). Only set bindings if pbcopy is available.
57
- if-shell 'command -v pbcopy >/dev/null 2>&1' '
58
- bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-pipe "pbcopy" \; if -F "#{==:#{scroll_position},0}" "send -X cancel"
59
- bind -T copy-mode MouseDragEnd1Pane send -X copy-pipe "pbcopy" \; if -F "#{==:#{scroll_position},0}" "send -X cancel"
60
- ' ''
56
+ # macOS clipboard: copy selection to clipboard when drag ends.
57
+ # Use copy-pipe-no-clear to preserve selection highlight after copying.
58
+ bind -T copy-mode-vi MouseDragEnd1Pane send -X copy-pipe-no-clear "pbcopy"
59
+ bind -T copy-mode MouseDragEnd1Pane send -X copy-pipe-no-clear "pbcopy"
61
60
 
62
- # Prevent mouse click from exiting copy-mode (stay in copy mode, just clear selection).
61
+ # MouseDrag1Pane: Clear old selection and start new one when dragging begins.
62
+ # This ensures selection only clears when starting a NEW drag, not on click.
63
+ bind -T copy-mode-vi MouseDrag1Pane select-pane \; send -X clear-selection \; send -X begin-selection
64
+ bind -T copy-mode MouseDrag1Pane select-pane \; send -X clear-selection \; send -X begin-selection
65
+
66
+ # MouseDown1Pane: Click clears selection but stays in copy-mode (no scroll).
67
+ # To exit copy-mode: scroll to bottom (auto-exit) or press q/Escape.
63
68
  bind -T copy-mode-vi MouseDown1Pane select-pane \; send -X clear-selection
64
69
  bind -T copy-mode MouseDown1Pane select-pane \; send -X clear-selection
65
70
 
71
+ # Escape key: exit copy-mode (also use this before Cmd+V paste in copy-mode).
72
+ bind -T copy-mode-vi Escape send -X cancel
73
+ bind -T copy-mode Escape send -X cancel
74
+
66
75
  # Type-to-Exit: Typing any alphanumeric character exits copy-mode and sends the key.
67
76
  """
68
77
  def _tmux_quote(value: str) -> str:
realign/context.py CHANGED
@@ -23,12 +23,19 @@ CONTEXT_ID_ENV_VAR = "ALINE_CONTEXT_ID"
23
23
  def _get_db():
24
24
  """Get database connection (lazy import to avoid circular deps)."""
25
25
  try:
26
- from .db.sqlite_db import SQLiteDatabase
26
+ from .db import get_database
27
27
 
28
- db_path = Path.home() / ".aline" / "realign.db"
29
- db = SQLiteDatabase(str(db_path))
30
- db.initialize()
31
- return db
28
+ return get_database(read_only=False)
29
+ except Exception:
30
+ return None
31
+
32
+
33
+ def _get_db_readonly():
34
+ """Get read-only database connection."""
35
+ try:
36
+ from .db import get_database
37
+
38
+ return get_database(read_only=True)
32
39
  except Exception:
33
40
  return None
34
41
 
@@ -95,13 +102,12 @@ class ContextConfig:
95
102
  def _load_context_config_from_db() -> Optional[ContextConfig]:
96
103
  """Load context configuration from database (best-effort)."""
97
104
  try:
98
- db = _get_db()
105
+ db = _get_db_readonly()
99
106
  if not db:
100
107
  return None
101
108
 
102
109
  db_contexts = db.list_agent_contexts(limit=100)
103
110
  if not db_contexts:
104
- db.close()
105
111
  return None
106
112
 
107
113
  contexts = []
@@ -115,7 +121,6 @@ def _load_context_config_from_db() -> Optional[ContextConfig]:
115
121
  )
116
122
  contexts.append(entry)
117
123
 
118
- db.close()
119
124
  return ContextConfig(contexts=contexts)
120
125
  except Exception:
121
126
  return None
@@ -190,7 +195,7 @@ def _sync_context_to_db(entry: ContextEntry) -> bool:
190
195
  else:
191
196
  db.set_agent_context_events(entry.context_id, [])
192
197
 
193
- db.close()
198
+ # Note: Don't close - get_database() returns a singleton
194
199
  return True
195
200
  except Exception:
196
201
  return False
@@ -439,10 +444,9 @@ def get_context_by_id(
439
444
  """
440
445
  # Phase 1: Try to load from database
441
446
  try:
442
- db = _get_db()
447
+ db = _get_db_readonly()
443
448
  if db:
444
449
  ctx = db.get_agent_context_by_id(context_id)
445
- db.close()
446
450
  if ctx:
447
451
  return ContextEntry(
448
452
  context_sessions=ctx.session_ids or [],
@@ -194,13 +194,10 @@ def _session_id_from_transcript_path(transcript_path: str | None) -> str | None:
194
194
  def _load_terminal_state_from_db() -> dict[str, dict[str, str]]:
195
195
  """Load terminal state from database (best-effort)."""
196
196
  try:
197
- from ..db.sqlite_db import SQLiteDatabase
198
-
199
- db_path = Path.home() / ".aline" / "realign.db"
200
- db = SQLiteDatabase(str(db_path), read_only=True)
197
+ from ..db import get_database
201
198
 
199
+ db = get_database(read_only=True)
202
200
  agents = db.list_agents(status="active", limit=100)
203
- db.close()
204
201
 
205
202
  out: dict[str, dict[str, str]] = {}
206
203
  for agent in agents:
@@ -240,8 +240,8 @@ def main():
240
240
  parser.add_argument(
241
241
  "--db-path",
242
242
  type=str,
243
- default=str(Path.home() / ".aline" / "realign.db"),
244
- help="Path to SQLite database",
243
+ default=None,
244
+ help="Path to SQLite database (uses config default if not specified)",
245
245
  )
246
246
 
247
247
  args = parser.parse_args()
@@ -262,13 +262,19 @@ def main():
262
262
 
263
263
  # Initialize database
264
264
  if not args.dry_run:
265
- from .sqlite_db import SQLiteDatabase
266
-
267
- print(f"\n[step] Initializing database at {args.db_path}")
268
- db = SQLiteDatabase(args.db_path)
269
- if not db.initialize():
270
- print("[error] Database initialization failed")
271
- sys.exit(1)
265
+ if args.db_path:
266
+ from .sqlite_db import SQLiteDatabase
267
+
268
+ print(f"\n[step] Initializing database at {args.db_path}")
269
+ db = SQLiteDatabase(args.db_path)
270
+ if not db.initialize():
271
+ print("[error] Database initialization failed")
272
+ sys.exit(1)
273
+ else:
274
+ from . import get_database
275
+
276
+ print("\n[step] Using configured database path")
277
+ db = get_database(read_only=False)
272
278
  else:
273
279
  db = None
274
280
 
realign/db/schema.py CHANGED
@@ -58,9 +58,13 @@ Schema V15: Agents and contexts tables (replaces terminal.json and load.json).
58
58
  - agent_contexts table: context definitions (replaces load.json)
59
59
  - agent_context_sessions table: M2M context-session links
60
60
  - agent_context_events table: M2M context-event links
61
+
62
+ Schema V16: Remove FK constraints from agent_context_sessions/events.
63
+ - Context may reference sessions/events not yet imported to DB
64
+ - Recreate M2M tables without FK constraints on session_id/event_id
61
65
  """
62
66
 
63
- SCHEMA_VERSION = 15
67
+ SCHEMA_VERSION = 16
64
68
 
65
69
  FTS_EVENTS_SCRIPTS = [
66
70
  # Full Text Search for Events
@@ -288,10 +292,11 @@ INIT_SCRIPTS = [
288
292
  """,
289
293
  "CREATE INDEX IF NOT EXISTS idx_agent_contexts_workspace ON agent_contexts(workspace);",
290
294
  # Agent context sessions (M2M)
295
+ # Note: No FK on session_id - context may reference sessions not yet imported
291
296
  """
292
297
  CREATE TABLE IF NOT EXISTS agent_context_sessions (
293
298
  context_id TEXT NOT NULL REFERENCES agent_contexts(id) ON DELETE CASCADE,
294
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
299
+ session_id TEXT NOT NULL,
295
300
  added_at TEXT DEFAULT (datetime('now')),
296
301
  PRIMARY KEY (context_id, session_id)
297
302
  );
@@ -299,10 +304,11 @@ INIT_SCRIPTS = [
299
304
  "CREATE INDEX IF NOT EXISTS idx_agent_context_sessions_context ON agent_context_sessions(context_id);",
300
305
  "CREATE INDEX IF NOT EXISTS idx_agent_context_sessions_session ON agent_context_sessions(session_id);",
301
306
  # Agent context events (M2M)
307
+ # Note: No FK on event_id - context may reference events not yet created
302
308
  """
303
309
  CREATE TABLE IF NOT EXISTS agent_context_events (
304
310
  context_id TEXT NOT NULL REFERENCES agent_contexts(id) ON DELETE CASCADE,
305
- event_id TEXT NOT NULL REFERENCES events(id) ON DELETE CASCADE,
311
+ event_id TEXT NOT NULL,
306
312
  added_at TEXT DEFAULT (datetime('now')),
307
313
  PRIMARY KEY (context_id, event_id)
308
314
  );
@@ -503,10 +509,11 @@ MIGRATION_V14_TO_V15 = [
503
509
  """,
504
510
  "CREATE INDEX IF NOT EXISTS idx_agent_contexts_workspace ON agent_contexts(workspace);",
505
511
  # Agent context sessions (M2M)
512
+ # Note: No FK on session_id - context may reference sessions not yet imported
506
513
  """
507
514
  CREATE TABLE IF NOT EXISTS agent_context_sessions (
508
515
  context_id TEXT NOT NULL REFERENCES agent_contexts(id) ON DELETE CASCADE,
509
- session_id TEXT NOT NULL REFERENCES sessions(id) ON DELETE CASCADE,
516
+ session_id TEXT NOT NULL,
510
517
  added_at TEXT DEFAULT (datetime('now')),
511
518
  PRIMARY KEY (context_id, session_id)
512
519
  );
@@ -514,14 +521,51 @@ MIGRATION_V14_TO_V15 = [
514
521
  "CREATE INDEX IF NOT EXISTS idx_agent_context_sessions_context ON agent_context_sessions(context_id);",
515
522
  "CREATE INDEX IF NOT EXISTS idx_agent_context_sessions_session ON agent_context_sessions(session_id);",
516
523
  # Agent context events (M2M)
524
+ # Note: No FK on event_id - context may reference events not yet created
517
525
  """
518
526
  CREATE TABLE IF NOT EXISTS agent_context_events (
519
527
  context_id TEXT NOT NULL REFERENCES agent_contexts(id) ON DELETE CASCADE,
520
- event_id TEXT NOT NULL REFERENCES events(id) ON DELETE CASCADE,
528
+ event_id TEXT NOT NULL,
529
+ added_at TEXT DEFAULT (datetime('now')),
530
+ PRIMARY KEY (context_id, event_id)
531
+ );
532
+ """,
533
+ "CREATE INDEX IF NOT EXISTS idx_agent_context_events_context ON agent_context_events(context_id);",
534
+ "CREATE INDEX IF NOT EXISTS idx_agent_context_events_event ON agent_context_events(event_id);",
535
+ ]
536
+
537
+ # V15 to V16: Remove FK constraints from agent_context_sessions/events
538
+ # Context may reference sessions/events not yet in the database
539
+ MIGRATION_V15_TO_V16 = [
540
+ # Step 1: Rename old tables
541
+ "ALTER TABLE agent_context_sessions RENAME TO agent_context_sessions_old;",
542
+ "ALTER TABLE agent_context_events RENAME TO agent_context_events_old;",
543
+ # Step 2: Create new tables without FK constraints on session_id/event_id
544
+ """
545
+ CREATE TABLE agent_context_sessions (
546
+ context_id TEXT NOT NULL REFERENCES agent_contexts(id) ON DELETE CASCADE,
547
+ session_id TEXT NOT NULL,
548
+ added_at TEXT DEFAULT (datetime('now')),
549
+ PRIMARY KEY (context_id, session_id)
550
+ );
551
+ """,
552
+ """
553
+ CREATE TABLE agent_context_events (
554
+ context_id TEXT NOT NULL REFERENCES agent_contexts(id) ON DELETE CASCADE,
555
+ event_id TEXT NOT NULL,
521
556
  added_at TEXT DEFAULT (datetime('now')),
522
557
  PRIMARY KEY (context_id, event_id)
523
558
  );
524
559
  """,
560
+ # Step 3: Copy data from old tables
561
+ "INSERT INTO agent_context_sessions SELECT * FROM agent_context_sessions_old;",
562
+ "INSERT INTO agent_context_events SELECT * FROM agent_context_events_old;",
563
+ # Step 4: Drop old tables
564
+ "DROP TABLE agent_context_sessions_old;",
565
+ "DROP TABLE agent_context_events_old;",
566
+ # Step 5: Recreate indexes
567
+ "CREATE INDEX IF NOT EXISTS idx_agent_context_sessions_context ON agent_context_sessions(context_id);",
568
+ "CREATE INDEX IF NOT EXISTS idx_agent_context_sessions_session ON agent_context_sessions(session_id);",
525
569
  "CREATE INDEX IF NOT EXISTS idx_agent_context_events_context ON agent_context_events(context_id);",
526
570
  "CREATE INDEX IF NOT EXISTS idx_agent_context_events_event ON agent_context_events(event_id);",
527
571
  ]
@@ -579,4 +623,10 @@ def get_migration_scripts(from_version: int, to_version: int) -> list:
579
623
  if from_version < 15 and to_version >= 15:
580
624
  scripts.extend(MIGRATION_V14_TO_V15)
581
625
 
626
+ if from_version < 16 and to_version >= 16:
627
+ # Only run V15->V16 if coming from exactly V15 (tables exist with FK)
628
+ # For V14 or earlier, V14_TO_V15 now creates tables without FK
629
+ if from_version == 15:
630
+ scripts.extend(MIGRATION_V15_TO_V16)
631
+
582
632
  return scripts