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.
Files changed (96) hide show
  1. dtSpark/__init__.py +0 -0
  2. dtSpark/_description.txt +1 -0
  3. dtSpark/_full_name.txt +1 -0
  4. dtSpark/_licence.txt +21 -0
  5. dtSpark/_metadata.yaml +6 -0
  6. dtSpark/_name.txt +1 -0
  7. dtSpark/_version.txt +1 -0
  8. dtSpark/aws/__init__.py +7 -0
  9. dtSpark/aws/authentication.py +296 -0
  10. dtSpark/aws/bedrock.py +578 -0
  11. dtSpark/aws/costs.py +318 -0
  12. dtSpark/aws/pricing.py +580 -0
  13. dtSpark/cli_interface.py +2645 -0
  14. dtSpark/conversation_manager.py +3050 -0
  15. dtSpark/core/__init__.py +12 -0
  16. dtSpark/core/application.py +3355 -0
  17. dtSpark/core/context_compaction.py +735 -0
  18. dtSpark/daemon/__init__.py +104 -0
  19. dtSpark/daemon/__main__.py +10 -0
  20. dtSpark/daemon/action_monitor.py +213 -0
  21. dtSpark/daemon/daemon_app.py +730 -0
  22. dtSpark/daemon/daemon_manager.py +289 -0
  23. dtSpark/daemon/execution_coordinator.py +194 -0
  24. dtSpark/daemon/pid_file.py +169 -0
  25. dtSpark/database/__init__.py +482 -0
  26. dtSpark/database/autonomous_actions.py +1191 -0
  27. dtSpark/database/backends.py +329 -0
  28. dtSpark/database/connection.py +122 -0
  29. dtSpark/database/conversations.py +520 -0
  30. dtSpark/database/credential_prompt.py +218 -0
  31. dtSpark/database/files.py +205 -0
  32. dtSpark/database/mcp_ops.py +355 -0
  33. dtSpark/database/messages.py +161 -0
  34. dtSpark/database/schema.py +673 -0
  35. dtSpark/database/tool_permissions.py +186 -0
  36. dtSpark/database/usage.py +167 -0
  37. dtSpark/files/__init__.py +4 -0
  38. dtSpark/files/manager.py +322 -0
  39. dtSpark/launch.py +39 -0
  40. dtSpark/limits/__init__.py +10 -0
  41. dtSpark/limits/costs.py +296 -0
  42. dtSpark/limits/tokens.py +342 -0
  43. dtSpark/llm/__init__.py +17 -0
  44. dtSpark/llm/anthropic_direct.py +446 -0
  45. dtSpark/llm/base.py +146 -0
  46. dtSpark/llm/context_limits.py +438 -0
  47. dtSpark/llm/manager.py +177 -0
  48. dtSpark/llm/ollama.py +578 -0
  49. dtSpark/mcp_integration/__init__.py +5 -0
  50. dtSpark/mcp_integration/manager.py +653 -0
  51. dtSpark/mcp_integration/tool_selector.py +225 -0
  52. dtSpark/resources/config.yaml.template +631 -0
  53. dtSpark/safety/__init__.py +22 -0
  54. dtSpark/safety/llm_service.py +111 -0
  55. dtSpark/safety/patterns.py +229 -0
  56. dtSpark/safety/prompt_inspector.py +442 -0
  57. dtSpark/safety/violation_logger.py +346 -0
  58. dtSpark/scheduler/__init__.py +20 -0
  59. dtSpark/scheduler/creation_tools.py +599 -0
  60. dtSpark/scheduler/execution_queue.py +159 -0
  61. dtSpark/scheduler/executor.py +1152 -0
  62. dtSpark/scheduler/manager.py +395 -0
  63. dtSpark/tools/__init__.py +4 -0
  64. dtSpark/tools/builtin.py +833 -0
  65. dtSpark/web/__init__.py +20 -0
  66. dtSpark/web/auth.py +152 -0
  67. dtSpark/web/dependencies.py +37 -0
  68. dtSpark/web/endpoints/__init__.py +17 -0
  69. dtSpark/web/endpoints/autonomous_actions.py +1125 -0
  70. dtSpark/web/endpoints/chat.py +621 -0
  71. dtSpark/web/endpoints/conversations.py +353 -0
  72. dtSpark/web/endpoints/main_menu.py +547 -0
  73. dtSpark/web/endpoints/streaming.py +421 -0
  74. dtSpark/web/server.py +578 -0
  75. dtSpark/web/session.py +167 -0
  76. dtSpark/web/ssl_utils.py +195 -0
  77. dtSpark/web/static/css/dark-theme.css +427 -0
  78. dtSpark/web/static/js/actions.js +1101 -0
  79. dtSpark/web/static/js/chat.js +614 -0
  80. dtSpark/web/static/js/main.js +496 -0
  81. dtSpark/web/static/js/sse-client.js +242 -0
  82. dtSpark/web/templates/actions.html +408 -0
  83. dtSpark/web/templates/base.html +93 -0
  84. dtSpark/web/templates/chat.html +814 -0
  85. dtSpark/web/templates/conversations.html +350 -0
  86. dtSpark/web/templates/goodbye.html +81 -0
  87. dtSpark/web/templates/login.html +90 -0
  88. dtSpark/web/templates/main_menu.html +983 -0
  89. dtSpark/web/templates/new_conversation.html +191 -0
  90. dtSpark/web/web_interface.py +137 -0
  91. dtspark-1.0.4.dist-info/METADATA +187 -0
  92. dtspark-1.0.4.dist-info/RECORD +96 -0
  93. dtspark-1.0.4.dist-info/WHEEL +5 -0
  94. dtspark-1.0.4.dist-info/entry_points.txt +3 -0
  95. dtspark-1.0.4.dist-info/licenses/LICENSE +21 -0
  96. 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()