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,182 @@
1
+ const llmService = require('../llm/llmService');
2
+ const toolRegistry = require('../tools/toolRegistry');
3
+ const securityService = require('../security/securityService');
4
+ const memoryService = require('../memory/memoryService');
5
+ const AuditLog = require('../../models/AuditLog');
6
+
7
+ class PlannerService {
8
+ /**
9
+ * Process a user message through the full pipeline:
10
+ * 1. Security scan input
11
+ * 2. Load user context & memories
12
+ * 3. Build system prompt with available tools
13
+ * 4. Send to LLM
14
+ * 5. Parse tool calls if any
15
+ * 6. Execute tools (with permission checks)
16
+ * 7. Log audit trail
17
+ * 8. Extract & store new memories
18
+ * 9. Return final response
19
+ */
20
+ async processMessage(user, conversationHistory, userMessage, onProgress = null) {
21
+ const result = {
22
+ response: '',
23
+ toolCalls: [],
24
+ sensitiveDataWarnings: [],
25
+ tokensUsed: 0,
26
+ model: '',
27
+ language: llmService.detectLanguage(userMessage)
28
+ };
29
+
30
+ // Step 1: Security scan on user input
31
+ const inputScan = securityService.scanForSensitiveData(userMessage);
32
+ if (inputScan.length > 0) {
33
+ result.sensitiveDataWarnings = inputScan;
34
+ }
35
+
36
+ // Step 2: Load memories
37
+ const memories = await memoryService.getUserContext(user._id || user.id);
38
+
39
+ // Step 3: Get tools for user's active pack
40
+ const tools = toolRegistry.getToolsForPack(user.activePack || 'personal');
41
+
42
+ // Step 4: Build system prompt and call LLM
43
+ const systemPrompt = llmService.buildSystemPrompt(user, tools, memories);
44
+
45
+ const messages = [
46
+ ...conversationHistory.map(m => ({ role: m.role, content: m.content })),
47
+ { role: 'user', content: userMessage }
48
+ ];
49
+
50
+ const llmResponse = await llmService.chat(messages, systemPrompt, user);
51
+ result.response = llmResponse.content;
52
+ result.tokensUsed = llmResponse.tokensUsed;
53
+ result.model = llmResponse.model;
54
+
55
+ // Step 5: Execute Agentic Loop (up to 20 steps for massive multi-tasking)
56
+ let loopCount = 0;
57
+ const maxLoops = 20;
58
+ let currentMessages = [...messages, { role: 'assistant', content: llmResponse.content }];
59
+ let currentResponse = llmResponse.content;
60
+
61
+ while (loopCount < maxLoops) {
62
+ loopCount++;
63
+
64
+ // Extract planning / thought process and stream it to the user
65
+ const thoughtProcess = currentResponse.replace(/```(?:tool_call|json)?\s*[\s\S]*?```/g, '').trim();
66
+ if (thoughtProcess && onProgress) {
67
+ // Send only if there's substantial text, maybe filtering short OKs
68
+ if (thoughtProcess.length > 10) {
69
+ await onProgress(`\n${thoughtProcess}\n`);
70
+ }
71
+ }
72
+
73
+ const toolCalls = llmService.parseToolCalls(currentResponse);
74
+ if (toolCalls.length === 0) {
75
+ // Done! No more tools to call
76
+ result.response = currentResponse;
77
+ break;
78
+ }
79
+
80
+ const stepToolResults = [];
81
+ for (const tc of toolCalls) {
82
+ const toolResult = {
83
+ toolName: tc.tool,
84
+ arguments: tc.args,
85
+ status: 'pending',
86
+ riskLevel: securityService.assessRisk(tc.tool),
87
+ result: null
88
+ };
89
+
90
+ const permCheck = securityService.requiresPermission(tc.tool, user.permissions);
91
+ if (permCheck === 'blocked') {
92
+ toolResult.status = 'blocked';
93
+ toolResult.result = `🚫 Tool "${tc.tool}" blocked by your permission settings.`;
94
+ } else {
95
+ try {
96
+ toolResult.status = 'running';
97
+ console.log(`🔧 Executing tool: ${tc.tool}`, JSON.stringify(tc.args));
98
+ const execResult = await toolRegistry.executeTool(tc.tool, tc.args, user);
99
+ toolResult.result = execResult.result;
100
+ if (execResult.notificationParams) toolResult.notificationParams = execResult.notificationParams;
101
+ toolResult.status = execResult.success ? 'completed' : 'failed';
102
+ console.log(`✅ Tool ${tc.tool}: ${toolResult.status}`);
103
+ } catch (error) {
104
+ toolResult.status = 'failed';
105
+ toolResult.result = `Error: ${error.message}`;
106
+ console.error(`❌ Tool ${tc.tool} error:`, error.message);
107
+ }
108
+ }
109
+
110
+ stepToolResults.push(toolResult);
111
+
112
+ // Audit log
113
+ try {
114
+ await AuditLog.create({
115
+ userId: user._id || user.id,
116
+ action: `Tool: ${tc.tool}`,
117
+ category: toolRegistry.getTool(tc.tool)?.category || 'tool',
118
+ details: {
119
+ toolName: tc.tool,
120
+ input: tc.args,
121
+ output: toolResult.result,
122
+ riskLevel: toolResult.riskLevel,
123
+ permissionGranted: toolResult.status !== 'blocked'
124
+ },
125
+ status: toolResult.status === 'completed' ? 'success' :
126
+ toolResult.status === 'blocked' ? 'blocked' : 'failed'
127
+ });
128
+ } catch (e) { console.error('Audit log error:', e); }
129
+ }
130
+
131
+ result.toolCalls.push(...stepToolResults);
132
+
133
+ if (stepToolResults.some(tc => tc.status === 'completed' || tc.status === 'failed')) {
134
+ const toolResultsText = stepToolResults
135
+ .map(tc => {
136
+ let text = `Tool "${tc.toolName}" result:\n${tc.result}`;
137
+ if (tc.status === 'failed') {
138
+ text += `\n\nFAILURE DETECTED. Please analyze the error, auto-correct your parameters, and retry.`;
139
+ }
140
+ return text;
141
+ })
142
+ .join('\n\n');
143
+
144
+ currentMessages.push({
145
+ role: 'user',
146
+ content: `[System Temporary Memory] Tool Execution Results:\n${toolResultsText}\n\nReview the results. If failed, apply FAILURE RECOVERY by retrying with different parameters. If all tasks are completed, output the final completion message.`
147
+ });
148
+
149
+ const followUp = await llmService.chat(currentMessages, systemPrompt, user);
150
+ currentResponse = followUp.content;
151
+ currentMessages.push({ role: 'assistant', content: currentResponse });
152
+ if (followUp.tokensUsed) result.tokensUsed += followUp.tokensUsed;
153
+ } else {
154
+ // Tools were blocked or something weird happened - exit loop early
155
+ result.response = currentResponse;
156
+ break;
157
+ }
158
+ }
159
+
160
+ if (loopCount >= maxLoops) {
161
+ result.response = currentResponse + '\n\n*(Note: Task reached maximum automated steps limit and was paused for your review.)*';
162
+ }
163
+
164
+ // Step 8: Scan output for sensitive data
165
+ const outputScan = securityService.scanForSensitiveData(result.response);
166
+ if (outputScan.length > 0) {
167
+ result.sensitiveDataWarnings = [...result.sensitiveDataWarnings, ...outputScan];
168
+ result.response = securityService.maskSensitiveData(result.response);
169
+ }
170
+
171
+ // Step 9: Extract memories from conversation
172
+ try {
173
+ await memoryService.extractFromMessage(user._id || user.id, userMessage);
174
+ } catch (e) {
175
+ // Non-critical
176
+ }
177
+
178
+ return result;
179
+ }
180
+ }
181
+
182
+ module.exports = new PlannerService();
@@ -0,0 +1,166 @@
1
+ const { SENSITIVE_PATTERNS } = require('../../config/constants');
2
+
3
+ class SecurityService {
4
+ /**
5
+ * Scan text for sensitive Indian data patterns (PAN, Aadhaar, Phone, etc.)
6
+ */
7
+ scanForSensitiveData(text) {
8
+ const findings = [];
9
+
10
+ if (!text || typeof text !== 'string') return findings;
11
+
12
+ // PAN Card
13
+ const panMatches = text.match(SENSITIVE_PATTERNS.PAN);
14
+ if (panMatches) {
15
+ findings.push({
16
+ type: 'PAN Card',
17
+ typeHi: 'पैन कार्ड',
18
+ count: panMatches.length,
19
+ risk: 'high',
20
+ icon: '🔴',
21
+ message: `${panMatches.length} PAN number(s) detected`,
22
+ messageHi: `${panMatches.length} पैन नंबर मिला`
23
+ });
24
+ }
25
+
26
+ // Aadhaar
27
+ const aadhaarMatches = text.match(SENSITIVE_PATTERNS.AADHAAR);
28
+ if (aadhaarMatches) {
29
+ findings.push({
30
+ type: 'Aadhaar Number',
31
+ typeHi: 'आधार नंबर',
32
+ count: aadhaarMatches.length,
33
+ risk: 'critical',
34
+ icon: '🔴',
35
+ message: `${aadhaarMatches.length} Aadhaar number(s) detected`,
36
+ messageHi: `${aadhaarMatches.length} आधार नंबर मिला`
37
+ });
38
+ }
39
+
40
+ // Phone Numbers
41
+ const phoneMatches = text.match(SENSITIVE_PATTERNS.PHONE);
42
+ if (phoneMatches) {
43
+ findings.push({
44
+ type: 'Phone Number',
45
+ typeHi: 'फ़ोन नंबर',
46
+ count: phoneMatches.length,
47
+ risk: 'medium',
48
+ icon: '🟡',
49
+ message: `${phoneMatches.length} phone number(s) detected`,
50
+ messageHi: `${phoneMatches.length} फ़ोन नंबर मिला`
51
+ });
52
+ }
53
+
54
+ // Email
55
+ const emailMatches = text.match(SENSITIVE_PATTERNS.EMAIL);
56
+ if (emailMatches) {
57
+ findings.push({
58
+ type: 'Email Address',
59
+ typeHi: 'ईमेल',
60
+ count: emailMatches.length,
61
+ risk: 'low',
62
+ icon: '🟢',
63
+ message: `${emailMatches.length} email address(es) detected`,
64
+ messageHi: `${emailMatches.length} ईमेल मिला`
65
+ });
66
+ }
67
+
68
+ // IFSC Code
69
+ const ifscMatches = text.match(SENSITIVE_PATTERNS.IFSC);
70
+ if (ifscMatches) {
71
+ findings.push({
72
+ type: 'IFSC Code',
73
+ typeHi: 'IFSC कोड',
74
+ count: ifscMatches.length,
75
+ risk: 'high',
76
+ icon: '🔴',
77
+ message: `${ifscMatches.length} IFSC code(s) detected`,
78
+ messageHi: `${ifscMatches.length} IFSC कोड मिला`
79
+ });
80
+ }
81
+
82
+ return findings;
83
+ }
84
+
85
+ /**
86
+ * Mask sensitive data in text
87
+ * Preserves UPI IDs and UPI links (phone numbers inside UPI should NOT be masked)
88
+ */
89
+ maskSensitiveData(text) {
90
+ if (!text) return text;
91
+ let masked = text;
92
+
93
+ // Step 1: Temporarily protect UPI links and UPI IDs from masking
94
+ const upiPlaceholders = [];
95
+ // Protect full UPI deep links: upi://pay?pa=...
96
+ masked = masked.replace(/upi:\/\/pay\?[^\s\n`"')]+/gi, (match) => {
97
+ const idx = upiPlaceholders.length;
98
+ upiPlaceholders.push(match);
99
+ return `__UPI_LINK_${idx}__`;
100
+ });
101
+ // Protect UPI IDs: something@upihandle (e.g. 9301105706@yespop)
102
+ masked = masked.replace(/[\w.\-+]+@[a-zA-Z]{2,}/g, (match) => {
103
+ // Only protect if it looks like a UPI ID (ends with known UPI handles or short handle)
104
+ const upiHandles = ['ybl', 'okhdfcbank', 'okicici', 'okaxis', 'oksbi', 'paytm',
105
+ 'apl', 'yespop', 'upi', 'ibl', 'sbi', 'axisbank', 'icici', 'hdfcbank',
106
+ 'kotak', 'pnb', 'boi', 'cnrb', 'unionbank', 'indianbank', 'federal'];
107
+ const handle = match.split('@')[1]?.toLowerCase();
108
+ if (handle && (upiHandles.includes(handle) || handle.length <= 6)) {
109
+ const idx = upiPlaceholders.length;
110
+ upiPlaceholders.push(match);
111
+ return `__UPI_LINK_${idx}__`;
112
+ }
113
+ return match; // Not a UPI ID, leave for email detection
114
+ });
115
+
116
+ // Step 2: Apply masking
117
+ masked = masked.replace(SENSITIVE_PATTERNS.PAN, (match) => match.substring(0, 2) + '***' + match.substring(match.length - 2));
118
+ masked = masked.replace(SENSITIVE_PATTERNS.AADHAAR, (match) => 'XXXX XXXX ' + match.trim().slice(-4));
119
+ masked = masked.replace(SENSITIVE_PATTERNS.PHONE, (match) => match.substring(0, 4) + '****' + match.substring(match.length - 2));
120
+
121
+ // Step 3: Restore protected UPI content
122
+ for (let i = 0; i < upiPlaceholders.length; i++) {
123
+ masked = masked.replace(`__UPI_LINK_${i}__`, upiPlaceholders[i]);
124
+ }
125
+
126
+ return masked;
127
+ }
128
+
129
+ /**
130
+ * Assess risk level of a tool action
131
+ */
132
+ assessRisk(toolName, args) {
133
+ const highRiskTools = ['file_delete', 'send_email', 'browser_automation'];
134
+ const mediumRiskTools = ['file_write', 'calendar_schedule'];
135
+ const lowRiskTools = ['web_search', 'calculate', 'weather_info', 'summarize_text', 'note_take'];
136
+
137
+ if (highRiskTools.includes(toolName)) return 'high';
138
+ if (mediumRiskTools.includes(toolName)) return 'medium';
139
+ if (lowRiskTools.includes(toolName)) return 'low';
140
+ return 'medium';
141
+ }
142
+
143
+ /**
144
+ * Check if action requires user permission
145
+ */
146
+ requiresPermission(toolName, userPermissions) {
147
+ const toolPermissionMap = {
148
+ 'file_read': 'fileAccess',
149
+ 'file_write': 'fileAccess',
150
+ 'file_delete': 'fileAccess',
151
+ 'send_email': 'emailAccess',
152
+ 'browser_automation': 'browserAccess',
153
+ 'calendar_schedule': 'calendarAccess'
154
+ };
155
+
156
+ const permKey = toolPermissionMap[toolName];
157
+ if (!permKey) return false;
158
+
159
+ const perm = userPermissions?.[permKey] || 'ask';
160
+ if (perm === 'allow_always') return false;
161
+ if (perm === 'never') return 'blocked';
162
+ return true;
163
+ }
164
+ }
165
+
166
+ module.exports = new SecurityService();
@@ -0,0 +1,49 @@
1
+ class TelegramService {
2
+ constructor() {
3
+ this.botToken = process.env.TELEGRAM_BOT_TOKEN;
4
+ this.apiUrl = `https://api.telegram.org/bot${this.botToken}`;
5
+ }
6
+
7
+ async sendMessage(chatId, text) {
8
+ if (!this.botToken) {
9
+ console.error('Telegram Bot Token not configured!');
10
+ return;
11
+ }
12
+
13
+ try {
14
+ const response = await fetch(`${this.apiUrl}/sendMessage`, {
15
+ method: 'POST',
16
+ headers: { 'Content-Type': 'application/json' },
17
+ body: JSON.stringify({
18
+ chat_id: chatId,
19
+ text: text,
20
+ parse_mode: 'Markdown'
21
+ })
22
+ });
23
+
24
+ if (!response.ok) {
25
+ const error = await response.text();
26
+ console.error('Telegram send error:', error);
27
+ return { success: false, error };
28
+ }
29
+
30
+ return { success: true };
31
+ } catch (error) {
32
+ console.error('Telegram Service Error:', error);
33
+ return { success: false, error: error.message };
34
+ }
35
+ }
36
+
37
+ async setWebhook(url) {
38
+ try {
39
+ const response = await fetch(`${this.apiUrl}/setWebhook?url=${url}`);
40
+ const data = await response.json();
41
+ console.log('Telegram Webhook Setup:', data);
42
+ return data;
43
+ } catch (error) {
44
+ console.error('Telegram webhook setup error:', error);
45
+ }
46
+ }
47
+ }
48
+
49
+ module.exports = new TelegramService();