deepdiver 0.1.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.
@@ -0,0 +1,723 @@
1
+ """
2
+ Session Tracker Module
3
+ Part of DeepDiver - NotebookLM Podcast Automation System
4
+
5
+ This module handles session management, tracking, and metadata
6
+ for DeepDiver podcast creation sessions.
7
+
8
+ Assembly Team: Jerry ⚡, Nyro ♠️, Aureon 🌿, JamAI 🎸, Synth 🧵
9
+ """
10
+
11
+ import json
12
+ import logging
13
+ import os
14
+ import uuid
15
+ from datetime import datetime
16
+ from pathlib import Path
17
+ from typing import Dict, List, Optional, Any
18
+
19
+ import yaml
20
+
21
+
22
+ class SessionTracker:
23
+ """
24
+ Manages DeepDiver sessions and metadata.
25
+
26
+ This class handles session creation, tracking, and persistence
27
+ for podcast creation workflows.
28
+ """
29
+
30
+ def __init__(self, config: Optional[Dict[str, Any]] = None):
31
+ """Initialize the session tracker with configuration."""
32
+ self.config = config or {}
33
+ self.logger = self._setup_logging()
34
+
35
+ # Session settings
36
+ self.session_dir = self.config.get('SESSION_TRACKING', {}).get(
37
+ 'session_dir', './sessions'
38
+ )
39
+ self.metadata_format = self.config.get('SESSION_TRACKING', {}).get(
40
+ 'metadata_format', 'yaml'
41
+ )
42
+ self.auto_save = self.config.get('SESSION_TRACKING', {}).get(
43
+ 'auto_save', True
44
+ )
45
+ self.max_sessions = self.config.get('SESSION_TRACKING', {}).get(
46
+ 'max_sessions', 100
47
+ )
48
+
49
+ # Ensure session directory exists
50
+ os.makedirs(self.session_dir, exist_ok=True)
51
+
52
+ # Current session
53
+ self.current_session = None
54
+ self.session_file = os.path.join(self.session_dir, 'current_session.json')
55
+
56
+ self.logger.info("♠️🌿🎸🧵 SessionTracker initialized")
57
+
58
+ def _setup_logging(self) -> logging.Logger:
59
+ """Set up logging configuration."""
60
+ logger = logging.getLogger('SessionTracker')
61
+ logger.setLevel(logging.INFO)
62
+
63
+ if not logger.handlers:
64
+ handler = logging.StreamHandler()
65
+ formatter = logging.Formatter(
66
+ '%(asctime)s - %(name)s - %(levelname)s - %(message)s'
67
+ )
68
+ handler.setFormatter(formatter)
69
+ logger.addHandler(handler)
70
+
71
+ return logger
72
+
73
+ def start_session(self, ai_assistant: str = 'claude',
74
+ issue_number: Optional[int] = None,
75
+ agents: Optional[List[str]] = None) -> Dict[str, Any]:
76
+ """
77
+ Start a new DeepDiver session.
78
+
79
+ Args:
80
+ ai_assistant (str): Name of the AI assistant
81
+ issue_number (int, optional): Associated issue number
82
+ agents (List[str], optional): List of active agents
83
+
84
+ Returns:
85
+ Dict[str, Any]: Session information
86
+ """
87
+ try:
88
+ # Generate session ID
89
+ session_id = str(uuid.uuid4())
90
+
91
+ # Default agents
92
+ if agents is None:
93
+ agents = ['Jerry ⚡', 'Nyro ♠️', 'Aureon 🌿', 'JamAI 🎸', 'Synth 🧵']
94
+
95
+ # Create session data
96
+ session_data = {
97
+ 'session_id': session_id,
98
+ 'ai_assistant': ai_assistant,
99
+ 'agents': agents,
100
+ 'issue_number': issue_number,
101
+ 'pr_number': None,
102
+ 'created_at': datetime.now().isoformat(),
103
+ 'status': 'active',
104
+ 'podcasts_created': [],
105
+ 'documents_processed': [],
106
+ 'notebooks': [],
107
+ 'active_notebook_id': None,
108
+ 'notes': [],
109
+ 'assembly_team': {
110
+ 'leader': 'Jerry ⚡',
111
+ 'nyro': '♠️ Structural Architect',
112
+ 'aureon': '🌿 Emotional Context',
113
+ 'jamai': '🎸 Musical Harmony',
114
+ 'synth': '🧵 Terminal Orchestration'
115
+ }
116
+ }
117
+
118
+ # Save session
119
+ self.current_session = session_data
120
+ self._save_current_session()
121
+
122
+ # Create session file
123
+ session_filename = f"session_{session_id}.json"
124
+ session_path = os.path.join(self.session_dir, session_filename)
125
+
126
+ with open(session_path, 'w', encoding='utf-8') as f:
127
+ json.dump(session_data, f, indent=2, ensure_ascii=False)
128
+
129
+ self.logger.info(f"✅ Session started: {session_id}")
130
+
131
+ return {
132
+ 'success': True,
133
+ 'session_id': session_id,
134
+ 'session_data': session_data,
135
+ 'session_path': session_path
136
+ }
137
+
138
+ except Exception as e:
139
+ self.logger.error(f"❌ Failed to start session: {e}")
140
+ return {
141
+ 'success': False,
142
+ 'error': str(e)
143
+ }
144
+
145
+ def write_to_session(self, message: str, message_type: str = 'note') -> bool:
146
+ """
147
+ Write a message to the current session.
148
+
149
+ Args:
150
+ message (str): Message to write
151
+ message_type (str): Type of message (note, podcast, document, etc.)
152
+
153
+ Returns:
154
+ bool: True if write successful, False otherwise
155
+ """
156
+ try:
157
+ if not self.current_session:
158
+ self.logger.warning("No active session to write to")
159
+ return False
160
+
161
+ # Create message entry
162
+ message_entry = {
163
+ 'timestamp': datetime.now().isoformat(),
164
+ 'type': message_type,
165
+ 'message': message
166
+ }
167
+
168
+ # Add to session notes
169
+ self.current_session['notes'].append(message_entry)
170
+
171
+ # Auto-save if enabled
172
+ if self.auto_save:
173
+ self._save_current_session()
174
+
175
+ self.logger.info(f"✅ Message written to session: {message_type}")
176
+ return True
177
+
178
+ except Exception as e:
179
+ self.logger.error(f"❌ Failed to write to session: {e}")
180
+ return False
181
+
182
+ def add_podcast_to_session(self, podcast_info: Dict[str, Any]) -> bool:
183
+ """
184
+ Add a podcast to the current session.
185
+
186
+ Args:
187
+ podcast_info (Dict[str, Any]): Information about the created podcast
188
+
189
+ Returns:
190
+ bool: True if add successful, False otherwise
191
+ """
192
+ try:
193
+ if not self.current_session:
194
+ self.logger.warning("No active session to add podcast to")
195
+ return False
196
+
197
+ # Add podcast info
198
+ podcast_entry = {
199
+ 'timestamp': datetime.now().isoformat(),
200
+ 'podcast_info': podcast_info
201
+ }
202
+
203
+ self.current_session['podcasts_created'].append(podcast_entry)
204
+
205
+ # Auto-save if enabled
206
+ if self.auto_save:
207
+ self._save_current_session()
208
+
209
+ self.logger.info(f"✅ Podcast added to session: {podcast_info.get('title', 'Unknown')}")
210
+ return True
211
+
212
+ except Exception as e:
213
+ self.logger.error(f"❌ Failed to add podcast to session: {e}")
214
+ return False
215
+
216
+ def add_document_to_session(self, document_info: Dict[str, Any]) -> bool:
217
+ """
218
+ Add a document to the current session.
219
+
220
+ Args:
221
+ document_info (Dict[str, Any]): Information about the processed document
222
+
223
+ Returns:
224
+ bool: True if add successful, False otherwise
225
+ """
226
+ try:
227
+ if not self.current_session:
228
+ self.logger.warning("No active session to add document to")
229
+ return False
230
+
231
+ # Add document info
232
+ document_entry = {
233
+ 'timestamp': datetime.now().isoformat(),
234
+ 'document_info': document_info
235
+ }
236
+
237
+ self.current_session['documents_processed'].append(document_entry)
238
+
239
+ # Auto-save if enabled
240
+ if self.auto_save:
241
+ self._save_current_session()
242
+
243
+ self.logger.info(f"✅ Document added to session: {document_info.get('filename', 'Unknown')}")
244
+ return True
245
+
246
+ except Exception as e:
247
+ self.logger.error(f"❌ Failed to add document to session: {e}")
248
+ return False
249
+
250
+ def add_notebook(self, notebook_data: Dict[str, Any]) -> bool:
251
+ """
252
+ Add a notebook to the current session.
253
+
254
+ Args:
255
+ notebook_data (Dict[str, Any]): Notebook metadata (id, url, title, etc.)
256
+
257
+ Returns:
258
+ bool: True if add successful, False otherwise
259
+ """
260
+ try:
261
+ if not self.current_session:
262
+ self.logger.warning("No active session to add notebook to")
263
+ return False
264
+
265
+ # Ensure notebooks list exists
266
+ if 'notebooks' not in self.current_session:
267
+ self.current_session['notebooks'] = []
268
+
269
+ # Add timestamp if not present
270
+ if 'created_at' not in notebook_data:
271
+ notebook_data['created_at'] = datetime.now().isoformat()
272
+
273
+ # Add notebook to session
274
+ self.current_session['notebooks'].append(notebook_data)
275
+
276
+ # Set as active notebook if it's the first or marked active
277
+ if not self.current_session.get('active_notebook_id') or notebook_data.get('active', False):
278
+ self.current_session['active_notebook_id'] = notebook_data.get('id')
279
+
280
+ # Auto-save if enabled
281
+ if self.auto_save:
282
+ self._save_current_session()
283
+
284
+ self.logger.info(f"✅ Notebook added to session: {notebook_data.get('id', 'Unknown')}")
285
+ return True
286
+
287
+ except Exception as e:
288
+ self.logger.error(f"❌ Failed to add notebook to session: {e}")
289
+ return False
290
+
291
+ def get_active_notebook(self) -> Optional[Dict[str, Any]]:
292
+ """
293
+ Get the currently active notebook.
294
+
295
+ Returns:
296
+ Optional[Dict[str, Any]]: Active notebook data or None
297
+ """
298
+ try:
299
+ if not self.current_session:
300
+ return None
301
+
302
+ active_id = self.current_session.get('active_notebook_id')
303
+ if not active_id:
304
+ return None
305
+
306
+ # Find notebook by ID
307
+ notebooks = self.current_session.get('notebooks', [])
308
+ for notebook in notebooks:
309
+ if notebook.get('id') == active_id:
310
+ return notebook
311
+
312
+ return None
313
+
314
+ except Exception as e:
315
+ self.logger.error(f"❌ Error getting active notebook: {e}")
316
+ return None
317
+
318
+ def set_active_notebook(self, notebook_id: str) -> bool:
319
+ """
320
+ Set a notebook as active.
321
+
322
+ Args:
323
+ notebook_id (str): ID of the notebook to set as active
324
+
325
+ Returns:
326
+ bool: True if set successful, False otherwise
327
+ """
328
+ try:
329
+ if not self.current_session:
330
+ self.logger.warning("No active session")
331
+ return False
332
+
333
+ # Verify notebook exists in session
334
+ notebooks = self.current_session.get('notebooks', [])
335
+ notebook_found = False
336
+
337
+ for notebook in notebooks:
338
+ if notebook.get('id') == notebook_id:
339
+ notebook_found = True
340
+ notebook['active'] = True
341
+ else:
342
+ notebook['active'] = False
343
+
344
+ if not notebook_found:
345
+ self.logger.warning(f"Notebook {notebook_id} not found in session")
346
+ return False
347
+
348
+ # Set as active
349
+ self.current_session['active_notebook_id'] = notebook_id
350
+
351
+ # Auto-save if enabled
352
+ if self.auto_save:
353
+ self._save_current_session()
354
+
355
+ self.logger.info(f"✅ Notebook set as active: {notebook_id}")
356
+ return True
357
+
358
+ except Exception as e:
359
+ self.logger.error(f"❌ Failed to set active notebook: {e}")
360
+ return False
361
+
362
+ def list_notebooks(self) -> List[Dict[str, Any]]:
363
+ """
364
+ List all notebooks in the current session.
365
+
366
+ Returns:
367
+ List[Dict[str, Any]]: List of notebook data
368
+ """
369
+ try:
370
+ if not self.current_session:
371
+ return []
372
+
373
+ return self.current_session.get('notebooks', [])
374
+
375
+ except Exception as e:
376
+ self.logger.error(f"❌ Error listing notebooks: {e}")
377
+ return []
378
+
379
+ def get_notebook_by_id(self, notebook_id: str) -> Optional[Dict[str, Any]]:
380
+ """
381
+ Get a specific notebook by ID.
382
+
383
+ Args:
384
+ notebook_id (str): ID of the notebook to retrieve
385
+
386
+ Returns:
387
+ Optional[Dict[str, Any]]: Notebook data or None if not found
388
+ """
389
+ try:
390
+ notebooks = self.list_notebooks()
391
+ for notebook in notebooks:
392
+ if notebook.get('id') == notebook_id:
393
+ return notebook
394
+
395
+ return None
396
+
397
+ except Exception as e:
398
+ self.logger.error(f"❌ Error getting notebook: {e}")
399
+ return None
400
+
401
+ def update_notebook(self, notebook_id: str, updates: Dict[str, Any]) -> bool:
402
+ """
403
+ Update notebook metadata.
404
+
405
+ Args:
406
+ notebook_id (str): ID of the notebook to update
407
+ updates (Dict[str, Any]): Fields to update
408
+
409
+ Returns:
410
+ bool: True if update successful, False otherwise
411
+ """
412
+ try:
413
+ if not self.current_session:
414
+ self.logger.warning("No active session")
415
+ return False
416
+
417
+ notebooks = self.current_session.get('notebooks', [])
418
+ notebook_found = False
419
+
420
+ for notebook in notebooks:
421
+ if notebook.get('id') == notebook_id:
422
+ notebook.update(updates)
423
+ notebook['updated_at'] = datetime.now().isoformat()
424
+ notebook_found = True
425
+ break
426
+
427
+ if not notebook_found:
428
+ self.logger.warning(f"Notebook {notebook_id} not found")
429
+ return False
430
+
431
+ # Auto-save if enabled
432
+ if self.auto_save:
433
+ self._save_current_session()
434
+
435
+ self.logger.info(f"✅ Notebook updated: {notebook_id}")
436
+ return True
437
+
438
+ except Exception as e:
439
+ self.logger.error(f"❌ Failed to update notebook: {e}")
440
+ return False
441
+
442
+ def add_source_to_notebook(self, notebook_id: str, source_data: Dict[str, Any]) -> bool:
443
+ """
444
+ Add a source to a notebook in the session.
445
+
446
+ Args:
447
+ notebook_id (str): ID of the notebook to add source to
448
+ source_data (Dict[str, Any]): Source metadata (filename, path, type, etc.)
449
+
450
+ Returns:
451
+ bool: True if add successful, False otherwise
452
+ """
453
+ try:
454
+ if not self.current_session:
455
+ self.logger.warning("No active session")
456
+ return False
457
+
458
+ # Find the notebook
459
+ notebook = self.get_notebook_by_id(notebook_id)
460
+ if not notebook:
461
+ self.logger.warning(f"Notebook {notebook_id} not found")
462
+ return False
463
+
464
+ # Ensure sources list exists
465
+ if 'sources' not in notebook:
466
+ notebook['sources'] = []
467
+
468
+ # Add timestamp and ID if not present
469
+ if 'added_at' not in source_data:
470
+ source_data['added_at'] = datetime.now().isoformat()
471
+ if 'source_id' not in source_data:
472
+ # Generate simple source ID from filename and timestamp
473
+ import hashlib
474
+ filename = source_data.get('filename', 'unknown')
475
+ timestamp = datetime.now().isoformat()
476
+ source_id = hashlib.md5(f"{filename}{timestamp}".encode()).hexdigest()[:8]
477
+ source_data['source_id'] = source_id
478
+
479
+ # Add source to notebook
480
+ notebook['sources'].append(source_data)
481
+
482
+ # Update notebook in session
483
+ self.update_notebook(notebook_id, {'sources': notebook['sources']})
484
+
485
+ self.logger.info(f"✅ Source added to notebook {notebook_id}: {source_data.get('filename', 'Unknown')}")
486
+ return True
487
+
488
+ except Exception as e:
489
+ self.logger.error(f"❌ Failed to add source to notebook: {e}")
490
+ return False
491
+
492
+ def list_notebook_sources(self, notebook_id: str) -> List[Dict[str, Any]]:
493
+ """
494
+ List all sources for a notebook.
495
+
496
+ Args:
497
+ notebook_id (str): ID of the notebook
498
+
499
+ Returns:
500
+ List[Dict[str, Any]]: List of source data
501
+ """
502
+ try:
503
+ notebook = self.get_notebook_by_id(notebook_id)
504
+ if not notebook:
505
+ return []
506
+
507
+ return notebook.get('sources', [])
508
+
509
+ except Exception as e:
510
+ self.logger.error(f"❌ Error listing notebook sources: {e}")
511
+ return []
512
+
513
+ def get_session_status(self) -> Optional[Dict[str, Any]]:
514
+ """
515
+ Get the current session status.
516
+
517
+ Returns:
518
+ Optional[Dict[str, Any]]: Session status or None if no active session
519
+ """
520
+ if not self.current_session:
521
+ return None
522
+
523
+ return {
524
+ 'session_id': self.current_session['session_id'],
525
+ 'ai_assistant': self.current_session['ai_assistant'],
526
+ 'agents': self.current_session['agents'],
527
+ 'issue_number': self.current_session['issue_number'],
528
+ 'created_at': self.current_session['created_at'],
529
+ 'status': self.current_session['status'],
530
+ 'podcasts_count': len(self.current_session['podcasts_created']),
531
+ 'documents_count': len(self.current_session['documents_processed']),
532
+ 'notebooks_count': len(self.current_session.get('notebooks', [])),
533
+ 'active_notebook_id': self.current_session.get('active_notebook_id'),
534
+ 'notes_count': len(self.current_session['notes'])
535
+ }
536
+
537
+ def load_session(self, session_id: str) -> bool:
538
+ """
539
+ Load a specific session.
540
+
541
+ Args:
542
+ session_id (str): ID of the session to load
543
+
544
+ Returns:
545
+ bool: True if load successful, False otherwise
546
+ """
547
+ try:
548
+ session_filename = f"session_{session_id}.json"
549
+ session_path = os.path.join(self.session_dir, session_filename)
550
+
551
+ if not os.path.exists(session_path):
552
+ self.logger.error(f"Session file not found: {session_path}")
553
+ return False
554
+
555
+ with open(session_path, 'r', encoding='utf-8') as f:
556
+ session_data = json.load(f)
557
+
558
+ self.current_session = session_data
559
+ self._save_current_session()
560
+
561
+ self.logger.info(f"✅ Session loaded: {session_id}")
562
+ return True
563
+
564
+ except Exception as e:
565
+ self.logger.error(f"❌ Failed to load session: {e}")
566
+ return False
567
+
568
+ def end_session(self) -> bool:
569
+ """
570
+ End the current session.
571
+
572
+ Returns:
573
+ bool: True if end successful, False otherwise
574
+ """
575
+ try:
576
+ if not self.current_session:
577
+ self.logger.warning("No active session to end")
578
+ return False
579
+
580
+ # Update session status
581
+ self.current_session['status'] = 'ended'
582
+ self.current_session['ended_at'] = datetime.now().isoformat()
583
+
584
+ # Save final session
585
+ self._save_current_session()
586
+
587
+ # Save to session file
588
+ session_filename = f"session_{self.current_session['session_id']}.json"
589
+ session_path = os.path.join(self.session_dir, session_filename)
590
+
591
+ with open(session_path, 'w', encoding='utf-8') as f:
592
+ json.dump(self.current_session, f, indent=2, ensure_ascii=False)
593
+
594
+ self.logger.info(f"✅ Session ended: {self.current_session['session_id']}")
595
+
596
+ # Clear current session
597
+ self.current_session = None
598
+ if os.path.exists(self.session_file):
599
+ os.remove(self.session_file)
600
+
601
+ return True
602
+
603
+ except Exception as e:
604
+ self.logger.error(f"❌ Failed to end session: {e}")
605
+ return False
606
+
607
+ def list_sessions(self) -> List[Dict[str, Any]]:
608
+ """
609
+ List all available sessions.
610
+
611
+ Returns:
612
+ List[Dict[str, Any]]: List of session information
613
+ """
614
+ sessions = []
615
+
616
+ try:
617
+ for file in os.listdir(self.session_dir):
618
+ if file.startswith('session_') and file.endswith('.json'):
619
+ session_path = os.path.join(self.session_dir, file)
620
+
621
+ try:
622
+ with open(session_path, 'r', encoding='utf-8') as f:
623
+ session_data = json.load(f)
624
+
625
+ # Extract summary info
626
+ session_summary = {
627
+ 'session_id': session_data.get('session_id'),
628
+ 'ai_assistant': session_data.get('ai_assistant'),
629
+ 'issue_number': session_data.get('issue_number'),
630
+ 'created_at': session_data.get('created_at'),
631
+ 'status': session_data.get('status'),
632
+ 'podcasts_count': len(session_data.get('podcasts_created', [])),
633
+ 'documents_count': len(session_data.get('documents_processed', [])),
634
+ 'notes_count': len(session_data.get('notes', []))
635
+ }
636
+
637
+ sessions.append(session_summary)
638
+
639
+ except Exception as e:
640
+ self.logger.warning(f"Error reading session file {file}: {e}")
641
+
642
+ # Sort by creation time (newest first)
643
+ sessions.sort(key=lambda x: x['created_at'], reverse=True)
644
+
645
+ except Exception as e:
646
+ self.logger.error(f"Error listing sessions: {e}")
647
+
648
+ return sessions
649
+
650
+ def _save_current_session(self):
651
+ """Save the current session to file."""
652
+ try:
653
+ if self.current_session:
654
+ with open(self.session_file, 'w', encoding='utf-8') as f:
655
+ json.dump(self.current_session, f, indent=2, ensure_ascii=False)
656
+ except Exception as e:
657
+ self.logger.error(f"Error saving current session: {e}")
658
+
659
+ def _load_current_session(self):
660
+ """Load the current session from file."""
661
+ try:
662
+ if os.path.exists(self.session_file):
663
+ with open(self.session_file, 'r', encoding='utf-8') as f:
664
+ self.current_session = json.load(f)
665
+ except Exception as e:
666
+ self.logger.error(f"Error loading current session: {e}")
667
+
668
+ def cleanup_old_sessions(self, days: int = 30) -> int:
669
+ """
670
+ Clean up sessions older than specified days.
671
+
672
+ Args:
673
+ days (int): Number of days to keep sessions
674
+
675
+ Returns:
676
+ int: Number of sessions deleted
677
+ """
678
+ deleted_count = 0
679
+ cutoff_time = datetime.now().timestamp() - (days * 24 * 60 * 60)
680
+
681
+ try:
682
+ for file in os.listdir(self.session_dir):
683
+ if file.startswith('session_') and file.endswith('.json'):
684
+ file_path = os.path.join(self.session_dir, file)
685
+
686
+ if os.path.getctime(file_path) < cutoff_time:
687
+ os.remove(file_path)
688
+ deleted_count += 1
689
+
690
+ self.logger.info(f"✅ Cleaned up {deleted_count} old sessions")
691
+
692
+ except Exception as e:
693
+ self.logger.error(f"Error during session cleanup: {e}")
694
+
695
+ return deleted_count
696
+
697
+
698
+ # Example usage and testing
699
+ def test_session_tracker():
700
+ """Test function for session tracker."""
701
+ tracker = SessionTracker()
702
+
703
+ # Test session creation
704
+ result = tracker.start_session(ai_assistant='claude', issue_number=1)
705
+ print(f"Session creation result: {result}")
706
+
707
+ # Test writing to session
708
+ tracker.write_to_session("Test message", "note")
709
+
710
+ # Test session status
711
+ status = tracker.get_session_status()
712
+ print(f"Session status: {status}")
713
+
714
+ # Test listing sessions
715
+ sessions = tracker.list_sessions()
716
+ print(f"Found {len(sessions)} sessions")
717
+
718
+ # Test ending session
719
+ tracker.end_session()
720
+
721
+
722
+ if __name__ == "__main__":
723
+ test_session_tracker()