kollabor 0.4.9__py3-none-any.whl → 0.4.15__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.
- agents/__init__.py +2 -0
- agents/coder/__init__.py +0 -0
- agents/coder/agent.json +4 -0
- agents/coder/api-integration.md +2150 -0
- agents/coder/cli-pretty.md +765 -0
- agents/coder/code-review.md +1092 -0
- agents/coder/database-design.md +1525 -0
- agents/coder/debugging.md +1102 -0
- agents/coder/dependency-management.md +1397 -0
- agents/coder/git-workflow.md +1099 -0
- agents/coder/refactoring.md +1454 -0
- agents/coder/security-hardening.md +1732 -0
- agents/coder/system_prompt.md +1448 -0
- agents/coder/tdd.md +1367 -0
- agents/creative-writer/__init__.py +0 -0
- agents/creative-writer/agent.json +4 -0
- agents/creative-writer/character-development.md +1852 -0
- agents/creative-writer/dialogue-craft.md +1122 -0
- agents/creative-writer/plot-structure.md +1073 -0
- agents/creative-writer/revision-editing.md +1484 -0
- agents/creative-writer/system_prompt.md +690 -0
- agents/creative-writer/worldbuilding.md +2049 -0
- agents/data-analyst/__init__.py +30 -0
- agents/data-analyst/agent.json +4 -0
- agents/data-analyst/data-visualization.md +992 -0
- agents/data-analyst/exploratory-data-analysis.md +1110 -0
- agents/data-analyst/pandas-data-manipulation.md +1081 -0
- agents/data-analyst/sql-query-optimization.md +881 -0
- agents/data-analyst/statistical-analysis.md +1118 -0
- agents/data-analyst/system_prompt.md +928 -0
- agents/default/__init__.py +0 -0
- agents/default/agent.json +4 -0
- agents/default/dead-code.md +794 -0
- agents/default/explore-agent-system.md +585 -0
- agents/default/system_prompt.md +1448 -0
- agents/kollabor/__init__.py +0 -0
- agents/kollabor/analyze-plugin-lifecycle.md +175 -0
- agents/kollabor/analyze-terminal-rendering.md +388 -0
- agents/kollabor/code-review.md +1092 -0
- agents/kollabor/debug-mcp-integration.md +521 -0
- agents/kollabor/debug-plugin-hooks.md +547 -0
- agents/kollabor/debugging.md +1102 -0
- agents/kollabor/dependency-management.md +1397 -0
- agents/kollabor/git-workflow.md +1099 -0
- agents/kollabor/inspect-llm-conversation.md +148 -0
- agents/kollabor/monitor-event-bus.md +558 -0
- agents/kollabor/profile-performance.md +576 -0
- agents/kollabor/refactoring.md +1454 -0
- agents/kollabor/system_prompt copy.md +1448 -0
- agents/kollabor/system_prompt.md +757 -0
- agents/kollabor/trace-command-execution.md +178 -0
- agents/kollabor/validate-config.md +879 -0
- agents/research/__init__.py +0 -0
- agents/research/agent.json +4 -0
- agents/research/architecture-mapping.md +1099 -0
- agents/research/codebase-analysis.md +1077 -0
- agents/research/dependency-audit.md +1027 -0
- agents/research/performance-profiling.md +1047 -0
- agents/research/security-review.md +1359 -0
- agents/research/system_prompt.md +492 -0
- agents/technical-writer/__init__.py +0 -0
- agents/technical-writer/agent.json +4 -0
- agents/technical-writer/api-documentation.md +2328 -0
- agents/technical-writer/changelog-management.md +1181 -0
- agents/technical-writer/readme-writing.md +1360 -0
- agents/technical-writer/style-guide.md +1410 -0
- agents/technical-writer/system_prompt.md +653 -0
- agents/technical-writer/tutorial-creation.md +1448 -0
- core/__init__.py +0 -2
- core/application.py +343 -88
- core/cli.py +229 -10
- core/commands/menu_renderer.py +463 -59
- core/commands/registry.py +14 -9
- core/commands/system_commands.py +2461 -14
- core/config/loader.py +151 -37
- core/config/service.py +18 -6
- core/events/bus.py +29 -9
- core/events/executor.py +205 -75
- core/events/models.py +27 -8
- core/fullscreen/command_integration.py +20 -24
- core/fullscreen/components/__init__.py +10 -1
- core/fullscreen/components/matrix_components.py +1 -2
- core/fullscreen/components/space_shooter_components.py +654 -0
- core/fullscreen/plugin.py +5 -0
- core/fullscreen/renderer.py +52 -13
- core/fullscreen/session.py +52 -15
- core/io/__init__.py +29 -5
- core/io/buffer_manager.py +6 -1
- core/io/config_status_view.py +7 -29
- core/io/core_status_views.py +267 -347
- core/io/input/__init__.py +25 -0
- core/io/input/command_mode_handler.py +711 -0
- core/io/input/display_controller.py +128 -0
- core/io/input/hook_registrar.py +286 -0
- core/io/input/input_loop_manager.py +421 -0
- core/io/input/key_press_handler.py +502 -0
- core/io/input/modal_controller.py +1011 -0
- core/io/input/paste_processor.py +339 -0
- core/io/input/status_modal_renderer.py +184 -0
- core/io/input_errors.py +5 -1
- core/io/input_handler.py +211 -2452
- core/io/key_parser.py +7 -0
- core/io/layout.py +15 -3
- core/io/message_coordinator.py +111 -2
- core/io/message_renderer.py +129 -4
- core/io/status_renderer.py +147 -607
- core/io/terminal_renderer.py +97 -51
- core/io/terminal_state.py +21 -4
- core/io/visual_effects.py +816 -165
- core/llm/agent_manager.py +1063 -0
- core/llm/api_adapters/__init__.py +44 -0
- core/llm/api_adapters/anthropic_adapter.py +432 -0
- core/llm/api_adapters/base.py +241 -0
- core/llm/api_adapters/openai_adapter.py +326 -0
- core/llm/api_communication_service.py +167 -113
- core/llm/conversation_logger.py +322 -16
- core/llm/conversation_manager.py +556 -30
- core/llm/file_operations_executor.py +84 -32
- core/llm/llm_service.py +934 -103
- core/llm/mcp_integration.py +541 -57
- core/llm/message_display_service.py +135 -18
- core/llm/plugin_sdk.py +1 -2
- core/llm/profile_manager.py +1183 -0
- core/llm/response_parser.py +274 -56
- core/llm/response_processor.py +16 -3
- core/llm/tool_executor.py +6 -1
- core/logging/__init__.py +2 -0
- core/logging/setup.py +34 -6
- core/models/resume.py +54 -0
- core/plugins/__init__.py +4 -2
- core/plugins/base.py +127 -0
- core/plugins/collector.py +23 -161
- core/plugins/discovery.py +37 -3
- core/plugins/factory.py +6 -12
- core/plugins/registry.py +5 -17
- core/ui/config_widgets.py +128 -28
- core/ui/live_modal_renderer.py +2 -1
- core/ui/modal_actions.py +5 -0
- core/ui/modal_overlay_renderer.py +0 -60
- core/ui/modal_renderer.py +268 -7
- core/ui/modal_state_manager.py +29 -4
- core/ui/widgets/base_widget.py +7 -0
- core/updates/__init__.py +10 -0
- core/updates/version_check_service.py +348 -0
- core/updates/version_comparator.py +103 -0
- core/utils/config_utils.py +685 -526
- core/utils/plugin_utils.py +1 -1
- core/utils/session_naming.py +111 -0
- fonts/LICENSE +21 -0
- fonts/README.md +46 -0
- fonts/SymbolsNerdFont-Regular.ttf +0 -0
- fonts/SymbolsNerdFontMono-Regular.ttf +0 -0
- fonts/__init__.py +44 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/METADATA +54 -4
- kollabor-0.4.15.dist-info/RECORD +228 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/top_level.txt +2 -0
- plugins/agent_orchestrator/__init__.py +39 -0
- plugins/agent_orchestrator/activity_monitor.py +181 -0
- plugins/agent_orchestrator/file_attacher.py +77 -0
- plugins/agent_orchestrator/message_injector.py +135 -0
- plugins/agent_orchestrator/models.py +48 -0
- plugins/agent_orchestrator/orchestrator.py +403 -0
- plugins/agent_orchestrator/plugin.py +976 -0
- plugins/agent_orchestrator/xml_parser.py +191 -0
- plugins/agent_orchestrator_plugin.py +9 -0
- plugins/enhanced_input/box_styles.py +1 -0
- plugins/enhanced_input/color_engine.py +19 -4
- plugins/enhanced_input/config.py +2 -2
- plugins/enhanced_input_plugin.py +61 -11
- plugins/fullscreen/__init__.py +6 -2
- plugins/fullscreen/example_plugin.py +1035 -222
- plugins/fullscreen/setup_wizard_plugin.py +592 -0
- plugins/fullscreen/space_shooter_plugin.py +131 -0
- plugins/hook_monitoring_plugin.py +436 -78
- plugins/query_enhancer_plugin.py +66 -30
- plugins/resume_conversation_plugin.py +1494 -0
- plugins/save_conversation_plugin.py +98 -32
- plugins/system_commands_plugin.py +70 -56
- plugins/tmux_plugin.py +154 -78
- plugins/workflow_enforcement_plugin.py +94 -92
- system_prompt/default.md +952 -886
- core/io/input_mode_manager.py +0 -402
- core/io/modal_interaction_handler.py +0 -315
- core/io/raw_input_processor.py +0 -946
- core/storage/__init__.py +0 -5
- core/storage/state_manager.py +0 -84
- core/ui/widget_integration.py +0 -222
- core/utils/key_reader.py +0 -171
- kollabor-0.4.9.dist-info/RECORD +0 -128
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/WHEEL +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/entry_points.txt +0 -0
- {kollabor-0.4.9.dist-info → kollabor-0.4.15.dist-info}/licenses/LICENSE +0 -0
core/llm/conversation_logger.py
CHANGED
|
@@ -14,6 +14,8 @@ from pathlib import Path
|
|
|
14
14
|
from typing import Any, Dict, List, Optional
|
|
15
15
|
from uuid import uuid4
|
|
16
16
|
|
|
17
|
+
from ..utils.session_naming import generate_session_name
|
|
18
|
+
|
|
17
19
|
logger = logging.getLogger(__name__)
|
|
18
20
|
|
|
19
21
|
|
|
@@ -33,9 +35,8 @@ class KollaborConversationLogger:
|
|
|
33
35
|
self.conversations_dir = conversations_dir
|
|
34
36
|
self.conversations_dir.mkdir(parents=True, exist_ok=True)
|
|
35
37
|
|
|
36
|
-
# Session management
|
|
37
|
-
|
|
38
|
-
self.session_id = f"session_{timestamp}"
|
|
38
|
+
# Session management - use memorable session names
|
|
39
|
+
self.session_id = generate_session_name()
|
|
39
40
|
self.session_file = self.conversations_dir / f"{self.session_id}.jsonl"
|
|
40
41
|
|
|
41
42
|
# Conversation state
|
|
@@ -47,19 +48,24 @@ class KollaborConversationLogger:
|
|
|
47
48
|
self.user_patterns = []
|
|
48
49
|
self.project_context = {}
|
|
49
50
|
self.conversation_themes = []
|
|
51
|
+
|
|
52
|
+
# Dynamic context (set by llm_service before log_conversation_start)
|
|
53
|
+
self.app_version = "unknown"
|
|
54
|
+
self.active_plugins = []
|
|
50
55
|
self.file_interactions = {}
|
|
51
56
|
|
|
52
|
-
# Memory management
|
|
53
|
-
self.memory_dir = self.conversations_dir
|
|
57
|
+
# Memory management (inside conversations/)
|
|
58
|
+
self.memory_dir = self.conversations_dir / "memory"
|
|
54
59
|
self.memory_dir.mkdir(parents=True, exist_ok=True)
|
|
55
60
|
self._load_conversation_memory()
|
|
56
61
|
|
|
57
62
|
logger.info(f"Conversation logger initialized: {self.session_id}")
|
|
58
63
|
|
|
59
|
-
async def initialize(self):
|
|
64
|
+
async def initialize(self) -> bool:
|
|
60
65
|
"""Initialize async resources for conversation logger."""
|
|
61
66
|
# Any async initialization can happen here
|
|
62
67
|
logger.debug("Conversation logger async initialization complete")
|
|
68
|
+
return True
|
|
63
69
|
|
|
64
70
|
async def shutdown(self):
|
|
65
71
|
"""Shutdown conversation logger and save state."""
|
|
@@ -299,6 +305,38 @@ class KollaborConversationLogger:
|
|
|
299
305
|
logger.warning(f"Failed to find related sessions: {e}")
|
|
300
306
|
return related
|
|
301
307
|
|
|
308
|
+
def log_message(self, role: str, content: str, parent_uuid: Optional[str] = None,
|
|
309
|
+
metadata: Optional[Dict] = None) -> None:
|
|
310
|
+
"""Generic message logging - routes to appropriate role-specific method.
|
|
311
|
+
|
|
312
|
+
This is a synchronous wrapper that schedules the async logging.
|
|
313
|
+
|
|
314
|
+
Args:
|
|
315
|
+
role: Message role (user, assistant, system)
|
|
316
|
+
content: Message content
|
|
317
|
+
parent_uuid: Optional parent message UUID
|
|
318
|
+
metadata: Optional additional metadata
|
|
319
|
+
"""
|
|
320
|
+
import asyncio
|
|
321
|
+
|
|
322
|
+
async def _log():
|
|
323
|
+
if role == "user":
|
|
324
|
+
await self.log_user_message(content, parent_uuid, metadata)
|
|
325
|
+
elif role == "assistant":
|
|
326
|
+
await self.log_assistant_message(content, parent_uuid or "", metadata)
|
|
327
|
+
elif role == "system":
|
|
328
|
+
await self.log_system_message(content, parent_uuid or "", metadata)
|
|
329
|
+
else:
|
|
330
|
+
logger.warning(f"Unknown role for logging: {role}")
|
|
331
|
+
|
|
332
|
+
# Try to get running loop and schedule, otherwise log synchronously
|
|
333
|
+
try:
|
|
334
|
+
loop = asyncio.get_running_loop()
|
|
335
|
+
loop.create_task(_log())
|
|
336
|
+
except RuntimeError:
|
|
337
|
+
# No running loop - skip async logging
|
|
338
|
+
logger.debug(f"Skipping async log for {role} message (no event loop)")
|
|
339
|
+
|
|
302
340
|
async def log_conversation_start(self):
|
|
303
341
|
"""Log conversation root structure with metadata."""
|
|
304
342
|
root_message = {
|
|
@@ -310,15 +348,9 @@ class KollaborConversationLogger:
|
|
|
310
348
|
"timestamp": datetime.now().isoformat() + "Z",
|
|
311
349
|
"cwd": self._get_working_directory(),
|
|
312
350
|
"gitBranch": self._get_git_branch(),
|
|
313
|
-
"version":
|
|
351
|
+
"version": self.app_version,
|
|
314
352
|
"conversation_context": {
|
|
315
|
-
"
|
|
316
|
-
"active_plugins": ["llm_service", "hook_system", "conversation_logger"],
|
|
317
|
-
"user_profile": {
|
|
318
|
-
"expertise_level": "advanced",
|
|
319
|
-
"preferred_communication": "direct",
|
|
320
|
-
"coding_style": "pythonic"
|
|
321
|
-
},
|
|
353
|
+
"active_plugins": self.active_plugins,
|
|
322
354
|
"session_goals": [],
|
|
323
355
|
"conversation_summary": ""
|
|
324
356
|
},
|
|
@@ -330,7 +362,7 @@ class KollaborConversationLogger:
|
|
|
330
362
|
}
|
|
331
363
|
}
|
|
332
364
|
}
|
|
333
|
-
|
|
365
|
+
|
|
334
366
|
await self._append_to_jsonl(root_message)
|
|
335
367
|
logger.info(f"Logged conversation start: {self.session_id}")
|
|
336
368
|
|
|
@@ -470,4 +502,278 @@ class KollaborConversationLogger:
|
|
|
470
502
|
# Save conversation memory
|
|
471
503
|
self._save_conversation_memory()
|
|
472
504
|
|
|
473
|
-
logger.info(f"Logged conversation end: {self.session_id}")
|
|
505
|
+
logger.info(f"Logged conversation end: {self.session_id}")
|
|
506
|
+
|
|
507
|
+
def list_sessions(self, filters: Dict = None) -> List[Dict]:
|
|
508
|
+
"""List available sessions with metadata.
|
|
509
|
+
|
|
510
|
+
Args:
|
|
511
|
+
filters: Optional filters for sessions
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
List of session metadata
|
|
515
|
+
"""
|
|
516
|
+
sessions = []
|
|
517
|
+
|
|
518
|
+
try:
|
|
519
|
+
# Find both old format (session_*) and new format (YYMMDDHHMM-*) files
|
|
520
|
+
all_files = (
|
|
521
|
+
list(self.conversations_dir.glob("session_*.jsonl")) +
|
|
522
|
+
list(self.conversations_dir.glob("[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-*.jsonl"))
|
|
523
|
+
)
|
|
524
|
+
|
|
525
|
+
for session_file in all_files:
|
|
526
|
+
try:
|
|
527
|
+
session_info = self._parse_session_file(session_file, filters)
|
|
528
|
+
if session_info:
|
|
529
|
+
sessions.append(session_info)
|
|
530
|
+
except Exception as e:
|
|
531
|
+
logger.warning(f"Failed to parse session file {session_file}: {e}")
|
|
532
|
+
continue
|
|
533
|
+
|
|
534
|
+
# Sort by start time (newest first)
|
|
535
|
+
sessions.sort(key=lambda x: x.get("start_time", ""), reverse=True)
|
|
536
|
+
|
|
537
|
+
except Exception as e:
|
|
538
|
+
logger.error(f"Failed to scan sessions directory: {e}")
|
|
539
|
+
|
|
540
|
+
return sessions
|
|
541
|
+
|
|
542
|
+
def _parse_session_file(self, session_file: Path, filters: Dict = None) -> Optional[Dict]:
|
|
543
|
+
"""Parse a session file and extract metadata.
|
|
544
|
+
|
|
545
|
+
Args:
|
|
546
|
+
session_file: Path to session JSONL file
|
|
547
|
+
filters: Optional filters
|
|
548
|
+
|
|
549
|
+
Returns:
|
|
550
|
+
Session metadata or None if should be filtered out
|
|
551
|
+
"""
|
|
552
|
+
try:
|
|
553
|
+
session_info = {
|
|
554
|
+
"session_id": session_file.stem,
|
|
555
|
+
"file_path": str(session_file),
|
|
556
|
+
"start_time": None,
|
|
557
|
+
"end_time": None,
|
|
558
|
+
"message_count": 0,
|
|
559
|
+
"working_directory": "unknown",
|
|
560
|
+
"git_branch": "unknown",
|
|
561
|
+
"topics": [],
|
|
562
|
+
"duration": None,
|
|
563
|
+
"size_bytes": session_file.stat().st_size,
|
|
564
|
+
"preview_messages": []
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
with open(session_file, 'r') as f:
|
|
568
|
+
lines = f.readlines()
|
|
569
|
+
|
|
570
|
+
user_messages = []
|
|
571
|
+
assistant_messages = []
|
|
572
|
+
|
|
573
|
+
for line in lines:
|
|
574
|
+
try:
|
|
575
|
+
data = json.loads(line.strip())
|
|
576
|
+
|
|
577
|
+
# Extract metadata from conversation start
|
|
578
|
+
if data.get("type") == "conversation_metadata":
|
|
579
|
+
session_info["start_time"] = data.get("startTime")
|
|
580
|
+
session_info["working_directory"] = data.get("cwd", "unknown")
|
|
581
|
+
session_info["git_branch"] = data.get("gitBranch", "unknown")
|
|
582
|
+
|
|
583
|
+
# Extract end time
|
|
584
|
+
elif data.get("type") == "conversation_end":
|
|
585
|
+
session_info["end_time"] = data.get("endTime")
|
|
586
|
+
summary = data.get("summary", {})
|
|
587
|
+
session_info["topics"] = summary.get("themes", [])
|
|
588
|
+
|
|
589
|
+
# Count messages
|
|
590
|
+
elif data.get("type") == "user":
|
|
591
|
+
user_messages.append(data)
|
|
592
|
+
session_info["message_count"] += 1
|
|
593
|
+
|
|
594
|
+
# Store preview messages (first 3 user messages)
|
|
595
|
+
if len(user_messages) <= 3:
|
|
596
|
+
content = data.get("message", {}).get("content", "")
|
|
597
|
+
preview = content[:100] + "..." if len(content) > 100 else content
|
|
598
|
+
session_info["preview_messages"].append({
|
|
599
|
+
"role": "user",
|
|
600
|
+
"content": preview,
|
|
601
|
+
"timestamp": data.get("timestamp")
|
|
602
|
+
})
|
|
603
|
+
|
|
604
|
+
elif data.get("type") == "assistant":
|
|
605
|
+
assistant_messages.append(data)
|
|
606
|
+
session_info["message_count"] += 1
|
|
607
|
+
|
|
608
|
+
# Store preview assistant messages
|
|
609
|
+
if len(assistant_messages) <= 2:
|
|
610
|
+
content = data.get("message", {}).get("content", [])
|
|
611
|
+
if isinstance(content, list) and content:
|
|
612
|
+
text_content = content[0].get("text", "")
|
|
613
|
+
else:
|
|
614
|
+
text_content = str(content)
|
|
615
|
+
|
|
616
|
+
preview = text_content[:100] + "..." if len(text_content) > 100 else text_content
|
|
617
|
+
session_info["preview_messages"].append({
|
|
618
|
+
"role": "assistant",
|
|
619
|
+
"content": preview,
|
|
620
|
+
"timestamp": data.get("timestamp")
|
|
621
|
+
})
|
|
622
|
+
|
|
623
|
+
except json.JSONDecodeError:
|
|
624
|
+
continue
|
|
625
|
+
|
|
626
|
+
# Calculate duration
|
|
627
|
+
if session_info["start_time"] and session_info["end_time"]:
|
|
628
|
+
try:
|
|
629
|
+
start = datetime.fromisoformat(session_info["start_time"].replace('Z', '+00:00'))
|
|
630
|
+
end = datetime.fromisoformat(session_info["end_time"].replace('Z', '+00:00'))
|
|
631
|
+
duration = end - start
|
|
632
|
+
session_info["duration"] = str(int(duration.total_seconds() / 60)) + "m"
|
|
633
|
+
except:
|
|
634
|
+
session_info["duration"] = "unknown"
|
|
635
|
+
|
|
636
|
+
# Apply filters
|
|
637
|
+
if filters:
|
|
638
|
+
if "date_range" in filters:
|
|
639
|
+
# Filter by date range
|
|
640
|
+
pass # TODO: Implement date filtering
|
|
641
|
+
|
|
642
|
+
if "working_directory" in filters:
|
|
643
|
+
if session_info["working_directory"] != filters["working_directory"]:
|
|
644
|
+
return None
|
|
645
|
+
|
|
646
|
+
if "git_branch" in filters:
|
|
647
|
+
if session_info["git_branch"] != filters["git_branch"]:
|
|
648
|
+
return None
|
|
649
|
+
|
|
650
|
+
return session_info
|
|
651
|
+
|
|
652
|
+
except Exception as e:
|
|
653
|
+
logger.warning(f"Failed to parse session file {session_file}: {e}")
|
|
654
|
+
return None
|
|
655
|
+
|
|
656
|
+
def get_session_summary(self, session_id: str) -> Dict:
|
|
657
|
+
"""Get session summary for preview.
|
|
658
|
+
|
|
659
|
+
Args:
|
|
660
|
+
session_id: Session identifier
|
|
661
|
+
|
|
662
|
+
Returns:
|
|
663
|
+
Session summary
|
|
664
|
+
"""
|
|
665
|
+
try:
|
|
666
|
+
# Find session file
|
|
667
|
+
session_file = None
|
|
668
|
+
for file_path in self.conversations_dir.glob(f"{session_id}*.jsonl"):
|
|
669
|
+
session_file = file_path
|
|
670
|
+
break
|
|
671
|
+
|
|
672
|
+
if not session_file:
|
|
673
|
+
return {}
|
|
674
|
+
|
|
675
|
+
session_info = self._parse_session_file(session_file)
|
|
676
|
+
if not session_info:
|
|
677
|
+
return {}
|
|
678
|
+
|
|
679
|
+
# Extract additional summary information
|
|
680
|
+
summary = {
|
|
681
|
+
"metadata": session_info,
|
|
682
|
+
"key_topics": session_info.get("topics", []),
|
|
683
|
+
"user_patterns": [], # TODO: Extract from memory
|
|
684
|
+
"project_context": {
|
|
685
|
+
"working_directory": session_info.get("working_directory"),
|
|
686
|
+
"git_branch": session_info.get("git_branch"),
|
|
687
|
+
"files_mentioned": [] # TODO: Extract from messages
|
|
688
|
+
},
|
|
689
|
+
"compatibility_score": 1.0 # TODO: Calculate based on environment
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
return summary
|
|
693
|
+
|
|
694
|
+
except Exception as e:
|
|
695
|
+
logger.error(f"Failed to get session summary for {session_id}: {e}")
|
|
696
|
+
return {}
|
|
697
|
+
|
|
698
|
+
def search_sessions(self, query: str) -> List[Dict]:
|
|
699
|
+
"""Search sessions by content.
|
|
700
|
+
|
|
701
|
+
Args:
|
|
702
|
+
query: Search query
|
|
703
|
+
|
|
704
|
+
Returns:
|
|
705
|
+
List of matching sessions
|
|
706
|
+
"""
|
|
707
|
+
results = []
|
|
708
|
+
query_lower = query.lower()
|
|
709
|
+
|
|
710
|
+
try:
|
|
711
|
+
# Search both old format (session_*) and new format (YYMMDDHHMM-*) files
|
|
712
|
+
all_files = (
|
|
713
|
+
list(self.conversations_dir.glob("session_*.jsonl")) +
|
|
714
|
+
list(self.conversations_dir.glob("[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-*.jsonl"))
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
for session_file in all_files:
|
|
718
|
+
try:
|
|
719
|
+
# Quick filename check first
|
|
720
|
+
if query_lower in session_file.name.lower():
|
|
721
|
+
session_info = self._parse_session_file(session_file)
|
|
722
|
+
if session_info:
|
|
723
|
+
results.append(session_info)
|
|
724
|
+
continue
|
|
725
|
+
|
|
726
|
+
# Content search
|
|
727
|
+
with open(session_file, 'r') as f:
|
|
728
|
+
content = f.read().lower()
|
|
729
|
+
|
|
730
|
+
if query_lower in content:
|
|
731
|
+
session_info = self._parse_session_file(session_file)
|
|
732
|
+
if session_info:
|
|
733
|
+
# Add search relevance info
|
|
734
|
+
session_info["search_relevance"] = self._calculate_search_relevance(
|
|
735
|
+
content, query_lower
|
|
736
|
+
)
|
|
737
|
+
results.append(session_info)
|
|
738
|
+
|
|
739
|
+
except Exception as e:
|
|
740
|
+
logger.warning(f"Failed to search session file {session_file}: {e}")
|
|
741
|
+
continue
|
|
742
|
+
|
|
743
|
+
# Sort by relevance (most relevant first)
|
|
744
|
+
results.sort(key=lambda x: x.get("search_relevance", 0), reverse=True)
|
|
745
|
+
|
|
746
|
+
except Exception as e:
|
|
747
|
+
logger.error(f"Failed to search sessions: {e}")
|
|
748
|
+
|
|
749
|
+
return results
|
|
750
|
+
|
|
751
|
+
def _calculate_search_relevance(self, content: str, query: str) -> float:
|
|
752
|
+
"""Calculate search relevance score.
|
|
753
|
+
|
|
754
|
+
Args:
|
|
755
|
+
content: Session content
|
|
756
|
+
query: Search query
|
|
757
|
+
|
|
758
|
+
Returns:
|
|
759
|
+
Relevance score (0.0 to 1.0)
|
|
760
|
+
"""
|
|
761
|
+
# Simple relevance calculation
|
|
762
|
+
query_words = query.split()
|
|
763
|
+
content_words = content.split()
|
|
764
|
+
|
|
765
|
+
if not query_words:
|
|
766
|
+
return 0.0
|
|
767
|
+
if not content_words:
|
|
768
|
+
return 0.0
|
|
769
|
+
|
|
770
|
+
# Count exact matches
|
|
771
|
+
exact_matches = content.count(query)
|
|
772
|
+
|
|
773
|
+
# Count word matches
|
|
774
|
+
word_matches = sum(1 for word in query_words if word in content)
|
|
775
|
+
|
|
776
|
+
# Calculate relevance based on frequency and coverage
|
|
777
|
+
relevance = (exact_matches * 0.5 + word_matches * 0.1) / len(content_words)
|
|
778
|
+
|
|
779
|
+
return min(relevance * 100, 1.0) # Normalize to 0-1
|