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,104 @@
1
+ """
2
+ Daemon module for autonomous action execution.
3
+
4
+ Provides a background daemon process that:
5
+ - Runs autonomous actions on schedule
6
+ - Polls database for action changes (new, modified, deleted)
7
+ - Coordinates execution to prevent conflicts with web UI/CLI
8
+
9
+
10
+ """
11
+
12
+ from .pid_file import PIDFile
13
+ from .daemon_manager import DaemonManager
14
+
15
+ __all__ = [
16
+ 'PIDFile',
17
+ 'DaemonManager',
18
+ ]
19
+
20
+
21
+ def daemon_main():
22
+ """
23
+ Entry point for daemon CLI commands.
24
+
25
+ Usage:
26
+ dtSpark daemon start [--poll-interval N]
27
+ dtSpark daemon stop
28
+ dtSpark daemon status
29
+ dtSpark daemon restart
30
+ """
31
+ import sys
32
+ import time
33
+ from pathlib import Path
34
+
35
+ # Get settings for PID file location
36
+ try:
37
+ from dtPyAppFramework.settings import Settings
38
+ settings = Settings()
39
+ pid_file_path = settings.get('daemon.pid_file', './daemon.pid')
40
+ except Exception:
41
+ # Fallback if settings can't be loaded
42
+ pid_file_path = './daemon.pid'
43
+
44
+ manager = DaemonManager(pid_file_path=pid_file_path)
45
+
46
+ # Handle direct module invocation: python -m dtSpark.daemon --run
47
+ # In this case, --run is at sys.argv[1] instead of sys.argv[2]
48
+ if len(sys.argv) >= 2 and sys.argv[1] == '--run':
49
+ command = '--run'
50
+ args = sys.argv[2:]
51
+ elif len(sys.argv) < 3:
52
+ print("Usage: dtSpark daemon {start|stop|status|restart} [options]")
53
+ print("")
54
+ print("Commands:")
55
+ print(" start Start the daemon in the background")
56
+ print(" stop Stop the running daemon")
57
+ print(" status Check if daemon is running")
58
+ print(" restart Restart the daemon")
59
+ print("")
60
+ print("Options for 'start':")
61
+ print(" --poll-interval N Seconds between database polls (default: 30)")
62
+ print(" --foreground Run in foreground (for debugging)")
63
+ sys.exit(1)
64
+ else:
65
+ command = sys.argv[2]
66
+ args = sys.argv[3:]
67
+
68
+ if command == 'start':
69
+ sys.exit(manager.start(args))
70
+ elif command == 'stop':
71
+ sys.exit(manager.stop())
72
+ elif command == 'status':
73
+ sys.exit(manager.status())
74
+ elif command == 'restart':
75
+ manager.stop()
76
+ time.sleep(2)
77
+ sys.exit(manager.start(args))
78
+ elif command == '--run':
79
+ # Internal: Actually run the daemon (called by start in background)
80
+ # Clean up sys.argv - AbstractApp expects program name and valid args
81
+ sys.argv = ['dtSpark-daemon'] + args
82
+
83
+ # Set up error logging to file for background mode debugging
84
+ error_log_path = './daemon_error.log'
85
+
86
+ try:
87
+ from .daemon_app import DaemonApplication
88
+ app = DaemonApplication()
89
+ app.run()
90
+ except Exception as e:
91
+ import traceback
92
+ error_msg = f"Daemon failed to start: {e}\n{traceback.format_exc()}"
93
+ print(error_msg)
94
+ # Also write to error log file for background mode
95
+ try:
96
+ with open(error_log_path, 'w') as f:
97
+ f.write(error_msg)
98
+ except Exception:
99
+ pass
100
+ sys.exit(1)
101
+ else:
102
+ print(f"Unknown command: {command}")
103
+ print("Use: dtSpark daemon {start|stop|status|restart}")
104
+ sys.exit(1)
@@ -0,0 +1,10 @@
1
+ """
2
+ Module entry point for daemon.
3
+
4
+ Allows running the daemon with: python -m dtSpark.daemon
5
+ """
6
+
7
+ from . import daemon_main
8
+
9
+ if __name__ == "__main__":
10
+ daemon_main()
@@ -0,0 +1,213 @@
1
+ """
2
+ Action change monitor for daemon process.
3
+
4
+ Polls the database for changes to autonomous actions and notifies
5
+ the scheduler when actions are added, modified, or deleted.
6
+
7
+
8
+ """
9
+
10
+ import logging
11
+ import threading
12
+ from typing import Dict, Callable, Optional, List
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class ActionChangeMonitor:
18
+ """
19
+ Monitors database for autonomous action changes.
20
+
21
+ Uses version column to detect modifications efficiently.
22
+ Notifies scheduler when actions are added, modified, or deleted.
23
+ """
24
+
25
+ def __init__(
26
+ self,
27
+ database,
28
+ user_guid: str,
29
+ poll_interval: int = 30,
30
+ on_action_added: Optional[Callable[[Dict], None]] = None,
31
+ on_action_modified: Optional[Callable[[Dict], None]] = None,
32
+ on_action_deleted: Optional[Callable[[int], None]] = None,
33
+ ):
34
+ """
35
+ Initialise the action change monitor.
36
+
37
+ Args:
38
+ database: Database instance with autonomous_actions methods
39
+ user_guid: User GUID for filtering actions
40
+ poll_interval: Seconds between database polls
41
+ on_action_added: Callback when new action detected
42
+ on_action_modified: Callback when action modified
43
+ on_action_deleted: Callback when action deleted
44
+ """
45
+ self.database = database
46
+ self.user_guid = user_guid
47
+ self.poll_interval = poll_interval
48
+
49
+ # Callbacks
50
+ self.on_action_added = on_action_added
51
+ self.on_action_modified = on_action_modified
52
+ self.on_action_deleted = on_action_deleted
53
+
54
+ # State tracking
55
+ self._known_actions: Dict[int, int] = {} # action_id -> version
56
+ self._stop_event = threading.Event()
57
+ self._monitor_thread: Optional[threading.Thread] = None
58
+ self._is_running = False
59
+
60
+ def start(self):
61
+ """Start the monitoring thread."""
62
+ if self._is_running:
63
+ print("Action monitor already running")
64
+ logger.warning("Action monitor already running")
65
+ return
66
+
67
+ self._stop_event.clear()
68
+ self._is_running = True
69
+
70
+ self._monitor_thread = threading.Thread(
71
+ target=self._monitor_loop,
72
+ name="ActionChangeMonitor",
73
+ daemon=True
74
+ )
75
+ self._monitor_thread.start()
76
+ print(f"Action monitor started (poll interval: {self.poll_interval}s)")
77
+ logger.info(f"Action monitor started (poll interval: {self.poll_interval}s)")
78
+
79
+ def stop(self, timeout: int = 10):
80
+ """
81
+ Stop the monitoring thread.
82
+
83
+ Args:
84
+ timeout: Seconds to wait for thread to stop
85
+ """
86
+ if not self._is_running:
87
+ return
88
+
89
+ logger.info("Stopping action monitor...")
90
+ self._stop_event.set()
91
+
92
+ if self._monitor_thread and self._monitor_thread.is_alive():
93
+ self._monitor_thread.join(timeout=timeout)
94
+ if self._monitor_thread.is_alive():
95
+ logger.warning("Action monitor thread did not stop gracefully")
96
+
97
+ self._is_running = False
98
+ logger.info("Action monitor stopped")
99
+
100
+ def is_running(self) -> bool:
101
+ """Check if monitor is running."""
102
+ return self._is_running
103
+
104
+ def load_initial_state(self):
105
+ """
106
+ Load initial action state from database.
107
+
108
+ Should be called before starting the monitor to establish baseline.
109
+ """
110
+ try:
111
+ print(f"Loading initial action state for user_guid: {self.user_guid}")
112
+ actions = self._get_actions_with_version()
113
+ for action in actions:
114
+ action_id = action['id']
115
+ version = action.get('version', 1)
116
+ self._known_actions[action_id] = version
117
+
118
+ print(f"Loaded initial state: {len(self._known_actions)} actions")
119
+ logger.info(f"Loaded initial state: {len(self._known_actions)} actions")
120
+ except Exception as e:
121
+ print(f"Failed to load initial action state: {e}")
122
+ logger.error(f"Failed to load initial action state: {e}")
123
+
124
+ def _monitor_loop(self):
125
+ """Main monitoring loop."""
126
+ # Load initial state on first run
127
+ if not self._known_actions:
128
+ self.load_initial_state()
129
+
130
+ poll_count = 0
131
+ while not self._stop_event.is_set():
132
+ try:
133
+ poll_count += 1
134
+ logger.debug(f"Action monitor poll #{poll_count}")
135
+ self._check_for_changes()
136
+ except Exception as e:
137
+ logger.error(f"Error in action monitor: {e}", exc_info=True)
138
+
139
+ # Wait for next poll or stop signal
140
+ self._stop_event.wait(timeout=self.poll_interval)
141
+
142
+ def _check_for_changes(self):
143
+ """Check database for action changes."""
144
+ try:
145
+ current_actions = self._get_actions_with_version()
146
+ logger.info(f"Action monitor: found {len(current_actions)} actions in database (tracking {len(self._known_actions)})")
147
+ except Exception as e:
148
+ logger.error(f"Failed to query actions: {e}")
149
+ return
150
+
151
+ current_ids = set()
152
+
153
+ for action in current_actions:
154
+ action_id = action['id']
155
+ version = action.get('version', 1)
156
+ current_ids.add(action_id)
157
+
158
+ if action_id not in self._known_actions:
159
+ # New action detected
160
+ logger.info(f"New action detected: {action['name']} (ID: {action_id})")
161
+ self._known_actions[action_id] = version
162
+ if self.on_action_added:
163
+ try:
164
+ self.on_action_added(action)
165
+ except Exception as e:
166
+ logger.error(f"Error in on_action_added callback: {e}")
167
+
168
+ elif self._known_actions[action_id] != version:
169
+ # Modified action detected
170
+ logger.info(f"Action modified: {action['name']} (ID: {action_id}, v{self._known_actions[action_id]} -> v{version})")
171
+ self._known_actions[action_id] = version
172
+ if self.on_action_modified:
173
+ try:
174
+ self.on_action_modified(action)
175
+ except Exception as e:
176
+ logger.error(f"Error in on_action_modified callback: {e}")
177
+
178
+ # Check for deleted actions
179
+ deleted_ids = set(self._known_actions.keys()) - current_ids
180
+ for action_id in deleted_ids:
181
+ logger.info(f"Action deleted: ID {action_id}")
182
+ del self._known_actions[action_id]
183
+ if self.on_action_deleted:
184
+ try:
185
+ self.on_action_deleted(action_id)
186
+ except Exception as e:
187
+ logger.error(f"Error in on_action_deleted callback: {e}")
188
+
189
+ def _get_actions_with_version(self) -> List[Dict]:
190
+ """Get all actions with version information."""
191
+ from dtSpark.database.autonomous_actions import get_all_actions_with_version
192
+ return get_all_actions_with_version(
193
+ conn=self.database.conn,
194
+ user_guid=self.user_guid,
195
+ include_disabled=False # Only monitor enabled actions
196
+ )
197
+
198
+ def force_refresh(self):
199
+ """
200
+ Force an immediate check for changes.
201
+
202
+ Useful after making changes locally to detect them immediately.
203
+ """
204
+ logger.debug("Force refresh triggered")
205
+ self._check_for_changes()
206
+
207
+ def get_known_action_ids(self) -> List[int]:
208
+ """Get list of known action IDs."""
209
+ return list(self._known_actions.keys())
210
+
211
+ def get_known_action_version(self, action_id: int) -> Optional[int]:
212
+ """Get the known version of an action."""
213
+ return self._known_actions.get(action_id)