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,265 @@
|
|
|
1
|
+
"""Activity model and CRUD operations."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sqlite3
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
from pydantic import BaseModel, Field, ConfigDict
|
|
7
|
+
|
|
8
|
+
from ..utils.ids import generate_activity_id
|
|
9
|
+
from ..utils.timestamps import now_iso
|
|
10
|
+
from ..utils.truncation import truncate_json
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ActivityCreate(BaseModel):
|
|
14
|
+
"""Input model for creating an activity."""
|
|
15
|
+
|
|
16
|
+
model_config = ConfigDict(
|
|
17
|
+
str_strip_whitespace=True,
|
|
18
|
+
validate_assignment=True,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
event_type: str = Field(
|
|
22
|
+
..., description="Event type: pre_tool_use, post_tool_use, decision, observation"
|
|
23
|
+
)
|
|
24
|
+
tool_name: Optional[str] = Field(None, description="Tool name if applicable")
|
|
25
|
+
tool_input: Optional[str] = Field(None, description="Tool input (JSON)")
|
|
26
|
+
tool_output: Optional[str] = Field(None, description="Tool output (JSON)")
|
|
27
|
+
duration_ms: Optional[int] = Field(None, description="Duration in milliseconds", ge=0)
|
|
28
|
+
success: bool = Field(True, description="Whether the operation succeeded")
|
|
29
|
+
error_message: Optional[str] = Field(None, description="Error message if failed")
|
|
30
|
+
file_path: Optional[str] = Field(None, description="File path if relevant")
|
|
31
|
+
agent_id: Optional[str] = Field(None, description="Agent ID")
|
|
32
|
+
# Command analytics fields
|
|
33
|
+
command_name: Optional[str] = Field(None, description="Slash command name (e.g., 'commit', 'build')")
|
|
34
|
+
command_scope: Optional[str] = Field(None, description="Command scope: 'universal' or 'project'")
|
|
35
|
+
mcp_server: Optional[str] = Field(None, description="MCP server name (e.g., 'grep-mcp')")
|
|
36
|
+
skill_name: Optional[str] = Field(None, description="Skill name from Skill tool calls")
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Activity(BaseModel):
|
|
40
|
+
"""Full activity model from database."""
|
|
41
|
+
|
|
42
|
+
id: str
|
|
43
|
+
session_id: Optional[str] = None
|
|
44
|
+
agent_id: Optional[str] = None
|
|
45
|
+
timestamp: str
|
|
46
|
+
event_type: str
|
|
47
|
+
tool_name: Optional[str] = None
|
|
48
|
+
tool_input: Optional[str] = None
|
|
49
|
+
tool_output: Optional[str] = None
|
|
50
|
+
duration_ms: Optional[int] = None
|
|
51
|
+
success: bool = True
|
|
52
|
+
error_message: Optional[str] = None
|
|
53
|
+
project_path: Optional[str] = None
|
|
54
|
+
file_path: Optional[str] = None
|
|
55
|
+
metadata: Optional[dict[str, Any]] = None
|
|
56
|
+
# Command analytics fields
|
|
57
|
+
command_name: Optional[str] = None
|
|
58
|
+
command_scope: Optional[str] = None
|
|
59
|
+
mcp_server: Optional[str] = None
|
|
60
|
+
skill_name: Optional[str] = None
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def create_activity(
|
|
64
|
+
conn: sqlite3.Connection,
|
|
65
|
+
data: ActivityCreate,
|
|
66
|
+
session_id: Optional[str] = None,
|
|
67
|
+
project_path: Optional[str] = None,
|
|
68
|
+
) -> Activity:
|
|
69
|
+
"""Create a new activity in the database.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
conn: Database connection
|
|
73
|
+
data: Activity creation data
|
|
74
|
+
session_id: Current session ID
|
|
75
|
+
project_path: Current project path
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
Created activity object
|
|
79
|
+
"""
|
|
80
|
+
activity_id = generate_activity_id()
|
|
81
|
+
timestamp = now_iso()
|
|
82
|
+
|
|
83
|
+
# Truncate large inputs/outputs
|
|
84
|
+
tool_input = data.tool_input
|
|
85
|
+
if tool_input and len(tool_input) > 10000:
|
|
86
|
+
tool_input = truncate_json(tool_input, 10000)
|
|
87
|
+
|
|
88
|
+
tool_output = data.tool_output
|
|
89
|
+
if tool_output and len(tool_output) > 10000:
|
|
90
|
+
tool_output = truncate_json(tool_output, 10000)
|
|
91
|
+
|
|
92
|
+
cursor = conn.cursor()
|
|
93
|
+
|
|
94
|
+
# Upsert agent BEFORE inserting activity (foreign key constraint)
|
|
95
|
+
if data.agent_id:
|
|
96
|
+
cursor.execute(
|
|
97
|
+
"""
|
|
98
|
+
INSERT INTO agents (id, type, first_seen, last_seen, total_activities)
|
|
99
|
+
VALUES (?, 'main', ?, ?, 1)
|
|
100
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
101
|
+
last_seen = ?,
|
|
102
|
+
total_activities = total_activities + 1
|
|
103
|
+
""",
|
|
104
|
+
(data.agent_id, timestamp, timestamp, timestamp),
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
cursor.execute(
|
|
108
|
+
"""
|
|
109
|
+
INSERT INTO activities (
|
|
110
|
+
id, session_id, agent_id, timestamp, event_type,
|
|
111
|
+
tool_name, tool_input, tool_output, duration_ms,
|
|
112
|
+
success, error_message, project_path, file_path,
|
|
113
|
+
command_name, command_scope, mcp_server, skill_name
|
|
114
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
115
|
+
""",
|
|
116
|
+
(
|
|
117
|
+
activity_id,
|
|
118
|
+
session_id,
|
|
119
|
+
data.agent_id,
|
|
120
|
+
timestamp,
|
|
121
|
+
data.event_type,
|
|
122
|
+
data.tool_name,
|
|
123
|
+
tool_input,
|
|
124
|
+
tool_output,
|
|
125
|
+
data.duration_ms,
|
|
126
|
+
1 if data.success else 0,
|
|
127
|
+
data.error_message,
|
|
128
|
+
project_path,
|
|
129
|
+
data.file_path,
|
|
130
|
+
data.command_name,
|
|
131
|
+
data.command_scope,
|
|
132
|
+
data.mcp_server,
|
|
133
|
+
data.skill_name,
|
|
134
|
+
),
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
conn.commit()
|
|
138
|
+
|
|
139
|
+
return Activity(
|
|
140
|
+
id=activity_id,
|
|
141
|
+
session_id=session_id,
|
|
142
|
+
agent_id=data.agent_id,
|
|
143
|
+
timestamp=timestamp,
|
|
144
|
+
event_type=data.event_type,
|
|
145
|
+
tool_name=data.tool_name,
|
|
146
|
+
tool_input=tool_input,
|
|
147
|
+
tool_output=tool_output,
|
|
148
|
+
duration_ms=data.duration_ms,
|
|
149
|
+
success=data.success,
|
|
150
|
+
error_message=data.error_message,
|
|
151
|
+
project_path=project_path,
|
|
152
|
+
file_path=data.file_path,
|
|
153
|
+
command_name=data.command_name,
|
|
154
|
+
command_scope=data.command_scope,
|
|
155
|
+
mcp_server=data.mcp_server,
|
|
156
|
+
skill_name=data.skill_name,
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def get_activity(conn: sqlite3.Connection, activity_id: str) -> Optional[Activity]:
|
|
161
|
+
"""Get an activity by ID."""
|
|
162
|
+
cursor = conn.cursor()
|
|
163
|
+
cursor.execute("SELECT * FROM activities WHERE id = ?", (activity_id,))
|
|
164
|
+
row = cursor.fetchone()
|
|
165
|
+
return _row_to_activity(row) if row else None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def get_activities(
|
|
169
|
+
conn: sqlite3.Connection,
|
|
170
|
+
session_id: Optional[str] = None,
|
|
171
|
+
agent_id: Optional[str] = None,
|
|
172
|
+
event_type: Optional[str] = None,
|
|
173
|
+
tool_name: Optional[str] = None,
|
|
174
|
+
since: Optional[str] = None,
|
|
175
|
+
until: Optional[str] = None,
|
|
176
|
+
limit: int = 50,
|
|
177
|
+
offset: int = 0,
|
|
178
|
+
) -> tuple[list[Activity], int]:
|
|
179
|
+
"""Get activities with filters.
|
|
180
|
+
|
|
181
|
+
Returns:
|
|
182
|
+
Tuple of (activities list, total count)
|
|
183
|
+
"""
|
|
184
|
+
where_clauses = []
|
|
185
|
+
params: list[Any] = []
|
|
186
|
+
|
|
187
|
+
if session_id:
|
|
188
|
+
where_clauses.append("session_id = ?")
|
|
189
|
+
params.append(session_id)
|
|
190
|
+
|
|
191
|
+
if agent_id:
|
|
192
|
+
where_clauses.append("agent_id = ?")
|
|
193
|
+
params.append(agent_id)
|
|
194
|
+
|
|
195
|
+
if event_type:
|
|
196
|
+
where_clauses.append("event_type = ?")
|
|
197
|
+
params.append(event_type)
|
|
198
|
+
|
|
199
|
+
if tool_name:
|
|
200
|
+
where_clauses.append("tool_name = ?")
|
|
201
|
+
params.append(tool_name)
|
|
202
|
+
|
|
203
|
+
if since:
|
|
204
|
+
where_clauses.append("timestamp >= ?")
|
|
205
|
+
params.append(since)
|
|
206
|
+
|
|
207
|
+
if until:
|
|
208
|
+
where_clauses.append("timestamp <= ?")
|
|
209
|
+
params.append(until)
|
|
210
|
+
|
|
211
|
+
where_sql = ""
|
|
212
|
+
if where_clauses:
|
|
213
|
+
where_sql = "WHERE " + " AND ".join(where_clauses)
|
|
214
|
+
|
|
215
|
+
cursor = conn.cursor()
|
|
216
|
+
|
|
217
|
+
# Get total count
|
|
218
|
+
cursor.execute(f"SELECT COUNT(*) FROM activities {where_sql}", params)
|
|
219
|
+
total = cursor.fetchone()[0]
|
|
220
|
+
|
|
221
|
+
# Get page
|
|
222
|
+
params_page = params + [limit, offset]
|
|
223
|
+
cursor.execute(
|
|
224
|
+
f"""
|
|
225
|
+
SELECT * FROM activities {where_sql}
|
|
226
|
+
ORDER BY timestamp DESC
|
|
227
|
+
LIMIT ? OFFSET ?
|
|
228
|
+
""",
|
|
229
|
+
params_page,
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
activities = [_row_to_activity(row) for row in cursor.fetchall()]
|
|
233
|
+
return activities, total
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _row_to_activity(row: sqlite3.Row) -> Activity:
|
|
237
|
+
"""Convert database row to Activity object."""
|
|
238
|
+
metadata = row["metadata"]
|
|
239
|
+
if metadata and isinstance(metadata, str):
|
|
240
|
+
metadata = json.loads(metadata)
|
|
241
|
+
|
|
242
|
+
# Get row keys for checking column existence (for older databases)
|
|
243
|
+
row_keys = row.keys() if hasattr(row, "keys") else []
|
|
244
|
+
|
|
245
|
+
return Activity(
|
|
246
|
+
id=row["id"],
|
|
247
|
+
session_id=row["session_id"],
|
|
248
|
+
agent_id=row["agent_id"],
|
|
249
|
+
timestamp=row["timestamp"],
|
|
250
|
+
event_type=row["event_type"],
|
|
251
|
+
tool_name=row["tool_name"],
|
|
252
|
+
tool_input=row["tool_input"],
|
|
253
|
+
tool_output=row["tool_output"],
|
|
254
|
+
duration_ms=row["duration_ms"],
|
|
255
|
+
success=bool(row["success"]),
|
|
256
|
+
error_message=row["error_message"],
|
|
257
|
+
project_path=row["project_path"],
|
|
258
|
+
file_path=row["file_path"],
|
|
259
|
+
metadata=metadata,
|
|
260
|
+
# Command analytics fields (may not exist in older databases)
|
|
261
|
+
command_name=row["command_name"] if "command_name" in row_keys else None,
|
|
262
|
+
command_scope=row["command_scope"] if "command_scope" in row_keys else None,
|
|
263
|
+
mcp_server=row["mcp_server"] if "mcp_server" in row_keys else None,
|
|
264
|
+
skill_name=row["skill_name"] if "skill_name" in row_keys else None,
|
|
265
|
+
)
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""Agent model and CRUD operations."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import sqlite3
|
|
5
|
+
from typing import Optional, Any
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
from ..utils.timestamps import now_iso
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Agent(BaseModel):
|
|
12
|
+
"""Agent model from database."""
|
|
13
|
+
|
|
14
|
+
id: str
|
|
15
|
+
name: Optional[str] = None
|
|
16
|
+
type: str = "main" # main, subagent, tool
|
|
17
|
+
first_seen: str
|
|
18
|
+
last_seen: str
|
|
19
|
+
total_activities: int = 0
|
|
20
|
+
metadata: Optional[dict[str, Any]] = None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_or_create_agent(
|
|
24
|
+
conn: sqlite3.Connection,
|
|
25
|
+
agent_id: str,
|
|
26
|
+
agent_type: str = "main",
|
|
27
|
+
name: Optional[str] = None,
|
|
28
|
+
) -> Agent:
|
|
29
|
+
"""Get an existing agent or create a new one.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
conn: Database connection
|
|
33
|
+
agent_id: Agent ID
|
|
34
|
+
agent_type: Type of agent (main, subagent, tool)
|
|
35
|
+
name: Optional agent name
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Agent object
|
|
39
|
+
"""
|
|
40
|
+
now = now_iso()
|
|
41
|
+
cursor = conn.cursor()
|
|
42
|
+
|
|
43
|
+
# Try to get existing
|
|
44
|
+
cursor.execute("SELECT * FROM agents WHERE id = ?", (agent_id,))
|
|
45
|
+
row = cursor.fetchone()
|
|
46
|
+
|
|
47
|
+
if row:
|
|
48
|
+
# Update last_seen
|
|
49
|
+
cursor.execute(
|
|
50
|
+
"UPDATE agents SET last_seen = ? WHERE id = ?",
|
|
51
|
+
(now, agent_id),
|
|
52
|
+
)
|
|
53
|
+
conn.commit()
|
|
54
|
+
return _row_to_agent(row)
|
|
55
|
+
|
|
56
|
+
# Create new
|
|
57
|
+
cursor.execute(
|
|
58
|
+
"""
|
|
59
|
+
INSERT INTO agents (id, name, type, first_seen, last_seen, total_activities)
|
|
60
|
+
VALUES (?, ?, ?, ?, ?, 0)
|
|
61
|
+
""",
|
|
62
|
+
(agent_id, name, agent_type, now, now),
|
|
63
|
+
)
|
|
64
|
+
conn.commit()
|
|
65
|
+
|
|
66
|
+
return Agent(
|
|
67
|
+
id=agent_id,
|
|
68
|
+
name=name,
|
|
69
|
+
type=agent_type,
|
|
70
|
+
first_seen=now,
|
|
71
|
+
last_seen=now,
|
|
72
|
+
total_activities=0,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def get_agent(conn: sqlite3.Connection, agent_id: str) -> Optional[Agent]:
|
|
77
|
+
"""Get an agent by ID."""
|
|
78
|
+
cursor = conn.cursor()
|
|
79
|
+
cursor.execute("SELECT * FROM agents WHERE id = ?", (agent_id,))
|
|
80
|
+
row = cursor.fetchone()
|
|
81
|
+
return _row_to_agent(row) if row else None
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def list_agents(
|
|
85
|
+
conn: sqlite3.Connection,
|
|
86
|
+
agent_type: Optional[str] = None,
|
|
87
|
+
limit: int = 50,
|
|
88
|
+
) -> list[Agent]:
|
|
89
|
+
"""List agents with optional type filter."""
|
|
90
|
+
cursor = conn.cursor()
|
|
91
|
+
|
|
92
|
+
if agent_type:
|
|
93
|
+
cursor.execute(
|
|
94
|
+
"""
|
|
95
|
+
SELECT * FROM agents
|
|
96
|
+
WHERE type = ?
|
|
97
|
+
ORDER BY last_seen DESC
|
|
98
|
+
LIMIT ?
|
|
99
|
+
""",
|
|
100
|
+
(agent_type, limit),
|
|
101
|
+
)
|
|
102
|
+
else:
|
|
103
|
+
cursor.execute(
|
|
104
|
+
"""
|
|
105
|
+
SELECT * FROM agents
|
|
106
|
+
ORDER BY last_seen DESC
|
|
107
|
+
LIMIT ?
|
|
108
|
+
""",
|
|
109
|
+
(limit,),
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return [_row_to_agent(row) for row in cursor.fetchall()]
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def increment_agent_activities(conn: sqlite3.Connection, agent_id: str) -> None:
|
|
116
|
+
"""Increment an agent's activity count."""
|
|
117
|
+
now = now_iso()
|
|
118
|
+
cursor = conn.cursor()
|
|
119
|
+
cursor.execute(
|
|
120
|
+
"""
|
|
121
|
+
UPDATE agents
|
|
122
|
+
SET total_activities = total_activities + 1, last_seen = ?
|
|
123
|
+
WHERE id = ?
|
|
124
|
+
""",
|
|
125
|
+
(now, agent_id),
|
|
126
|
+
)
|
|
127
|
+
conn.commit()
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _row_to_agent(row: sqlite3.Row) -> Agent:
|
|
131
|
+
"""Convert database row to Agent object."""
|
|
132
|
+
metadata = row["metadata"]
|
|
133
|
+
if metadata and isinstance(metadata, str):
|
|
134
|
+
metadata = json.loads(metadata)
|
|
135
|
+
|
|
136
|
+
return Agent(
|
|
137
|
+
id=row["id"],
|
|
138
|
+
name=row["name"],
|
|
139
|
+
type=row["type"],
|
|
140
|
+
first_seen=row["first_seen"],
|
|
141
|
+
last_seen=row["last_seen"],
|
|
142
|
+
total_activities=row["total_activities"],
|
|
143
|
+
metadata=metadata,
|
|
144
|
+
)
|