blockmine 1.22.0 → 1.23.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/.claude/settings.json +5 -1
- package/.claude/settings.local.json +10 -1
- package/CHANGELOG.md +27 -3
- package/CLAUDE.md +284 -0
- package/README.md +302 -152
- package/backend/package-lock.json +681 -9
- package/backend/package.json +8 -0
- package/backend/prisma/migrations/20251116111851_add_execution_trace/migration.sql +22 -0
- package/backend/prisma/migrations/20251120154914_add_panel_api_keys/migration.sql +21 -0
- package/backend/prisma/migrations/20251121110241_add_proxy_table/migration.sql +45 -0
- package/backend/prisma/migrations/migration_lock.toml +2 -2
- package/backend/prisma/schema.prisma +70 -1
- package/backend/src/__tests__/services/BotLifecycleService.test.js +9 -4
- package/backend/src/ai/plugin-assistant-system-prompt.md +788 -0
- package/backend/src/api/middleware/auth.js +27 -0
- package/backend/src/api/middleware/botAccess.js +7 -3
- package/backend/src/api/middleware/panelApiAuth.js +135 -0
- package/backend/src/api/routes/aiAssistant.js +995 -0
- package/backend/src/api/routes/auth.js +669 -633
- package/backend/src/api/routes/botCommands.js +107 -0
- package/backend/src/api/routes/botGroups.js +165 -0
- package/backend/src/api/routes/botHistory.js +108 -0
- package/backend/src/api/routes/botPermissions.js +99 -0
- package/backend/src/api/routes/botStatus.js +36 -0
- package/backend/src/api/routes/botUsers.js +162 -0
- package/backend/src/api/routes/bots.js +2451 -2402
- package/backend/src/api/routes/eventGraphs.js +4 -1
- package/backend/src/api/routes/logs.js +13 -3
- package/backend/src/api/routes/panel.js +66 -66
- package/backend/src/api/routes/panelApiKeys.js +179 -0
- package/backend/src/api/routes/pluginIde.js +1715 -135
- package/backend/src/api/routes/plugins.js +376 -219
- package/backend/src/api/routes/proxies.js +130 -0
- package/backend/src/api/routes/search.js +4 -0
- package/backend/src/api/routes/servers.js +20 -3
- package/backend/src/api/routes/settings.js +5 -0
- package/backend/src/api/routes/system.js +174 -174
- package/backend/src/api/routes/traces.js +131 -0
- package/backend/src/config/debug.config.js +36 -0
- package/backend/src/core/BotHistoryStore.js +180 -0
- package/backend/src/core/BotManager.js +14 -4
- package/backend/src/core/BotProcess.js +1517 -1092
- package/backend/src/core/EventGraphManager.js +37 -123
- package/backend/src/core/GraphExecutionEngine.js +977 -321
- package/backend/src/core/MessageQueue.js +12 -6
- package/backend/src/core/PluginLoader.js +99 -5
- package/backend/src/core/PluginManager.js +74 -13
- package/backend/src/core/TaskScheduler.js +1 -1
- package/backend/src/core/commands/whois.js +1 -1
- package/backend/src/core/node-registries/actions.js +70 -0
- package/backend/src/core/node-registries/arrays.js +18 -0
- package/backend/src/core/node-registries/data.js +1 -1
- package/backend/src/core/node-registries/events.js +14 -0
- package/backend/src/core/node-registries/logic.js +17 -0
- package/backend/src/core/node-registries/strings.js +34 -0
- package/backend/src/core/node-registries/type.js +25 -0
- package/backend/src/core/nodes/actions/bot_look_at.js +1 -1
- package/backend/src/core/nodes/actions/create_command.js +189 -0
- package/backend/src/core/nodes/actions/delete_command.js +92 -0
- package/backend/src/core/nodes/actions/update_command.js +133 -0
- package/backend/src/core/nodes/arrays/join.js +28 -0
- package/backend/src/core/nodes/data/cast.js +2 -1
- package/backend/src/core/nodes/logic/not.js +22 -0
- package/backend/src/core/nodes/strings/starts_with.js +1 -1
- package/backend/src/core/nodes/strings/to_lower.js +22 -0
- package/backend/src/core/nodes/strings/to_upper.js +22 -0
- package/backend/src/core/nodes/type/to_string.js +32 -0
- package/backend/src/core/services/BotLifecycleService.js +255 -16
- package/backend/src/core/services/CommandExecutionService.js +430 -351
- package/backend/src/core/services/DebugSessionManager.js +347 -0
- package/backend/src/core/services/GraphCollaborationManager.js +501 -0
- package/backend/src/core/services/MinecraftBotManager.js +259 -0
- package/backend/src/core/services/MinecraftViewerService.js +216 -0
- package/backend/src/core/services/TraceCollectorService.js +545 -0
- package/backend/src/core/system/RuntimeCommandRegistry.js +116 -0
- package/backend/src/core/system/Transport.js +0 -4
- package/backend/src/core/validation/nodeSchemas.js +6 -6
- package/backend/src/real-time/botApi/handlers/graphHandlers.js +2 -2
- package/backend/src/real-time/botApi/handlers/graphWebSocketHandlers.js +1 -1
- package/backend/src/real-time/botApi/utils.js +11 -0
- package/backend/src/real-time/panelNamespace.js +387 -0
- package/backend/src/real-time/presence.js +7 -2
- package/backend/src/real-time/socketHandler.js +395 -4
- package/backend/src/server.js +18 -0
- package/frontend/dist/assets/index-B1serztM.js +11210 -0
- package/frontend/dist/assets/index-t6K1u4OV.css +32 -0
- package/frontend/dist/index.html +2 -2
- package/frontend/package-lock.json +9437 -0
- package/frontend/package.json +8 -0
- package/package.json +2 -2
- package/screen/console.png +0 -0
- package/screen/dashboard.png +0 -0
- package/screen/graph_collabe.png +0 -0
- package/screen/graph_live_debug.png +0 -0
- package/screen/management_command.png +0 -0
- package/screen/node_debug_trace.png +0 -0
- package/screen/plugin_/320/276/320/261/320/267/320/276/321/200.png +0 -0
- package/screen/websocket.png +0 -0
- package/screen//320/275/320/260/321/201/321/202/321/200/320/276/320/271/320/272/320/270_/320/276/321/202/320/264/320/265/320/273/321/214/320/275/321/213/321/205_/320/272/320/276/320/274/320/260/320/275/320/264_/320/272/320/260/320/266/320/264/321/203_/320/272/320/276/320/274/320/260/320/275/320/273/320/264/321/203_/320/274/320/276/320/266/320/275/320/276_/320/275/320/260/321/201/321/202/321/200/320/260/320/270/320/262/320/260/321/202/321/214.png +0 -0
- package/screen//320/277/320/273/320/260/320/275/320/270/321/200/320/276/320/262/321/211/320/270/320/272_/320/274/320/276/320/266/320/275/320/276_/320/267/320/260/320/264/320/260/320/262/320/260/321/202/321/214_/320/264/320/265/320/271/321/201/321/202/320/262/320/270/321/217_/320/277/320/276_/320/262/321/200/320/265/320/274/320/265/320/275/320/270.png +0 -0
- package/frontend/dist/assets/index-CfTo92bP.css +0 -1
- package/frontend/dist/assets/index-CiFD5X9Z.js +0 -8344
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
const prisma = require('../../lib/prisma');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Сервис для сбора и управления трассировками выполнения графов
|
|
5
|
+
*/
|
|
6
|
+
class TraceCollectorService {
|
|
7
|
+
constructor() {
|
|
8
|
+
// Хранилище активных трассировок в памяти
|
|
9
|
+
// Ключ: traceId, Значение: { trace, steps: [] }
|
|
10
|
+
this.activeTraces = new Map();
|
|
11
|
+
|
|
12
|
+
// Socket.IO instance (будет установлен извне)
|
|
13
|
+
this.io = null;
|
|
14
|
+
|
|
15
|
+
// Конфигурация
|
|
16
|
+
this.config = {
|
|
17
|
+
// Хранить ли трассировки в БД
|
|
18
|
+
persistToDb: process.env.TRACE_PERSIST_TO_DB !== 'false', // По умолчанию true
|
|
19
|
+
// Максимальное количество трассировок в памяти на бота
|
|
20
|
+
maxTracesPerBot: parseInt(process.env.TRACE_MAX_PER_BOT) || 50,
|
|
21
|
+
// Автоочистка старых трассировок (дни)
|
|
22
|
+
cleanupAfterDays: parseInt(process.env.TRACE_CLEANUP_DAYS) || 7,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// Хранилище завершённых трассировок в памяти
|
|
26
|
+
// Ключ: botId, Значение: [traces] (отсортированные по времени)
|
|
27
|
+
this.completedTraces = new Map();
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Установить Socket.IO instance для real-time уведомлений
|
|
32
|
+
*/
|
|
33
|
+
setSocketIO(io) {
|
|
34
|
+
this.io = io;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Начать новую трассировку выполнения
|
|
39
|
+
*/
|
|
40
|
+
async startTrace(botId, graphId, eventType, eventArgs = {}) {
|
|
41
|
+
const traceId = this._generateTraceId();
|
|
42
|
+
|
|
43
|
+
const trace = {
|
|
44
|
+
id: traceId,
|
|
45
|
+
graphId,
|
|
46
|
+
botId,
|
|
47
|
+
eventType,
|
|
48
|
+
eventArgs: JSON.stringify(eventArgs),
|
|
49
|
+
startTime: new Date(),
|
|
50
|
+
endTime: null,
|
|
51
|
+
status: 'running',
|
|
52
|
+
error: null,
|
|
53
|
+
steps: []
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
this.activeTraces.set(traceId, trace);
|
|
57
|
+
|
|
58
|
+
// Эмитим событие начала трассировки
|
|
59
|
+
this._emitTraceEvent(botId, graphId, 'trace:start', {
|
|
60
|
+
traceId,
|
|
61
|
+
graphId,
|
|
62
|
+
eventType,
|
|
63
|
+
startTime: trace.startTime,
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
return traceId;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Записать шаг выполнения ноды
|
|
71
|
+
*/
|
|
72
|
+
recordStep(traceId, stepData) {
|
|
73
|
+
const trace = this.activeTraces.get(traceId);
|
|
74
|
+
if (!trace) {
|
|
75
|
+
console.warn(`[TraceCollector] Трассировка ${traceId} не найдена`);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const step = {
|
|
80
|
+
nodeId: stepData.nodeId,
|
|
81
|
+
nodeType: stepData.nodeType,
|
|
82
|
+
timestamp: new Date(),
|
|
83
|
+
status: stepData.status || 'executed', // executed, skipped, error
|
|
84
|
+
inputs: stepData.inputs || {},
|
|
85
|
+
outputs: stepData.outputs || {},
|
|
86
|
+
error: stepData.error || null,
|
|
87
|
+
duration: stepData.duration || null,
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
trace.steps.push(step);
|
|
91
|
+
|
|
92
|
+
// Эмитим событие шага
|
|
93
|
+
this._emitTraceEvent(trace.botId, trace.graphId, 'trace:step', {
|
|
94
|
+
traceId,
|
|
95
|
+
step,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Обновить outputs для последнего шага с указанным nodeId
|
|
101
|
+
*/
|
|
102
|
+
updateStepOutputs(traceId, nodeId, outputs) {
|
|
103
|
+
const trace = this.activeTraces.get(traceId);
|
|
104
|
+
if (!trace) {
|
|
105
|
+
// Трассировка уже завершена - это нормально, просто игнорируем
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Находим последний шаг с этим nodeId
|
|
110
|
+
for (let i = trace.steps.length - 1; i >= 0; i--) {
|
|
111
|
+
if (trace.steps[i].nodeId === nodeId && trace.steps[i].type !== 'traversal') {
|
|
112
|
+
trace.steps[i].outputs = outputs;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Обновить duration для последнего шага с указанным nodeId
|
|
120
|
+
*/
|
|
121
|
+
updateStepDuration(traceId, nodeId, duration) {
|
|
122
|
+
const trace = this.activeTraces.get(traceId);
|
|
123
|
+
if (!trace) {
|
|
124
|
+
// Трассировка уже завершена - это нормально, просто игнорируем
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Находим последний шаг с этим nodeId
|
|
129
|
+
for (let i = trace.steps.length - 1; i >= 0; i--) {
|
|
130
|
+
if (trace.steps[i].nodeId === nodeId && trace.steps[i].type !== 'traversal') {
|
|
131
|
+
trace.steps[i].duration = duration;
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Обновить статус ошибки для последнего шага с указанным nodeId
|
|
139
|
+
*/
|
|
140
|
+
updateStepError(traceId, nodeId, error, duration) {
|
|
141
|
+
const trace = this.activeTraces.get(traceId);
|
|
142
|
+
if (!trace) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Находим последний шаг с этим nodeId
|
|
147
|
+
for (let i = trace.steps.length - 1; i >= 0; i--) {
|
|
148
|
+
if (trace.steps[i].nodeId === nodeId && trace.steps[i].type !== 'traversal') {
|
|
149
|
+
trace.steps[i].status = 'error';
|
|
150
|
+
trace.steps[i].error = error;
|
|
151
|
+
trace.steps[i].duration = duration;
|
|
152
|
+
break;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Записать прохождение по связи (exec flow)
|
|
159
|
+
*/
|
|
160
|
+
recordTraversal(traceId, fromNodeId, toPinId, toNodeId) {
|
|
161
|
+
const trace = this.activeTraces.get(traceId);
|
|
162
|
+
if (!trace) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const step = {
|
|
167
|
+
type: 'traversal',
|
|
168
|
+
fromNodeId,
|
|
169
|
+
toPinId,
|
|
170
|
+
toNodeId,
|
|
171
|
+
timestamp: new Date(),
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
trace.steps.push(step);
|
|
175
|
+
|
|
176
|
+
this._emitTraceEvent(trace.botId, trace.graphId, 'trace:traversal', {
|
|
177
|
+
traceId,
|
|
178
|
+
step,
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Завершить трассировку (успешно)
|
|
184
|
+
*/
|
|
185
|
+
async completeTrace(traceId) {
|
|
186
|
+
const trace = this.activeTraces.get(traceId);
|
|
187
|
+
if (!trace) {
|
|
188
|
+
console.warn(`[TraceCollector] Трассировка ${traceId} не найдена`);
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Защита от race condition - помечаем trace как завершаемый
|
|
193
|
+
if (trace.isCompleting) {
|
|
194
|
+
console.warn(`[TraceCollector] Трассировка ${traceId} уже завершается`);
|
|
195
|
+
return trace;
|
|
196
|
+
}
|
|
197
|
+
trace.isCompleting = true;
|
|
198
|
+
|
|
199
|
+
trace.endTime = new Date();
|
|
200
|
+
trace.status = 'completed';
|
|
201
|
+
|
|
202
|
+
// Удаляем с небольшой задержкой, чтобы updateStepOutputs успел завершиться
|
|
203
|
+
setTimeout(() => {
|
|
204
|
+
this.activeTraces.delete(traceId);
|
|
205
|
+
}, 100);
|
|
206
|
+
|
|
207
|
+
await this._storeCompletedTrace(trace);
|
|
208
|
+
|
|
209
|
+
// Не сохраняем трассировки команд в БД (только события)
|
|
210
|
+
if (this.config.persistToDb && trace.eventType !== 'command') {
|
|
211
|
+
await this._persistTraceToDb(trace);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
this._emitTraceEvent(trace.botId, trace.graphId, 'trace:complete', {
|
|
215
|
+
traceId,
|
|
216
|
+
status: 'completed',
|
|
217
|
+
endTime: trace.endTime,
|
|
218
|
+
duration: trace.endTime - trace.startTime,
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
return trace;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Завершить трассировку с ошибкой
|
|
226
|
+
*/
|
|
227
|
+
async failTrace(traceId, error) {
|
|
228
|
+
const trace = this.activeTraces.get(traceId);
|
|
229
|
+
if (!trace) {
|
|
230
|
+
console.warn(`[TraceCollector] Трассировка ${traceId} не найдена`);
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
trace.endTime = new Date();
|
|
235
|
+
trace.status = 'error';
|
|
236
|
+
trace.error = error?.message || String(error);
|
|
237
|
+
|
|
238
|
+
this.activeTraces.delete(traceId);
|
|
239
|
+
|
|
240
|
+
await this._storeCompletedTrace(trace);
|
|
241
|
+
|
|
242
|
+
// Не сохраняем трассировки команд в БД (только события)
|
|
243
|
+
if (this.config.persistToDb && trace.eventType !== 'command') {
|
|
244
|
+
await this._persistTraceToDb(trace);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
this._emitTraceEvent(trace.botId, trace.graphId, 'trace:error', {
|
|
248
|
+
traceId,
|
|
249
|
+
status: 'error',
|
|
250
|
+
error: trace.error,
|
|
251
|
+
endTime: trace.endTime,
|
|
252
|
+
duration: trace.endTime - trace.startTime,
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
return trace;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Получить трассировку по ID (из активных или завершённых)
|
|
260
|
+
*/
|
|
261
|
+
getTrace(traceId) {
|
|
262
|
+
const activeTrace = this.activeTraces.get(traceId);
|
|
263
|
+
if (activeTrace) {
|
|
264
|
+
return activeTrace;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
for (const traces of this.completedTraces.values()) {
|
|
268
|
+
const trace = traces.find(t => t.id === traceId);
|
|
269
|
+
if (trace) {
|
|
270
|
+
return trace;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Получить все трассировки для бота
|
|
279
|
+
*/
|
|
280
|
+
getTracesForBot(botId, options = {}) {
|
|
281
|
+
const {
|
|
282
|
+
limit = 50,
|
|
283
|
+
status = null, // 'running', 'completed', 'error'
|
|
284
|
+
graphId = null,
|
|
285
|
+
} = options;
|
|
286
|
+
|
|
287
|
+
let traces = [];
|
|
288
|
+
|
|
289
|
+
for (const trace of this.activeTraces.values()) {
|
|
290
|
+
if (trace.botId === botId) {
|
|
291
|
+
traces.push(trace);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const completedForBot = this.completedTraces.get(botId) || [];
|
|
296
|
+
traces.push(...completedForBot);
|
|
297
|
+
|
|
298
|
+
if (status) {
|
|
299
|
+
traces = traces.filter(t => t.status === status);
|
|
300
|
+
}
|
|
301
|
+
if (graphId) {
|
|
302
|
+
traces = traces.filter(t => t.graphId === graphId);
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
traces.sort((a, b) => b.startTime - a.startTime);
|
|
306
|
+
|
|
307
|
+
return traces.slice(0, limit);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* Получить последнюю трассировку для графа
|
|
312
|
+
* @param {number} botId - ID бота
|
|
313
|
+
* @param {number} graphId - ID графа
|
|
314
|
+
* @param {string} eventType - Опциональный фильтр по типу события (например, 'command', 'chat', 'botStartup')
|
|
315
|
+
*/
|
|
316
|
+
async getLastTraceForGraph(botId, graphId, eventType = null) {
|
|
317
|
+
let traces = this.getTracesForBot(botId, { graphId, limit: 100 }); // Увеличиваем лимит для фильтрации
|
|
318
|
+
|
|
319
|
+
if (eventType) {
|
|
320
|
+
traces = traces.filter(t => t.eventType === eventType);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
if (traces.length > 0) {
|
|
324
|
+
return traces[0];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
if (this.config.persistToDb) {
|
|
328
|
+
try {
|
|
329
|
+
const where = {
|
|
330
|
+
botId,
|
|
331
|
+
graphId,
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
if (eventType) {
|
|
335
|
+
where.eventType = eventType;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const dbTrace = await prisma.executionTrace.findFirst({
|
|
339
|
+
where,
|
|
340
|
+
orderBy: {
|
|
341
|
+
startTime: 'desc'
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
if (dbTrace) {
|
|
346
|
+
console.log('[TraceCollector] Loaded trace from DB:', {
|
|
347
|
+
traceId: dbTrace.id,
|
|
348
|
+
eventType: dbTrace.eventType,
|
|
349
|
+
stepsType: typeof dbTrace.steps,
|
|
350
|
+
stepsIsString: typeof dbTrace.steps === 'string',
|
|
351
|
+
stepsLength: dbTrace.steps?.length,
|
|
352
|
+
rawSteps: dbTrace.steps,
|
|
353
|
+
});
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
...dbTrace,
|
|
357
|
+
eventArgs: typeof dbTrace.eventArgs === 'string' ? JSON.parse(dbTrace.eventArgs) : dbTrace.eventArgs,
|
|
358
|
+
steps: typeof dbTrace.steps === 'string' ? JSON.parse(dbTrace.steps) : dbTrace.steps,
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
} catch (error) {
|
|
362
|
+
console.error('[TraceCollector] Ошибка загрузки последней трассировки из БД:', error);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Очистить все трассировки для конкретного бота (при остановке бота)
|
|
371
|
+
*/
|
|
372
|
+
clearForBot(botId) {
|
|
373
|
+
// Удаляем активные трассировки этого бота
|
|
374
|
+
for (const [traceId, trace] of this.activeTraces.entries()) {
|
|
375
|
+
if (trace.botId === botId) {
|
|
376
|
+
this.activeTraces.delete(traceId);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Удаляем завершённые трассировки
|
|
381
|
+
this.completedTraces.delete(botId);
|
|
382
|
+
|
|
383
|
+
console.log(`[TraceCollector] Очищены все трассировки для бота ${botId}`);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Очистить старые трассировки (периодическая очистка)
|
|
388
|
+
*/
|
|
389
|
+
async cleanup() {
|
|
390
|
+
const cutoffDate = new Date();
|
|
391
|
+
cutoffDate.setDate(cutoffDate.getDate() - this.config.cleanupAfterDays);
|
|
392
|
+
|
|
393
|
+
for (const [botId, traces] of this.completedTraces.entries()) {
|
|
394
|
+
const filtered = traces.filter(t => t.startTime > cutoffDate);
|
|
395
|
+
if (filtered.length === 0) {
|
|
396
|
+
this.completedTraces.delete(botId);
|
|
397
|
+
} else {
|
|
398
|
+
this.completedTraces.set(botId, filtered);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (this.config.persistToDb) {
|
|
403
|
+
await prisma.executionTrace.deleteMany({
|
|
404
|
+
where: {
|
|
405
|
+
createdAt: {
|
|
406
|
+
lt: cutoffDate
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
console.log(`[TraceCollector] Очистка старых трассировок завершена`);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Сохранить завершённую трассировку в памяти
|
|
417
|
+
*/
|
|
418
|
+
async _storeCompletedTrace(trace) {
|
|
419
|
+
const botId = trace.botId;
|
|
420
|
+
|
|
421
|
+
if (!this.completedTraces.has(botId)) {
|
|
422
|
+
this.completedTraces.set(botId, []);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
const traces = this.completedTraces.get(botId);
|
|
426
|
+
traces.unshift(trace); // Добавляем в начало (новые первыми)
|
|
427
|
+
|
|
428
|
+
if (traces.length > this.config.maxTracesPerBot) {
|
|
429
|
+
traces.splice(this.config.maxTracesPerBot);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
this.completedTraces.set(botId, traces);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Сохранить трассировку в БД
|
|
437
|
+
*/
|
|
438
|
+
async _persistTraceToDb(trace) {
|
|
439
|
+
try {
|
|
440
|
+
await prisma.executionTrace.create({
|
|
441
|
+
data: {
|
|
442
|
+
id: trace.id,
|
|
443
|
+
botId: trace.botId,
|
|
444
|
+
graphId: trace.graphId,
|
|
445
|
+
eventType: trace.eventType,
|
|
446
|
+
eventArgs: trace.eventArgs,
|
|
447
|
+
startTime: trace.startTime,
|
|
448
|
+
endTime: trace.endTime,
|
|
449
|
+
status: trace.status,
|
|
450
|
+
error: trace.error,
|
|
451
|
+
steps: JSON.stringify(trace.steps),
|
|
452
|
+
}
|
|
453
|
+
});
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.error('[TraceCollector] Ошибка сохранения в БД:', error);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Генерировать уникальный ID трассировки
|
|
461
|
+
*/
|
|
462
|
+
_generateTraceId() {
|
|
463
|
+
return `trace_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Загрузить трассировку из БД
|
|
468
|
+
*/
|
|
469
|
+
async loadTraceFromDb(traceId) {
|
|
470
|
+
if (!this.config.persistToDb) {
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
try {
|
|
475
|
+
const trace = await prisma.executionTrace.findUnique({
|
|
476
|
+
where: { id: traceId }
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
if (!trace) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return {
|
|
484
|
+
...trace,
|
|
485
|
+
eventArgs: typeof trace.eventArgs === 'string' ? JSON.parse(trace.eventArgs) : trace.eventArgs,
|
|
486
|
+
steps: typeof trace.steps === 'string' ? JSON.parse(trace.steps) : trace.steps,
|
|
487
|
+
};
|
|
488
|
+
} catch (error) {
|
|
489
|
+
console.error('[TraceCollector] Ошибка загрузки из БД:', error);
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Получить статистику трассировок
|
|
496
|
+
*/
|
|
497
|
+
getStats() {
|
|
498
|
+
let totalActive = this.activeTraces.size;
|
|
499
|
+
let totalCompleted = 0;
|
|
500
|
+
|
|
501
|
+
for (const traces of this.completedTraces.values()) {
|
|
502
|
+
totalCompleted += traces.length;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
active: totalActive,
|
|
507
|
+
completed: totalCompleted,
|
|
508
|
+
botsWithTraces: this.completedTraces.size,
|
|
509
|
+
config: this.config,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/**
|
|
514
|
+
* Эмитить Socket.IO событие для трассировки
|
|
515
|
+
*/
|
|
516
|
+
_emitTraceEvent(botId, graphId, eventName, data) {
|
|
517
|
+
if (!this.io) {
|
|
518
|
+
return; // Socket.IO не инициализирован
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
this.io.emit(`bot:${botId}:${eventName}`, {
|
|
522
|
+
botId,
|
|
523
|
+
graphId,
|
|
524
|
+
...data,
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
this.io.emit(`graph:${graphId}:${eventName}`, {
|
|
528
|
+
botId,
|
|
529
|
+
graphId,
|
|
530
|
+
...data,
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
let instance = null;
|
|
536
|
+
|
|
537
|
+
module.exports = {
|
|
538
|
+
TraceCollectorService,
|
|
539
|
+
getTraceCollector: () => {
|
|
540
|
+
if (!instance) {
|
|
541
|
+
instance = new TraceCollectorService();
|
|
542
|
+
}
|
|
543
|
+
return instance;
|
|
544
|
+
}
|
|
545
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RuntimeCommandRegistry - реестр временных команд, созданных во время выполнения
|
|
3
|
+
* Хранит команды в памяти для каждого бота
|
|
4
|
+
*/
|
|
5
|
+
class RuntimeCommandRegistry {
|
|
6
|
+
constructor() {
|
|
7
|
+
// Map<botId, Map<commandName, Command>>
|
|
8
|
+
this.commands = new Map();
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Добавить временную команду для бота
|
|
13
|
+
* @param {string} botId - ID бота
|
|
14
|
+
* @param {string} commandName - Имя команды
|
|
15
|
+
* @param {object} commandData - Данные команды
|
|
16
|
+
*/
|
|
17
|
+
register(botId, commandName, commandData) {
|
|
18
|
+
if (!this.commands.has(botId)) {
|
|
19
|
+
this.commands.set(botId, new Map());
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const botCommands = this.commands.get(botId);
|
|
23
|
+
botCommands.set(commandName, commandData);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Удалить временную команду
|
|
28
|
+
* @param {string} botId - ID бота
|
|
29
|
+
* @param {string} commandName - Имя команды
|
|
30
|
+
* @returns {boolean} - Успешно ли удалена команда
|
|
31
|
+
*/
|
|
32
|
+
unregister(botId, commandName) {
|
|
33
|
+
if (!this.commands.has(botId)) {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const botCommands = this.commands.get(botId);
|
|
38
|
+
return botCommands.delete(commandName);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Получить временную команду
|
|
43
|
+
* @param {string} botId - ID бота
|
|
44
|
+
* @param {string} commandName - Имя команды
|
|
45
|
+
* @returns {object|null} - Данные команды или null
|
|
46
|
+
*/
|
|
47
|
+
get(botId, commandName) {
|
|
48
|
+
if (!this.commands.has(botId)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return this.commands.get(botId).get(commandName) || null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Получить все временные команды для бота
|
|
57
|
+
* @param {string} botId - ID бота
|
|
58
|
+
* @returns {Map<string, object>} - Map команд
|
|
59
|
+
*/
|
|
60
|
+
getAllForBot(botId) {
|
|
61
|
+
return this.commands.get(botId) || new Map();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Проверить существование команды
|
|
66
|
+
* @param {string} botId - ID бота
|
|
67
|
+
* @param {string} commandName - Имя команды
|
|
68
|
+
* @returns {boolean}
|
|
69
|
+
*/
|
|
70
|
+
has(botId, commandName) {
|
|
71
|
+
if (!this.commands.has(botId)) {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return this.commands.get(botId).has(commandName);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Очистить все команды для бота (при остановке бота)
|
|
80
|
+
* @param {string} botId - ID бота
|
|
81
|
+
*/
|
|
82
|
+
clearForBot(botId) {
|
|
83
|
+
if (this.commands.has(botId)) {
|
|
84
|
+
this.commands.delete(botId);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Получить статистику
|
|
90
|
+
* @returns {object}
|
|
91
|
+
*/
|
|
92
|
+
getStats() {
|
|
93
|
+
let totalCommands = 0;
|
|
94
|
+
for (const botCommands of this.commands.values()) {
|
|
95
|
+
totalCommands += botCommands.size;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
totalBots: this.commands.size,
|
|
100
|
+
totalCommands,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
let instance = null;
|
|
107
|
+
|
|
108
|
+
module.exports = {
|
|
109
|
+
RuntimeCommandRegistry,
|
|
110
|
+
getRuntimeCommandRegistry: () => {
|
|
111
|
+
if (!instance) {
|
|
112
|
+
instance = new RuntimeCommandRegistry();
|
|
113
|
+
}
|
|
114
|
+
return instance;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
@@ -23,10 +23,6 @@ class Transport {
|
|
|
23
23
|
if (this.bot && this.bot.api) {
|
|
24
24
|
this.bot.api.sendMessage(this.type, message, recipient);
|
|
25
25
|
}
|
|
26
|
-
} else if (this.type === 'telegram') {
|
|
27
|
-
// Telegram - будущая интеграция
|
|
28
|
-
// TODO: Implement telegram sending
|
|
29
|
-
console.warn('[Transport] Telegram transport not implemented yet');
|
|
30
26
|
}
|
|
31
27
|
}
|
|
32
28
|
|
|
@@ -18,11 +18,11 @@ const NodeConfigSchema = z.object({
|
|
|
18
18
|
graphType: z.enum([GRAPH_TYPES.ALL, GRAPH_TYPES.COMMAND, GRAPH_TYPES.EVENT]).optional(),
|
|
19
19
|
isEvent: z.boolean().optional(),
|
|
20
20
|
dynamicPins: z.boolean().optional(),
|
|
21
|
-
inputs: z.array(PinSchema).
|
|
22
|
-
outputs: z.array(PinSchema).
|
|
21
|
+
inputs: z.array(PinSchema).default([]),
|
|
22
|
+
outputs: z.array(PinSchema).default([]),
|
|
23
23
|
pins: z.object({
|
|
24
|
-
inputs: z.array(PinSchema).
|
|
25
|
-
outputs: z.array(PinSchema).
|
|
24
|
+
inputs: z.array(PinSchema).default([]),
|
|
25
|
+
outputs: z.array(PinSchema).default([]),
|
|
26
26
|
}).optional(),
|
|
27
27
|
executor: z.function().optional(),
|
|
28
28
|
evaluator: z.function().optional(),
|
|
@@ -35,7 +35,7 @@ const NodeDataSchema = z.object({
|
|
|
35
35
|
x: z.number(),
|
|
36
36
|
y: z.number(),
|
|
37
37
|
}),
|
|
38
|
-
data: z.any().
|
|
38
|
+
data: z.any().default({}),
|
|
39
39
|
deletable: z.boolean().optional(),
|
|
40
40
|
});
|
|
41
41
|
|
|
@@ -55,7 +55,7 @@ const GraphSchema = z.object({
|
|
|
55
55
|
name: z.string().min(1),
|
|
56
56
|
type: z.enum(['string', 'number', 'boolean', 'array', 'object']),
|
|
57
57
|
value: z.any(),
|
|
58
|
-
})).
|
|
58
|
+
})).default([]),
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
function validateNodeConfig(nodeConfig) {
|
|
@@ -66,8 +66,8 @@ async function handleTriggerGraph(socket, payload) {
|
|
|
66
66
|
// Парсим граф
|
|
67
67
|
const parsedGraph = JSON.parse(graph.graphJson);
|
|
68
68
|
|
|
69
|
-
// Запускаем граф
|
|
70
|
-
await botManager.eventGraphManager.
|
|
69
|
+
// Запускаем граф в child process через IPC
|
|
70
|
+
await botManager.eventGraphManager.executeGraphInChildProcess(
|
|
71
71
|
socket.botId,
|
|
72
72
|
'api_trigger', // Тип события
|
|
73
73
|
{
|