samarthya-bot 2.2.1 → 2.3.0
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.
- package/CHANGELOG.md +25 -0
- package/README.md +340 -353
- package/backend/bin/samarthya.js +29 -10
- package/backend/config/constants.js +4 -4
- package/backend/controllers/chatController.js +72 -0
- package/backend/controllers/telegramController.js +8 -0
- package/backend/package.json +1 -1
- package/backend/public/assets/index-DiMx9ERJ.css +1 -0
- package/backend/public/assets/index-Dn5WYZTH.js +32 -0
- package/backend/public/index.html +186 -16
- package/backend/public/robots.txt +32 -0
- package/backend/public/sitemap.xml +39 -0
- package/backend/services/agent/commandService.js +168 -0
- package/backend/services/llm/llmService.js +8 -0
- package/backend/services/planner/plannerService.js +17 -0
- package/backend/services/security/sandboxService.js +11 -4
- package/backend/services/system/platform.js +150 -0
- package/backend/services/tools/toolRegistry.js +521 -36
- package/backend/services/worker/workerClient.js +141 -29
- package/package.json +1 -1
- package/backend/public/assets/index-6PCzI3K2.js +0 -40
- package/backend/public/assets/index-6TF5jVRQ.js +0 -149
- package/backend/public/assets/index-B0U7rt6f.js +0 -46
- package/backend/public/assets/index-BF0RZh9i.js +0 -149
- package/backend/public/assets/index-BFRAq8Y1.js +0 -149
- package/backend/public/assets/index-CGw8cc8z.js +0 -149
- package/backend/public/assets/index-Ckf0GO1B.css +0 -1
- package/backend/public/assets/index-Cx0Ei-z7.js +0 -149
- package/backend/public/assets/index-DIPdcLv-.js +0 -25
- package/backend/public/assets/index-Da1E-MYB.js +0 -53
- package/backend/public/assets/index-DdCKkq38.js +0 -149
- package/backend/public/assets/index-Do4jNsZS.js +0 -19
- package/backend/public/assets/index-DyjpBYmS.js +0 -51
- package/backend/public/assets/index-DzlXcaXT.js +0 -149
- package/backend/public/assets/index-J7XSVHCz.css +0 -1
- package/backend/public/assets/index-Ui-pyZvK.js +0 -25
- package/backend/public/assets/index-kzffNwzo.js +0 -149
|
@@ -1,42 +1,95 @@
|
|
|
1
1
|
const { spawn } = require('child_process');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
+
const fs = require('fs');
|
|
4
5
|
const { v4: uuidv4 } = require('uuid');
|
|
5
|
-
|
|
6
|
+
const platform = require('../system/platform');
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* WorkerClient
|
|
10
|
+
* ------------------------------------------------------------------
|
|
11
|
+
* Streams long-running shell commands back to Node.
|
|
12
|
+
*
|
|
13
|
+
* Fast path : the pre-built Go micro-worker (`worker/samarthya-worker`).
|
|
14
|
+
* Fallback : a pure-Node executor using the platform shell.
|
|
15
|
+
*
|
|
16
|
+
* The Go binary is only shipped for one architecture, so on Windows /
|
|
17
|
+
* macOS / unsupported CPUs we transparently fall back to Node. This is
|
|
18
|
+
* what makes `devops_execute_stream` work on every OS.
|
|
19
|
+
*/
|
|
6
20
|
class WorkerClient {
|
|
7
21
|
constructor() {
|
|
8
22
|
this.workerProcess = null;
|
|
9
23
|
this.pendingRequests = new Map();
|
|
24
|
+
this.useNativeFallback = false; // flipped on when the Go binary can't run
|
|
25
|
+
this.startAttempts = 0;
|
|
10
26
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
27
|
+
const projectRoot = path.resolve(__dirname, '../../../');
|
|
28
|
+
const binName = 'samarthya-worker' + (platform.isWindows ? '.exe' : '');
|
|
29
|
+
this.workerPath = path.join(projectRoot, 'worker', binName);
|
|
30
|
+
}
|
|
14
31
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
/** Whether the Go binary exists for THIS platform. */
|
|
33
|
+
_binaryAvailable() {
|
|
34
|
+
try {
|
|
35
|
+
return fs.existsSync(this.workerPath);
|
|
36
|
+
} catch {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
18
39
|
}
|
|
19
40
|
|
|
20
41
|
start() {
|
|
21
|
-
if (this.workerProcess) return;
|
|
42
|
+
if (this.workerProcess || this.useNativeFallback) return;
|
|
43
|
+
|
|
44
|
+
if (!this._binaryAvailable()) {
|
|
45
|
+
console.log('[Worker] Go micro-worker binary not found for this platform — using native Node executor.');
|
|
46
|
+
this.useNativeFallback = true;
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Avoid infinite respawn loops if the binary exists but cannot run
|
|
51
|
+
// (e.g. wrong CPU architecture). After 3 failed starts, fall back.
|
|
52
|
+
if (this.startAttempts >= 3) {
|
|
53
|
+
console.log('[Worker] Go worker failed repeatedly — switching to native Node executor.');
|
|
54
|
+
this.useNativeFallback = true;
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
this.startAttempts++;
|
|
22
58
|
|
|
23
59
|
console.log(`[Worker] Starting Go Micro-Worker from: ${this.workerPath}`);
|
|
24
60
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
61
|
+
try {
|
|
62
|
+
this.workerProcess = spawn(this.workerPath, [], {
|
|
63
|
+
stdio: ['pipe', 'pipe', 'inherit'],
|
|
64
|
+
});
|
|
65
|
+
} catch (err) {
|
|
66
|
+
console.error('[Worker] Could not spawn Go worker:', err.message);
|
|
67
|
+
this.useNativeFallback = true;
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
28
70
|
|
|
29
71
|
this.workerProcess.on('error', (err) => {
|
|
30
72
|
console.error('[Worker] Failed to start Go worker:', err.message);
|
|
31
|
-
|
|
73
|
+
// Binary is present but not runnable on this host → go native.
|
|
74
|
+
this.useNativeFallback = true;
|
|
75
|
+
this.workerProcess = null;
|
|
32
76
|
});
|
|
33
77
|
|
|
34
78
|
this.workerProcess.on('exit', (code) => {
|
|
35
|
-
console.log(`[Worker] Go worker exited with code ${code}. Restarting in 5s...`);
|
|
36
79
|
this.workerProcess = null;
|
|
80
|
+
if (this.useNativeFallback) return;
|
|
81
|
+
if (this.startAttempts >= 3) {
|
|
82
|
+
this.useNativeFallback = true;
|
|
83
|
+
console.log('[Worker] Go worker keeps exiting — switching to native Node executor.');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
console.log(`[Worker] Go worker exited with code ${code}. Restarting in 5s...`);
|
|
37
87
|
setTimeout(() => this.start(), 5000);
|
|
38
88
|
});
|
|
39
89
|
|
|
90
|
+
// Reset attempt counter once we have stayed up for a while.
|
|
91
|
+
setTimeout(() => { if (this.workerProcess) this.startAttempts = 0; }, 10000);
|
|
92
|
+
|
|
40
93
|
// Listen to JSON stream line by line
|
|
41
94
|
let buffer = '';
|
|
42
95
|
this.workerProcess.stdout.on('data', (chunk) => {
|
|
@@ -60,7 +113,6 @@ class WorkerClient {
|
|
|
60
113
|
|
|
61
114
|
_handleResponse(res) {
|
|
62
115
|
if (!res.id || !this.pendingRequests.has(res.id)) {
|
|
63
|
-
// Unbound message or logging
|
|
64
116
|
if (res.type === 'error') {
|
|
65
117
|
console.error(`[Worker Msg] ${res.data}`);
|
|
66
118
|
}
|
|
@@ -73,7 +125,6 @@ class WorkerClient {
|
|
|
73
125
|
if (handlers.onStream) {
|
|
74
126
|
handlers.onStream(res.data, res.type);
|
|
75
127
|
} else {
|
|
76
|
-
// If the user didn't provide a stream handler, buffer it internally
|
|
77
128
|
if (!handlers.buffer) handlers.buffer = '';
|
|
78
129
|
handlers.buffer += res.data + '\n';
|
|
79
130
|
}
|
|
@@ -84,7 +135,7 @@ class WorkerClient {
|
|
|
84
135
|
success: res.exitCode === 0,
|
|
85
136
|
exitCode: res.exitCode,
|
|
86
137
|
output: finalData,
|
|
87
|
-
elapsed: res.elapsedTimeMs
|
|
138
|
+
elapsed: res.elapsedTimeMs,
|
|
88
139
|
});
|
|
89
140
|
this.pendingRequests.delete(res.id);
|
|
90
141
|
}
|
|
@@ -93,40 +144,102 @@ class WorkerClient {
|
|
|
93
144
|
success: false,
|
|
94
145
|
exitCode: -1,
|
|
95
146
|
output: res.data,
|
|
96
|
-
elapsed: 0
|
|
147
|
+
elapsed: 0,
|
|
97
148
|
});
|
|
98
149
|
this.pendingRequests.delete(res.id);
|
|
99
150
|
}
|
|
100
151
|
}
|
|
101
152
|
|
|
153
|
+
/**
|
|
154
|
+
* Native pure-Node fallback executor. Runs the command through the
|
|
155
|
+
* platform shell and streams stdout/stderr back via the callback,
|
|
156
|
+
* resolving with the same shape the Go worker produces.
|
|
157
|
+
*/
|
|
158
|
+
_executeNative(command, dir = '', streamCallback = null) {
|
|
159
|
+
return new Promise((resolve) => {
|
|
160
|
+
const { shell, flag } = platform.getShell();
|
|
161
|
+
const flagArgs = flag.split(' ');
|
|
162
|
+
const startTime = Date.now();
|
|
163
|
+
let output = '';
|
|
164
|
+
|
|
165
|
+
let child;
|
|
166
|
+
try {
|
|
167
|
+
child = spawn(shell, [...flagArgs, command], {
|
|
168
|
+
cwd: dir && fs.existsSync(dir) ? dir : process.cwd(),
|
|
169
|
+
env: process.env,
|
|
170
|
+
windowsHide: true,
|
|
171
|
+
});
|
|
172
|
+
} catch (err) {
|
|
173
|
+
return resolve({ success: false, exitCode: -1, output: `Spawn error: ${err.message}`, elapsed: 0 });
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Hard timeout so a hung command can't wedge the agent forever.
|
|
177
|
+
const timeout = setTimeout(() => {
|
|
178
|
+
try { child.kill('SIGKILL'); } catch (_) { }
|
|
179
|
+
output += '\n[timed out after 5 minutes]';
|
|
180
|
+
}, 5 * 60 * 1000);
|
|
181
|
+
|
|
182
|
+
const onData = (data, type) => {
|
|
183
|
+
const text = data.toString();
|
|
184
|
+
output += text;
|
|
185
|
+
if (output.length > 1024 * 1024) output = output.slice(-1024 * 1024); // cap 1MB
|
|
186
|
+
if (streamCallback) streamCallback(text, type);
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
child.stdout.on('data', (d) => onData(d, 'stdout'));
|
|
190
|
+
child.stderr.on('data', (d) => onData(d, 'stderr'));
|
|
191
|
+
|
|
192
|
+
child.on('error', (err) => {
|
|
193
|
+
clearTimeout(timeout);
|
|
194
|
+
resolve({ success: false, exitCode: -1, output: `${output}\n${err.message}`, elapsed: Date.now() - startTime });
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
child.on('close', (code) => {
|
|
198
|
+
clearTimeout(timeout);
|
|
199
|
+
resolve({
|
|
200
|
+
success: code === 0,
|
|
201
|
+
exitCode: code === null ? -1 : code,
|
|
202
|
+
output: output || '(no output)',
|
|
203
|
+
elapsed: Date.now() - startTime,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
102
209
|
executeCommand(command, dir = '', streamCallback = null) {
|
|
103
|
-
|
|
210
|
+
// Decide on transport.
|
|
211
|
+
if (!this.workerProcess && !this.useNativeFallback) {
|
|
104
212
|
this.start();
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
213
|
+
}
|
|
214
|
+
if (this.useNativeFallback || !this.workerProcess) {
|
|
215
|
+
return this._executeNative(command, dir, streamCallback);
|
|
108
216
|
}
|
|
109
217
|
|
|
110
218
|
return new Promise((resolve) => {
|
|
111
219
|
const reqId = uuidv4();
|
|
112
220
|
|
|
113
|
-
// Register handlers
|
|
114
221
|
this.pendingRequests.set(reqId, {
|
|
115
222
|
resolve,
|
|
116
223
|
onStream: streamCallback,
|
|
117
|
-
buffer: ''
|
|
224
|
+
buffer: '',
|
|
118
225
|
});
|
|
119
226
|
|
|
120
227
|
const req = {
|
|
121
228
|
id: reqId,
|
|
122
|
-
command
|
|
123
|
-
dir
|
|
229
|
+
command,
|
|
230
|
+
dir,
|
|
124
231
|
stream: true,
|
|
125
|
-
timeoutMs: 0
|
|
232
|
+
timeoutMs: 0,
|
|
126
233
|
};
|
|
127
234
|
|
|
128
|
-
|
|
129
|
-
|
|
235
|
+
try {
|
|
236
|
+
this.workerProcess.stdin.write(JSON.stringify(req) + '\n');
|
|
237
|
+
} catch (err) {
|
|
238
|
+
// Pipe broke mid-flight → fall back to native for this call.
|
|
239
|
+
this.pendingRequests.delete(reqId);
|
|
240
|
+
this.useNativeFallback = true;
|
|
241
|
+
this._executeNative(command, dir, streamCallback).then(resolve);
|
|
242
|
+
}
|
|
130
243
|
});
|
|
131
244
|
}
|
|
132
245
|
|
|
@@ -138,6 +251,5 @@ class WorkerClient {
|
|
|
138
251
|
}
|
|
139
252
|
}
|
|
140
253
|
|
|
141
|
-
// Export singleton instance
|
|
142
254
|
const workerClient = new WorkerClient();
|
|
143
255
|
module.exports = workerClient;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "samarthya-bot",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "SamarthyaBot — Privacy-First Local Agentic AI Operating System. Self-hosted multi-agent RPA engine with Telegram, Discord, Web Dashboard, Puppeteer browser control, SSH deployment, encrypted memory, voice transcription, and Indian workflow automation (GST, UPI, IRCTC). Supports Gemini, Claude, GPT, Ollama, DeepSeek, Qwen, OpenRouter. Made in India.",
|
|
5
5
|
"main": "backend/server.js",
|
|
6
6
|
"bin": {
|