@intranefr/superbackend 1.5.2 → 1.5.3
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/index.js +2 -0
- package/manage.js +745 -0
- package/package.json +4 -2
- package/src/controllers/admin.controller.js +11 -5
- package/src/controllers/adminAgents.controller.js +37 -0
- package/src/controllers/adminLlm.controller.js +19 -0
- package/src/controllers/adminMarkdowns.controller.js +157 -0
- package/src/controllers/adminScripts.controller.js +138 -0
- package/src/controllers/adminTelegram.controller.js +72 -0
- package/src/controllers/markdowns.controller.js +42 -0
- package/src/helpers/mongooseHelper.js +6 -6
- package/src/helpers/scriptBase.js +2 -2
- package/src/middleware.js +136 -29
- package/src/models/Agent.js +105 -0
- package/src/models/AgentMessage.js +82 -0
- package/src/models/Markdown.js +75 -0
- package/src/models/ScriptRun.js +8 -0
- package/src/models/TelegramBot.js +42 -0
- package/src/routes/adminAgents.routes.js +13 -0
- package/src/routes/adminLlm.routes.js +1 -0
- package/src/routes/adminMarkdowns.routes.js +16 -0
- package/src/routes/adminScripts.routes.js +4 -1
- package/src/routes/adminTelegram.routes.js +14 -0
- package/src/routes/markdowns.routes.js +16 -0
- package/src/services/agent.service.js +546 -0
- package/src/services/agentHistory.service.js +345 -0
- package/src/services/agentTools.service.js +578 -0
- package/src/services/jsonConfigs.service.js +22 -10
- package/src/services/llm.service.js +219 -6
- package/src/services/markdowns.service.js +522 -0
- package/src/services/scriptsRunner.service.js +328 -37
- package/src/services/telegram.service.js +130 -0
- package/views/admin-agents.ejs +273 -0
- package/views/admin-coolify-deploy.ejs +8 -8
- package/views/admin-dashboard.ejs +36 -5
- package/views/admin-experiments.ejs +1 -1
- package/views/admin-markdowns.ejs +905 -0
- package/views/admin-scripts.ejs +221 -4
- package/views/admin-telegram.ejs +269 -0
- package/views/partials/dashboard/nav-items.ejs +3 -0
- package/analysis-only.skill +0 -0
|
@@ -28,22 +28,51 @@ const MAX_TAIL_BYTES = 64 * 1024;
|
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
function wrapInAsyncIife(code) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
const body = String(code || '');
|
|
32
|
+
return [
|
|
33
|
+
'(async () => {',
|
|
34
|
+
body,
|
|
35
|
+
'})().then((result) => {',
|
|
36
|
+
' // Store result globally for VM2 to capture',
|
|
37
|
+
' global.__scriptResult = result;',
|
|
38
|
+
'}).catch((err) => {',
|
|
39
|
+
' try { console.error(err && err.stack ? err.stack : err); } catch {}',
|
|
40
|
+
' global.__scriptResult = { error: err.message || String(err) };',
|
|
41
|
+
'});',
|
|
42
|
+
'',
|
|
43
|
+
].join('\n');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function wrapExistingAsyncIife(code) {
|
|
47
|
+
const body = String(code || '');
|
|
48
|
+
// Remove the final closing parenthesis and semicolon, then add our result capture
|
|
49
|
+
const codeWithoutEnding = body.replace(/\)\s*;?\s*$/, '');
|
|
50
|
+
return [
|
|
51
|
+
'// Wrapped to capture return value',
|
|
52
|
+
codeWithoutEnding,
|
|
53
|
+
').then((result) => {',
|
|
54
|
+
' // Store result globally for VM2 to capture',
|
|
55
|
+
' global.__scriptResult = result;',
|
|
56
|
+
'}).catch((err) => {',
|
|
57
|
+
' try { console.error(err && err.stack ? err.stack : err); } catch {}',
|
|
58
|
+
' global.__scriptResult = { error: err.message || String(err) };',
|
|
59
|
+
'});',
|
|
60
|
+
'',
|
|
61
|
+
].join('\n');
|
|
40
62
|
}
|
|
41
63
|
|
|
42
64
|
function prepareVmCodeForExecution(code) {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
65
|
+
const raw = String(code || '');
|
|
66
|
+
if (!shouldAutoWrapAsyncScripts()) return { code: raw, wrapped: false };
|
|
67
|
+
if (!detectTopLevelAwait(raw)) return { code: raw, wrapped: false };
|
|
68
|
+
|
|
69
|
+
// Check if it's already an async IIFE that doesn't expose its result
|
|
70
|
+
if (/^\s*\(\s*async\s+function\s*\(/.test(raw) && !/global\.__scriptResult\s*=/.test(raw)) {
|
|
71
|
+
// Wrap the existing async IIFE to capture its result
|
|
72
|
+
return { code: wrapExistingAsyncIife(raw), wrapped: true };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return { code: wrapInAsyncIife(raw), wrapped: true };
|
|
47
76
|
}
|
|
48
77
|
|
|
49
78
|
function buildAwaitSyntaxHelpMessage() {
|
|
@@ -71,15 +100,88 @@ function decodeScriptContent(script, format) {
|
|
|
71
100
|
return script;
|
|
72
101
|
}
|
|
73
102
|
|
|
74
|
-
|
|
75
|
-
|
|
103
|
+
const nowIso = () => new Date().toISOString();
|
|
104
|
+
|
|
105
|
+
const appendTail = (tail, more) => {
|
|
106
|
+
const max = 20000; // keep last 20k chars
|
|
107
|
+
tail = (tail + more).slice(-max);
|
|
108
|
+
return tail;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
// Infrastructure log patterns to filter out
|
|
112
|
+
const infrastructurePatterns = [
|
|
113
|
+
'Using existing app database connection',
|
|
114
|
+
'No existing connection found',
|
|
115
|
+
'Auto-wrapping script in async function',
|
|
116
|
+
'=== SCRIPT START ===',
|
|
117
|
+
'=== SCRIPT END ===',
|
|
118
|
+
'Executing script',
|
|
119
|
+
'Script preview',
|
|
120
|
+
'Database connection established',
|
|
121
|
+
'chars)',
|
|
122
|
+
'Infrastructure logs'
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
// Utility functions for output processing
|
|
126
|
+
function isInfrastructureLog(line) {
|
|
127
|
+
return infrastructurePatterns.some(pattern => line.includes(pattern));
|
|
76
128
|
}
|
|
77
129
|
|
|
78
|
-
function
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
130
|
+
function isMeaningfulConsoleLog(line) {
|
|
131
|
+
return !isInfrastructureLog(line) &&
|
|
132
|
+
line.trim().length > 0 &&
|
|
133
|
+
!line.startsWith('[') &&
|
|
134
|
+
!line.includes('===');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function formatOutput(value) {
|
|
138
|
+
if (typeof value === 'object') {
|
|
139
|
+
return JSON.stringify(value, null, 2);
|
|
140
|
+
}
|
|
141
|
+
return String(value);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function tryParseJson(str) {
|
|
145
|
+
try {
|
|
146
|
+
return JSON.parse(str);
|
|
147
|
+
} catch {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function isJsonString(str) {
|
|
153
|
+
return tryParseJson(str) !== null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function determineProgrammaticOutput(returnValue, lastConsoleLog) {
|
|
157
|
+
// Priority 1: Return value
|
|
158
|
+
if (returnValue !== undefined && returnValue !== null) {
|
|
159
|
+
const formatted = formatOutput(returnValue);
|
|
160
|
+
return {
|
|
161
|
+
programmaticOutput: formatted,
|
|
162
|
+
outputType: 'return',
|
|
163
|
+
isJson: isJsonString(formatted),
|
|
164
|
+
parsedResult: tryParseJson(formatted)
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Priority 2: Last meaningful console.log
|
|
169
|
+
if (lastConsoleLog && !isInfrastructureLog(lastConsoleLog)) {
|
|
170
|
+
return {
|
|
171
|
+
programmaticOutput: lastConsoleLog,
|
|
172
|
+
outputType: 'console',
|
|
173
|
+
isJson: isJsonString(lastConsoleLog),
|
|
174
|
+
parsedResult: tryParseJson(lastConsoleLog)
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Priority 3: No output
|
|
179
|
+
return {
|
|
180
|
+
programmaticOutput: 'No output',
|
|
181
|
+
outputType: 'none',
|
|
182
|
+
isJson: false,
|
|
183
|
+
parsedResult: null
|
|
184
|
+
};
|
|
83
185
|
}
|
|
84
186
|
|
|
85
187
|
function safeJsonParse(str) {
|
|
@@ -272,12 +374,31 @@ async function runSpawned({ runId, bus, command, args, env, cwd, timeoutMs }) {
|
|
|
272
374
|
|
|
273
375
|
async function runHostWithDatabase({ runId, bus, code, env, cwd, timeoutMs }) {
|
|
274
376
|
let tail = '';
|
|
377
|
+
let scriptResult = null;
|
|
378
|
+
let lastConsoleLog = null;
|
|
275
379
|
|
|
276
380
|
function pushLog(stream, line) {
|
|
277
381
|
const s = String(line || '');
|
|
278
382
|
tail = appendTail(tail, s);
|
|
279
383
|
bus.push({ type: 'log', ts: nowIso(), stream, line: s });
|
|
280
|
-
|
|
384
|
+
|
|
385
|
+
// Track last console.log for programmatic output
|
|
386
|
+
if (stream === 'stdout' && isMeaningfulConsoleLog(s)) {
|
|
387
|
+
lastConsoleLog = s.trim();
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
// Update both outputTail and fullOutput using string concatenation
|
|
391
|
+
return ScriptRun.findById(runId).then(run => {
|
|
392
|
+
if (run) {
|
|
393
|
+
run.outputTail = tail;
|
|
394
|
+
run.fullOutput = (run.fullOutput || '') + s;
|
|
395
|
+
run.lastConsoleLog = lastConsoleLog;
|
|
396
|
+
run.lastOutputUpdate = new Date();
|
|
397
|
+
run.outputSize = (run.outputSize || 0) + s.length;
|
|
398
|
+
run.lineCount = (run.lineCount || 0) + (s.split('\n').length - 1);
|
|
399
|
+
return run.save();
|
|
400
|
+
}
|
|
401
|
+
});
|
|
281
402
|
}
|
|
282
403
|
|
|
283
404
|
try {
|
|
@@ -299,7 +420,7 @@ async function runHostWithDatabase({ runId, bus, code, env, cwd, timeoutMs }) {
|
|
|
299
420
|
|
|
300
421
|
const prepared = prepareVmCodeForExecution(code);
|
|
301
422
|
if (prepared.wrapped) {
|
|
302
|
-
await pushLog('stdout', 'Auto-wrapping script
|
|
423
|
+
await pushLog('stdout', 'Auto-wrapping script to capture return value\n');
|
|
303
424
|
}
|
|
304
425
|
|
|
305
426
|
// Create a VM with database context
|
|
@@ -366,11 +487,21 @@ async function runHostWithDatabase({ runId, bus, code, env, cwd, timeoutMs }) {
|
|
|
366
487
|
// Process environment
|
|
367
488
|
process: {
|
|
368
489
|
env: { ...process.env, ...env }
|
|
369
|
-
}
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
// Debug: Log available models
|
|
493
|
+
debugModels: () => {
|
|
494
|
+
console.log('Available models:', Object.keys(mongoose.models || {}));
|
|
495
|
+
return mongoose.models || {};
|
|
496
|
+
},
|
|
497
|
+
|
|
498
|
+
// Global variables for async result capture
|
|
499
|
+
global: {},
|
|
500
|
+
__scriptResult: undefined
|
|
370
501
|
},
|
|
371
502
|
require: {
|
|
372
503
|
external: false,
|
|
373
|
-
builtin: ['util', 'path', 'os'], // Allow
|
|
504
|
+
builtin: ['util', 'path', 'os', 'mongoose'], // Allow mongoose for parent app model access
|
|
374
505
|
},
|
|
375
506
|
timeout: timeoutMs,
|
|
376
507
|
eval: false,
|
|
@@ -379,30 +510,138 @@ async function runHostWithDatabase({ runId, bus, code, env, cwd, timeoutMs }) {
|
|
|
379
510
|
|
|
380
511
|
// Set up console redirection
|
|
381
512
|
vm.on('console.log', (...args) => {
|
|
382
|
-
|
|
513
|
+
const message = args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ') + '\n';
|
|
514
|
+
|
|
515
|
+
// Send to UI output
|
|
516
|
+
pushLog('stdout', message);
|
|
517
|
+
|
|
518
|
+
// Also send to parent process (backend logs)
|
|
519
|
+
console.log('[Script]', message.trim());
|
|
383
520
|
});
|
|
384
521
|
vm.on('console.error', (...args) => {
|
|
385
|
-
|
|
522
|
+
const message = args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ') + '\n';
|
|
523
|
+
|
|
524
|
+
// Send to UI output
|
|
525
|
+
pushLog('stderr', message);
|
|
526
|
+
|
|
527
|
+
// Also send to parent process (backend logs)
|
|
528
|
+
console.error('[Script]', message.trim());
|
|
529
|
+
});
|
|
530
|
+
|
|
531
|
+
// Handle unhandled promise rejections within the VM
|
|
532
|
+
vm.on('unhandledRejection', (reason, promise) => {
|
|
533
|
+
let errorMsg = 'Unhandled Promise Rejection: ';
|
|
534
|
+
|
|
535
|
+
if (reason instanceof Error) {
|
|
536
|
+
errorMsg += reason.message;
|
|
537
|
+
if (reason.stack) {
|
|
538
|
+
errorMsg += '\n' + reason.stack;
|
|
539
|
+
}
|
|
540
|
+
} else {
|
|
541
|
+
errorMsg += JSON.stringify(reason);
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
// Send to UI output
|
|
545
|
+
pushLog('stderr', errorMsg + '\n');
|
|
546
|
+
|
|
547
|
+
// Also send to parent process (backend logs)
|
|
548
|
+
console.error('[Script]', errorMsg);
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Handle uncaught exceptions within the VM
|
|
552
|
+
vm.on('error', (error) => {
|
|
553
|
+
let errorMsg = 'VM Error: ';
|
|
554
|
+
|
|
555
|
+
if (error instanceof Error) {
|
|
556
|
+
errorMsg += error.message;
|
|
557
|
+
if (error.stack) {
|
|
558
|
+
errorMsg += '\n' + error.stack;
|
|
559
|
+
}
|
|
560
|
+
} else {
|
|
561
|
+
errorMsg += JSON.stringify(error);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Send to UI output
|
|
565
|
+
pushLog('stderr', errorMsg + '\n');
|
|
566
|
+
|
|
567
|
+
// Also send to parent process (backend logs)
|
|
568
|
+
console.error('[Script]', errorMsg);
|
|
386
569
|
});
|
|
387
570
|
|
|
388
571
|
// Run the script code with better error handling
|
|
389
572
|
try {
|
|
573
|
+
await pushLog('stdout', `=== SCRIPT START ===\n`);
|
|
574
|
+
await pushLog('stdout', `Executing script (${prepared.code.length} chars)...\n`);
|
|
575
|
+
|
|
576
|
+
// Show first few lines of script for debugging
|
|
577
|
+
const scriptPreview = prepared.code.split('\n').slice(0, 5).join('\n');
|
|
578
|
+
await pushLog('stdout', `Script preview:\n${scriptPreview}\n...\n`);
|
|
579
|
+
|
|
580
|
+
// Run the script
|
|
390
581
|
vm.run(prepared.code, 'script.host.js');
|
|
582
|
+
|
|
583
|
+
// Capture result based on whether the script was wrapped
|
|
584
|
+
if (prepared.wrapped) {
|
|
585
|
+
// For wrapped scripts, wait and capture from global variable
|
|
586
|
+
await new Promise(resolve => setTimeout(resolve, 100)); // Wait for async completion
|
|
587
|
+
scriptResult = vm.sandbox.__scriptResult;
|
|
588
|
+
} else {
|
|
589
|
+
// For non-wrapped scripts, try to capture direct return or use global variable
|
|
590
|
+
scriptResult = vm.sandbox.__scriptResult;
|
|
591
|
+
// Also try to get the last meaningful console.log as fallback
|
|
592
|
+
if (!scriptResult && lastConsoleLog) {
|
|
593
|
+
scriptResult = lastConsoleLog;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
await pushLog('stdout', `=== SCRIPT END ===\n`);
|
|
598
|
+
|
|
599
|
+
// Determine and save programmatic output
|
|
600
|
+
const programmaticOutput = determineProgrammaticOutput(scriptResult, lastConsoleLog);
|
|
601
|
+
await ScriptRun.updateOne(
|
|
602
|
+
{ _id: runId },
|
|
603
|
+
{
|
|
604
|
+
$set: {
|
|
605
|
+
programmaticOutput: programmaticOutput.programmaticOutput,
|
|
606
|
+
returnResult: scriptResult !== undefined && scriptResult !== null ? formatOutput(scriptResult) : '',
|
|
607
|
+
outputType: programmaticOutput.outputType
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
);
|
|
391
611
|
} catch (vmError) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
612
|
+
let errorMsg = 'VM execution error: ';
|
|
613
|
+
|
|
614
|
+
if (vmError instanceof Error) {
|
|
615
|
+
errorMsg += vmError.message;
|
|
616
|
+
if (vmError.stack) {
|
|
617
|
+
errorMsg += '\n' + vmError.stack;
|
|
618
|
+
}
|
|
619
|
+
} else {
|
|
620
|
+
errorMsg += JSON.stringify(vmError);
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
const help = vmError?.message?.includes('await is only valid in async functions') ? `\n\n${buildAwaitSyntaxHelpMessage()}` : '';
|
|
624
|
+
await pushLog('stderr', errorMsg + help + '\n');
|
|
396
625
|
return 1;
|
|
397
626
|
}
|
|
398
627
|
|
|
399
628
|
return 0;
|
|
400
629
|
|
|
401
630
|
} catch (err) {
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
631
|
+
let errorMsg = 'Host script error: ';
|
|
632
|
+
|
|
633
|
+
if (err instanceof Error) {
|
|
634
|
+
errorMsg += err.message;
|
|
635
|
+
if (err.stack) {
|
|
636
|
+
errorMsg += '\n' + err.stack;
|
|
637
|
+
}
|
|
638
|
+
} else {
|
|
639
|
+
errorMsg += JSON.stringify(err);
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
await pushLog('stderr', errorMsg + '\n');
|
|
643
|
+
return 1;
|
|
644
|
+
} finally {
|
|
406
645
|
// Don't disconnect here - let mongooseHelper manage connection pooling
|
|
407
646
|
// The connection will be cleaned up when the helper decides
|
|
408
647
|
}
|
|
@@ -410,17 +649,39 @@ async function runHostWithDatabase({ runId, bus, code, env, cwd, timeoutMs }) {
|
|
|
410
649
|
|
|
411
650
|
async function runVm2({ runId, bus, code, timeoutMs }) {
|
|
412
651
|
let tail = '';
|
|
652
|
+
let scriptResult = null;
|
|
653
|
+
let lastConsoleLog = null;
|
|
413
654
|
|
|
414
655
|
function pushLog(stream, line) {
|
|
415
656
|
const s = String(line || '');
|
|
416
657
|
tail = appendTail(tail, s);
|
|
417
658
|
bus.push({ type: 'log', ts: nowIso(), stream, line: s });
|
|
418
|
-
|
|
659
|
+
|
|
660
|
+
// Track last console.log for programmatic output
|
|
661
|
+
if (stream === 'stdout' && isMeaningfulConsoleLog(s)) {
|
|
662
|
+
lastConsoleLog = s.trim();
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// Update both outputTail and fullOutput using string concatenation
|
|
666
|
+
return ScriptRun.findById(runId).then(run => {
|
|
667
|
+
if (run) {
|
|
668
|
+
run.outputTail = tail;
|
|
669
|
+
run.fullOutput = (run.fullOutput || '') + s;
|
|
670
|
+
run.lastConsoleLog = lastConsoleLog;
|
|
671
|
+
run.lastOutputUpdate = new Date();
|
|
672
|
+
run.outputSize = (run.outputSize || 0) + s.length;
|
|
673
|
+
run.lineCount = (run.lineCount || 0) + (s.split('\n').length - 1);
|
|
674
|
+
return run.save();
|
|
675
|
+
}
|
|
676
|
+
});
|
|
419
677
|
}
|
|
420
678
|
|
|
421
679
|
const vm = new NodeVM({
|
|
422
680
|
console: 'redirect',
|
|
423
|
-
sandbox: {
|
|
681
|
+
sandbox: {
|
|
682
|
+
global: {},
|
|
683
|
+
__scriptResult: undefined
|
|
684
|
+
},
|
|
424
685
|
require: {
|
|
425
686
|
external: false,
|
|
426
687
|
builtin: [],
|
|
@@ -440,9 +701,39 @@ async function runVm2({ runId, bus, code, timeoutMs }) {
|
|
|
440
701
|
try {
|
|
441
702
|
const prepared = prepareVmCodeForExecution(code);
|
|
442
703
|
if (prepared.wrapped) {
|
|
443
|
-
await pushLog('stdout', 'Auto-wrapping script
|
|
704
|
+
await pushLog('stdout', 'Auto-wrapping script to capture return value\n');
|
|
444
705
|
}
|
|
706
|
+
|
|
707
|
+
// Run the script
|
|
445
708
|
vm.run(prepared.code, 'script.vm2.js');
|
|
709
|
+
|
|
710
|
+
// Capture result based on whether the script was wrapped
|
|
711
|
+
if (prepared.wrapped) {
|
|
712
|
+
// For wrapped scripts, wait and capture from global variable
|
|
713
|
+
await new Promise(resolve => setTimeout(resolve, 100)); // Wait for async completion
|
|
714
|
+
scriptResult = vm.sandbox.__scriptResult;
|
|
715
|
+
} else {
|
|
716
|
+
// For non-wrapped scripts, try to capture direct return or use global variable
|
|
717
|
+
scriptResult = vm.sandbox.__scriptResult;
|
|
718
|
+
// Also try to get the last meaningful console.log as fallback
|
|
719
|
+
if (!scriptResult && lastConsoleLog) {
|
|
720
|
+
scriptResult = lastConsoleLog;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// Determine and save programmatic output
|
|
725
|
+
const programmaticOutput = determineProgrammaticOutput(scriptResult, lastConsoleLog);
|
|
726
|
+
await ScriptRun.updateOne(
|
|
727
|
+
{ _id: runId },
|
|
728
|
+
{
|
|
729
|
+
$set: {
|
|
730
|
+
programmaticOutput: programmaticOutput.programmaticOutput,
|
|
731
|
+
returnResult: scriptResult !== undefined && scriptResult !== null ? formatOutput(scriptResult) : '',
|
|
732
|
+
outputType: programmaticOutput.outputType
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
);
|
|
736
|
+
|
|
446
737
|
return 0;
|
|
447
738
|
} catch (err) {
|
|
448
739
|
const baseMsg = err?.message || 'vm2 error';
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
const TelegramBot = require('node-telegram-bot-api');
|
|
2
|
+
const TelegramBotModel = require('../models/TelegramBot');
|
|
3
|
+
const Agent = require('../models/Agent');
|
|
4
|
+
const agentService = require('./agent.service');
|
|
5
|
+
|
|
6
|
+
const activeBots = new Map();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Start a telegram bot by its ID
|
|
10
|
+
*/
|
|
11
|
+
async function startBot(botId) {
|
|
12
|
+
try {
|
|
13
|
+
const botDoc = await TelegramBotModel.findById(botId);
|
|
14
|
+
if (!botDoc) throw new Error('Bot not found');
|
|
15
|
+
|
|
16
|
+
if (activeBots.has(botId.toString())) {
|
|
17
|
+
await stopBot(botId);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const bot = new TelegramBot(botDoc.token, { polling: true });
|
|
21
|
+
|
|
22
|
+
bot.on('message', async (msg) => {
|
|
23
|
+
if (!msg.text) return;
|
|
24
|
+
|
|
25
|
+
// Security: check if user is allowed
|
|
26
|
+
if (botDoc.allowedUserIds && botDoc.allowedUserIds.length > 0) {
|
|
27
|
+
if (!botDoc.allowedUserIds.includes(msg.from.id.toString())) {
|
|
28
|
+
console.warn(`Unauthorized access attempt from ${msg.from.id} to bot ${botDoc.name}`);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
console.log(`[TelegramBot: ${botDoc.name}] Received: ${msg.text} from ${msg.from.id}`);
|
|
34
|
+
|
|
35
|
+
// Delegate to agent service
|
|
36
|
+
try {
|
|
37
|
+
if (msg.text.toLowerCase() === '/start') {
|
|
38
|
+
await bot.sendMessage(msg.chat.id, 'Hi there! I am your SuperBackend Agent. How can I help you today?');
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (!botDoc.defaultAgentId) {
|
|
43
|
+
console.log(`[TelegramBot: ${botDoc.name}] No agent configured, ignoring message from ${msg.from.id}`);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const response = await agentService.processMessage(botDoc.defaultAgentId, {
|
|
48
|
+
content: msg.text,
|
|
49
|
+
senderId: msg.from.id.toString(),
|
|
50
|
+
chatId: msg.chat.id.toString(),
|
|
51
|
+
metadata: {
|
|
52
|
+
firstName: msg.from.first_name,
|
|
53
|
+
lastName: msg.from.last_name,
|
|
54
|
+
username: msg.from.username
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await bot.sendMessage(msg.chat.id, response.text || response);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
console.error('Error processing message:', err);
|
|
61
|
+
await bot.sendMessage(msg.chat.id, 'Sorry, I encountered an error processing your request.');
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
bot.on('polling_error', (err) => {
|
|
66
|
+
console.error(`Telegram polling error [${botDoc.name}]:`, err);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
activeBots.set(botId.toString(), bot);
|
|
70
|
+
|
|
71
|
+
botDoc.status = 'running';
|
|
72
|
+
botDoc.lastError = null;
|
|
73
|
+
await botDoc.save();
|
|
74
|
+
|
|
75
|
+
console.log(`Telegram bot [${botDoc.name}] started`);
|
|
76
|
+
return true;
|
|
77
|
+
} catch (err) {
|
|
78
|
+
console.error(`Failed to start Telegram bot [${botId}]:`, err);
|
|
79
|
+
const botDoc = await TelegramBotModel.findById(botId);
|
|
80
|
+
if (botDoc) {
|
|
81
|
+
botDoc.status = 'error';
|
|
82
|
+
botDoc.lastError = err.message;
|
|
83
|
+
await botDoc.save();
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Stop a telegram bot
|
|
91
|
+
*/
|
|
92
|
+
async function stopBot(botId) {
|
|
93
|
+
const bot = activeBots.get(botId.toString());
|
|
94
|
+
if (bot) {
|
|
95
|
+
await bot.stopPolling();
|
|
96
|
+
activeBots.delete(botId.toString());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const botDoc = await TelegramBotModel.findById(botId);
|
|
100
|
+
if (botDoc) {
|
|
101
|
+
botDoc.status = 'stopped';
|
|
102
|
+
await botDoc.save();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(`Telegram bot [${botId}] stopped`);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Initialize all active bots on startup
|
|
110
|
+
*/
|
|
111
|
+
async function init() {
|
|
112
|
+
try {
|
|
113
|
+
const activeBotsDocs = await TelegramBotModel.find({ isActive: true });
|
|
114
|
+
for (const botDoc of activeBotsDocs) {
|
|
115
|
+
try {
|
|
116
|
+
await startBot(botDoc._id);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
// Continue with other bots
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
} catch (err) {
|
|
122
|
+
console.error('Failed to initialize Telegram bots:', err);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
module.exports = {
|
|
127
|
+
startBot,
|
|
128
|
+
stopBot,
|
|
129
|
+
init
|
|
130
|
+
};
|