claude-self-reflect 3.2.4 → 3.3.0
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.
- package/.claude/agents/claude-self-reflect-test.md +595 -528
- package/.claude/agents/reflection-specialist.md +59 -3
- package/README.md +14 -5
- package/mcp-server/run-mcp.sh +49 -5
- package/mcp-server/src/app_context.py +64 -0
- package/mcp-server/src/config.py +57 -0
- package/mcp-server/src/connection_pool.py +286 -0
- package/mcp-server/src/decay_manager.py +106 -0
- package/mcp-server/src/embedding_manager.py +64 -40
- package/mcp-server/src/embeddings_old.py +141 -0
- package/mcp-server/src/models.py +64 -0
- package/mcp-server/src/parallel_search.py +371 -0
- package/mcp-server/src/project_resolver.py +5 -0
- package/mcp-server/src/reflection_tools.py +206 -0
- package/mcp-server/src/rich_formatting.py +196 -0
- package/mcp-server/src/search_tools.py +826 -0
- package/mcp-server/src/server.py +127 -1720
- package/mcp-server/src/temporal_design.py +132 -0
- package/mcp-server/src/temporal_tools.py +597 -0
- package/mcp-server/src/temporal_utils.py +384 -0
- package/mcp-server/src/utils.py +150 -67
- package/package.json +10 -1
- package/scripts/add-timestamp-indexes.py +134 -0
- package/scripts/check-collections.py +29 -0
- package/scripts/debug-august-parsing.py +76 -0
- package/scripts/debug-import-single.py +91 -0
- package/scripts/debug-project-resolver.py +82 -0
- package/scripts/debug-temporal-tools.py +135 -0
- package/scripts/delta-metadata-update.py +547 -0
- package/scripts/import-conversations-unified.py +53 -2
- package/scripts/precompact-hook.sh +33 -0
- package/scripts/streaming-watcher.py +1443 -0
- package/scripts/utils.py +39 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
"""Decay calculation manager for Claude Self-Reflect MCP server."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
|
+
from typing import List, Tuple, Optional
|
|
6
|
+
try:
|
|
7
|
+
from .config import (
|
|
8
|
+
USE_DECAY,
|
|
9
|
+
DECAY_SCALE_DAYS,
|
|
10
|
+
DECAY_WEIGHT,
|
|
11
|
+
USE_NATIVE_DECAY,
|
|
12
|
+
logger
|
|
13
|
+
)
|
|
14
|
+
except ImportError:
|
|
15
|
+
# Fallback for direct execution
|
|
16
|
+
import os
|
|
17
|
+
import logging
|
|
18
|
+
USE_DECAY = os.getenv('USE_DECAY', 'false').lower() == 'true'
|
|
19
|
+
DECAY_SCALE_DAYS = float(os.getenv('DECAY_SCALE_DAYS', '90'))
|
|
20
|
+
DECAY_WEIGHT = float(os.getenv('DECAY_WEIGHT', '0.3'))
|
|
21
|
+
USE_NATIVE_DECAY = os.getenv('USE_NATIVE_DECAY', 'false').lower() == 'true'
|
|
22
|
+
logger = logging.getLogger(__name__)
|
|
23
|
+
|
|
24
|
+
class DecayManager:
|
|
25
|
+
"""Manages memory decay calculations for search results."""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
self.scale_ms = DECAY_SCALE_DAYS * 24 * 60 * 60 * 1000
|
|
29
|
+
self.weight = DECAY_WEIGHT
|
|
30
|
+
self.use_decay = USE_DECAY
|
|
31
|
+
self.use_native = USE_NATIVE_DECAY
|
|
32
|
+
|
|
33
|
+
def calculate_decay_score(
|
|
34
|
+
self,
|
|
35
|
+
base_score: float,
|
|
36
|
+
timestamp: str
|
|
37
|
+
) -> float:
|
|
38
|
+
"""Calculate decayed score for a single result."""
|
|
39
|
+
if not self.use_decay:
|
|
40
|
+
return base_score
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
# Parse timestamp
|
|
44
|
+
if timestamp.endswith('Z'):
|
|
45
|
+
timestamp = timestamp.replace('Z', '+00:00')
|
|
46
|
+
|
|
47
|
+
result_time = datetime.fromisoformat(timestamp)
|
|
48
|
+
if result_time.tzinfo is None:
|
|
49
|
+
result_time = result_time.replace(tzinfo=timezone.utc)
|
|
50
|
+
|
|
51
|
+
# Calculate age
|
|
52
|
+
now = datetime.now(timezone.utc)
|
|
53
|
+
age_ms = (now - result_time).total_seconds() * 1000
|
|
54
|
+
|
|
55
|
+
# Calculate decay factor using half-life formula
|
|
56
|
+
# decay = exp(-ln(2) * age / half_life)
|
|
57
|
+
decay_factor = math.exp(-0.693147 * age_ms / self.scale_ms)
|
|
58
|
+
|
|
59
|
+
# Apply decay with weight
|
|
60
|
+
final_score = base_score * (1 - self.weight) + base_score * self.weight * decay_factor
|
|
61
|
+
|
|
62
|
+
return final_score
|
|
63
|
+
|
|
64
|
+
except Exception as e:
|
|
65
|
+
logger.error(f"Failed to calculate decay: {e}")
|
|
66
|
+
return base_score
|
|
67
|
+
|
|
68
|
+
def apply_decay_to_results(
|
|
69
|
+
self,
|
|
70
|
+
results: List[Tuple[float, str, dict]]
|
|
71
|
+
) -> List[Tuple[float, str, dict]]:
|
|
72
|
+
"""Apply decay to a list of results and re-sort."""
|
|
73
|
+
if not self.use_decay:
|
|
74
|
+
return results
|
|
75
|
+
|
|
76
|
+
decayed_results = []
|
|
77
|
+
for score, id_str, payload in results:
|
|
78
|
+
timestamp = payload.get('timestamp', datetime.now().isoformat())
|
|
79
|
+
decayed_score = self.calculate_decay_score(score, timestamp)
|
|
80
|
+
decayed_results.append((decayed_score, id_str, payload))
|
|
81
|
+
|
|
82
|
+
# Re-sort by decayed score
|
|
83
|
+
decayed_results.sort(key=lambda x: x[0], reverse=True)
|
|
84
|
+
|
|
85
|
+
return decayed_results
|
|
86
|
+
|
|
87
|
+
def get_native_decay_config(self) -> Optional[dict]:
|
|
88
|
+
"""Get configuration for native Qdrant decay."""
|
|
89
|
+
if not self.use_native:
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
'scale_seconds': self.scale_ms / 1000,
|
|
94
|
+
'weight': self.weight,
|
|
95
|
+
'midpoint': 0.5 # Half-life semantics
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
def should_use_decay(self, explicit_setting: Optional[int] = None) -> bool:
|
|
99
|
+
"""Determine if decay should be used for a query."""
|
|
100
|
+
if explicit_setting is not None:
|
|
101
|
+
if explicit_setting == 1:
|
|
102
|
+
return True
|
|
103
|
+
elif explicit_setting == 0:
|
|
104
|
+
return False
|
|
105
|
+
|
|
106
|
+
return self.use_decay
|
|
@@ -16,16 +16,16 @@ class EmbeddingManager:
|
|
|
16
16
|
"""Manages embedding models with proper cache and lock handling."""
|
|
17
17
|
|
|
18
18
|
def __init__(self):
|
|
19
|
-
self.
|
|
20
|
-
self.model_type = None # 'local' or 'voyage'
|
|
19
|
+
self.local_model = None
|
|
21
20
|
self.voyage_client = None
|
|
22
|
-
|
|
21
|
+
self.model_type = None # Default model type ('local' or 'voyage')
|
|
22
|
+
|
|
23
23
|
# Configuration
|
|
24
24
|
self.prefer_local = os.getenv('PREFER_LOCAL_EMBEDDINGS', 'true').lower() == 'true'
|
|
25
25
|
self.voyage_key = os.getenv('VOYAGE_KEY') or os.getenv('VOYAGE_KEY-2')
|
|
26
26
|
self.embedding_model = os.getenv('EMBEDDING_MODEL', 'sentence-transformers/all-MiniLM-L6-v2')
|
|
27
27
|
self.download_timeout = int(os.getenv('FASTEMBED_DOWNLOAD_TIMEOUT', '30'))
|
|
28
|
-
|
|
28
|
+
|
|
29
29
|
# Set cache directory to our controlled location
|
|
30
30
|
self.cache_dir = Path(__file__).parent.parent / '.fastembed-cache'
|
|
31
31
|
|
|
@@ -50,27 +50,35 @@ class EmbeddingManager:
|
|
|
50
50
|
logger.warning(f"Error cleaning locks: {e}")
|
|
51
51
|
|
|
52
52
|
def initialize(self) -> bool:
|
|
53
|
-
"""Initialize embedding
|
|
54
|
-
logger.info("Initializing embedding manager...")
|
|
55
|
-
|
|
53
|
+
"""Initialize BOTH embedding models to support mixed collections."""
|
|
54
|
+
logger.info("Initializing embedding manager for dual-mode support...")
|
|
55
|
+
|
|
56
56
|
# Clean up any stale locks first
|
|
57
57
|
self._clean_stale_locks()
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
58
|
+
|
|
59
|
+
# Initialize both models for mixed collection support
|
|
60
|
+
local_success = self._try_initialize_local()
|
|
61
|
+
voyage_success = False
|
|
62
|
+
|
|
63
|
+
if self.voyage_key:
|
|
64
|
+
voyage_success = self._try_initialize_voyage()
|
|
65
|
+
|
|
66
|
+
# Set default model type based on preference and availability
|
|
67
|
+
if self.prefer_local and local_success:
|
|
68
|
+
self.model_type = 'local'
|
|
69
|
+
logger.info("Default model set to LOCAL embeddings")
|
|
70
|
+
elif voyage_success:
|
|
71
|
+
self.model_type = 'voyage'
|
|
72
|
+
logger.info("Default model set to VOYAGE embeddings")
|
|
73
|
+
elif local_success:
|
|
74
|
+
self.model_type = 'local'
|
|
75
|
+
logger.info("Default model set to LOCAL embeddings (fallback)")
|
|
65
76
|
else:
|
|
66
|
-
|
|
67
|
-
if self.voyage_key and self._try_initialize_voyage():
|
|
68
|
-
return True
|
|
69
|
-
logger.warning("Voyage AI failed, trying local as fallback...")
|
|
70
|
-
if self._try_initialize_local():
|
|
71
|
-
return True
|
|
72
|
-
logger.error("Both Voyage AI and local embeddings failed")
|
|
77
|
+
logger.error("Failed to initialize any embedding model")
|
|
73
78
|
return False
|
|
79
|
+
|
|
80
|
+
logger.info(f"Embedding models available - Local: {local_success}, Voyage: {voyage_success}")
|
|
81
|
+
return True
|
|
74
82
|
|
|
75
83
|
def _try_initialize_local(self) -> bool:
|
|
76
84
|
"""Try to initialize local FastEmbed model with timeout and optimizations."""
|
|
@@ -119,11 +127,10 @@ class EmbeddingManager:
|
|
|
119
127
|
from fastembed import TextEmbedding
|
|
120
128
|
# Initialize with optimized settings
|
|
121
129
|
# Note: FastEmbed uses these environment variables internally
|
|
122
|
-
self.
|
|
130
|
+
self.local_model = TextEmbedding(
|
|
123
131
|
model_name=self.embedding_model,
|
|
124
132
|
threads=1 # Single thread per worker to prevent over-subscription
|
|
125
133
|
)
|
|
126
|
-
self.model_type = 'local'
|
|
127
134
|
success = True
|
|
128
135
|
logger.info(f"Successfully initialized local model: {self.embedding_model} with single-thread mode")
|
|
129
136
|
except Exception as e:
|
|
@@ -177,39 +184,48 @@ class EmbeddingManager:
|
|
|
177
184
|
logger.error(f"Failed to initialize Voyage AI: {e}")
|
|
178
185
|
return False
|
|
179
186
|
|
|
180
|
-
def embed(self, texts: Union[str, List[str]], input_type: str = "document") -> Optional[List[List[float]]]:
|
|
181
|
-
"""Generate embeddings using the
|
|
182
|
-
|
|
183
|
-
|
|
187
|
+
def embed(self, texts: Union[str, List[str]], input_type: str = "document", force_type: str = None) -> Optional[List[List[float]]]:
|
|
188
|
+
"""Generate embeddings using the specified or default model."""
|
|
189
|
+
# Determine which model to use
|
|
190
|
+
use_type = force_type if force_type else self.model_type
|
|
191
|
+
logger.debug(f"Embedding with: force_type={force_type}, self.model_type={self.model_type}, use_type={use_type}")
|
|
192
|
+
|
|
193
|
+
if use_type == 'local' and not self.local_model:
|
|
194
|
+
logger.error("Local model not initialized")
|
|
184
195
|
return None
|
|
185
|
-
|
|
196
|
+
elif use_type == 'voyage' and not self.voyage_client:
|
|
197
|
+
logger.error("Voyage client not initialized")
|
|
198
|
+
return None
|
|
199
|
+
|
|
186
200
|
# Ensure texts is a list
|
|
187
201
|
if isinstance(texts, str):
|
|
188
202
|
texts = [texts]
|
|
189
|
-
|
|
203
|
+
|
|
190
204
|
try:
|
|
191
|
-
if
|
|
205
|
+
if use_type == 'local':
|
|
192
206
|
# FastEmbed returns a generator, convert to list
|
|
193
|
-
embeddings = list(self.
|
|
207
|
+
embeddings = list(self.local_model.embed(texts))
|
|
194
208
|
return [emb.tolist() for emb in embeddings]
|
|
195
|
-
|
|
196
|
-
elif
|
|
209
|
+
|
|
210
|
+
elif use_type == 'voyage':
|
|
211
|
+
# Always use voyage-3 for consistency with collection dimensions (1024)
|
|
197
212
|
result = self.voyage_client.embed(
|
|
198
213
|
texts=texts,
|
|
199
|
-
model="voyage-3
|
|
214
|
+
model="voyage-3",
|
|
200
215
|
input_type=input_type
|
|
201
216
|
)
|
|
202
217
|
return result.embeddings
|
|
203
|
-
|
|
218
|
+
|
|
204
219
|
except Exception as e:
|
|
205
|
-
logger.error(f"Error generating embeddings: {e}")
|
|
220
|
+
logger.error(f"Error generating embeddings with {use_type}: {e}")
|
|
206
221
|
return None
|
|
207
222
|
|
|
208
|
-
def get_vector_dimension(self) -> int:
|
|
209
|
-
"""Get the dimension of embeddings."""
|
|
210
|
-
if self.model_type
|
|
223
|
+
def get_vector_dimension(self, force_type: str = None) -> int:
|
|
224
|
+
"""Get the dimension of embeddings for a specific type."""
|
|
225
|
+
use_type = force_type if force_type else self.model_type
|
|
226
|
+
if use_type == 'local':
|
|
211
227
|
return 384 # all-MiniLM-L6-v2 dimension
|
|
212
|
-
elif
|
|
228
|
+
elif use_type == 'voyage':
|
|
213
229
|
return 1024 # voyage-3 dimension
|
|
214
230
|
return 0
|
|
215
231
|
|
|
@@ -222,6 +238,14 @@ class EmbeddingManager:
|
|
|
222
238
|
'prefer_local': self.prefer_local,
|
|
223
239
|
'has_voyage_key': bool(self.voyage_key)
|
|
224
240
|
}
|
|
241
|
+
|
|
242
|
+
async def generate_embedding(self, text: str, force_type: str = None) -> Optional[List[float]]:
|
|
243
|
+
"""Generate embedding for a single text (async wrapper for compatibility)."""
|
|
244
|
+
# Use the force_type if specified, otherwise use default
|
|
245
|
+
result = self.embed(text, input_type="query", force_type=force_type)
|
|
246
|
+
if result and len(result) > 0:
|
|
247
|
+
return result[0]
|
|
248
|
+
return None
|
|
225
249
|
|
|
226
250
|
|
|
227
251
|
# Global instance
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
"""Embedding generation module for Claude Self-Reflect MCP server."""
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import voyageai
|
|
5
|
+
from typing import Dict, List, Optional, Any
|
|
6
|
+
from fastembed import TextEmbedding
|
|
7
|
+
from config import (
|
|
8
|
+
VOYAGE_API_KEY,
|
|
9
|
+
VOYAGE_MODEL,
|
|
10
|
+
LOCAL_MODEL,
|
|
11
|
+
PREFER_LOCAL_EMBEDDINGS,
|
|
12
|
+
logger
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
class EmbeddingManager:
|
|
16
|
+
"""Manages embedding generation for both local and Voyage AI models."""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.local_model = None
|
|
20
|
+
self.voyage_client = None
|
|
21
|
+
self.embedding_cache = {}
|
|
22
|
+
|
|
23
|
+
# Initialize based on preference
|
|
24
|
+
if PREFER_LOCAL_EMBEDDINGS or not VOYAGE_API_KEY:
|
|
25
|
+
self._init_local_model()
|
|
26
|
+
|
|
27
|
+
if VOYAGE_API_KEY:
|
|
28
|
+
self._init_voyage_client()
|
|
29
|
+
|
|
30
|
+
def _init_local_model(self):
|
|
31
|
+
"""Initialize local FastEmbed model."""
|
|
32
|
+
try:
|
|
33
|
+
self.local_model = TextEmbedding(
|
|
34
|
+
model_name=LOCAL_MODEL,
|
|
35
|
+
cache_dir=str(os.path.expanduser("~/.cache/fastembed"))
|
|
36
|
+
)
|
|
37
|
+
logger.info(f"Initialized local embedding model: {LOCAL_MODEL}")
|
|
38
|
+
except Exception as e:
|
|
39
|
+
logger.error(f"Failed to initialize local model: {e}")
|
|
40
|
+
|
|
41
|
+
def _init_voyage_client(self):
|
|
42
|
+
"""Initialize Voyage AI client."""
|
|
43
|
+
try:
|
|
44
|
+
self.voyage_client = voyageai.Client(api_key=VOYAGE_API_KEY)
|
|
45
|
+
logger.info("Initialized Voyage AI client")
|
|
46
|
+
except Exception as e:
|
|
47
|
+
logger.error(f"Failed to initialize Voyage client: {e}")
|
|
48
|
+
|
|
49
|
+
async def generate_embedding(
|
|
50
|
+
self,
|
|
51
|
+
text: str,
|
|
52
|
+
embedding_type: Optional[str] = None
|
|
53
|
+
) -> Optional[List[float]]:
|
|
54
|
+
"""Generate embedding for text using specified or default model."""
|
|
55
|
+
|
|
56
|
+
# Use cache if available
|
|
57
|
+
cache_key = f"{embedding_type or 'default'}:{text[:100]}"
|
|
58
|
+
if cache_key in self.embedding_cache:
|
|
59
|
+
return self.embedding_cache[cache_key]
|
|
60
|
+
|
|
61
|
+
# Determine which model to use
|
|
62
|
+
use_local = True
|
|
63
|
+
if embedding_type:
|
|
64
|
+
use_local = 'local' in embedding_type
|
|
65
|
+
elif not PREFER_LOCAL_EMBEDDINGS and self.voyage_client:
|
|
66
|
+
use_local = False
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
if use_local and self.local_model:
|
|
70
|
+
# Generate local embedding
|
|
71
|
+
embeddings = list(self.local_model.embed([text]))
|
|
72
|
+
if embeddings:
|
|
73
|
+
embedding = list(embeddings[0])
|
|
74
|
+
self.embedding_cache[cache_key] = embedding
|
|
75
|
+
return embedding
|
|
76
|
+
|
|
77
|
+
elif self.voyage_client:
|
|
78
|
+
# Generate Voyage embedding
|
|
79
|
+
result = self.voyage_client.embed(
|
|
80
|
+
[text],
|
|
81
|
+
model=VOYAGE_MODEL,
|
|
82
|
+
input_type="document"
|
|
83
|
+
)
|
|
84
|
+
if result.embeddings:
|
|
85
|
+
embedding = result.embeddings[0]
|
|
86
|
+
self.embedding_cache[cache_key] = embedding
|
|
87
|
+
return embedding
|
|
88
|
+
|
|
89
|
+
except Exception as e:
|
|
90
|
+
logger.error(f"Failed to generate embedding: {e}")
|
|
91
|
+
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
async def generate_embeddings_batch(
|
|
95
|
+
self,
|
|
96
|
+
texts: List[str],
|
|
97
|
+
embedding_type: Optional[str] = None
|
|
98
|
+
) -> Dict[str, List[float]]:
|
|
99
|
+
"""Generate embeddings for multiple texts efficiently."""
|
|
100
|
+
results = {}
|
|
101
|
+
|
|
102
|
+
# Determine which model to use
|
|
103
|
+
use_local = True
|
|
104
|
+
if embedding_type:
|
|
105
|
+
use_local = 'local' in embedding_type
|
|
106
|
+
elif not PREFER_LOCAL_EMBEDDINGS and self.voyage_client:
|
|
107
|
+
use_local = False
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
if use_local and self.local_model:
|
|
111
|
+
# Batch process with local model
|
|
112
|
+
embeddings = list(self.local_model.embed(texts))
|
|
113
|
+
for text, embedding in zip(texts, embeddings):
|
|
114
|
+
results[text] = list(embedding)
|
|
115
|
+
|
|
116
|
+
elif self.voyage_client:
|
|
117
|
+
# Batch process with Voyage
|
|
118
|
+
result = self.voyage_client.embed(
|
|
119
|
+
texts,
|
|
120
|
+
model=VOYAGE_MODEL,
|
|
121
|
+
input_type="document"
|
|
122
|
+
)
|
|
123
|
+
for text, embedding in zip(texts, result.embeddings):
|
|
124
|
+
results[text] = embedding
|
|
125
|
+
|
|
126
|
+
except Exception as e:
|
|
127
|
+
logger.error(f"Failed to generate batch embeddings: {e}")
|
|
128
|
+
|
|
129
|
+
return results
|
|
130
|
+
|
|
131
|
+
def get_embedding_dimension(self, embedding_type: str = "local") -> int:
|
|
132
|
+
"""Get the dimension of embeddings for a given type."""
|
|
133
|
+
if "local" in embedding_type:
|
|
134
|
+
return 384 # all-MiniLM-L6-v2 dimension
|
|
135
|
+
else:
|
|
136
|
+
return 1024 # voyage-3-lite dimension
|
|
137
|
+
|
|
138
|
+
def clear_cache(self):
|
|
139
|
+
"""Clear the embedding cache."""
|
|
140
|
+
self.embedding_cache.clear()
|
|
141
|
+
logger.info("Cleared embedding cache")
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""Pydantic models for Claude Self-Reflect MCP server."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List, Dict, Any, Set
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pydantic import BaseModel, Field
|
|
6
|
+
|
|
7
|
+
class SearchResult(BaseModel):
|
|
8
|
+
"""Model for search results."""
|
|
9
|
+
id: str
|
|
10
|
+
score: float
|
|
11
|
+
timestamp: str
|
|
12
|
+
role: str
|
|
13
|
+
excerpt: str
|
|
14
|
+
project_name: str
|
|
15
|
+
conversation_id: Optional[str] = None
|
|
16
|
+
base_conversation_id: Optional[str] = None
|
|
17
|
+
collection_name: str
|
|
18
|
+
raw_payload: Optional[Dict[str, Any]] = None
|
|
19
|
+
code_patterns: Optional[Dict[str, List[str]]] = None
|
|
20
|
+
files_analyzed: Optional[List[str]] = None
|
|
21
|
+
files_edited: Optional[List[str]] = None
|
|
22
|
+
tools_used: Optional[List[str]] = None
|
|
23
|
+
concepts: Optional[List[str]] = None
|
|
24
|
+
|
|
25
|
+
class ConversationGroup(BaseModel):
|
|
26
|
+
"""Model for grouped conversations."""
|
|
27
|
+
conversation_id: str
|
|
28
|
+
base_conversation_id: str
|
|
29
|
+
timestamp: datetime
|
|
30
|
+
message_count: int
|
|
31
|
+
excerpts: List[str]
|
|
32
|
+
files: Set[str] = Field(default_factory=set)
|
|
33
|
+
tools: Set[str] = Field(default_factory=set)
|
|
34
|
+
concepts: Set[str] = Field(default_factory=set)
|
|
35
|
+
|
|
36
|
+
class WorkSession(BaseModel):
|
|
37
|
+
"""Model for work sessions."""
|
|
38
|
+
start_time: datetime
|
|
39
|
+
end_time: datetime
|
|
40
|
+
conversations: List[ConversationGroup]
|
|
41
|
+
total_messages: int
|
|
42
|
+
files_touched: Set[str] = Field(default_factory=set)
|
|
43
|
+
tools_used: Set[str] = Field(default_factory=set)
|
|
44
|
+
concepts: Set[str] = Field(default_factory=set)
|
|
45
|
+
|
|
46
|
+
class ActivityStats(BaseModel):
|
|
47
|
+
"""Model for activity statistics."""
|
|
48
|
+
total_conversations: int
|
|
49
|
+
total_messages: int
|
|
50
|
+
unique_files: int
|
|
51
|
+
unique_tools: int
|
|
52
|
+
peak_hour: Optional[str] = None
|
|
53
|
+
peak_day: Optional[str] = None
|
|
54
|
+
|
|
55
|
+
class TimelineEntry(BaseModel):
|
|
56
|
+
"""Model for timeline entries."""
|
|
57
|
+
period: str
|
|
58
|
+
start_time: datetime
|
|
59
|
+
end_time: datetime
|
|
60
|
+
conversation_count: int
|
|
61
|
+
message_count: int
|
|
62
|
+
files: Set[str] = Field(default_factory=set)
|
|
63
|
+
tools: Set[str] = Field(default_factory=set)
|
|
64
|
+
concepts: Set[str] = Field(default_factory=set)
|