dtSpark 1.1.0a2__py3-none-any.whl → 1.1.0a6__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 (56) hide show
  1. dtSpark/_version.txt +1 -1
  2. dtSpark/aws/authentication.py +1 -1
  3. dtSpark/aws/bedrock.py +238 -239
  4. dtSpark/aws/costs.py +9 -5
  5. dtSpark/aws/pricing.py +25 -21
  6. dtSpark/cli_interface.py +69 -62
  7. dtSpark/conversation_manager.py +54 -47
  8. dtSpark/core/application.py +151 -111
  9. dtSpark/core/context_compaction.py +241 -226
  10. dtSpark/daemon/__init__.py +36 -22
  11. dtSpark/daemon/action_monitor.py +46 -17
  12. dtSpark/daemon/daemon_app.py +126 -104
  13. dtSpark/daemon/daemon_manager.py +59 -23
  14. dtSpark/daemon/pid_file.py +3 -2
  15. dtSpark/database/autonomous_actions.py +3 -0
  16. dtSpark/database/credential_prompt.py +52 -54
  17. dtSpark/files/manager.py +6 -12
  18. dtSpark/limits/__init__.py +1 -1
  19. dtSpark/limits/tokens.py +2 -2
  20. dtSpark/llm/anthropic_direct.py +246 -141
  21. dtSpark/llm/ollama.py +3 -1
  22. dtSpark/mcp_integration/manager.py +4 -4
  23. dtSpark/mcp_integration/tool_selector.py +83 -77
  24. dtSpark/resources/config.yaml.template +10 -0
  25. dtSpark/safety/patterns.py +45 -46
  26. dtSpark/safety/prompt_inspector.py +8 -1
  27. dtSpark/scheduler/creation_tools.py +273 -181
  28. dtSpark/scheduler/executor.py +503 -221
  29. dtSpark/tools/builtin.py +70 -53
  30. dtSpark/web/endpoints/autonomous_actions.py +12 -9
  31. dtSpark/web/endpoints/chat.py +18 -6
  32. dtSpark/web/endpoints/conversations.py +57 -17
  33. dtSpark/web/endpoints/main_menu.py +132 -105
  34. dtSpark/web/endpoints/streaming.py +2 -2
  35. dtSpark/web/server.py +65 -5
  36. dtSpark/web/ssl_utils.py +3 -3
  37. dtSpark/web/static/css/dark-theme.css +8 -29
  38. dtSpark/web/static/js/actions.js +2 -1
  39. dtSpark/web/static/js/chat.js +6 -8
  40. dtSpark/web/static/js/main.js +8 -8
  41. dtSpark/web/static/js/sse-client.js +130 -122
  42. dtSpark/web/templates/actions.html +5 -5
  43. dtSpark/web/templates/base.html +13 -0
  44. dtSpark/web/templates/chat.html +52 -50
  45. dtSpark/web/templates/conversations.html +50 -22
  46. dtSpark/web/templates/goodbye.html +2 -2
  47. dtSpark/web/templates/main_menu.html +17 -17
  48. dtSpark/web/templates/new_conversation.html +51 -20
  49. dtSpark/web/web_interface.py +2 -2
  50. {dtspark-1.1.0a2.dist-info → dtspark-1.1.0a6.dist-info}/METADATA +9 -2
  51. dtspark-1.1.0a6.dist-info/RECORD +96 -0
  52. dtspark-1.1.0a2.dist-info/RECORD +0 -96
  53. {dtspark-1.1.0a2.dist-info → dtspark-1.1.0a6.dist-info}/WHEEL +0 -0
  54. {dtspark-1.1.0a2.dist-info → dtspark-1.1.0a6.dist-info}/entry_points.txt +0 -0
  55. {dtspark-1.1.0a2.dist-info → dtspark-1.1.0a6.dist-info}/licenses/LICENSE +0 -0
  56. {dtspark-1.1.0a2.dist-info → dtspark-1.1.0a6.dist-info}/top_level.txt +0 -0
@@ -624,7 +624,8 @@ async function loadModels() {
624
624
  const response = await fetch('/api/models');
625
625
  if (!response.ok) throw new Error('Failed to load models');
626
626
 
627
- availableModels = await response.json();
627
+ const data = await response.json();
628
+ availableModels = data.models || data;
628
629
 
629
630
  const select = document.getElementById('actionModel');
630
631
  select.innerHTML = '<option value="">Select a model...</option>';
@@ -50,8 +50,6 @@ async function loadChatHistory(conversationId) {
50
50
  * @param {string} timestamp - Message timestamp (optional)
51
51
  */
52
52
  function appendMessage(role, content, timestamp = null) {
53
- const messagesContainer = document.getElementById('chat-messages');
54
-
55
53
  // Check if content contains tool results
56
54
  if (content.startsWith('[TOOL_RESULTS]')) {
57
55
  appendToolResults(content, timestamp);
@@ -107,7 +105,7 @@ function appendRegularMessage(role, content, timestamp = null) {
107
105
  messageDiv.className = `chat-message ${role}`;
108
106
 
109
107
  // Generate unique ID for the copy button
110
- const copyBtnId = 'copy-btn-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
108
+ const copyBtnId = 'copy-btn-' + Date.now() + '-' + Math.random().toString(36).substring(2, 11);
111
109
 
112
110
  // Create message header with copy icon
113
111
  const header = document.createElement('div');
@@ -308,8 +306,8 @@ function removeStatus(idOrElement) {
308
306
  document.getElementById(idOrElement) :
309
307
  idOrElement;
310
308
 
311
- if (element && element.parentNode) {
312
- element.parentNode.removeChild(element);
309
+ if (element?.parentNode) {
310
+ element.remove();
313
311
  }
314
312
  }
315
313
 
@@ -360,7 +358,7 @@ function updateStreamingMessage(content, messageElement = null) {
360
358
  messageElement.className = 'chat-message assistant';
361
359
 
362
360
  // Generate unique ID for the copy button
363
- const copyBtnId = 'stream-copy-btn-' + Date.now() + '-' + Math.random().toString(36).substr(2, 9);
361
+ const copyBtnId = 'stream-copy-btn-' + Date.now() + '-' + Math.random().toString(36).substring(2, 11);
364
362
 
365
363
  messageElement.innerHTML = `
366
364
  <div class="message-header d-flex justify-content-between align-items-center">
@@ -463,7 +461,7 @@ function showToast(message, type = 'info') {
463
461
  const toast = document.createElement('div');
464
462
  toast.id = toastId;
465
463
  toast.className = 'toast';
466
- toast.setAttribute('role', 'alert');
464
+ toast.role = 'alert';
467
465
 
468
466
  let bgClass = 'bg-primary';
469
467
  if (type === 'success') bgClass = 'bg-success';
@@ -550,7 +548,7 @@ async function showToolPermissionDialog(requestId, toolName, toolDescription) {
550
548
  const buttons = modalElement.querySelectorAll('.permission-btn');
551
549
  buttons.forEach(button => {
552
550
  button.addEventListener('click', async () => {
553
- const response = button.getAttribute('data-response');
551
+ const response = button.dataset.response;
554
552
 
555
553
  // Send response to server
556
554
  try {
@@ -74,7 +74,7 @@ if (typeof marked !== 'undefined') {
74
74
 
75
75
  // Handle mermaid diagrams
76
76
  if (lang === 'mermaid') {
77
- const id = 'mermaid-' + Math.random().toString(36).substr(2, 9);
77
+ const id = 'mermaid-' + Math.random().toString(36).substring(2, 11);
78
78
  return `<div class="mermaid-container"><pre class="mermaid" id="${id}">${escapeHtmlForMermaid(code)}</pre></div>`;
79
79
  }
80
80
 
@@ -157,7 +157,7 @@ async function copySvgToClipboard(svgElement, button) {
157
157
  bgRect.setAttribute('width', '100%');
158
158
  bgRect.setAttribute('height', '100%');
159
159
  bgRect.setAttribute('fill', '#1a1a1a');
160
- svgClone.insertBefore(bgRect, svgClone.firstChild);
160
+ svgClone.prepend(bgRect);
161
161
 
162
162
  // Serialise SVG to string
163
163
  const serializer = new XMLSerializer();
@@ -255,7 +255,7 @@ async function renderMermaidDiagrams(container) {
255
255
 
256
256
  for (const block of mermaidBlocks) {
257
257
  try {
258
- const id = block.id || 'mermaid-' + Math.random().toString(36).substr(2, 9);
258
+ const id = block.id || 'mermaid-' + Math.random().toString(36).substring(2, 11);
259
259
  const code = block.textContent;
260
260
 
261
261
  // Render the diagram
@@ -287,7 +287,7 @@ async function renderMermaidDiagrams(container) {
287
287
  }
288
288
  };
289
289
 
290
- block.parentNode.replaceChild(wrapper, block);
290
+ block.replaceWith(wrapper);
291
291
  } catch (e) {
292
292
  console.error('Mermaid rendering error:', e);
293
293
  // Show error message in the block
@@ -370,9 +370,9 @@ function showToast(message, type = 'info') {
370
370
  // Create toast
371
371
  const toast = document.createElement('div');
372
372
  toast.className = `toast align-items-center text-white bg-${type === 'error' ? 'danger' : type} border-0`;
373
- toast.setAttribute('role', 'alert');
374
- toast.setAttribute('aria-live', 'assertive');
375
- toast.setAttribute('aria-atomic', 'true');
373
+ toast.role = 'alert';
374
+ toast.ariaLive = 'assertive';
375
+ toast.ariaAtomic = 'true';
376
376
 
377
377
  toast.innerHTML = `
378
378
  <div class="d-flex">
@@ -432,7 +432,7 @@ function downloadFile(content, filename, mimeType = 'text/plain') {
432
432
  link.download = filename;
433
433
  document.body.appendChild(link);
434
434
  link.click();
435
- document.body.removeChild(link);
435
+ link.remove();
436
436
  URL.revokeObjectURL(url);
437
437
  }
438
438
 
@@ -17,151 +17,159 @@ async function sendMessageWithSSE(conversationId, message) {
17
17
  let streamingMessageElement = null;
18
18
  let accumulatedContent = '';
19
19
 
20
- try {
21
- // Create EventSource for SSE
22
- const encodedMessage = encodeURIComponent(message);
23
- const eventSource = new EventSource(
24
- `/api/stream/chat?message=${encodedMessage}&conversation_id=${conversationId}`
25
- );
26
-
27
- // Handle different event types
28
- eventSource.addEventListener('status', (event) => {
29
- const data = JSON.parse(event.data);
30
- console.log('Status:', data);
31
-
32
- // Update typing indicator with status
33
- if (typingIndicator && typingIndicator.parentNode) {
34
- const statusText = data.message || '';
35
- typingIndicator.querySelector('.visually-hidden').nextSibling.textContent = statusText;
36
- }
37
- });
20
+ return new Promise((resolve) => {
21
+ try {
22
+ // Create EventSource for SSE
23
+ const encodedMessage = encodeURIComponent(message);
24
+ const eventSource = new EventSource(
25
+ `/api/stream/chat?message=${encodedMessage}&conversation_id=${conversationId}`
26
+ );
27
+
28
+ // Handle different event types
29
+ eventSource.addEventListener('status', (event) => {
30
+ const data = JSON.parse(event.data);
31
+ console.log('Status:', data);
32
+
33
+ // Update typing indicator with status
34
+ if (typingIndicator && typingIndicator.parentNode) {
35
+ const statusText = data.message || '';
36
+ typingIndicator.querySelector('.visually-hidden').nextSibling.textContent = statusText;
37
+ }
38
+ });
38
39
 
39
- eventSource.addEventListener('response', (event) => {
40
- const data = JSON.parse(event.data);
41
- console.log('Received response event:', data);
40
+ eventSource.addEventListener('response', (event) => {
41
+ const data = JSON.parse(event.data);
42
+ console.log('Received response event:', data);
42
43
 
43
- // Hide typing indicator on first response
44
- if (typingIndicator && typingIndicator.parentNode) {
45
- console.log('Hiding typing indicator');
46
- hideTypingIndicator();
47
- typingIndicator = null; // Clear reference after hiding
48
- }
44
+ // Hide typing indicator on first response
45
+ if (typingIndicator && typingIndicator.parentNode) {
46
+ console.log('Hiding typing indicator');
47
+ hideTypingIndicator();
48
+ typingIndicator = null; // Clear reference after hiding
49
+ }
49
50
 
50
- if (data.type === 'text') {
51
- if (data.final) {
52
- // Final response - this is the main assistant response after tool execution
53
- console.log('Final response received, content length:', data.content ? data.content.length : 0);
54
- accumulatedContent += data.content;
55
- console.log('Accumulated content:', accumulatedContent);
56
-
57
- // Ensure we have content before displaying
58
- if (accumulatedContent.trim().length > 0) {
59
- streamingMessageElement = updateStreamingMessage(
60
- accumulatedContent,
61
- streamingMessageElement
62
- );
63
- console.log('Updated streaming message element:', streamingMessageElement);
51
+ if (data.type === 'text') {
52
+ if (data.final) {
53
+ // Final response - this is the main assistant response after tool execution
54
+ console.log('Final response received, content length:', data.content ? data.content.length : 0);
55
+ accumulatedContent += data.content;
56
+ console.log('Accumulated content:', accumulatedContent);
57
+
58
+ // Ensure we have content before displaying
59
+ if (accumulatedContent.trim().length > 0) {
60
+ streamingMessageElement = updateStreamingMessage(
61
+ accumulatedContent,
62
+ streamingMessageElement
63
+ );
64
+ console.log('Updated streaming message element:', streamingMessageElement);
65
+ } else {
66
+ console.warn('No content to display');
67
+ }
68
+ eventSource.close();
69
+ resolve();
64
70
  } else {
65
- console.warn('No content to display');
71
+ // Non-final response - text that appears with tool calls
72
+ // Display immediately as a separate message
73
+ console.log('Non-final response, appending:', data.content);
74
+ appendMessage('assistant', data.content);
66
75
  }
67
- eventSource.close();
68
- } else {
69
- // Non-final response - text that appears with tool calls
70
- // Display immediately as a separate message
71
- console.log('Non-final response, appending:', data.content);
72
- appendMessage('assistant', data.content);
73
76
  }
74
- }
75
- });
76
-
77
- eventSource.addEventListener('tool_start', (event) => {
78
- const data = JSON.parse(event.data);
79
- appendToolCall(data.tool_name, data.input);
80
- });
81
-
82
- eventSource.addEventListener('tool_complete', (event) => {
83
- const data = JSON.parse(event.data);
84
- // Display tool result
85
- appendToolResult(data.tool_use_id, { content: data.content });
86
- });
77
+ });
78
+
79
+ eventSource.addEventListener('tool_start', (event) => {
80
+ const data = JSON.parse(event.data);
81
+ appendToolCall(data.tool_name, data.input);
82
+ });
83
+
84
+ eventSource.addEventListener('tool_complete', (event) => {
85
+ const data = JSON.parse(event.data);
86
+ // Display tool result
87
+ appendToolResult(data.tool_use_id, { content: data.content });
88
+ });
89
+
90
+ eventSource.addEventListener('tool_error', (event) => {
91
+ const data = JSON.parse(event.data);
92
+ appendToolResult(data.tool_name, { error: data.error });
93
+ });
94
+
95
+ eventSource.addEventListener('permission_request', (event) => {
96
+ const data = JSON.parse(event.data);
97
+ console.log('Permission request received:', data);
98
+
99
+ // Show permission modal/dialog
100
+ showToolPermissionDialog(data.request_id, data.tool_name, data.tool_description);
101
+ });
102
+
103
+ eventSource.addEventListener('progress', (event) => {
104
+ const data = JSON.parse(event.data);
105
+ // Update progress (if we want to show a progress bar)
106
+ console.log('Progress:', data);
107
+ });
108
+
109
+ eventSource.addEventListener('complete', (event) => {
110
+ // Stream complete
111
+ console.log('Stream complete');
112
+ eventSource.close();
87
113
 
88
- eventSource.addEventListener('tool_error', (event) => {
89
- const data = JSON.parse(event.data);
90
- appendToolResult(data.tool_name, { error: data.error });
91
- });
114
+ // Hide typing indicator if still visible
115
+ if (typingIndicator && typingIndicator.parentNode) {
116
+ console.log('Hiding typing indicator on complete');
117
+ hideTypingIndicator();
118
+ typingIndicator = null;
119
+ }
92
120
 
93
- eventSource.addEventListener('permission_request', (event) => {
94
- const data = JSON.parse(event.data);
95
- console.log('Permission request received:', data);
121
+ resolve();
122
+ });
96
123
 
97
- // Show permission modal/dialog
98
- showToolPermissionDialog(data.request_id, data.tool_name, data.tool_description);
99
- });
124
+ eventSource.addEventListener('error', (event) => {
125
+ console.error('SSE error:', event);
126
+ const data = JSON.parse(event.data);
100
127
 
101
- eventSource.addEventListener('progress', (event) => {
102
- const data = JSON.parse(event.data);
103
- // Update progress (if we want to show a progress bar)
104
- console.log('Progress:', data);
105
- });
128
+ // Hide typing indicator
129
+ if (typingIndicator && typingIndicator.parentNode) {
130
+ console.log('Hiding typing indicator on error');
131
+ hideTypingIndicator();
132
+ typingIndicator = null;
133
+ }
106
134
 
107
- eventSource.addEventListener('complete', (event) => {
108
- // Stream complete
109
- console.log('Stream complete');
110
- eventSource.close();
135
+ // Show error message
136
+ appendMessage('system', `Error: ${data.message}`);
111
137
 
112
- // Hide typing indicator if still visible
113
- if (typingIndicator && typingIndicator.parentNode) {
114
- console.log('Hiding typing indicator on complete');
115
- hideTypingIndicator();
116
- typingIndicator = null;
117
- }
118
- });
138
+ eventSource.close();
139
+ resolve();
140
+ });
119
141
 
120
- eventSource.addEventListener('error', (event) => {
121
- console.error('SSE error:', event);
122
- const data = JSON.parse(event.data);
142
+ eventSource.onerror = (error) => {
143
+ console.error('EventSource failed:', error);
123
144
 
124
- // Hide typing indicator
125
- if (typingIndicator && typingIndicator.parentNode) {
126
- console.log('Hiding typing indicator on error');
127
- hideTypingIndicator();
128
- typingIndicator = null;
129
- }
145
+ // Hide typing indicator
146
+ if (typingIndicator && typingIndicator.parentNode) {
147
+ console.log('Hiding typing indicator on connection error');
148
+ hideTypingIndicator();
149
+ typingIndicator = null;
150
+ }
130
151
 
131
- // Show error message
132
- appendMessage('system', `Error: ${data.message}`);
152
+ // If we haven't received any content, show error
153
+ if (!streamingMessageElement) {
154
+ appendMessage('system', 'Connection error. Please try again.');
155
+ }
133
156
 
134
- eventSource.close();
135
- });
157
+ eventSource.close();
158
+ resolve();
159
+ };
136
160
 
137
- eventSource.onerror = (error) => {
138
- console.error('EventSource failed:', error);
161
+ } catch (error) {
162
+ console.error('Error sending message:', error);
139
163
 
140
164
  // Hide typing indicator
141
- if (typingIndicator && typingIndicator.parentNode) {
142
- console.log('Hiding typing indicator on connection error');
165
+ if (typingIndicator) {
143
166
  hideTypingIndicator();
144
- typingIndicator = null;
145
- }
146
-
147
- // If we haven't received any content, show error
148
- if (!streamingMessageElement) {
149
- appendMessage('system', 'Connection error. Please try again.');
150
167
  }
151
168
 
152
- eventSource.close();
153
- };
154
-
155
- } catch (error) {
156
- console.error('Error sending message:', error);
157
-
158
- // Hide typing indicator
159
- if (typingIndicator) {
160
- hideTypingIndicator();
169
+ showToast('Failed to send message', 'error');
170
+ resolve();
161
171
  }
162
-
163
- showToast('Failed to send message', 'error');
164
- }
172
+ });
165
173
  }
166
174
 
167
175
  /**
@@ -53,7 +53,7 @@
53
53
  <tbody id="actions-tbody">
54
54
  <tr>
55
55
  <td colspan="9" class="text-center text-muted py-4">
56
- <div class="spinner-border spinner-border-sm" role="status"></div>
56
+ <output class="spinner-border spinner-border-sm"></output>
57
57
  Loading actions...
58
58
  </td>
59
59
  </tr>
@@ -85,7 +85,7 @@
85
85
  <tbody id="runs-tbody">
86
86
  <tr>
87
87
  <td colspan="7" class="text-center text-muted py-4">
88
- <div class="spinner-border spinner-border-sm" role="status"></div>
88
+ <output class="spinner-border spinner-border-sm"></output>
89
89
  Loading recent runs...
90
90
  </td>
91
91
  </tr>
@@ -172,7 +172,7 @@
172
172
 
173
173
  <!-- Tool Permissions -->
174
174
  <div class="mb-3">
175
- <label class="form-label">Tool Permissions</label>
175
+ <span class="form-label d-block">Tool Permissions</span>
176
176
  <div class="card">
177
177
  <div class="card-body" style="max-height: 200px; overflow-y: auto;">
178
178
  <div id="toolPermissions">
@@ -374,14 +374,14 @@
374
374
  <div class="alert alert-success mb-0">
375
375
  <i class="bi bi-check-circle-fill"></i>
376
376
  Action created successfully!
377
- <a href="#" id="viewCreatedAction" onclick="viewCreatedAction(); return false;">View action details</a>
377
+ <button type="button" id="viewCreatedAction" class="btn btn-link p-0 align-baseline" onclick="viewCreatedAction();">View action details</button>
378
378
  </div>
379
379
  </div>
380
380
  </div>
381
381
 
382
382
  <!-- Loading Indicator -->
383
383
  <div id="aiCreateLoading" class="d-none text-center py-4">
384
- <div class="spinner-border text-primary" role="status"></div>
384
+ <output class="spinner-border text-primary"></output>
385
385
  <p class="mt-2 text-muted">AI is thinking...</p>
386
386
  </div>
387
387
  </div>
@@ -53,11 +53,13 @@
53
53
  <i class="bi bi-plus-circle"></i> New
54
54
  </a>
55
55
  </li>
56
+ {% if actions_enabled %}
56
57
  <li class="nav-item">
57
58
  <a class="nav-link" href="/actions">
58
59
  <i class="bi bi-robot"></i> Actions
59
60
  </a>
60
61
  </li>
62
+ {% endif %}
61
63
  </ul>
62
64
  <ul class="navbar-nav">
63
65
  <li class="nav-item">
@@ -89,5 +91,16 @@
89
91
  <script src="/static/js/main.js"></script>
90
92
 
91
93
  {% block extra_scripts %}{% endblock %}
94
+
95
+ {% if heartbeat_enabled %}
96
+ <!-- Browser heartbeat - auto-shutdown when browser closes -->
97
+ <script>
98
+ setInterval(() => {
99
+ fetch('/api/heartbeat', { method: 'POST' }).catch(() => {});
100
+ }, {{ heartbeat_interval_ms }});
101
+ // Send initial heartbeat immediately
102
+ fetch('/api/heartbeat', { method: 'POST' }).catch(() => {});
103
+ </script>
104
+ {% endif %}
92
105
  </body>
93
106
  </html>