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,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,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)
|