samarthya-bot 1.0.2

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 (50) hide show
  1. package/README.md +92 -0
  2. package/backend/.env.example +23 -0
  3. package/backend/bin/samarthya.js +384 -0
  4. package/backend/config/constants.js +71 -0
  5. package/backend/config/db.js +13 -0
  6. package/backend/controllers/auditController.js +86 -0
  7. package/backend/controllers/authController.js +154 -0
  8. package/backend/controllers/chatController.js +158 -0
  9. package/backend/controllers/fileController.js +268 -0
  10. package/backend/controllers/platformController.js +54 -0
  11. package/backend/controllers/screenController.js +91 -0
  12. package/backend/controllers/telegramController.js +120 -0
  13. package/backend/controllers/toolsController.js +56 -0
  14. package/backend/controllers/whatsappController.js +214 -0
  15. package/backend/fix_toolRegistry.js +25 -0
  16. package/backend/middleware/auth.js +28 -0
  17. package/backend/models/AuditLog.js +28 -0
  18. package/backend/models/BackgroundJob.js +13 -0
  19. package/backend/models/Conversation.js +40 -0
  20. package/backend/models/Memory.js +17 -0
  21. package/backend/models/User.js +24 -0
  22. package/backend/package-lock.json +3766 -0
  23. package/backend/package.json +41 -0
  24. package/backend/public/assets/index-Ckf0GO1B.css +1 -0
  25. package/backend/public/assets/index-Do4jNsZS.js +19 -0
  26. package/backend/public/assets/index-Ui-pyZvK.js +25 -0
  27. package/backend/public/favicon.svg +17 -0
  28. package/backend/public/index.html +18 -0
  29. package/backend/public/manifest.json +16 -0
  30. package/backend/routes/audit.js +9 -0
  31. package/backend/routes/auth.js +11 -0
  32. package/backend/routes/chat.js +11 -0
  33. package/backend/routes/files.js +14 -0
  34. package/backend/routes/platform.js +18 -0
  35. package/backend/routes/screen.js +10 -0
  36. package/backend/routes/telegram.js +8 -0
  37. package/backend/routes/tools.js +9 -0
  38. package/backend/routes/whatsapp.js +11 -0
  39. package/backend/server.js +134 -0
  40. package/backend/services/background/backgroundService.js +81 -0
  41. package/backend/services/llm/llmService.js +444 -0
  42. package/backend/services/memory/memoryService.js +159 -0
  43. package/backend/services/planner/plannerService.js +182 -0
  44. package/backend/services/security/securityService.js +166 -0
  45. package/backend/services/telegram/telegramService.js +49 -0
  46. package/backend/services/tools/toolRegistry.js +879 -0
  47. package/backend/services/whatsapp/whatsappService.js +254 -0
  48. package/backend/test_email.js +29 -0
  49. package/backend/test_parser.js +10 -0
  50. package/package.json +49 -0
@@ -0,0 +1,154 @@
1
+ const User = require('../models/User');
2
+ const bcrypt = require('bcryptjs');
3
+ const jwt = require('jsonwebtoken');
4
+
5
+ // Register
6
+ exports.register = async (req, res) => {
7
+ try {
8
+ const { name, email, password, language, city, workType } = req.body;
9
+
10
+ const exists = await User.findOne({ email });
11
+ if (exists) {
12
+ return res.status(400).json({ success: false, message: 'Email already registered' });
13
+ }
14
+
15
+ const salt = await bcrypt.genSalt(10);
16
+ const hashed = await bcrypt.hash(password, salt);
17
+
18
+ const user = await User.create({
19
+ name,
20
+ email,
21
+ password: hashed,
22
+ language: language || 'hinglish',
23
+ city: city || '',
24
+ workType: workType || 'personal',
25
+ activePack: workType || 'personal'
26
+ });
27
+
28
+ const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '30d' });
29
+
30
+ res.status(201).json({
31
+ success: true,
32
+ token,
33
+ user: {
34
+ id: user._id,
35
+ name: user.name,
36
+ email: user.email,
37
+ language: user.language,
38
+ city: user.city,
39
+ workType: user.workType,
40
+ activePack: user.activePack,
41
+ permissions: user.permissions
42
+ }
43
+ });
44
+ } catch (error) {
45
+ res.status(500).json({ success: false, message: error.message });
46
+ }
47
+ };
48
+
49
+ // Login
50
+ exports.login = async (req, res) => {
51
+ try {
52
+ const { email, password } = req.body;
53
+
54
+ const user = await User.findOne({ email });
55
+ if (!user) {
56
+ return res.status(401).json({ success: false, message: 'Invalid credentials' });
57
+ }
58
+
59
+ const isMatch = await bcrypt.compare(password, user.password);
60
+ if (!isMatch) {
61
+ return res.status(401).json({ success: false, message: 'Invalid credentials' });
62
+ }
63
+
64
+ const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '30d' });
65
+
66
+ res.json({
67
+ success: true,
68
+ token,
69
+ user: {
70
+ id: user._id,
71
+ name: user.name,
72
+ email: user.email,
73
+ language: user.language,
74
+ city: user.city,
75
+ workType: user.workType,
76
+ activePack: user.activePack,
77
+ permissions: user.permissions
78
+ }
79
+ });
80
+ } catch (error) {
81
+ res.status(500).json({ success: false, message: error.message });
82
+ }
83
+ };
84
+
85
+ // Get Profile
86
+ exports.getProfile = async (req, res) => {
87
+ try {
88
+ const user = await User.findById(req.user.id).select('-password');
89
+ if (!user) return res.status(404).json({ success: false, message: 'User not found' });
90
+ res.json({ success: true, user });
91
+ } catch (error) {
92
+ res.status(500).json({ success: false, message: error.message });
93
+ }
94
+ };
95
+
96
+ // Update Profile
97
+ exports.updateProfile = async (req, res) => {
98
+ try {
99
+ const { name, language, city, workType, activePack, permissions } = req.body;
100
+
101
+ const updateData = {};
102
+ if (name) updateData.name = name;
103
+ if (language) updateData.language = language;
104
+ if (city) updateData.city = city;
105
+ if (workType) updateData.workType = workType;
106
+ if (activePack) updateData.activePack = activePack;
107
+ if (permissions) updateData.permissions = permissions;
108
+
109
+ const user = await User.findByIdAndUpdate(req.user.id, updateData, { new: true }).select('-password');
110
+ res.json({ success: true, user });
111
+ } catch (error) {
112
+ res.status(500).json({ success: false, message: error.message });
113
+ }
114
+ };
115
+
116
+ // Quick Demo Login (No password needed - for development)
117
+ exports.demoLogin = async (req, res) => {
118
+ try {
119
+ let user = await User.findOne({ email: 'demo@samarthya.com' });
120
+
121
+ if (!user) {
122
+ const salt = await bcrypt.genSalt(10);
123
+ const hashed = await bcrypt.hash('demo123', salt);
124
+ user = await User.create({
125
+ name: 'Demo User',
126
+ email: 'demo@samarthya.com',
127
+ password: hashed,
128
+ language: 'hinglish',
129
+ city: 'Mumbai',
130
+ workType: 'personal',
131
+ activePack: 'personal'
132
+ });
133
+ }
134
+
135
+ const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '30d' });
136
+
137
+ res.json({
138
+ success: true,
139
+ token,
140
+ user: {
141
+ id: user._id,
142
+ name: user.name,
143
+ email: user.email,
144
+ language: user.language,
145
+ city: user.city,
146
+ workType: user.workType,
147
+ activePack: user.activePack,
148
+ permissions: user.permissions
149
+ }
150
+ });
151
+ } catch (error) {
152
+ res.status(500).json({ success: false, message: error.message });
153
+ }
154
+ };
@@ -0,0 +1,158 @@
1
+ const Conversation = require('../models/Conversation');
2
+ const User = require('../models/User');
3
+ const plannerService = require('../services/planner/plannerService');
4
+ const securityService = require('../services/security/securityService');
5
+
6
+ // Send message and get AI response
7
+ exports.sendMessage = async (req, res) => {
8
+ try {
9
+ const { message, conversationId } = req.body;
10
+ const userId = req.user.id;
11
+
12
+ if (!message || !message.trim()) {
13
+ return res.status(400).json({ success: false, message: 'Message is required' });
14
+ }
15
+
16
+ const user = await User.findById(userId);
17
+ if (!user) return res.status(404).json({ success: false, message: 'User not found' });
18
+
19
+ // Get or create conversation
20
+ let conversation;
21
+ if (conversationId) {
22
+ conversation = await Conversation.findOne({ _id: conversationId, userId });
23
+ if (!conversation) {
24
+ return res.status(404).json({ success: false, message: 'Conversation not found' });
25
+ }
26
+ } else {
27
+ conversation = await Conversation.create({
28
+ userId,
29
+ title: message.substring(0, 60) + (message.length > 60 ? '...' : ''),
30
+ messages: [],
31
+ context: {
32
+ activePack: user.activePack,
33
+ language: user.language
34
+ }
35
+ });
36
+ }
37
+
38
+ // Add user message
39
+ conversation.messages.push({
40
+ role: 'user',
41
+ content: message,
42
+ language: user.language
43
+ });
44
+
45
+ // Process through planner
46
+ const conversationHistory = conversation.messages.slice(-20).map(m => ({
47
+ role: m.role,
48
+ content: m.content
49
+ }));
50
+
51
+ const result = await plannerService.processMessage(user, conversationHistory, message);
52
+
53
+ // Add assistant response
54
+ conversation.messages.push({
55
+ role: 'assistant',
56
+ content: result.response,
57
+ language: result.language,
58
+ toolCalls: result.toolCalls,
59
+ metadata: {
60
+ tokensUsed: result.tokensUsed,
61
+ model: result.model,
62
+ sensitiveDataDetected: result.sensitiveDataWarnings.map(w => w.type)
63
+ }
64
+ });
65
+
66
+ await conversation.save();
67
+
68
+ // Emit via socket if available
69
+ if (req.io) {
70
+ req.io.to(userId).emit('message', {
71
+ conversationId: conversation._id,
72
+ message: {
73
+ role: 'assistant',
74
+ content: result.response,
75
+ toolCalls: result.toolCalls,
76
+ sensitiveDataWarnings: result.sensitiveDataWarnings,
77
+ timestamp: new Date()
78
+ }
79
+ });
80
+ }
81
+
82
+ res.json({
83
+ success: true,
84
+ conversationId: conversation._id,
85
+ message: {
86
+ role: 'assistant',
87
+ content: result.response,
88
+ toolCalls: result.toolCalls,
89
+ sensitiveDataWarnings: result.sensitiveDataWarnings,
90
+ tokensUsed: result.tokensUsed,
91
+ model: result.model,
92
+ language: result.language
93
+ }
94
+ });
95
+ } catch (error) {
96
+ console.error('Chat error:', error);
97
+ res.status(500).json({ success: false, message: error.message });
98
+ }
99
+ };
100
+
101
+ // Get all conversations
102
+ exports.getConversations = async (req, res) => {
103
+ try {
104
+ const conversations = await Conversation.find({ isActive: true })
105
+ .select('title context isPinned createdAt updatedAt')
106
+ .sort({ isPinned: -1, updatedAt: -1 })
107
+ .limit(50);
108
+
109
+ res.json({ success: true, conversations });
110
+ } catch (error) {
111
+ res.status(500).json({ success: false, message: error.message });
112
+ }
113
+ };
114
+
115
+ // Get single conversation with messages
116
+ exports.getConversation = async (req, res) => {
117
+ try {
118
+ const conversation = await Conversation.findOne({
119
+ _id: req.params.id
120
+ });
121
+
122
+ if (!conversation) {
123
+ return res.status(404).json({ success: false, message: 'Conversation not found' });
124
+ }
125
+
126
+ res.json({ success: true, conversation });
127
+ } catch (error) {
128
+ res.status(500).json({ success: false, message: error.message });
129
+ }
130
+ };
131
+
132
+ // Delete conversation
133
+ exports.deleteConversation = async (req, res) => {
134
+ try {
135
+ await Conversation.findOneAndUpdate(
136
+ { _id: req.params.id },
137
+ { isActive: false }
138
+ );
139
+ res.json({ success: true, message: 'Conversation deleted' });
140
+ } catch (error) {
141
+ res.status(500).json({ success: false, message: error.message });
142
+ }
143
+ };
144
+
145
+ // Pin/Unpin conversation
146
+ exports.togglePin = async (req, res) => {
147
+ try {
148
+ const conv = await Conversation.findOne({ _id: req.params.id });
149
+ if (!conv) return res.status(404).json({ success: false, message: 'Not found' });
150
+
151
+ conv.isPinned = !conv.isPinned;
152
+ await conv.save();
153
+
154
+ res.json({ success: true, isPinned: conv.isPinned });
155
+ } catch (error) {
156
+ res.status(500).json({ success: false, message: error.message });
157
+ }
158
+ };
@@ -0,0 +1,268 @@
1
+ const fs = require('fs').promises;
2
+ const fsSync = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ const BASE_DIR = path.join(os.homedir(), 'SamarthyaBot_Files');
7
+
8
+ /**
9
+ * Get user's sandboxed directory
10
+ */
11
+ function getUserDir(userId) {
12
+ return path.join(BASE_DIR, String(userId));
13
+ }
14
+
15
+ /**
16
+ * Ensure user directory exists
17
+ */
18
+ async function ensureUserDir(userId) {
19
+ const userDir = getUserDir(userId);
20
+ await fs.mkdir(userDir, { recursive: true });
21
+ // Create default subdirectories
22
+ await fs.mkdir(path.join(userDir, 'notes'), { recursive: true });
23
+ await fs.mkdir(path.join(userDir, 'reminders'), { recursive: true });
24
+ await fs.mkdir(path.join(userDir, 'calendar'), { recursive: true });
25
+ await fs.mkdir(path.join(userDir, 'downloads'), { recursive: true });
26
+ return userDir;
27
+ }
28
+
29
+ /**
30
+ * Validate path is within user's sandbox
31
+ */
32
+ function validatePath(userDir, requestedPath) {
33
+ const resolved = path.resolve(userDir, requestedPath || '');
34
+ if (!resolved.startsWith(userDir)) {
35
+ throw new Error('Access denied: Path outside sandbox');
36
+ }
37
+ return resolved;
38
+ }
39
+
40
+ /**
41
+ * GET /api/files/list — List files and folders
42
+ */
43
+ exports.listFiles = async (req, res) => {
44
+ try {
45
+ const userId = req.user._id || req.user.id;
46
+ const userDir = await ensureUserDir(userId);
47
+ const subPath = req.query.path || '';
48
+ const fullPath = validatePath(userDir, subPath);
49
+
50
+ const entries = await fs.readdir(fullPath, { withFileTypes: true });
51
+ const items = [];
52
+
53
+ for (const entry of entries) {
54
+ const entryPath = path.join(fullPath, entry.name);
55
+ try {
56
+ const stat = await fs.stat(entryPath);
57
+ items.push({
58
+ name: entry.name,
59
+ type: entry.isDirectory() ? 'folder' : 'file',
60
+ size: entry.isDirectory() ? null : stat.size,
61
+ modified: stat.mtime,
62
+ extension: entry.isDirectory() ? null : path.extname(entry.name).slice(1).toLowerCase(),
63
+ path: subPath ? `${subPath}/${entry.name}` : entry.name
64
+ });
65
+ } catch (e) {
66
+ // Skip inaccessible
67
+ }
68
+ }
69
+
70
+ // Sort: folders first, then alphabetical
71
+ items.sort((a, b) => {
72
+ if (a.type !== b.type) return a.type === 'folder' ? -1 : 1;
73
+ return a.name.localeCompare(b.name);
74
+ });
75
+
76
+ res.json({
77
+ success: true,
78
+ currentPath: subPath || '/',
79
+ items,
80
+ totalItems: items.length
81
+ });
82
+ } catch (error) {
83
+ res.status(400).json({ success: false, error: error.message });
84
+ }
85
+ };
86
+
87
+ /**
88
+ * GET /api/files/read — Read file contents (text only)
89
+ */
90
+ exports.readFile = async (req, res) => {
91
+ try {
92
+ const userId = req.user._id || req.user.id;
93
+ const userDir = await ensureUserDir(userId);
94
+ const filePath = validatePath(userDir, req.query.path);
95
+
96
+ const stat = await fs.stat(filePath);
97
+ if (stat.isDirectory()) {
98
+ return res.status(400).json({ success: false, error: 'Cannot read a directory' });
99
+ }
100
+ if (stat.size > 2 * 1024 * 1024) {
101
+ return res.status(400).json({ success: false, error: 'File too large (max 2MB for preview)' });
102
+ }
103
+
104
+ const content = await fs.readFile(filePath, 'utf-8');
105
+ const ext = path.extname(filePath).slice(1).toLowerCase();
106
+
107
+ res.json({
108
+ success: true,
109
+ name: path.basename(filePath),
110
+ content,
111
+ size: stat.size,
112
+ extension: ext,
113
+ modified: stat.mtime
114
+ });
115
+ } catch (error) {
116
+ res.status(400).json({ success: false, error: error.message });
117
+ }
118
+ };
119
+
120
+ /**
121
+ * GET /api/files/download — Download a file
122
+ */
123
+ exports.downloadFile = async (req, res) => {
124
+ try {
125
+ const userId = req.user._id || req.user.id;
126
+ const userDir = await ensureUserDir(userId);
127
+ const filePath = validatePath(userDir, req.query.path);
128
+
129
+ const stat = await fs.stat(filePath);
130
+ if (stat.isDirectory()) {
131
+ return res.status(400).json({ success: false, error: 'Cannot download a directory' });
132
+ }
133
+
134
+ res.download(filePath, path.basename(filePath));
135
+ } catch (error) {
136
+ res.status(400).json({ success: false, error: error.message });
137
+ }
138
+ };
139
+
140
+ /**
141
+ * POST /api/files/mkdir — Create a folder
142
+ */
143
+ exports.createFolder = async (req, res) => {
144
+ try {
145
+ const userId = req.user._id || req.user.id;
146
+ const userDir = await ensureUserDir(userId);
147
+ const { name, parentPath } = req.body;
148
+
149
+ if (!name || /[/\\<>:|?*"]/.test(name)) {
150
+ return res.status(400).json({ success: false, error: 'Invalid folder name' });
151
+ }
152
+
153
+ const folderPath = validatePath(userDir, parentPath ? `${parentPath}/${name}` : name);
154
+ await fs.mkdir(folderPath, { recursive: true });
155
+
156
+ res.json({ success: true, message: `Folder "${name}" created`, path: parentPath ? `${parentPath}/${name}` : name });
157
+ } catch (error) {
158
+ res.status(400).json({ success: false, error: error.message });
159
+ }
160
+ };
161
+
162
+ /**
163
+ * DELETE /api/files/delete — Delete a file or empty folder
164
+ */
165
+ exports.deleteFile = async (req, res) => {
166
+ try {
167
+ const userId = req.user._id || req.user.id;
168
+ const userDir = await ensureUserDir(userId);
169
+ const filePath = validatePath(userDir, req.query.path);
170
+
171
+ // Prevent deleting root
172
+ if (filePath === userDir) {
173
+ return res.status(400).json({ success: false, error: 'Cannot delete root directory' });
174
+ }
175
+
176
+ const stat = await fs.stat(filePath);
177
+ if (stat.isDirectory()) {
178
+ await fs.rm(filePath, { recursive: true });
179
+ } else {
180
+ await fs.unlink(filePath);
181
+ }
182
+
183
+ res.json({ success: true, message: 'Deleted successfully' });
184
+ } catch (error) {
185
+ res.status(400).json({ success: false, error: error.message });
186
+ }
187
+ };
188
+
189
+ /**
190
+ * POST /api/files/upload — Upload a file
191
+ */
192
+ exports.uploadFile = async (req, res) => {
193
+ try {
194
+ const userId = req.user._id || req.user.id;
195
+ const userDir = await ensureUserDir(userId);
196
+ const { filename, content, parentPath } = req.body;
197
+
198
+ if (!filename || !content) {
199
+ return res.status(400).json({ success: false, error: 'Filename and content required' });
200
+ }
201
+
202
+ const safeName = filename.replace(/[/\\<>:|?*"]/g, '_');
203
+ const filePath = validatePath(userDir, parentPath ? `${parentPath}/${safeName}` : safeName);
204
+
205
+ // Handle base64 content (for binary files)
206
+ if (content.startsWith('data:')) {
207
+ const base64Data = content.split(',')[1];
208
+ await fs.writeFile(filePath, Buffer.from(base64Data, 'base64'));
209
+ } else {
210
+ await fs.writeFile(filePath, content, 'utf-8');
211
+ }
212
+
213
+ const stat = await fs.stat(filePath);
214
+ res.json({
215
+ success: true,
216
+ message: `File "${safeName}" uploaded`,
217
+ size: stat.size,
218
+ path: parentPath ? `${parentPath}/${safeName}` : safeName
219
+ });
220
+ } catch (error) {
221
+ res.status(400).json({ success: false, error: error.message });
222
+ }
223
+ };
224
+
225
+ /**
226
+ * GET /api/files/stats — Get storage stats
227
+ */
228
+ exports.getStats = async (req, res) => {
229
+ try {
230
+ const userId = req.user._id || req.user.id;
231
+ const userDir = await ensureUserDir(userId);
232
+
233
+ let totalSize = 0;
234
+ let fileCount = 0;
235
+ let folderCount = 0;
236
+
237
+ async function walk(dir) {
238
+ const entries = await fs.readdir(dir, { withFileTypes: true });
239
+ for (const entry of entries) {
240
+ const fullPath = path.join(dir, entry.name);
241
+ if (entry.isDirectory()) {
242
+ folderCount++;
243
+ await walk(fullPath);
244
+ } else {
245
+ fileCount++;
246
+ const stat = await fs.stat(fullPath);
247
+ totalSize += stat.size;
248
+ }
249
+ }
250
+ }
251
+
252
+ await walk(userDir);
253
+
254
+ res.json({
255
+ success: true,
256
+ stats: {
257
+ totalSize,
258
+ totalSizeFormatted: totalSize < 1024 ? `${totalSize} B` :
259
+ totalSize < 1048576 ? `${(totalSize / 1024).toFixed(1)} KB` :
260
+ `${(totalSize / 1048576).toFixed(1)} MB`,
261
+ fileCount,
262
+ folderCount
263
+ }
264
+ });
265
+ } catch (error) {
266
+ res.status(400).json({ success: false, error: error.message });
267
+ }
268
+ };
@@ -0,0 +1,54 @@
1
+ const BackgroundJob = require('../models/BackgroundJob');
2
+ const fs = require('fs').promises;
3
+ const path = require('path');
4
+ const os = require('os');
5
+ const toolRegistry = require('../services/tools/toolRegistry');
6
+
7
+ exports.getPlatformStatus = async (req, res) => {
8
+ try {
9
+ const activeJobs = await BackgroundJob.countDocuments({ isActive: true });
10
+
11
+ let pluginsCount = 0;
12
+ try {
13
+ const pluginDir = path.join(os.homedir(), 'SamarthyaBot_Files', 'plugins');
14
+ const files = await fs.readdir(pluginDir);
15
+ pluginsCount = files.filter(f => f.endsWith('.js')).length;
16
+ } catch (e) { }
17
+
18
+ res.json({
19
+ success: true,
20
+ status: {
21
+ uptime: process.uptime(),
22
+ totalTools: toolRegistry.getAllTools().length,
23
+ activeBackgroundJobs: activeJobs,
24
+ loadedPlugins: pluginsCount,
25
+ osName: os.type() + ' ' + os.release(),
26
+ ramUsage: Math.round((os.totalmem() - os.freemem()) / os.totalmem() * 100)
27
+ }
28
+ });
29
+ } catch (error) {
30
+ res.status(500).json({ success: false, message: error.message });
31
+ }
32
+ };
33
+
34
+ exports.getBackgroundJobs = async (req, res) => {
35
+ try {
36
+ const jobs = await BackgroundJob.find().sort({ nextRunAt: 1 }).limit(20);
37
+ res.json({ success: true, jobs });
38
+ } catch (error) {
39
+ res.status(500).json({ success: false, message: error.message });
40
+ }
41
+ };
42
+
43
+ exports.emergencyStop = async (req, res) => {
44
+ try {
45
+ // Stop all active background jobs
46
+ await BackgroundJob.updateMany({ isActive: true }, { $set: { isActive: false } });
47
+
48
+ // In a real system you might also want to set a global flag in plannerService to reject new processing
49
+ res.json({ success: true, message: "🚨 EMERGENCY STOP ENGAGED. All background jobs halted." });
50
+ console.warn("🛑 EMERGENCY KILL SWITCH ACTIVATED BY USER 🛑");
51
+ } catch (error) {
52
+ res.status(500).json({ success: false, message: error.message });
53
+ }
54
+ };