omni-cortex 1.17.1__py3-none-any.whl → 1.17.3__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.
- omni_cortex/__init__.py +3 -0
- omni_cortex/_bundled/dashboard/backend/.env.example +12 -0
- omni_cortex/_bundled/dashboard/backend/backfill_summaries.py +280 -0
- omni_cortex/_bundled/dashboard/backend/chat_service.py +631 -0
- omni_cortex/_bundled/dashboard/backend/database.py +1773 -0
- omni_cortex/_bundled/dashboard/backend/image_service.py +552 -0
- omni_cortex/_bundled/dashboard/backend/logging_config.py +122 -0
- omni_cortex/_bundled/dashboard/backend/main.py +1888 -0
- omni_cortex/_bundled/dashboard/backend/models.py +472 -0
- omni_cortex/_bundled/dashboard/backend/project_config.py +170 -0
- omni_cortex/_bundled/dashboard/backend/project_scanner.py +164 -0
- omni_cortex/_bundled/dashboard/backend/prompt_security.py +111 -0
- omni_cortex/_bundled/dashboard/backend/pyproject.toml +23 -0
- omni_cortex/_bundled/dashboard/backend/security.py +104 -0
- omni_cortex/_bundled/dashboard/backend/uv.lock +1110 -0
- omni_cortex/_bundled/dashboard/backend/websocket_manager.py +104 -0
- omni_cortex/_bundled/hooks/post_tool_use.py +497 -0
- omni_cortex/_bundled/hooks/pre_tool_use.py +277 -0
- omni_cortex/_bundled/hooks/session_utils.py +186 -0
- omni_cortex/_bundled/hooks/stop.py +219 -0
- omni_cortex/_bundled/hooks/subagent_stop.py +120 -0
- omni_cortex/_bundled/hooks/user_prompt.py +220 -0
- omni_cortex/categorization/__init__.py +9 -0
- omni_cortex/categorization/auto_tags.py +166 -0
- omni_cortex/categorization/auto_type.py +165 -0
- omni_cortex/config.py +141 -0
- omni_cortex/dashboard.py +238 -0
- omni_cortex/database/__init__.py +24 -0
- omni_cortex/database/connection.py +137 -0
- omni_cortex/database/migrations.py +210 -0
- omni_cortex/database/schema.py +212 -0
- omni_cortex/database/sync.py +421 -0
- omni_cortex/decay/__init__.py +7 -0
- omni_cortex/decay/importance.py +147 -0
- omni_cortex/embeddings/__init__.py +35 -0
- omni_cortex/embeddings/local.py +442 -0
- omni_cortex/models/__init__.py +20 -0
- omni_cortex/models/activity.py +265 -0
- omni_cortex/models/agent.py +144 -0
- omni_cortex/models/memory.py +395 -0
- omni_cortex/models/relationship.py +206 -0
- omni_cortex/models/session.py +290 -0
- omni_cortex/resources/__init__.py +1 -0
- omni_cortex/search/__init__.py +22 -0
- omni_cortex/search/hybrid.py +197 -0
- omni_cortex/search/keyword.py +204 -0
- omni_cortex/search/ranking.py +127 -0
- omni_cortex/search/semantic.py +232 -0
- omni_cortex/server.py +360 -0
- omni_cortex/setup.py +284 -0
- omni_cortex/tools/__init__.py +13 -0
- omni_cortex/tools/activities.py +453 -0
- omni_cortex/tools/memories.py +536 -0
- omni_cortex/tools/sessions.py +311 -0
- omni_cortex/tools/utilities.py +477 -0
- omni_cortex/utils/__init__.py +13 -0
- omni_cortex/utils/formatting.py +282 -0
- omni_cortex/utils/ids.py +72 -0
- omni_cortex/utils/timestamps.py +129 -0
- omni_cortex/utils/truncation.py +111 -0
- {omni_cortex-1.17.1.dist-info → omni_cortex-1.17.3.dist-info}/METADATA +1 -1
- omni_cortex-1.17.3.dist-info/RECORD +86 -0
- omni_cortex-1.17.1.dist-info/RECORD +0 -26
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/.env.example +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/backfill_summaries.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/chat_service.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/database.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/image_service.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/logging_config.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/main.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/models.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/project_config.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/project_scanner.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/prompt_security.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/pyproject.toml +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/security.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/uv.lock +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/dashboard/backend/websocket_manager.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/post_tool_use.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/pre_tool_use.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/session_utils.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/stop.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/subagent_stop.py +0 -0
- {omni_cortex-1.17.1.data → omni_cortex-1.17.3.data}/data/share/omni-cortex/hooks/user_prompt.py +0 -0
- {omni_cortex-1.17.1.dist-info → omni_cortex-1.17.3.dist-info}/WHEEL +0 -0
- {omni_cortex-1.17.1.dist-info → omni_cortex-1.17.3.dist-info}/entry_points.txt +0 -0
- {omni_cortex-1.17.1.dist-info → omni_cortex-1.17.3.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
"""Pydantic models for the dashboard API."""
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from pydantic import BaseModel, Field
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ProjectInfo(BaseModel):
|
|
10
|
+
"""Information about a project with omni-cortex database."""
|
|
11
|
+
|
|
12
|
+
name: str
|
|
13
|
+
path: str
|
|
14
|
+
db_path: str
|
|
15
|
+
last_modified: Optional[datetime] = None
|
|
16
|
+
memory_count: int = 0
|
|
17
|
+
is_global: bool = False
|
|
18
|
+
is_favorite: bool = False
|
|
19
|
+
is_registered: bool = False
|
|
20
|
+
display_name: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ScanDirectory(BaseModel):
|
|
24
|
+
"""A directory being scanned for projects."""
|
|
25
|
+
|
|
26
|
+
path: str
|
|
27
|
+
project_count: int = 0
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class ProjectRegistration(BaseModel):
|
|
31
|
+
"""Request to register a project."""
|
|
32
|
+
|
|
33
|
+
path: str
|
|
34
|
+
display_name: Optional[str] = None
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ProjectConfigResponse(BaseModel):
|
|
38
|
+
"""Response with project configuration."""
|
|
39
|
+
|
|
40
|
+
scan_directories: list[str]
|
|
41
|
+
registered_count: int
|
|
42
|
+
favorites_count: int
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class Memory(BaseModel):
|
|
46
|
+
"""Memory record from the database."""
|
|
47
|
+
|
|
48
|
+
id: str
|
|
49
|
+
content: str
|
|
50
|
+
context: Optional[str] = None
|
|
51
|
+
memory_type: str = Field(default="other", validation_alias="type")
|
|
52
|
+
status: str = "fresh"
|
|
53
|
+
importance_score: int = 50
|
|
54
|
+
access_count: int = 0
|
|
55
|
+
created_at: datetime
|
|
56
|
+
last_accessed: Optional[datetime] = None
|
|
57
|
+
tags: list[str] = []
|
|
58
|
+
|
|
59
|
+
model_config = {"populate_by_name": True}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
class MemoryStats(BaseModel):
|
|
63
|
+
"""Statistics about memories in a database."""
|
|
64
|
+
|
|
65
|
+
total_count: int
|
|
66
|
+
by_type: dict[str, int]
|
|
67
|
+
by_status: dict[str, int]
|
|
68
|
+
avg_importance: float
|
|
69
|
+
total_access_count: int
|
|
70
|
+
tags: list[dict[str, int | str]]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class FilterParams(BaseModel):
|
|
74
|
+
"""Query filter parameters."""
|
|
75
|
+
|
|
76
|
+
memory_type: Optional[str] = None
|
|
77
|
+
status: Optional[str] = None
|
|
78
|
+
tags: Optional[list[str]] = None
|
|
79
|
+
search: Optional[str] = None
|
|
80
|
+
min_importance: Optional[int] = None
|
|
81
|
+
max_importance: Optional[int] = None
|
|
82
|
+
sort_by: str = "last_accessed"
|
|
83
|
+
sort_order: str = "desc"
|
|
84
|
+
limit: int = 50
|
|
85
|
+
offset: int = 0
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class AggregateMemoryRequest(BaseModel):
|
|
89
|
+
"""Request for aggregate memory data across projects."""
|
|
90
|
+
|
|
91
|
+
projects: list[str] = Field(..., description="List of project db paths")
|
|
92
|
+
filters: Optional[FilterParams] = None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
class AggregateStatsRequest(BaseModel):
|
|
96
|
+
"""Request for aggregate statistics."""
|
|
97
|
+
|
|
98
|
+
projects: list[str] = Field(..., description="List of project db paths")
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class AggregateStatsResponse(BaseModel):
|
|
102
|
+
"""Aggregate statistics across multiple projects."""
|
|
103
|
+
|
|
104
|
+
total_count: int
|
|
105
|
+
total_access_count: int
|
|
106
|
+
avg_importance: float
|
|
107
|
+
by_type: dict[str, int]
|
|
108
|
+
by_status: dict[str, int]
|
|
109
|
+
project_count: int
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class AggregateChatRequest(BaseModel):
|
|
113
|
+
"""Request for chat across multiple projects."""
|
|
114
|
+
|
|
115
|
+
projects: list[str] = Field(..., description="List of project db paths")
|
|
116
|
+
question: str = Field(..., min_length=1, max_length=2000)
|
|
117
|
+
max_memories_per_project: int = Field(default=5, ge=1, le=20)
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
class Activity(BaseModel):
|
|
121
|
+
"""Activity log record."""
|
|
122
|
+
|
|
123
|
+
id: str
|
|
124
|
+
session_id: Optional[str] = None
|
|
125
|
+
event_type: str
|
|
126
|
+
tool_name: Optional[str] = None
|
|
127
|
+
tool_input: Optional[str] = None
|
|
128
|
+
tool_output: Optional[str] = None
|
|
129
|
+
success: bool = True
|
|
130
|
+
error_message: Optional[str] = None
|
|
131
|
+
duration_ms: Optional[int] = None
|
|
132
|
+
file_path: Optional[str] = None
|
|
133
|
+
timestamp: datetime
|
|
134
|
+
# Command analytics fields
|
|
135
|
+
command_name: Optional[str] = None
|
|
136
|
+
command_scope: Optional[str] = None
|
|
137
|
+
mcp_server: Optional[str] = None
|
|
138
|
+
skill_name: Optional[str] = None
|
|
139
|
+
# Natural language summary fields
|
|
140
|
+
summary: Optional[str] = None
|
|
141
|
+
summary_detail: Optional[str] = None
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class Session(BaseModel):
|
|
145
|
+
"""Session record."""
|
|
146
|
+
|
|
147
|
+
id: str
|
|
148
|
+
project_path: str
|
|
149
|
+
started_at: datetime
|
|
150
|
+
ended_at: Optional[datetime] = None
|
|
151
|
+
summary: Optional[str] = None
|
|
152
|
+
activity_count: int = 0
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
class TimelineEntry(BaseModel):
|
|
156
|
+
"""Entry in the timeline view."""
|
|
157
|
+
|
|
158
|
+
timestamp: datetime
|
|
159
|
+
entry_type: str # "memory" or "activity"
|
|
160
|
+
data: dict
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class MemoryCreateRequest(BaseModel):
|
|
164
|
+
"""Create request for a new memory."""
|
|
165
|
+
|
|
166
|
+
content: str = Field(..., min_length=1, max_length=50000)
|
|
167
|
+
memory_type: str = Field(default="general")
|
|
168
|
+
context: Optional[str] = None
|
|
169
|
+
importance_score: int = Field(default=50, ge=1, le=100)
|
|
170
|
+
tags: list[str] = Field(default_factory=list)
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class MemoryUpdate(BaseModel):
|
|
174
|
+
"""Update request for a memory."""
|
|
175
|
+
|
|
176
|
+
content: Optional[str] = None
|
|
177
|
+
context: Optional[str] = None
|
|
178
|
+
memory_type: Optional[str] = Field(None, validation_alias="type")
|
|
179
|
+
status: Optional[str] = None
|
|
180
|
+
importance_score: Optional[int] = Field(None, ge=1, le=100)
|
|
181
|
+
tags: Optional[list[str]] = None
|
|
182
|
+
|
|
183
|
+
model_config = {"populate_by_name": True}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
class WSEvent(BaseModel):
|
|
187
|
+
"""WebSocket event message."""
|
|
188
|
+
|
|
189
|
+
event_type: str
|
|
190
|
+
data: dict
|
|
191
|
+
timestamp: datetime = Field(default_factory=datetime.now)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class ChatRequest(BaseModel):
|
|
195
|
+
"""Request for the chat endpoint."""
|
|
196
|
+
|
|
197
|
+
question: str = Field(..., min_length=1, max_length=2000)
|
|
198
|
+
max_memories: int = Field(default=10, ge=1, le=50)
|
|
199
|
+
use_style: bool = Field(default=False)
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
class ChatSource(BaseModel):
|
|
203
|
+
"""Source memory reference in chat response."""
|
|
204
|
+
|
|
205
|
+
id: str
|
|
206
|
+
type: str
|
|
207
|
+
content_preview: str
|
|
208
|
+
tags: list[str]
|
|
209
|
+
project_path: Optional[str] = None
|
|
210
|
+
project_name: Optional[str] = None
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
class ChatResponse(BaseModel):
|
|
214
|
+
"""Response from the chat endpoint."""
|
|
215
|
+
|
|
216
|
+
answer: str
|
|
217
|
+
sources: list[ChatSource]
|
|
218
|
+
error: Optional[str] = None
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class ConversationMessage(BaseModel):
|
|
222
|
+
"""A message in a conversation."""
|
|
223
|
+
|
|
224
|
+
role: str # 'user' or 'assistant'
|
|
225
|
+
content: str
|
|
226
|
+
timestamp: str
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
class ConversationSaveRequest(BaseModel):
|
|
230
|
+
"""Request to save a conversation as memory."""
|
|
231
|
+
|
|
232
|
+
messages: list[ConversationMessage]
|
|
233
|
+
referenced_memory_ids: Optional[list[str]] = None
|
|
234
|
+
importance: Optional[int] = Field(default=60, ge=1, le=100)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
class ConversationSaveResponse(BaseModel):
|
|
238
|
+
"""Response after saving a conversation."""
|
|
239
|
+
|
|
240
|
+
memory_id: str
|
|
241
|
+
summary: str
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
# --- Image Generation Models ---
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
class SingleImageRequestModel(BaseModel):
|
|
248
|
+
"""Request for a single image in a batch."""
|
|
249
|
+
preset: str = "custom" # Maps to ImagePreset enum
|
|
250
|
+
custom_prompt: str = ""
|
|
251
|
+
aspect_ratio: str = "16:9"
|
|
252
|
+
image_size: str = "2K"
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class BatchImageGenerationRequest(BaseModel):
|
|
256
|
+
"""Request for generating multiple images."""
|
|
257
|
+
images: list[SingleImageRequestModel] # 1, 2, or 4 images
|
|
258
|
+
memory_ids: list[str] = []
|
|
259
|
+
chat_messages: list[dict] = [] # Recent chat for context
|
|
260
|
+
use_search_grounding: bool = False
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class ImageRefineRequest(BaseModel):
|
|
264
|
+
"""Request for refining an existing image."""
|
|
265
|
+
image_id: str
|
|
266
|
+
refinement_prompt: str
|
|
267
|
+
aspect_ratio: Optional[str] = None
|
|
268
|
+
image_size: Optional[str] = None
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class SingleImageResponseModel(BaseModel):
|
|
272
|
+
"""Response for a single generated image."""
|
|
273
|
+
success: bool
|
|
274
|
+
image_data: Optional[str] = None # Base64 encoded
|
|
275
|
+
text_response: Optional[str] = None
|
|
276
|
+
thought_signature: Optional[str] = None
|
|
277
|
+
image_id: Optional[str] = None
|
|
278
|
+
error: Optional[str] = None
|
|
279
|
+
index: int = 0
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class BatchImageGenerationResponse(BaseModel):
|
|
283
|
+
"""Response for batch image generation."""
|
|
284
|
+
success: bool
|
|
285
|
+
images: list[SingleImageResponseModel] = []
|
|
286
|
+
errors: list[str] = []
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
# --- User Messages & Style Profile Models ---
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
class UserMessage(BaseModel):
|
|
293
|
+
"""User message record from the database."""
|
|
294
|
+
|
|
295
|
+
id: str
|
|
296
|
+
session_id: Optional[str] = None
|
|
297
|
+
timestamp: Optional[str] = None # Backward compatibility
|
|
298
|
+
created_at: Optional[str] = None # Frontend expects created_at
|
|
299
|
+
content: str
|
|
300
|
+
word_count: Optional[int] = None
|
|
301
|
+
char_count: Optional[int] = None
|
|
302
|
+
line_count: Optional[int] = None
|
|
303
|
+
has_code_blocks: bool = False
|
|
304
|
+
has_questions: bool = False
|
|
305
|
+
has_commands: bool = False
|
|
306
|
+
tone: Optional[str] = None # Primary tone for frontend
|
|
307
|
+
tone_indicators: list[str] = []
|
|
308
|
+
project_path: Optional[str] = None
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
class UserMessageFilters(BaseModel):
|
|
312
|
+
"""Query filter parameters for user messages."""
|
|
313
|
+
|
|
314
|
+
session_id: Optional[str] = None
|
|
315
|
+
search: Optional[str] = None
|
|
316
|
+
has_code_blocks: Optional[bool] = None
|
|
317
|
+
has_questions: Optional[bool] = None
|
|
318
|
+
has_commands: Optional[bool] = None
|
|
319
|
+
tone_filter: Optional[str] = None
|
|
320
|
+
sort_by: str = "timestamp"
|
|
321
|
+
sort_order: str = "desc"
|
|
322
|
+
limit: int = Field(default=50, ge=1, le=500)
|
|
323
|
+
offset: int = Field(default=0, ge=0)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
class UserMessagesResponse(BaseModel):
|
|
327
|
+
"""Response containing user messages with pagination info."""
|
|
328
|
+
|
|
329
|
+
messages: list[UserMessage]
|
|
330
|
+
total_count: int
|
|
331
|
+
limit: int
|
|
332
|
+
offset: int
|
|
333
|
+
has_more: bool = False # Whether more results are available
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
class StyleSample(BaseModel):
|
|
337
|
+
"""A sample message for style preview."""
|
|
338
|
+
|
|
339
|
+
id: str
|
|
340
|
+
timestamp: str
|
|
341
|
+
content_preview: str
|
|
342
|
+
word_count: Optional[int] = None
|
|
343
|
+
has_code_blocks: bool = False
|
|
344
|
+
has_questions: bool = False
|
|
345
|
+
tone_indicators: list[str] = []
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
class StyleProfile(BaseModel):
|
|
349
|
+
"""User style profile for aggregated style analysis."""
|
|
350
|
+
|
|
351
|
+
id: str
|
|
352
|
+
project_path: Optional[str] = None
|
|
353
|
+
total_messages: int = 0
|
|
354
|
+
avg_word_count: Optional[float] = None
|
|
355
|
+
avg_char_count: Optional[float] = None
|
|
356
|
+
common_phrases: Optional[list[str]] = None
|
|
357
|
+
vocabulary_richness: Optional[float] = None
|
|
358
|
+
formality_score: Optional[float] = None
|
|
359
|
+
question_frequency: Optional[float] = None
|
|
360
|
+
command_frequency: Optional[float] = None
|
|
361
|
+
code_block_frequency: Optional[float] = None
|
|
362
|
+
punctuation_style: Optional[dict] = None
|
|
363
|
+
greeting_patterns: Optional[list[str]] = None
|
|
364
|
+
instruction_style: Optional[dict] = None
|
|
365
|
+
sample_messages: Optional[list[str]] = None
|
|
366
|
+
created_at: str
|
|
367
|
+
updated_at: str
|
|
368
|
+
|
|
369
|
+
|
|
370
|
+
class BulkDeleteRequest(BaseModel):
|
|
371
|
+
"""Request body for bulk delete operations."""
|
|
372
|
+
|
|
373
|
+
message_ids: list[str] = Field(..., min_length=1, max_length=100)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# --- Response Composer Models ---
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class ComposeRequest(BaseModel):
|
|
380
|
+
"""Request for composing a response in user's style."""
|
|
381
|
+
|
|
382
|
+
incoming_message: str = Field(..., min_length=1, max_length=5000)
|
|
383
|
+
context_type: str = Field(default="general") # skool_post, dm, email, comment, general
|
|
384
|
+
template: Optional[str] = None # answer, guide, redirect, acknowledge
|
|
385
|
+
tone_level: int = Field(default=50, ge=0, le=100) # 0=casual, 100=professional
|
|
386
|
+
include_memories: bool = Field(default=True)
|
|
387
|
+
custom_instructions: Optional[str] = Field(default=None, max_length=2000)
|
|
388
|
+
include_explanation: bool = Field(default=False)
|
|
389
|
+
|
|
390
|
+
|
|
391
|
+
class ComposeResponse(BaseModel):
|
|
392
|
+
"""Response from compose endpoint."""
|
|
393
|
+
|
|
394
|
+
id: str
|
|
395
|
+
response: str
|
|
396
|
+
sources: list[ChatSource]
|
|
397
|
+
style_applied: bool
|
|
398
|
+
tone_level: int
|
|
399
|
+
template_used: Optional[str]
|
|
400
|
+
incoming_message: str
|
|
401
|
+
context_type: str
|
|
402
|
+
created_at: str
|
|
403
|
+
custom_instructions: Optional[str] = None
|
|
404
|
+
explanation: Optional[str] = None
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
# --- Agent & ADW Models ---
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
class Agent(BaseModel):
|
|
411
|
+
"""Agent from the agents table."""
|
|
412
|
+
|
|
413
|
+
id: str
|
|
414
|
+
name: Optional[str] = None
|
|
415
|
+
type: str # 'main', 'subagent', 'tool'
|
|
416
|
+
first_seen: datetime
|
|
417
|
+
last_seen: datetime
|
|
418
|
+
total_activities: int
|
|
419
|
+
recent_activity_count: int = 0 # Activities in last hour
|
|
420
|
+
is_active: bool = False # Has activity in last 5 minutes
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
class AgentToolStats(BaseModel):
|
|
424
|
+
"""Tool usage breakdown for an agent."""
|
|
425
|
+
|
|
426
|
+
tool_name: str
|
|
427
|
+
count: int
|
|
428
|
+
avg_duration_ms: float
|
|
429
|
+
success_rate: float
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
class AgentStats(BaseModel):
|
|
433
|
+
"""Detailed stats for a single agent."""
|
|
434
|
+
|
|
435
|
+
agent: Agent
|
|
436
|
+
tool_breakdown: list[AgentToolStats]
|
|
437
|
+
files_touched: list[str]
|
|
438
|
+
parent_agent_id: Optional[str] = None # If subagent, who spawned it
|
|
439
|
+
adw_phase: Optional[str] = None # Which ADW phase this agent ran in
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
class ADWPhaseInfo(BaseModel):
|
|
443
|
+
"""Info about a single ADW phase."""
|
|
444
|
+
|
|
445
|
+
name: str # 'plan', 'build', 'validate', 'release'
|
|
446
|
+
status: str # 'pending', 'running', 'completed', 'failed', 'skipped'
|
|
447
|
+
duration_seconds: Optional[float] = None
|
|
448
|
+
agent_ids: list[str] = [] # Agents that ran in this phase
|
|
449
|
+
|
|
450
|
+
|
|
451
|
+
class ADWState(BaseModel):
|
|
452
|
+
"""Full ADW state with agent correlation."""
|
|
453
|
+
|
|
454
|
+
adw_id: str
|
|
455
|
+
task_description: str
|
|
456
|
+
created_at: datetime
|
|
457
|
+
current_phase: str
|
|
458
|
+
completed_phases: list[str]
|
|
459
|
+
status: str # 'running', 'completed', 'failed'
|
|
460
|
+
phases: list[ADWPhaseInfo]
|
|
461
|
+
project_path: str
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
class ADWListItem(BaseModel):
|
|
465
|
+
"""Summary for ADW list."""
|
|
466
|
+
|
|
467
|
+
adw_id: str
|
|
468
|
+
created_at: datetime
|
|
469
|
+
status: str
|
|
470
|
+
current_phase: str
|
|
471
|
+
phases_completed: int
|
|
472
|
+
phases_total: int
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Project configuration manager for user preferences."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import platform
|
|
5
|
+
from datetime import datetime
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Optional
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RegisteredProject(BaseModel):
|
|
13
|
+
"""A manually registered project."""
|
|
14
|
+
|
|
15
|
+
path: str
|
|
16
|
+
display_name: Optional[str] = None
|
|
17
|
+
added_at: datetime
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class RecentProject(BaseModel):
|
|
21
|
+
"""A recently accessed project."""
|
|
22
|
+
|
|
23
|
+
path: str
|
|
24
|
+
last_accessed: datetime
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class ProjectConfig(BaseModel):
|
|
28
|
+
"""User project configuration."""
|
|
29
|
+
|
|
30
|
+
version: int = 1
|
|
31
|
+
scan_directories: list[str] = []
|
|
32
|
+
registered_projects: list[RegisteredProject] = []
|
|
33
|
+
favorites: list[str] = []
|
|
34
|
+
recent: list[RecentProject] = []
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
CONFIG_PATH = Path.home() / ".omni-cortex" / "projects.json"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def get_default_scan_dirs() -> list[str]:
|
|
41
|
+
"""Return platform-appropriate default scan directories."""
|
|
42
|
+
home = Path.home()
|
|
43
|
+
|
|
44
|
+
dirs = [
|
|
45
|
+
str(home / "projects"),
|
|
46
|
+
str(home / "Projects"),
|
|
47
|
+
str(home / "code"),
|
|
48
|
+
str(home / "Code"),
|
|
49
|
+
str(home / "dev"),
|
|
50
|
+
str(home / "workspace"),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
if platform.system() == "Windows":
|
|
54
|
+
dirs.insert(0, "D:/Projects")
|
|
55
|
+
|
|
56
|
+
return [d for d in dirs if Path(d).exists()]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def load_config() -> ProjectConfig:
|
|
60
|
+
"""Load config from disk, creating defaults if missing."""
|
|
61
|
+
if CONFIG_PATH.exists():
|
|
62
|
+
try:
|
|
63
|
+
data = json.loads(CONFIG_PATH.read_text(encoding="utf-8"))
|
|
64
|
+
return ProjectConfig(**data)
|
|
65
|
+
except Exception:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
# Create default config
|
|
69
|
+
config = ProjectConfig(scan_directories=get_default_scan_dirs())
|
|
70
|
+
save_config(config)
|
|
71
|
+
return config
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def save_config(config: ProjectConfig) -> None:
|
|
75
|
+
"""Save config to disk."""
|
|
76
|
+
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
77
|
+
CONFIG_PATH.write_text(config.model_dump_json(indent=2), encoding="utf-8")
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def add_registered_project(path: str, display_name: Optional[str] = None) -> bool:
|
|
81
|
+
"""Register a new project by path."""
|
|
82
|
+
config = load_config()
|
|
83
|
+
|
|
84
|
+
# Validate path has cortex.db
|
|
85
|
+
db_path = Path(path) / ".omni-cortex" / "cortex.db"
|
|
86
|
+
if not db_path.exists():
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
# Check if already registered
|
|
90
|
+
if any(p.path == path for p in config.registered_projects):
|
|
91
|
+
return False
|
|
92
|
+
|
|
93
|
+
config.registered_projects.append(
|
|
94
|
+
RegisteredProject(path=path, display_name=display_name, added_at=datetime.now())
|
|
95
|
+
)
|
|
96
|
+
save_config(config)
|
|
97
|
+
return True
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def remove_registered_project(path: str) -> bool:
|
|
101
|
+
"""Remove a registered project."""
|
|
102
|
+
config = load_config()
|
|
103
|
+
original_len = len(config.registered_projects)
|
|
104
|
+
config.registered_projects = [
|
|
105
|
+
p for p in config.registered_projects if p.path != path
|
|
106
|
+
]
|
|
107
|
+
|
|
108
|
+
if len(config.registered_projects) < original_len:
|
|
109
|
+
save_config(config)
|
|
110
|
+
return True
|
|
111
|
+
return False
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def toggle_favorite(path: str) -> bool:
|
|
115
|
+
"""Toggle favorite status for a project. Returns new favorite status."""
|
|
116
|
+
config = load_config()
|
|
117
|
+
|
|
118
|
+
if path in config.favorites:
|
|
119
|
+
config.favorites.remove(path)
|
|
120
|
+
is_favorite = False
|
|
121
|
+
else:
|
|
122
|
+
config.favorites.append(path)
|
|
123
|
+
is_favorite = True
|
|
124
|
+
|
|
125
|
+
save_config(config)
|
|
126
|
+
return is_favorite
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def update_recent(path: str) -> None:
|
|
130
|
+
"""Update recent projects list."""
|
|
131
|
+
config = load_config()
|
|
132
|
+
|
|
133
|
+
# Remove if already in list
|
|
134
|
+
config.recent = [r for r in config.recent if r.path != path]
|
|
135
|
+
|
|
136
|
+
# Add to front
|
|
137
|
+
config.recent.insert(0, RecentProject(path=path, last_accessed=datetime.now()))
|
|
138
|
+
|
|
139
|
+
# Keep only last 10
|
|
140
|
+
config.recent = config.recent[:10]
|
|
141
|
+
|
|
142
|
+
save_config(config)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def add_scan_directory(directory: str) -> bool:
|
|
146
|
+
"""Add a directory to scan list."""
|
|
147
|
+
config = load_config()
|
|
148
|
+
|
|
149
|
+
# Expand user path
|
|
150
|
+
expanded = str(Path(directory).expanduser())
|
|
151
|
+
|
|
152
|
+
if not Path(expanded).is_dir():
|
|
153
|
+
return False
|
|
154
|
+
|
|
155
|
+
if expanded not in config.scan_directories:
|
|
156
|
+
config.scan_directories.append(expanded)
|
|
157
|
+
save_config(config)
|
|
158
|
+
return True
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def remove_scan_directory(directory: str) -> bool:
|
|
163
|
+
"""Remove a directory from scan list."""
|
|
164
|
+
config = load_config()
|
|
165
|
+
|
|
166
|
+
if directory in config.scan_directories:
|
|
167
|
+
config.scan_directories.remove(directory)
|
|
168
|
+
save_config(config)
|
|
169
|
+
return True
|
|
170
|
+
return False
|