kollabor 0.4.9__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.
- core/__init__.py +18 -0
- core/application.py +578 -0
- core/cli.py +193 -0
- core/commands/__init__.py +43 -0
- core/commands/executor.py +277 -0
- core/commands/menu_renderer.py +319 -0
- core/commands/parser.py +186 -0
- core/commands/registry.py +331 -0
- core/commands/system_commands.py +479 -0
- core/config/__init__.py +7 -0
- core/config/llm_task_config.py +110 -0
- core/config/loader.py +501 -0
- core/config/manager.py +112 -0
- core/config/plugin_config_manager.py +346 -0
- core/config/plugin_schema.py +424 -0
- core/config/service.py +399 -0
- core/effects/__init__.py +1 -0
- core/events/__init__.py +12 -0
- core/events/bus.py +129 -0
- core/events/executor.py +154 -0
- core/events/models.py +258 -0
- core/events/processor.py +176 -0
- core/events/registry.py +289 -0
- core/fullscreen/__init__.py +19 -0
- core/fullscreen/command_integration.py +290 -0
- core/fullscreen/components/__init__.py +12 -0
- core/fullscreen/components/animation.py +258 -0
- core/fullscreen/components/drawing.py +160 -0
- core/fullscreen/components/matrix_components.py +177 -0
- core/fullscreen/manager.py +302 -0
- core/fullscreen/plugin.py +204 -0
- core/fullscreen/renderer.py +282 -0
- core/fullscreen/session.py +324 -0
- core/io/__init__.py +52 -0
- core/io/buffer_manager.py +362 -0
- core/io/config_status_view.py +272 -0
- core/io/core_status_views.py +410 -0
- core/io/input_errors.py +313 -0
- core/io/input_handler.py +2655 -0
- core/io/input_mode_manager.py +402 -0
- core/io/key_parser.py +344 -0
- core/io/layout.py +587 -0
- core/io/message_coordinator.py +204 -0
- core/io/message_renderer.py +601 -0
- core/io/modal_interaction_handler.py +315 -0
- core/io/raw_input_processor.py +946 -0
- core/io/status_renderer.py +845 -0
- core/io/terminal_renderer.py +586 -0
- core/io/terminal_state.py +551 -0
- core/io/visual_effects.py +734 -0
- core/llm/__init__.py +26 -0
- core/llm/api_communication_service.py +863 -0
- core/llm/conversation_logger.py +473 -0
- core/llm/conversation_manager.py +414 -0
- core/llm/file_operations_executor.py +1401 -0
- core/llm/hook_system.py +402 -0
- core/llm/llm_service.py +1629 -0
- core/llm/mcp_integration.py +386 -0
- core/llm/message_display_service.py +450 -0
- core/llm/model_router.py +214 -0
- core/llm/plugin_sdk.py +396 -0
- core/llm/response_parser.py +848 -0
- core/llm/response_processor.py +364 -0
- core/llm/tool_executor.py +520 -0
- core/logging/__init__.py +19 -0
- core/logging/setup.py +208 -0
- core/models/__init__.py +5 -0
- core/models/base.py +23 -0
- core/plugins/__init__.py +13 -0
- core/plugins/collector.py +212 -0
- core/plugins/discovery.py +386 -0
- core/plugins/factory.py +263 -0
- core/plugins/registry.py +152 -0
- core/storage/__init__.py +5 -0
- core/storage/state_manager.py +84 -0
- core/ui/__init__.py +6 -0
- core/ui/config_merger.py +176 -0
- core/ui/config_widgets.py +369 -0
- core/ui/live_modal_renderer.py +276 -0
- core/ui/modal_actions.py +162 -0
- core/ui/modal_overlay_renderer.py +373 -0
- core/ui/modal_renderer.py +591 -0
- core/ui/modal_state_manager.py +443 -0
- core/ui/widget_integration.py +222 -0
- core/ui/widgets/__init__.py +27 -0
- core/ui/widgets/base_widget.py +136 -0
- core/ui/widgets/checkbox.py +85 -0
- core/ui/widgets/dropdown.py +140 -0
- core/ui/widgets/label.py +78 -0
- core/ui/widgets/slider.py +185 -0
- core/ui/widgets/text_input.py +224 -0
- core/utils/__init__.py +11 -0
- core/utils/config_utils.py +656 -0
- core/utils/dict_utils.py +212 -0
- core/utils/error_utils.py +275 -0
- core/utils/key_reader.py +171 -0
- core/utils/plugin_utils.py +267 -0
- core/utils/prompt_renderer.py +151 -0
- kollabor-0.4.9.dist-info/METADATA +298 -0
- kollabor-0.4.9.dist-info/RECORD +128 -0
- kollabor-0.4.9.dist-info/WHEEL +5 -0
- kollabor-0.4.9.dist-info/entry_points.txt +2 -0
- kollabor-0.4.9.dist-info/licenses/LICENSE +21 -0
- kollabor-0.4.9.dist-info/top_level.txt +4 -0
- kollabor_cli_main.py +20 -0
- plugins/__init__.py +1 -0
- plugins/enhanced_input/__init__.py +18 -0
- plugins/enhanced_input/box_renderer.py +103 -0
- plugins/enhanced_input/box_styles.py +142 -0
- plugins/enhanced_input/color_engine.py +165 -0
- plugins/enhanced_input/config.py +150 -0
- plugins/enhanced_input/cursor_manager.py +72 -0
- plugins/enhanced_input/geometry.py +81 -0
- plugins/enhanced_input/state.py +130 -0
- plugins/enhanced_input/text_processor.py +115 -0
- plugins/enhanced_input_plugin.py +385 -0
- plugins/fullscreen/__init__.py +9 -0
- plugins/fullscreen/example_plugin.py +327 -0
- plugins/fullscreen/matrix_plugin.py +132 -0
- plugins/hook_monitoring_plugin.py +1299 -0
- plugins/query_enhancer_plugin.py +350 -0
- plugins/save_conversation_plugin.py +502 -0
- plugins/system_commands_plugin.py +93 -0
- plugins/tmux_plugin.py +795 -0
- plugins/workflow_enforcement_plugin.py +629 -0
- system_prompt/default.md +1286 -0
- system_prompt/default_win.md +265 -0
- system_prompt/example_with_trender.md +47 -0
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""Conversation logging system with intelligence features.
|
|
2
|
+
|
|
3
|
+
This module provides comprehensive JSONL logging for all conversations,
|
|
4
|
+
including message threading, session management, and intelligence features
|
|
5
|
+
that learn from user patterns and project context.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import json
|
|
9
|
+
import logging
|
|
10
|
+
import subprocess
|
|
11
|
+
import time
|
|
12
|
+
from datetime import datetime
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
from uuid import uuid4
|
|
16
|
+
|
|
17
|
+
logger = logging.getLogger(__name__)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class KollaborConversationLogger:
|
|
21
|
+
"""Conversation logger with intelligence features.
|
|
22
|
+
|
|
23
|
+
Logs every terminal interaction as structured JSON objects with
|
|
24
|
+
conversation threading, user context analysis, and learning capabilities.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, conversations_dir: Path):
|
|
28
|
+
"""Initialize the conversation logger.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
conversations_dir: Directory to store conversation JSONL files
|
|
32
|
+
"""
|
|
33
|
+
self.conversations_dir = conversations_dir
|
|
34
|
+
self.conversations_dir.mkdir(parents=True, exist_ok=True)
|
|
35
|
+
|
|
36
|
+
# Session management
|
|
37
|
+
timestamp = datetime.now().strftime('%Y-%m-%d_%H%M%S')
|
|
38
|
+
self.session_id = f"session_{timestamp}"
|
|
39
|
+
self.session_file = self.conversations_dir / f"{self.session_id}.jsonl"
|
|
40
|
+
|
|
41
|
+
# Conversation state
|
|
42
|
+
self.conversation_start_time = datetime.now()
|
|
43
|
+
self.message_count = 0
|
|
44
|
+
self.current_thread_uuid = None
|
|
45
|
+
|
|
46
|
+
# Intelligence features
|
|
47
|
+
self.user_patterns = []
|
|
48
|
+
self.project_context = {}
|
|
49
|
+
self.conversation_themes = []
|
|
50
|
+
self.file_interactions = {}
|
|
51
|
+
|
|
52
|
+
# Memory management
|
|
53
|
+
self.memory_dir = self.conversations_dir.parent / "conversation_memory"
|
|
54
|
+
self.memory_dir.mkdir(parents=True, exist_ok=True)
|
|
55
|
+
self._load_conversation_memory()
|
|
56
|
+
|
|
57
|
+
logger.info(f"Conversation logger initialized: {self.session_id}")
|
|
58
|
+
|
|
59
|
+
async def initialize(self):
|
|
60
|
+
"""Initialize async resources for conversation logger."""
|
|
61
|
+
# Any async initialization can happen here
|
|
62
|
+
logger.debug("Conversation logger async initialization complete")
|
|
63
|
+
|
|
64
|
+
async def shutdown(self):
|
|
65
|
+
"""Shutdown conversation logger and save state."""
|
|
66
|
+
# Save any pending data
|
|
67
|
+
self._save_conversation_memory()
|
|
68
|
+
logger.info("Conversation logger shutdown complete")
|
|
69
|
+
|
|
70
|
+
def _load_conversation_memory(self):
|
|
71
|
+
"""Load conversation memory from previous sessions."""
|
|
72
|
+
try:
|
|
73
|
+
# Load user patterns
|
|
74
|
+
patterns_file = self.memory_dir / "user_patterns.json"
|
|
75
|
+
if patterns_file.exists():
|
|
76
|
+
with open(patterns_file, 'r') as f:
|
|
77
|
+
self.user_patterns = json.load(f)
|
|
78
|
+
|
|
79
|
+
# Load project context
|
|
80
|
+
context_file = self.memory_dir / "project_context.json"
|
|
81
|
+
if context_file.exists():
|
|
82
|
+
with open(context_file, 'r') as f:
|
|
83
|
+
self.project_context = json.load(f)
|
|
84
|
+
|
|
85
|
+
# Load solution history
|
|
86
|
+
solutions_file = self.memory_dir / "solution_history.json"
|
|
87
|
+
if solutions_file.exists():
|
|
88
|
+
with open(solutions_file, 'r') as f:
|
|
89
|
+
self.solution_history = json.load(f)
|
|
90
|
+
else:
|
|
91
|
+
self.solution_history = []
|
|
92
|
+
|
|
93
|
+
logger.info("Loaded conversation memory from previous sessions")
|
|
94
|
+
|
|
95
|
+
except Exception as e:
|
|
96
|
+
logger.warning(f"Failed to load conversation memory: {e}")
|
|
97
|
+
self.solution_history = []
|
|
98
|
+
|
|
99
|
+
def _save_conversation_memory(self):
|
|
100
|
+
"""Save conversation memory for future sessions."""
|
|
101
|
+
try:
|
|
102
|
+
# Save user patterns
|
|
103
|
+
patterns_file = self.memory_dir / "user_patterns.json"
|
|
104
|
+
with open(patterns_file, 'w') as f:
|
|
105
|
+
json.dump(self.user_patterns, f, indent=2)
|
|
106
|
+
|
|
107
|
+
# Save project context
|
|
108
|
+
context_file = self.memory_dir / "project_context.json"
|
|
109
|
+
with open(context_file, 'w') as f:
|
|
110
|
+
json.dump(self.project_context, f, indent=2)
|
|
111
|
+
|
|
112
|
+
# Save solution history
|
|
113
|
+
solutions_file = self.memory_dir / "solution_history.json"
|
|
114
|
+
with open(solutions_file, 'w') as f:
|
|
115
|
+
json.dump(self.solution_history, f, indent=2)
|
|
116
|
+
|
|
117
|
+
logger.debug("Saved conversation memory for future sessions")
|
|
118
|
+
|
|
119
|
+
except Exception as e:
|
|
120
|
+
logger.error(f"Failed to save conversation memory: {e}")
|
|
121
|
+
|
|
122
|
+
def _get_git_branch(self) -> str:
|
|
123
|
+
"""Get current git branch."""
|
|
124
|
+
try:
|
|
125
|
+
result = subprocess.run(
|
|
126
|
+
["git", "branch", "--show-current"],
|
|
127
|
+
capture_output=True,
|
|
128
|
+
text=True,
|
|
129
|
+
timeout=2
|
|
130
|
+
)
|
|
131
|
+
if result.returncode == 0:
|
|
132
|
+
return result.stdout.strip()
|
|
133
|
+
except:
|
|
134
|
+
pass
|
|
135
|
+
return "unknown"
|
|
136
|
+
|
|
137
|
+
def _get_working_directory(self) -> str:
|
|
138
|
+
"""Get current working directory."""
|
|
139
|
+
return str(Path.cwd())
|
|
140
|
+
|
|
141
|
+
async def _append_to_jsonl(self, message: Dict[str, Any]):
|
|
142
|
+
"""Append message to JSONL file."""
|
|
143
|
+
try:
|
|
144
|
+
with open(self.session_file, 'a') as f:
|
|
145
|
+
f.write(json.dumps(message) + '\n')
|
|
146
|
+
self.message_count += 1
|
|
147
|
+
except Exception as e:
|
|
148
|
+
logger.error(f"Failed to write to JSONL: {e}")
|
|
149
|
+
|
|
150
|
+
def _analyze_user_context(self, content: str) -> Dict[str, Any]:
|
|
151
|
+
"""Analyze user context from message content."""
|
|
152
|
+
context = {
|
|
153
|
+
"message_length": len(content),
|
|
154
|
+
"has_code": "```" in content,
|
|
155
|
+
"has_question": "?" in content,
|
|
156
|
+
"has_command": any(cmd in content.lower() for cmd in ["fix", "create", "update", "delete", "implement"]),
|
|
157
|
+
"detected_intent": self._detect_intent(content)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
# Learn from patterns (deduplicated)
|
|
161
|
+
new_patterns = []
|
|
162
|
+
if context["has_command"]:
|
|
163
|
+
new_patterns.append("prefers_direct_commands")
|
|
164
|
+
if context["message_length"] > 200:
|
|
165
|
+
new_patterns.append("provides_detailed_context")
|
|
166
|
+
if context["has_code"]:
|
|
167
|
+
new_patterns.append("shares_code_frequently")
|
|
168
|
+
if context["has_question"]:
|
|
169
|
+
new_patterns.append("asks_clarifying_questions")
|
|
170
|
+
|
|
171
|
+
# Add new patterns (deduplicated)
|
|
172
|
+
for pattern in new_patterns:
|
|
173
|
+
if pattern not in self.user_patterns:
|
|
174
|
+
self.user_patterns.append(pattern)
|
|
175
|
+
logger.debug(f"Learned user pattern: {pattern}")
|
|
176
|
+
|
|
177
|
+
# Update project context based on content
|
|
178
|
+
self._update_project_context(content)
|
|
179
|
+
|
|
180
|
+
return context
|
|
181
|
+
|
|
182
|
+
def _update_project_context(self, content: str):
|
|
183
|
+
"""Update project context based on message content."""
|
|
184
|
+
# Track file mentions
|
|
185
|
+
import re
|
|
186
|
+
file_mentions = re.findall(r'(?:core/|plugins/|tests/|\.py|\.json|\.md)\S*', content)
|
|
187
|
+
for file_path in file_mentions:
|
|
188
|
+
if file_path not in self.project_context:
|
|
189
|
+
self.project_context[file_path] = {
|
|
190
|
+
"mentions": 0,
|
|
191
|
+
"first_mentioned": datetime.now().isoformat(),
|
|
192
|
+
"context": "user_discussion"
|
|
193
|
+
}
|
|
194
|
+
self.project_context[file_path]["mentions"] += 1
|
|
195
|
+
self.project_context[file_path]["last_mentioned"] = datetime.now().isoformat()
|
|
196
|
+
|
|
197
|
+
# Track technologies mentioned
|
|
198
|
+
technologies = ["python", "async", "json", "mcp", "terminal", "llm", "hook", "plugin"]
|
|
199
|
+
mentioned_tech = [tech for tech in technologies if tech in content.lower()]
|
|
200
|
+
if mentioned_tech:
|
|
201
|
+
if "technologies" not in self.project_context:
|
|
202
|
+
self.project_context["technologies"] = {}
|
|
203
|
+
for tech in mentioned_tech:
|
|
204
|
+
if tech not in self.project_context["technologies"]:
|
|
205
|
+
self.project_context["technologies"][tech] = 0
|
|
206
|
+
self.project_context["technologies"][tech] += 1
|
|
207
|
+
|
|
208
|
+
def _analyze_assistant_response(self, content: str):
|
|
209
|
+
"""Analyze assistant response to learn solution patterns."""
|
|
210
|
+
# Track successful solution patterns
|
|
211
|
+
solution_patterns = []
|
|
212
|
+
|
|
213
|
+
if "<terminal>" in content:
|
|
214
|
+
solution_patterns.append("uses_terminal_commands")
|
|
215
|
+
if "<tool" in content:
|
|
216
|
+
solution_patterns.append("uses_mcp_tools")
|
|
217
|
+
if "```" in content:
|
|
218
|
+
solution_patterns.append("provides_code_examples")
|
|
219
|
+
if len(content) > 500:
|
|
220
|
+
solution_patterns.append("provides_detailed_explanations")
|
|
221
|
+
if any(word in content.lower() for word in ["because", "therefore", "however", "first", "next", "then"]):
|
|
222
|
+
solution_patterns.append("explains_reasoning")
|
|
223
|
+
|
|
224
|
+
# Add to solution history
|
|
225
|
+
if solution_patterns:
|
|
226
|
+
solution_entry = {
|
|
227
|
+
"timestamp": datetime.now().isoformat(),
|
|
228
|
+
"patterns": solution_patterns,
|
|
229
|
+
"content_length": len(content),
|
|
230
|
+
"session_id": self.session_id
|
|
231
|
+
}
|
|
232
|
+
self.solution_history.append(solution_entry)
|
|
233
|
+
|
|
234
|
+
# Keep only last 100 solutions
|
|
235
|
+
if len(self.solution_history) > 100:
|
|
236
|
+
self.solution_history = self.solution_history[-100:]
|
|
237
|
+
|
|
238
|
+
def _detect_intent(self, content: str) -> str:
|
|
239
|
+
"""Detect user intent from message."""
|
|
240
|
+
content_lower = content.lower()
|
|
241
|
+
|
|
242
|
+
if any(word in content_lower for word in ["fix", "bug", "error", "broken"]):
|
|
243
|
+
return "debugging"
|
|
244
|
+
elif any(word in content_lower for word in ["create", "new", "add", "implement"]):
|
|
245
|
+
return "feature_development"
|
|
246
|
+
elif any(word in content_lower for word in ["refactor", "clean", "improve", "optimize"]):
|
|
247
|
+
return "refactoring"
|
|
248
|
+
elif any(word in content_lower for word in ["help", "how", "what", "explain"]):
|
|
249
|
+
return "seeking_help"
|
|
250
|
+
elif any(word in content_lower for word in ["test", "check", "verify"]):
|
|
251
|
+
return "testing"
|
|
252
|
+
else:
|
|
253
|
+
return "general_conversation"
|
|
254
|
+
|
|
255
|
+
def _get_session_context(self) -> Dict[str, Any]:
|
|
256
|
+
"""Get current session context."""
|
|
257
|
+
return {
|
|
258
|
+
"conversation_phase": self._determine_conversation_phase(),
|
|
259
|
+
"message_count": self.message_count,
|
|
260
|
+
"session_duration": (datetime.now() - self.conversation_start_time).total_seconds(),
|
|
261
|
+
"recurring_themes": list(set(self.conversation_themes[-10:])) if self.conversation_themes else [],
|
|
262
|
+
"active_files": list(self.file_interactions.keys())[-5:] if self.file_interactions else []
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
def _determine_conversation_phase(self) -> str:
|
|
266
|
+
"""Determine current phase of conversation."""
|
|
267
|
+
if self.message_count < 2:
|
|
268
|
+
return "initiation"
|
|
269
|
+
elif self.message_count < 10:
|
|
270
|
+
return "exploration"
|
|
271
|
+
elif self.message_count < 30:
|
|
272
|
+
return "development"
|
|
273
|
+
else:
|
|
274
|
+
return "deep_work"
|
|
275
|
+
|
|
276
|
+
def _get_project_awareness(self) -> Dict[str, Any]:
|
|
277
|
+
"""Get project awareness context."""
|
|
278
|
+
return {
|
|
279
|
+
"project_type": self.project_context.get("type", "python_terminal_app"),
|
|
280
|
+
"architecture": self.project_context.get("architecture", "plugin_based"),
|
|
281
|
+
"recent_changes": self.project_context.get("recent_changes", []),
|
|
282
|
+
"known_issues": self.project_context.get("known_issues", []),
|
|
283
|
+
"coding_standards": self.project_context.get("coding_standards", {})
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
def _get_related_sessions(self) -> List[str]:
|
|
287
|
+
"""Find related previous sessions."""
|
|
288
|
+
related = []
|
|
289
|
+
try:
|
|
290
|
+
# Look for sessions with similar themes
|
|
291
|
+
for session_file in self.conversations_dir.glob("session_*.jsonl"):
|
|
292
|
+
if session_file.name != self.session_file.name:
|
|
293
|
+
# Simple heuristic: sessions from same day
|
|
294
|
+
if session_file.name[:10] == self.session_file.name[:10]:
|
|
295
|
+
related.append(session_file.stem)
|
|
296
|
+
if len(related) >= 3:
|
|
297
|
+
break
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.warning(f"Failed to find related sessions: {e}")
|
|
300
|
+
return related
|
|
301
|
+
|
|
302
|
+
async def log_conversation_start(self):
|
|
303
|
+
"""Log conversation root structure with metadata."""
|
|
304
|
+
root_message = {
|
|
305
|
+
"type": "conversation_metadata",
|
|
306
|
+
"sessionId": self.session_id,
|
|
307
|
+
"startTime": self.conversation_start_time.isoformat() + "Z",
|
|
308
|
+
"endTime": None,
|
|
309
|
+
"uuid": str(uuid4()),
|
|
310
|
+
"timestamp": datetime.now().isoformat() + "Z",
|
|
311
|
+
"cwd": self._get_working_directory(),
|
|
312
|
+
"gitBranch": self._get_git_branch(),
|
|
313
|
+
"version": "1.0.0",
|
|
314
|
+
"conversation_context": {
|
|
315
|
+
"project_type": "python_terminal_app",
|
|
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
|
+
},
|
|
322
|
+
"session_goals": [],
|
|
323
|
+
"conversation_summary": ""
|
|
324
|
+
},
|
|
325
|
+
"kollabor_intelligence": {
|
|
326
|
+
"conversation_memory": {
|
|
327
|
+
"related_sessions": self._get_related_sessions(),
|
|
328
|
+
"recurring_themes": [],
|
|
329
|
+
"user_patterns": self.user_patterns[:10] if self.user_patterns else []
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
await self._append_to_jsonl(root_message)
|
|
335
|
+
logger.info(f"Logged conversation start: {self.session_id}")
|
|
336
|
+
|
|
337
|
+
async def log_user_message(self, content: str, parent_uuid: Optional[str] = None,
|
|
338
|
+
user_context: Optional[Dict] = None) -> str:
|
|
339
|
+
"""Log user message with intelligence features."""
|
|
340
|
+
message_uuid = str(uuid4())
|
|
341
|
+
|
|
342
|
+
message = {
|
|
343
|
+
"parentUuid": parent_uuid,
|
|
344
|
+
"isSidechain": False,
|
|
345
|
+
"userType": "external",
|
|
346
|
+
"cwd": self._get_working_directory(),
|
|
347
|
+
"sessionId": self.session_id,
|
|
348
|
+
"version": "1.0.0",
|
|
349
|
+
"gitBranch": self._get_git_branch(),
|
|
350
|
+
"type": "user",
|
|
351
|
+
"message": {
|
|
352
|
+
"role": "user",
|
|
353
|
+
"content": content
|
|
354
|
+
},
|
|
355
|
+
"uuid": message_uuid,
|
|
356
|
+
"timestamp": datetime.now().isoformat() + "Z",
|
|
357
|
+
"kollabor_intelligence": {
|
|
358
|
+
"user_context": user_context or self._analyze_user_context(content),
|
|
359
|
+
"session_context": self._get_session_context(),
|
|
360
|
+
"project_awareness": self._get_project_awareness()
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await self._append_to_jsonl(message)
|
|
365
|
+
|
|
366
|
+
# Update conversation themes
|
|
367
|
+
intent = message["kollabor_intelligence"]["user_context"].get("detected_intent")
|
|
368
|
+
if intent:
|
|
369
|
+
self.conversation_themes.append(intent)
|
|
370
|
+
|
|
371
|
+
# Save updated conversation memory
|
|
372
|
+
self._save_conversation_memory()
|
|
373
|
+
|
|
374
|
+
return message_uuid
|
|
375
|
+
|
|
376
|
+
async def log_assistant_message(self, content: str, parent_uuid: str,
|
|
377
|
+
usage_stats: Optional[Dict] = None) -> str:
|
|
378
|
+
"""Log assistant response with usage statistics."""
|
|
379
|
+
message_uuid = str(uuid4())
|
|
380
|
+
|
|
381
|
+
message = {
|
|
382
|
+
"parentUuid": parent_uuid,
|
|
383
|
+
"isSidechain": False,
|
|
384
|
+
"userType": "external",
|
|
385
|
+
"cwd": self._get_working_directory(),
|
|
386
|
+
"sessionId": self.session_id,
|
|
387
|
+
"version": "1.0.0",
|
|
388
|
+
"gitBranch": self._get_git_branch(),
|
|
389
|
+
"message": {
|
|
390
|
+
"id": f"msg_kollabor_{int(time.time())}",
|
|
391
|
+
"type": "message",
|
|
392
|
+
"role": "assistant",
|
|
393
|
+
"model": "qwen/qwen3-4b",
|
|
394
|
+
"content": [
|
|
395
|
+
{
|
|
396
|
+
"type": "text",
|
|
397
|
+
"text": content
|
|
398
|
+
}
|
|
399
|
+
],
|
|
400
|
+
"stop_reason": None,
|
|
401
|
+
"stop_sequence": None,
|
|
402
|
+
"usage": usage_stats or {}
|
|
403
|
+
},
|
|
404
|
+
"requestId": f"req_kollabor_{int(time.time())}",
|
|
405
|
+
"type": "assistant",
|
|
406
|
+
"uuid": message_uuid,
|
|
407
|
+
"timestamp": datetime.now().isoformat() + "Z"
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
await self._append_to_jsonl(message)
|
|
411
|
+
|
|
412
|
+
# Analyze assistant response for learning
|
|
413
|
+
self._analyze_assistant_response(content)
|
|
414
|
+
|
|
415
|
+
# Save updated conversation memory
|
|
416
|
+
self._save_conversation_memory()
|
|
417
|
+
|
|
418
|
+
return message_uuid
|
|
419
|
+
|
|
420
|
+
async def log_system_message(self, content: str, parent_uuid: str,
|
|
421
|
+
subtype: str = "informational",
|
|
422
|
+
tool_use_id: Optional[str] = None) -> str:
|
|
423
|
+
"""Log system messages including hook outputs and tool calls."""
|
|
424
|
+
message_uuid = str(uuid4())
|
|
425
|
+
|
|
426
|
+
message = {
|
|
427
|
+
"parentUuid": parent_uuid,
|
|
428
|
+
"isSidechain": False,
|
|
429
|
+
"userType": "external",
|
|
430
|
+
"cwd": self._get_working_directory(),
|
|
431
|
+
"sessionId": self.session_id,
|
|
432
|
+
"version": "1.0.0",
|
|
433
|
+
"gitBranch": self._get_git_branch(),
|
|
434
|
+
"type": "system",
|
|
435
|
+
"subtype": subtype,
|
|
436
|
+
"content": content,
|
|
437
|
+
"isMeta": False,
|
|
438
|
+
"timestamp": datetime.now().isoformat() + "Z",
|
|
439
|
+
"uuid": message_uuid,
|
|
440
|
+
"level": "info"
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if tool_use_id:
|
|
444
|
+
message["toolUseID"] = tool_use_id
|
|
445
|
+
|
|
446
|
+
await self._append_to_jsonl(message)
|
|
447
|
+
return message_uuid
|
|
448
|
+
|
|
449
|
+
async def log_conversation_end(self):
|
|
450
|
+
"""Log conversation end and save memory."""
|
|
451
|
+
# Update the root message with end time
|
|
452
|
+
# Note: In production, we'd update the first line of JSONL
|
|
453
|
+
# For now, append an end marker
|
|
454
|
+
end_message = {
|
|
455
|
+
"type": "conversation_end",
|
|
456
|
+
"sessionId": self.session_id,
|
|
457
|
+
"endTime": datetime.now().isoformat() + "Z",
|
|
458
|
+
"uuid": str(uuid4()),
|
|
459
|
+
"timestamp": datetime.now().isoformat() + "Z",
|
|
460
|
+
"summary": {
|
|
461
|
+
"total_messages": self.message_count,
|
|
462
|
+
"duration": (datetime.now() - self.conversation_start_time).total_seconds(),
|
|
463
|
+
"themes": list(set(self.conversation_themes)) if self.conversation_themes else [],
|
|
464
|
+
"files_modified": list(self.file_interactions.keys()) if self.file_interactions else []
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
await self._append_to_jsonl(end_message)
|
|
469
|
+
|
|
470
|
+
# Save conversation memory
|
|
471
|
+
self._save_conversation_memory()
|
|
472
|
+
|
|
473
|
+
logger.info(f"Logged conversation end: {self.session_id}")
|