dtSpark 1.0.4__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.
- dtSpark/__init__.py +0 -0
- dtSpark/_description.txt +1 -0
- dtSpark/_full_name.txt +1 -0
- dtSpark/_licence.txt +21 -0
- dtSpark/_metadata.yaml +6 -0
- dtSpark/_name.txt +1 -0
- dtSpark/_version.txt +1 -0
- dtSpark/aws/__init__.py +7 -0
- dtSpark/aws/authentication.py +296 -0
- dtSpark/aws/bedrock.py +578 -0
- dtSpark/aws/costs.py +318 -0
- dtSpark/aws/pricing.py +580 -0
- dtSpark/cli_interface.py +2645 -0
- dtSpark/conversation_manager.py +3050 -0
- dtSpark/core/__init__.py +12 -0
- dtSpark/core/application.py +3355 -0
- dtSpark/core/context_compaction.py +735 -0
- dtSpark/daemon/__init__.py +104 -0
- dtSpark/daemon/__main__.py +10 -0
- dtSpark/daemon/action_monitor.py +213 -0
- dtSpark/daemon/daemon_app.py +730 -0
- dtSpark/daemon/daemon_manager.py +289 -0
- dtSpark/daemon/execution_coordinator.py +194 -0
- dtSpark/daemon/pid_file.py +169 -0
- dtSpark/database/__init__.py +482 -0
- dtSpark/database/autonomous_actions.py +1191 -0
- dtSpark/database/backends.py +329 -0
- dtSpark/database/connection.py +122 -0
- dtSpark/database/conversations.py +520 -0
- dtSpark/database/credential_prompt.py +218 -0
- dtSpark/database/files.py +205 -0
- dtSpark/database/mcp_ops.py +355 -0
- dtSpark/database/messages.py +161 -0
- dtSpark/database/schema.py +673 -0
- dtSpark/database/tool_permissions.py +186 -0
- dtSpark/database/usage.py +167 -0
- dtSpark/files/__init__.py +4 -0
- dtSpark/files/manager.py +322 -0
- dtSpark/launch.py +39 -0
- dtSpark/limits/__init__.py +10 -0
- dtSpark/limits/costs.py +296 -0
- dtSpark/limits/tokens.py +342 -0
- dtSpark/llm/__init__.py +17 -0
- dtSpark/llm/anthropic_direct.py +446 -0
- dtSpark/llm/base.py +146 -0
- dtSpark/llm/context_limits.py +438 -0
- dtSpark/llm/manager.py +177 -0
- dtSpark/llm/ollama.py +578 -0
- dtSpark/mcp_integration/__init__.py +5 -0
- dtSpark/mcp_integration/manager.py +653 -0
- dtSpark/mcp_integration/tool_selector.py +225 -0
- dtSpark/resources/config.yaml.template +631 -0
- dtSpark/safety/__init__.py +22 -0
- dtSpark/safety/llm_service.py +111 -0
- dtSpark/safety/patterns.py +229 -0
- dtSpark/safety/prompt_inspector.py +442 -0
- dtSpark/safety/violation_logger.py +346 -0
- dtSpark/scheduler/__init__.py +20 -0
- dtSpark/scheduler/creation_tools.py +599 -0
- dtSpark/scheduler/execution_queue.py +159 -0
- dtSpark/scheduler/executor.py +1152 -0
- dtSpark/scheduler/manager.py +395 -0
- dtSpark/tools/__init__.py +4 -0
- dtSpark/tools/builtin.py +833 -0
- dtSpark/web/__init__.py +20 -0
- dtSpark/web/auth.py +152 -0
- dtSpark/web/dependencies.py +37 -0
- dtSpark/web/endpoints/__init__.py +17 -0
- dtSpark/web/endpoints/autonomous_actions.py +1125 -0
- dtSpark/web/endpoints/chat.py +621 -0
- dtSpark/web/endpoints/conversations.py +353 -0
- dtSpark/web/endpoints/main_menu.py +547 -0
- dtSpark/web/endpoints/streaming.py +421 -0
- dtSpark/web/server.py +578 -0
- dtSpark/web/session.py +167 -0
- dtSpark/web/ssl_utils.py +195 -0
- dtSpark/web/static/css/dark-theme.css +427 -0
- dtSpark/web/static/js/actions.js +1101 -0
- dtSpark/web/static/js/chat.js +614 -0
- dtSpark/web/static/js/main.js +496 -0
- dtSpark/web/static/js/sse-client.js +242 -0
- dtSpark/web/templates/actions.html +408 -0
- dtSpark/web/templates/base.html +93 -0
- dtSpark/web/templates/chat.html +814 -0
- dtSpark/web/templates/conversations.html +350 -0
- dtSpark/web/templates/goodbye.html +81 -0
- dtSpark/web/templates/login.html +90 -0
- dtSpark/web/templates/main_menu.html +983 -0
- dtSpark/web/templates/new_conversation.html +191 -0
- dtSpark/web/web_interface.py +137 -0
- dtspark-1.0.4.dist-info/METADATA +187 -0
- dtspark-1.0.4.dist-info/RECORD +96 -0
- dtspark-1.0.4.dist-info/WHEEL +5 -0
- dtspark-1.0.4.dist-info/entry_points.txt +3 -0
- dtspark-1.0.4.dist-info/licenses/LICENSE +21 -0
- dtspark-1.0.4.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Database schema management for conversation storage.
|
|
3
|
+
|
|
4
|
+
This module handles:
|
|
5
|
+
- Table creation
|
|
6
|
+
- Schema migrations
|
|
7
|
+
- Index management
|
|
8
|
+
|
|
9
|
+
Note: Current schema is optimized for SQLite. Support for other databases
|
|
10
|
+
(MySQL, PostgreSQL, MSSQL) requires schema adaptations for proper data types
|
|
11
|
+
and auto-increment syntax.
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import sqlite3
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def initialise_schema(conn, backend=None):
|
|
21
|
+
"""
|
|
22
|
+
Create database tables and indices if they don't exist.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
conn: Database connection (SQLite or other backend)
|
|
26
|
+
backend: DatabaseBackend instance for SQL dialect-specific operations
|
|
27
|
+
|
|
28
|
+
Note: Schema is currently SQLite-optimized. Future versions will use
|
|
29
|
+
backend parameter to generate database-specific SQL.
|
|
30
|
+
"""
|
|
31
|
+
cursor = conn.cursor()
|
|
32
|
+
|
|
33
|
+
# TODO: Use backend.get_autoincrement_syntax() for cross-database support
|
|
34
|
+
# For now, schema works with SQLite and databases that support similar DDL
|
|
35
|
+
|
|
36
|
+
# Conversations table
|
|
37
|
+
cursor.execute('''
|
|
38
|
+
CREATE TABLE IF NOT EXISTS conversations (
|
|
39
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
40
|
+
name TEXT NOT NULL,
|
|
41
|
+
model_id TEXT NOT NULL,
|
|
42
|
+
created_at TIMESTAMP NOT NULL,
|
|
43
|
+
last_updated TIMESTAMP NOT NULL,
|
|
44
|
+
total_tokens INTEGER DEFAULT 0,
|
|
45
|
+
is_active INTEGER DEFAULT 1,
|
|
46
|
+
instructions TEXT,
|
|
47
|
+
tokens_sent INTEGER DEFAULT 0,
|
|
48
|
+
tokens_received INTEGER DEFAULT 0
|
|
49
|
+
)
|
|
50
|
+
''')
|
|
51
|
+
|
|
52
|
+
# Migration: Add instructions column if it doesn't exist
|
|
53
|
+
try:
|
|
54
|
+
cursor.execute("SELECT instructions FROM conversations LIMIT 1")
|
|
55
|
+
except sqlite3.OperationalError:
|
|
56
|
+
# Column doesn't exist, add it
|
|
57
|
+
cursor.execute("ALTER TABLE conversations ADD COLUMN instructions TEXT")
|
|
58
|
+
conn.commit()
|
|
59
|
+
logging.info("Added instructions column to conversations table")
|
|
60
|
+
|
|
61
|
+
# Migration: Add token tracking columns if they don't exist
|
|
62
|
+
try:
|
|
63
|
+
cursor.execute("SELECT tokens_sent, tokens_received FROM conversations LIMIT 1")
|
|
64
|
+
except sqlite3.OperationalError:
|
|
65
|
+
# Columns don't exist, add them
|
|
66
|
+
try:
|
|
67
|
+
cursor.execute("ALTER TABLE conversations ADD COLUMN tokens_sent INTEGER DEFAULT 0")
|
|
68
|
+
except sqlite3.OperationalError:
|
|
69
|
+
pass # Column already exists
|
|
70
|
+
try:
|
|
71
|
+
cursor.execute("ALTER TABLE conversations ADD COLUMN tokens_received INTEGER DEFAULT 0")
|
|
72
|
+
except sqlite3.OperationalError:
|
|
73
|
+
pass # Column already exists
|
|
74
|
+
conn.commit()
|
|
75
|
+
logging.info("Added token tracking columns to conversations table")
|
|
76
|
+
|
|
77
|
+
# Migration: Add max_tokens column if it doesn't exist
|
|
78
|
+
try:
|
|
79
|
+
cursor.execute("SELECT max_tokens FROM conversations LIMIT 1")
|
|
80
|
+
except sqlite3.OperationalError:
|
|
81
|
+
# Column doesn't exist, add it (NULL means use global default)
|
|
82
|
+
cursor.execute("ALTER TABLE conversations ADD COLUMN max_tokens INTEGER DEFAULT NULL")
|
|
83
|
+
conn.commit()
|
|
84
|
+
logging.info("Added max_tokens column to conversations table")
|
|
85
|
+
|
|
86
|
+
# Migration: Add is_predefined column if it doesn't exist
|
|
87
|
+
try:
|
|
88
|
+
cursor.execute("SELECT is_predefined FROM conversations LIMIT 1")
|
|
89
|
+
except sqlite3.OperationalError:
|
|
90
|
+
# Column doesn't exist, add it
|
|
91
|
+
cursor.execute("ALTER TABLE conversations ADD COLUMN is_predefined INTEGER DEFAULT 0")
|
|
92
|
+
conn.commit()
|
|
93
|
+
logging.info("Added is_predefined column to conversations table")
|
|
94
|
+
|
|
95
|
+
# Migration: Add config_hash column if it doesn't exist
|
|
96
|
+
try:
|
|
97
|
+
cursor.execute("SELECT config_hash FROM conversations LIMIT 1")
|
|
98
|
+
except sqlite3.OperationalError:
|
|
99
|
+
# Column doesn't exist, add it (stores hash of config to detect changes)
|
|
100
|
+
cursor.execute("ALTER TABLE conversations ADD COLUMN config_hash TEXT DEFAULT NULL")
|
|
101
|
+
conn.commit()
|
|
102
|
+
logging.info("Added config_hash column to conversations table")
|
|
103
|
+
|
|
104
|
+
# Migration: Add compaction_threshold column if it doesn't exist
|
|
105
|
+
try:
|
|
106
|
+
cursor.execute("SELECT compaction_threshold FROM conversations LIMIT 1")
|
|
107
|
+
except sqlite3.OperationalError:
|
|
108
|
+
# Column doesn't exist, add it (NULL means use global default from config)
|
|
109
|
+
cursor.execute("ALTER TABLE conversations ADD COLUMN compaction_threshold REAL DEFAULT NULL")
|
|
110
|
+
conn.commit()
|
|
111
|
+
logging.info("Added compaction_threshold column to conversations table")
|
|
112
|
+
|
|
113
|
+
# Messages table
|
|
114
|
+
cursor.execute('''
|
|
115
|
+
CREATE TABLE IF NOT EXISTS messages (
|
|
116
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
117
|
+
conversation_id INTEGER NOT NULL,
|
|
118
|
+
role TEXT NOT NULL,
|
|
119
|
+
content TEXT NOT NULL,
|
|
120
|
+
token_count INTEGER NOT NULL,
|
|
121
|
+
timestamp TIMESTAMP NOT NULL,
|
|
122
|
+
is_rolled_up INTEGER DEFAULT 0,
|
|
123
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id)
|
|
124
|
+
)
|
|
125
|
+
''')
|
|
126
|
+
|
|
127
|
+
# Rollup history table
|
|
128
|
+
cursor.execute('''
|
|
129
|
+
CREATE TABLE IF NOT EXISTS rollup_history (
|
|
130
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
131
|
+
conversation_id INTEGER NOT NULL,
|
|
132
|
+
original_message_count INTEGER NOT NULL,
|
|
133
|
+
summarised_content TEXT NOT NULL,
|
|
134
|
+
original_token_count INTEGER NOT NULL,
|
|
135
|
+
summarised_token_count INTEGER NOT NULL,
|
|
136
|
+
rollup_timestamp TIMESTAMP NOT NULL,
|
|
137
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id)
|
|
138
|
+
)
|
|
139
|
+
''')
|
|
140
|
+
|
|
141
|
+
# Conversation files table
|
|
142
|
+
cursor.execute('''
|
|
143
|
+
CREATE TABLE IF NOT EXISTS conversation_files (
|
|
144
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
145
|
+
conversation_id INTEGER NOT NULL,
|
|
146
|
+
filename TEXT NOT NULL,
|
|
147
|
+
file_type TEXT NOT NULL,
|
|
148
|
+
file_size INTEGER,
|
|
149
|
+
content_text TEXT,
|
|
150
|
+
content_base64 TEXT,
|
|
151
|
+
mime_type TEXT,
|
|
152
|
+
token_count INTEGER DEFAULT 0,
|
|
153
|
+
added_at TIMESTAMP NOT NULL,
|
|
154
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id)
|
|
155
|
+
)
|
|
156
|
+
''')
|
|
157
|
+
|
|
158
|
+
# Migration: Add tags column to conversation_files if it doesn't exist
|
|
159
|
+
try:
|
|
160
|
+
cursor.execute("SELECT tags FROM conversation_files LIMIT 1")
|
|
161
|
+
except sqlite3.OperationalError:
|
|
162
|
+
# Column doesn't exist, add it
|
|
163
|
+
cursor.execute("ALTER TABLE conversation_files ADD COLUMN tags TEXT DEFAULT NULL")
|
|
164
|
+
conn.commit()
|
|
165
|
+
logging.info("Added tags column to conversation_files table")
|
|
166
|
+
|
|
167
|
+
# MCP transactions table - for Cyber Security monitoring and audit trails
|
|
168
|
+
cursor.execute('''
|
|
169
|
+
CREATE TABLE IF NOT EXISTS mcp_transactions (
|
|
170
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
171
|
+
conversation_id INTEGER NOT NULL,
|
|
172
|
+
message_id INTEGER,
|
|
173
|
+
user_prompt TEXT NOT NULL,
|
|
174
|
+
tool_name TEXT NOT NULL,
|
|
175
|
+
tool_server TEXT NOT NULL,
|
|
176
|
+
tool_input TEXT NOT NULL,
|
|
177
|
+
tool_response TEXT NOT NULL,
|
|
178
|
+
is_error INTEGER DEFAULT 0,
|
|
179
|
+
execution_time_ms INTEGER,
|
|
180
|
+
transaction_timestamp TIMESTAMP NOT NULL,
|
|
181
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id),
|
|
182
|
+
FOREIGN KEY (message_id) REFERENCES messages(id)
|
|
183
|
+
)
|
|
184
|
+
''')
|
|
185
|
+
|
|
186
|
+
# Conversation model usage table - tracks token usage per model
|
|
187
|
+
cursor.execute('''
|
|
188
|
+
CREATE TABLE IF NOT EXISTS conversation_model_usage (
|
|
189
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
190
|
+
conversation_id INTEGER NOT NULL,
|
|
191
|
+
model_id TEXT NOT NULL,
|
|
192
|
+
input_tokens INTEGER DEFAULT 0,
|
|
193
|
+
output_tokens INTEGER DEFAULT 0,
|
|
194
|
+
first_used TIMESTAMP NOT NULL,
|
|
195
|
+
last_used TIMESTAMP NOT NULL,
|
|
196
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id),
|
|
197
|
+
UNIQUE(conversation_id, model_id)
|
|
198
|
+
)
|
|
199
|
+
''')
|
|
200
|
+
|
|
201
|
+
# Conversation MCP servers table - tracks which MCP servers are enabled per conversation
|
|
202
|
+
cursor.execute('''
|
|
203
|
+
CREATE TABLE IF NOT EXISTS conversation_mcp_servers (
|
|
204
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
205
|
+
conversation_id INTEGER NOT NULL,
|
|
206
|
+
server_name TEXT NOT NULL,
|
|
207
|
+
enabled INTEGER DEFAULT 1,
|
|
208
|
+
updated_at TIMESTAMP NOT NULL,
|
|
209
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id),
|
|
210
|
+
UNIQUE(conversation_id, server_name)
|
|
211
|
+
)
|
|
212
|
+
''')
|
|
213
|
+
|
|
214
|
+
# Usage tracking table - for token management and billing
|
|
215
|
+
cursor.execute('''
|
|
216
|
+
CREATE TABLE IF NOT EXISTS usage_tracking (
|
|
217
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
218
|
+
conversation_id INTEGER NOT NULL,
|
|
219
|
+
model_id TEXT NOT NULL,
|
|
220
|
+
region TEXT NOT NULL,
|
|
221
|
+
input_tokens INTEGER NOT NULL,
|
|
222
|
+
output_tokens INTEGER NOT NULL,
|
|
223
|
+
cost REAL NOT NULL,
|
|
224
|
+
timestamp TIMESTAMP NOT NULL,
|
|
225
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id)
|
|
226
|
+
)
|
|
227
|
+
''')
|
|
228
|
+
|
|
229
|
+
# Prompt inspection violations table - for Cyber Security audit trail
|
|
230
|
+
cursor.execute('''
|
|
231
|
+
CREATE TABLE IF NOT EXISTS prompt_inspection_violations (
|
|
232
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
233
|
+
user_guid TEXT NOT NULL,
|
|
234
|
+
conversation_id INTEGER,
|
|
235
|
+
violation_type TEXT NOT NULL,
|
|
236
|
+
severity TEXT NOT NULL,
|
|
237
|
+
prompt_snippet TEXT NOT NULL,
|
|
238
|
+
detection_method TEXT NOT NULL,
|
|
239
|
+
action_taken TEXT NOT NULL,
|
|
240
|
+
confidence_score REAL,
|
|
241
|
+
timestamp TIMESTAMP NOT NULL,
|
|
242
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id)
|
|
243
|
+
)
|
|
244
|
+
''')
|
|
245
|
+
|
|
246
|
+
# Tool permissions table - tracks user permissions for tool usage per conversation
|
|
247
|
+
cursor.execute('''
|
|
248
|
+
CREATE TABLE IF NOT EXISTS conversation_tool_permissions (
|
|
249
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
250
|
+
conversation_id INTEGER NOT NULL,
|
|
251
|
+
tool_name TEXT NOT NULL,
|
|
252
|
+
permission_state TEXT NOT NULL,
|
|
253
|
+
granted_at TIMESTAMP NOT NULL,
|
|
254
|
+
updated_at TIMESTAMP NOT NULL,
|
|
255
|
+
FOREIGN KEY (conversation_id) REFERENCES conversations(id),
|
|
256
|
+
UNIQUE(conversation_id, tool_name)
|
|
257
|
+
)
|
|
258
|
+
''')
|
|
259
|
+
|
|
260
|
+
# Autonomous actions table - stores scheduled action definitions
|
|
261
|
+
cursor.execute('''
|
|
262
|
+
CREATE TABLE IF NOT EXISTS autonomous_actions (
|
|
263
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
264
|
+
name TEXT NOT NULL,
|
|
265
|
+
description TEXT NOT NULL,
|
|
266
|
+
action_prompt TEXT NOT NULL,
|
|
267
|
+
model_id TEXT NOT NULL,
|
|
268
|
+
schedule_type TEXT NOT NULL,
|
|
269
|
+
schedule_config TEXT NOT NULL,
|
|
270
|
+
context_mode TEXT NOT NULL DEFAULT 'fresh',
|
|
271
|
+
max_failures INTEGER NOT NULL DEFAULT 3,
|
|
272
|
+
failure_count INTEGER DEFAULT 0,
|
|
273
|
+
is_enabled INTEGER DEFAULT 1,
|
|
274
|
+
max_tokens INTEGER NOT NULL DEFAULT 8192,
|
|
275
|
+
created_at TIMESTAMP NOT NULL,
|
|
276
|
+
last_run_at TIMESTAMP,
|
|
277
|
+
next_run_at TIMESTAMP,
|
|
278
|
+
user_guid TEXT
|
|
279
|
+
)
|
|
280
|
+
''')
|
|
281
|
+
|
|
282
|
+
# Action runs table - stores execution history for autonomous actions
|
|
283
|
+
cursor.execute('''
|
|
284
|
+
CREATE TABLE IF NOT EXISTS action_runs (
|
|
285
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
286
|
+
action_id INTEGER NOT NULL,
|
|
287
|
+
started_at TIMESTAMP NOT NULL,
|
|
288
|
+
completed_at TIMESTAMP,
|
|
289
|
+
status TEXT NOT NULL,
|
|
290
|
+
result_text TEXT,
|
|
291
|
+
result_html TEXT,
|
|
292
|
+
error_message TEXT,
|
|
293
|
+
input_tokens INTEGER DEFAULT 0,
|
|
294
|
+
output_tokens INTEGER DEFAULT 0,
|
|
295
|
+
context_snapshot TEXT,
|
|
296
|
+
user_guid TEXT,
|
|
297
|
+
FOREIGN KEY (action_id) REFERENCES autonomous_actions(id)
|
|
298
|
+
)
|
|
299
|
+
''')
|
|
300
|
+
|
|
301
|
+
# Action tool permissions table - snapshotted tool permissions for actions
|
|
302
|
+
cursor.execute('''
|
|
303
|
+
CREATE TABLE IF NOT EXISTS action_tool_permissions (
|
|
304
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
305
|
+
action_id INTEGER NOT NULL,
|
|
306
|
+
tool_name TEXT NOT NULL,
|
|
307
|
+
server_name TEXT,
|
|
308
|
+
permission_state TEXT NOT NULL,
|
|
309
|
+
granted_at TIMESTAMP NOT NULL,
|
|
310
|
+
user_guid TEXT,
|
|
311
|
+
FOREIGN KEY (action_id) REFERENCES autonomous_actions(id),
|
|
312
|
+
UNIQUE(action_id, tool_name)
|
|
313
|
+
)
|
|
314
|
+
''')
|
|
315
|
+
|
|
316
|
+
# Create indices for better query performance
|
|
317
|
+
_create_indices(conn)
|
|
318
|
+
|
|
319
|
+
# Migration: Add user_guid columns to all tables for multi-user support
|
|
320
|
+
_add_user_guid_columns(conn)
|
|
321
|
+
|
|
322
|
+
# Migration: Add max_tokens column to autonomous_actions table
|
|
323
|
+
_add_max_tokens_column(conn)
|
|
324
|
+
|
|
325
|
+
# Migration: Add daemon support columns and tables
|
|
326
|
+
_add_daemon_support(conn)
|
|
327
|
+
|
|
328
|
+
conn.commit()
|
|
329
|
+
logging.info("Database schema initialised")
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
def _create_indices(conn: sqlite3.Connection):
|
|
333
|
+
"""
|
|
334
|
+
Create database indices for improved query performance.
|
|
335
|
+
|
|
336
|
+
Args:
|
|
337
|
+
conn: SQLite database connection
|
|
338
|
+
"""
|
|
339
|
+
cursor = conn.cursor()
|
|
340
|
+
|
|
341
|
+
# Messages indices
|
|
342
|
+
cursor.execute('''
|
|
343
|
+
CREATE INDEX IF NOT EXISTS idx_messages_conversation
|
|
344
|
+
ON messages(conversation_id)
|
|
345
|
+
''')
|
|
346
|
+
|
|
347
|
+
cursor.execute('''
|
|
348
|
+
CREATE INDEX IF NOT EXISTS idx_messages_timestamp
|
|
349
|
+
ON messages(timestamp)
|
|
350
|
+
''')
|
|
351
|
+
|
|
352
|
+
# Conversations indices
|
|
353
|
+
cursor.execute('''
|
|
354
|
+
CREATE INDEX IF NOT EXISTS idx_conversations_active
|
|
355
|
+
ON conversations(is_active)
|
|
356
|
+
''')
|
|
357
|
+
|
|
358
|
+
# Files indices
|
|
359
|
+
cursor.execute('''
|
|
360
|
+
CREATE INDEX IF NOT EXISTS idx_files_conversation
|
|
361
|
+
ON conversation_files(conversation_id)
|
|
362
|
+
''')
|
|
363
|
+
|
|
364
|
+
# MCP transactions indices
|
|
365
|
+
cursor.execute('''
|
|
366
|
+
CREATE INDEX IF NOT EXISTS idx_mcp_transactions_conversation
|
|
367
|
+
ON mcp_transactions(conversation_id)
|
|
368
|
+
''')
|
|
369
|
+
|
|
370
|
+
cursor.execute('''
|
|
371
|
+
CREATE INDEX IF NOT EXISTS idx_mcp_transactions_timestamp
|
|
372
|
+
ON mcp_transactions(transaction_timestamp)
|
|
373
|
+
''')
|
|
374
|
+
|
|
375
|
+
cursor.execute('''
|
|
376
|
+
CREATE INDEX IF NOT EXISTS idx_mcp_transactions_tool
|
|
377
|
+
ON mcp_transactions(tool_name)
|
|
378
|
+
''')
|
|
379
|
+
|
|
380
|
+
# Model usage indices
|
|
381
|
+
cursor.execute('''
|
|
382
|
+
CREATE INDEX IF NOT EXISTS idx_model_usage_conversation
|
|
383
|
+
ON conversation_model_usage(conversation_id)
|
|
384
|
+
''')
|
|
385
|
+
|
|
386
|
+
# MCP servers indices
|
|
387
|
+
cursor.execute('''
|
|
388
|
+
CREATE INDEX IF NOT EXISTS idx_conversation_mcp_servers
|
|
389
|
+
ON conversation_mcp_servers(conversation_id)
|
|
390
|
+
''')
|
|
391
|
+
|
|
392
|
+
# Usage tracking indices
|
|
393
|
+
cursor.execute('''
|
|
394
|
+
CREATE INDEX IF NOT EXISTS idx_usage_tracking_timestamp
|
|
395
|
+
ON usage_tracking(timestamp)
|
|
396
|
+
''')
|
|
397
|
+
|
|
398
|
+
cursor.execute('''
|
|
399
|
+
CREATE INDEX IF NOT EXISTS idx_usage_tracking_conversation
|
|
400
|
+
ON usage_tracking(conversation_id)
|
|
401
|
+
''')
|
|
402
|
+
|
|
403
|
+
# Prompt inspection violations indices
|
|
404
|
+
cursor.execute('''
|
|
405
|
+
CREATE INDEX IF NOT EXISTS idx_violations_user_guid
|
|
406
|
+
ON prompt_inspection_violations(user_guid)
|
|
407
|
+
''')
|
|
408
|
+
|
|
409
|
+
cursor.execute('''
|
|
410
|
+
CREATE INDEX IF NOT EXISTS idx_violations_timestamp
|
|
411
|
+
ON prompt_inspection_violations(timestamp)
|
|
412
|
+
''')
|
|
413
|
+
|
|
414
|
+
cursor.execute('''
|
|
415
|
+
CREATE INDEX IF NOT EXISTS idx_violations_conversation
|
|
416
|
+
ON prompt_inspection_violations(conversation_id)
|
|
417
|
+
''')
|
|
418
|
+
|
|
419
|
+
cursor.execute('''
|
|
420
|
+
CREATE INDEX IF NOT EXISTS idx_violations_severity
|
|
421
|
+
ON prompt_inspection_violations(severity)
|
|
422
|
+
''')
|
|
423
|
+
|
|
424
|
+
# Tool permissions indices
|
|
425
|
+
cursor.execute('''
|
|
426
|
+
CREATE INDEX IF NOT EXISTS idx_tool_permissions_conversation
|
|
427
|
+
ON conversation_tool_permissions(conversation_id)
|
|
428
|
+
''')
|
|
429
|
+
|
|
430
|
+
cursor.execute('''
|
|
431
|
+
CREATE INDEX IF NOT EXISTS idx_tool_permissions_tool_name
|
|
432
|
+
ON conversation_tool_permissions(tool_name)
|
|
433
|
+
''')
|
|
434
|
+
|
|
435
|
+
# Autonomous actions indices
|
|
436
|
+
cursor.execute('''
|
|
437
|
+
CREATE INDEX IF NOT EXISTS idx_autonomous_actions_enabled
|
|
438
|
+
ON autonomous_actions(is_enabled)
|
|
439
|
+
''')
|
|
440
|
+
|
|
441
|
+
cursor.execute('''
|
|
442
|
+
CREATE INDEX IF NOT EXISTS idx_autonomous_actions_schedule_type
|
|
443
|
+
ON autonomous_actions(schedule_type)
|
|
444
|
+
''')
|
|
445
|
+
|
|
446
|
+
cursor.execute('''
|
|
447
|
+
CREATE INDEX IF NOT EXISTS idx_autonomous_actions_next_run
|
|
448
|
+
ON autonomous_actions(next_run_at)
|
|
449
|
+
''')
|
|
450
|
+
|
|
451
|
+
# Action runs indices
|
|
452
|
+
cursor.execute('''
|
|
453
|
+
CREATE INDEX IF NOT EXISTS idx_action_runs_action_id
|
|
454
|
+
ON action_runs(action_id)
|
|
455
|
+
''')
|
|
456
|
+
|
|
457
|
+
cursor.execute('''
|
|
458
|
+
CREATE INDEX IF NOT EXISTS idx_action_runs_status
|
|
459
|
+
ON action_runs(status)
|
|
460
|
+
''')
|
|
461
|
+
|
|
462
|
+
cursor.execute('''
|
|
463
|
+
CREATE INDEX IF NOT EXISTS idx_action_runs_started_at
|
|
464
|
+
ON action_runs(started_at)
|
|
465
|
+
''')
|
|
466
|
+
|
|
467
|
+
# Action tool permissions indices
|
|
468
|
+
cursor.execute('''
|
|
469
|
+
CREATE INDEX IF NOT EXISTS idx_action_tool_permissions_action_id
|
|
470
|
+
ON action_tool_permissions(action_id)
|
|
471
|
+
''')
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def _add_user_guid_columns(conn: sqlite3.Connection):
|
|
475
|
+
"""
|
|
476
|
+
Add user_guid columns to all tables for multi-user database support.
|
|
477
|
+
|
|
478
|
+
This migration prepares the database for future MySQL/MariaDB/PostgreSQL support
|
|
479
|
+
where multiple users may share the same database instance.
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
conn: SQLite database connection
|
|
483
|
+
"""
|
|
484
|
+
cursor = conn.cursor()
|
|
485
|
+
tables_to_migrate = [
|
|
486
|
+
'conversations',
|
|
487
|
+
'messages',
|
|
488
|
+
'rollup_history',
|
|
489
|
+
'conversation_files',
|
|
490
|
+
'mcp_transactions',
|
|
491
|
+
'conversation_model_usage',
|
|
492
|
+
'conversation_mcp_servers',
|
|
493
|
+
'usage_tracking',
|
|
494
|
+
'conversation_tool_permissions',
|
|
495
|
+
'autonomous_actions',
|
|
496
|
+
'action_runs',
|
|
497
|
+
'action_tool_permissions'
|
|
498
|
+
]
|
|
499
|
+
|
|
500
|
+
for table in tables_to_migrate:
|
|
501
|
+
try:
|
|
502
|
+
# Check if user_guid column exists
|
|
503
|
+
cursor.execute(f"SELECT user_guid FROM {table} LIMIT 1")
|
|
504
|
+
except sqlite3.OperationalError:
|
|
505
|
+
# Column doesn't exist, add it
|
|
506
|
+
cursor.execute(f"ALTER TABLE {table} ADD COLUMN user_guid TEXT")
|
|
507
|
+
conn.commit()
|
|
508
|
+
logging.info(f"Added user_guid column to {table} table")
|
|
509
|
+
|
|
510
|
+
# Create indices for user_guid columns for better query performance
|
|
511
|
+
for table in tables_to_migrate:
|
|
512
|
+
try:
|
|
513
|
+
cursor.execute(f'''
|
|
514
|
+
CREATE INDEX IF NOT EXISTS idx_{table}_user_guid
|
|
515
|
+
ON {table}(user_guid)
|
|
516
|
+
''')
|
|
517
|
+
except sqlite3.OperationalError as e:
|
|
518
|
+
logging.warning(f"Could not create index on {table}.user_guid: {e}")
|
|
519
|
+
|
|
520
|
+
conn.commit()
|
|
521
|
+
|
|
522
|
+
|
|
523
|
+
def _add_max_tokens_column(conn: sqlite3.Connection):
|
|
524
|
+
"""
|
|
525
|
+
Add max_tokens column to autonomous_actions table for existing databases.
|
|
526
|
+
|
|
527
|
+
This migration adds support for configurable max_tokens per action,
|
|
528
|
+
allowing different actions to have different token limits based on their needs.
|
|
529
|
+
|
|
530
|
+
Args:
|
|
531
|
+
conn: SQLite database connection
|
|
532
|
+
"""
|
|
533
|
+
cursor = conn.cursor()
|
|
534
|
+
|
|
535
|
+
try:
|
|
536
|
+
# Check if max_tokens column exists
|
|
537
|
+
cursor.execute("SELECT max_tokens FROM autonomous_actions LIMIT 1")
|
|
538
|
+
except sqlite3.OperationalError:
|
|
539
|
+
# Column doesn't exist, add it with default value
|
|
540
|
+
cursor.execute(
|
|
541
|
+
"ALTER TABLE autonomous_actions ADD COLUMN max_tokens INTEGER NOT NULL DEFAULT 8192"
|
|
542
|
+
)
|
|
543
|
+
conn.commit()
|
|
544
|
+
logging.info("Added max_tokens column to autonomous_actions table")
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
def _add_daemon_support(conn: sqlite3.Connection):
|
|
548
|
+
"""
|
|
549
|
+
Add daemon support columns and tables for autonomous action execution.
|
|
550
|
+
|
|
551
|
+
This migration adds:
|
|
552
|
+
- version column to autonomous_actions for change detection
|
|
553
|
+
- locked_by/locked_at columns for execution coordination
|
|
554
|
+
- daemon_registry table for daemon process tracking
|
|
555
|
+
|
|
556
|
+
Args:
|
|
557
|
+
conn: SQLite database connection
|
|
558
|
+
"""
|
|
559
|
+
cursor = conn.cursor()
|
|
560
|
+
|
|
561
|
+
# Add version column for change detection
|
|
562
|
+
try:
|
|
563
|
+
cursor.execute("SELECT version FROM autonomous_actions LIMIT 1")
|
|
564
|
+
except sqlite3.OperationalError:
|
|
565
|
+
cursor.execute(
|
|
566
|
+
"ALTER TABLE autonomous_actions ADD COLUMN version INTEGER DEFAULT 1"
|
|
567
|
+
)
|
|
568
|
+
conn.commit()
|
|
569
|
+
logging.info("Added version column to autonomous_actions table")
|
|
570
|
+
|
|
571
|
+
# Add locked_by column for execution coordination
|
|
572
|
+
try:
|
|
573
|
+
cursor.execute("SELECT locked_by FROM autonomous_actions LIMIT 1")
|
|
574
|
+
except sqlite3.OperationalError:
|
|
575
|
+
cursor.execute(
|
|
576
|
+
"ALTER TABLE autonomous_actions ADD COLUMN locked_by TEXT DEFAULT NULL"
|
|
577
|
+
)
|
|
578
|
+
conn.commit()
|
|
579
|
+
logging.info("Added locked_by column to autonomous_actions table")
|
|
580
|
+
|
|
581
|
+
# Add locked_at column for execution coordination
|
|
582
|
+
try:
|
|
583
|
+
cursor.execute("SELECT locked_at FROM autonomous_actions LIMIT 1")
|
|
584
|
+
except sqlite3.OperationalError:
|
|
585
|
+
cursor.execute(
|
|
586
|
+
"ALTER TABLE autonomous_actions ADD COLUMN locked_at TIMESTAMP DEFAULT NULL"
|
|
587
|
+
)
|
|
588
|
+
conn.commit()
|
|
589
|
+
logging.info("Added locked_at column to autonomous_actions table")
|
|
590
|
+
|
|
591
|
+
# Add updated_at column for tracking modifications
|
|
592
|
+
try:
|
|
593
|
+
cursor.execute("SELECT updated_at FROM autonomous_actions LIMIT 1")
|
|
594
|
+
except sqlite3.OperationalError:
|
|
595
|
+
cursor.execute(
|
|
596
|
+
"ALTER TABLE autonomous_actions ADD COLUMN updated_at TIMESTAMP DEFAULT NULL"
|
|
597
|
+
)
|
|
598
|
+
conn.commit()
|
|
599
|
+
logging.info("Added updated_at column to autonomous_actions table")
|
|
600
|
+
|
|
601
|
+
# Create daemon_registry table for tracking daemon processes
|
|
602
|
+
cursor.execute('''
|
|
603
|
+
CREATE TABLE IF NOT EXISTS daemon_registry (
|
|
604
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
605
|
+
daemon_id TEXT NOT NULL UNIQUE,
|
|
606
|
+
hostname TEXT NOT NULL,
|
|
607
|
+
pid INTEGER NOT NULL,
|
|
608
|
+
started_at TIMESTAMP NOT NULL,
|
|
609
|
+
last_heartbeat TIMESTAMP NOT NULL,
|
|
610
|
+
status TEXT NOT NULL DEFAULT 'running',
|
|
611
|
+
user_guid TEXT
|
|
612
|
+
)
|
|
613
|
+
''')
|
|
614
|
+
|
|
615
|
+
# Create index for daemon_registry
|
|
616
|
+
cursor.execute('''
|
|
617
|
+
CREATE INDEX IF NOT EXISTS idx_daemon_registry_status
|
|
618
|
+
ON daemon_registry(status)
|
|
619
|
+
''')
|
|
620
|
+
|
|
621
|
+
cursor.execute('''
|
|
622
|
+
CREATE INDEX IF NOT EXISTS idx_daemon_registry_user_guid
|
|
623
|
+
ON daemon_registry(user_guid)
|
|
624
|
+
''')
|
|
625
|
+
|
|
626
|
+
conn.commit()
|
|
627
|
+
logging.info("Daemon support schema migration complete")
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def migrate_user_guid(conn: sqlite3.Connection, user_guid: str):
|
|
631
|
+
"""
|
|
632
|
+
Migrate existing records to assign current user's GUID.
|
|
633
|
+
|
|
634
|
+
This function assigns the current user's GUID to all records that don't have one,
|
|
635
|
+
ensuring backward compatibility with existing databases.
|
|
636
|
+
|
|
637
|
+
Args:
|
|
638
|
+
conn: SQLite database connection
|
|
639
|
+
user_guid: The current user's GUID
|
|
640
|
+
"""
|
|
641
|
+
cursor = conn.cursor()
|
|
642
|
+
tables_to_migrate = [
|
|
643
|
+
'conversations',
|
|
644
|
+
'messages',
|
|
645
|
+
'rollup_history',
|
|
646
|
+
'conversation_files',
|
|
647
|
+
'mcp_transactions',
|
|
648
|
+
'conversation_model_usage',
|
|
649
|
+
'conversation_mcp_servers',
|
|
650
|
+
'usage_tracking',
|
|
651
|
+
'conversation_tool_permissions',
|
|
652
|
+
'autonomous_actions',
|
|
653
|
+
'action_runs',
|
|
654
|
+
'action_tool_permissions'
|
|
655
|
+
]
|
|
656
|
+
|
|
657
|
+
for table in tables_to_migrate:
|
|
658
|
+
try:
|
|
659
|
+
# Update records with NULL or empty user_guid
|
|
660
|
+
cursor.execute(f'''
|
|
661
|
+
UPDATE {table}
|
|
662
|
+
SET user_guid = ?
|
|
663
|
+
WHERE user_guid IS NULL OR user_guid = ''
|
|
664
|
+
''', (user_guid,))
|
|
665
|
+
|
|
666
|
+
rows_updated = cursor.rowcount
|
|
667
|
+
if rows_updated > 0:
|
|
668
|
+
logging.info(f"Migrated {rows_updated} records in {table} table to user GUID: {user_guid}")
|
|
669
|
+
except sqlite3.OperationalError as e:
|
|
670
|
+
# Table might not have user_guid column yet, or doesn't exist
|
|
671
|
+
logging.debug(f"Could not migrate {table}: {e}")
|
|
672
|
+
|
|
673
|
+
conn.commit()
|