blockmine 1.25.0 → 1.27.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 +46 -3
- package/backend/cli.js +1 -1
- package/backend/package.json +2 -2
- package/backend/prisma/migrations/20260328173000_add_plugin_source_ref/migration.sql +2 -0
- package/backend/prisma/migrations/migration_lock.toml +2 -2
- package/backend/prisma/schema.prisma +2 -0
- package/backend/src/api/routes/apiKeys.js +8 -0
- package/backend/src/api/routes/bots.js +258 -9
- package/backend/src/api/routes/eventGraphs.js +151 -1
- package/backend/src/api/routes/health.js +38 -0
- package/backend/src/api/routes/nodeRegistry.js +63 -0
- package/backend/src/api/routes/plugins.js +254 -29
- package/backend/src/container.js +11 -8
- package/backend/src/core/BotCommandLoader.js +161 -0
- package/backend/src/core/BotConnection.js +125 -0
- package/backend/src/core/BotEventHandlers.js +234 -0
- package/backend/src/core/BotIPCHandler.js +445 -0
- package/backend/src/core/BotManager.js +15 -7
- package/backend/src/core/BotProcess.js +75 -142
- package/backend/src/core/EventGraphManager.js +7 -3
- package/backend/src/core/GraphDebugHandler.js +229 -0
- package/backend/src/core/GraphDebugIPC.js +117 -0
- package/backend/src/core/GraphExecutionEngine.js +545 -978
- package/backend/src/core/GraphTraversal.js +80 -0
- package/backend/src/core/GraphValidation.js +73 -0
- package/backend/src/core/NodeDefinition.js +138 -0
- package/backend/src/core/NodeRegistry.js +153 -141
- package/backend/src/core/PluginManager.js +272 -31
- package/backend/src/core/RewindSignal.js +9 -0
- package/backend/src/core/config/ConfigValidator.js +72 -0
- package/backend/src/core/config/FeatureFlags.js +52 -0
- package/backend/src/core/config/__tests__/ConfigValidator.test.js +232 -0
- package/backend/src/core/domain/entities/Bot.js +39 -0
- package/backend/src/core/domain/entities/Command.js +41 -0
- package/backend/src/core/domain/entities/EventGraph.js +39 -0
- package/backend/src/core/domain/entities/Plugin.js +45 -0
- package/backend/src/core/domain/entities/User.js +40 -0
- package/backend/src/core/domain/services/DependencyResolver.js +168 -0
- package/backend/src/core/domain/services/GraphValidator.js +117 -0
- package/backend/src/core/domain/services/PermissionChecker.js +34 -0
- package/backend/src/core/domain/services/__tests__/DependencyResolver.test.js +126 -0
- package/backend/src/core/domain/valueObjects/BotConfig.js +27 -0
- package/backend/src/core/domain/valueObjects/DependencyGraph.js +86 -0
- package/backend/src/core/domain/valueObjects/PluginManifest.js +36 -0
- package/backend/src/core/errors/BaseError.js +29 -0
- package/backend/src/core/errors/ErrorHandler.js +81 -0
- package/backend/src/core/errors/__tests__/ErrorHandler.test.js +188 -0
- package/backend/src/core/errors/index.js +68 -0
- package/backend/src/core/infrastructure/BatchingUtility.js +66 -0
- package/backend/src/core/infrastructure/CircuitBreaker.js +103 -0
- package/backend/src/core/infrastructure/ConnectionPool.js +81 -0
- package/backend/src/core/infrastructure/RateLimiter.js +64 -0
- package/backend/src/core/infrastructure/__tests__/BatchingUtility.test.js +86 -0
- package/backend/src/core/infrastructure/__tests__/CircuitBreaker.test.js +156 -0
- package/backend/src/core/infrastructure/__tests__/ConnectionPool.test.js +146 -0
- package/backend/src/core/infrastructure/__tests__/RateLimiter.test.js +171 -0
- package/backend/src/core/ipc/botApiFactory.js +72 -0
- package/backend/src/core/ipc/ipcMessageTypes.js +115 -0
- package/backend/src/core/logging/AuditLogger.js +61 -0
- package/backend/src/core/logging/StructuredLogger.js +80 -0
- package/backend/src/core/logging/__tests__/StructuredLogger.test.js +213 -0
- package/backend/src/core/logging/index.js +7 -0
- package/backend/src/core/metrics/MetricsCollector.js +104 -0
- package/backend/src/core/metrics/__tests__/MetricsCollector.test.js +131 -0
- package/backend/src/core/node-registries/actionsNodes.js +191 -0
- package/backend/src/core/node-registries/arraysNodes.js +152 -0
- package/backend/src/core/node-registries/botNodes.js +48 -0
- package/backend/src/core/node-registries/containerNodes.js +141 -0
- package/backend/src/core/node-registries/dataNodes.js +284 -0
- package/backend/src/core/node-registries/debugNodes.js +23 -0
- package/backend/src/core/node-registries/eventsNodes.js +223 -0
- package/backend/src/core/node-registries/flowNodes.js +151 -0
- package/backend/src/core/node-registries/furnaceNodes.js +123 -0
- package/backend/src/core/node-registries/index.js +108 -0
- package/backend/src/core/node-registries/inventory.js +102 -106
- package/backend/src/core/node-registries/logicNodes.js +54 -0
- package/backend/src/core/node-registries/mathNodes.js +38 -0
- package/backend/src/core/node-registries/navigationNodes.js +109 -0
- package/backend/src/core/node-registries/objectsNodes.js +90 -0
- package/backend/src/core/node-registries/stringsNodes.js +165 -0
- package/backend/src/core/node-registries/timeNodes.js +105 -0
- package/backend/src/core/node-registries/typeNodes.js +22 -0
- package/backend/src/core/node-registries/usersNodes.js +126 -0
- package/backend/src/core/nodes/arrays/shuffle.js +14 -0
- package/backend/src/core/nodes/bot/get_name.js +8 -0
- package/backend/src/core/nodes/bot/stop_bot.js +5 -0
- package/backend/src/core/nodes/container/open.js +101 -111
- package/backend/src/core/nodes/data/store_read.js +26 -0
- package/backend/src/core/nodes/data/store_write.js +23 -0
- package/backend/src/core/nodes/event/call_event.js +31 -0
- package/backend/src/core/nodes/event/custom_event.js +8 -0
- package/backend/src/core/nodes/flow/timer.js +35 -0
- package/backend/src/core/nodes/inventory/drop.js +73 -65
- package/backend/src/core/nodes/inventory/equip.js +54 -45
- package/backend/src/core/nodes/inventory/select_slot.js +48 -46
- package/backend/src/core/nodes/navigation/follow.js +54 -51
- package/backend/src/core/nodes/navigation/go_to.js +41 -53
- package/backend/src/core/nodes/navigation/go_to_entity.js +65 -69
- package/backend/src/core/nodes/navigation/go_to_player.js +65 -70
- package/backend/src/core/nodes/navigation/stop.js +17 -26
- package/backend/src/core/nodes/users/add_to_group.js +24 -0
- package/backend/src/core/nodes/users/check_permission.js +26 -0
- package/backend/src/core/nodes/users/remove_from_group.js +24 -0
- package/backend/src/core/services/BotIPCMessageRouter.js +337 -0
- package/backend/src/core/services/BotLifecycleService.js +41 -632
- package/backend/src/core/services/CacheManager.js +83 -23
- package/backend/src/core/services/CrashRestartManager.js +42 -0
- package/backend/src/core/services/DebugSessionManager.js +114 -12
- package/backend/src/core/services/EventGraphService.js +69 -0
- package/backend/src/core/services/MinecraftBotManager.js +9 -1
- package/backend/src/core/services/PluginManagementService.js +84 -0
- package/backend/src/core/services/TestModeContext.js +65 -0
- package/backend/src/core/services/__tests__/CacheManager.test.js +168 -0
- package/backend/src/core/services.js +1 -11
- package/backend/src/core/validation/InputValidator.js +167 -0
- package/backend/src/core/validation/__tests__/InputValidator.test.js +296 -0
- package/backend/src/real-time/botApi/index.js +1 -1
- package/backend/src/real-time/socketHandler.js +26 -0
- package/backend/src/server.js +10 -5
- package/frontend/dist/assets/{browser-ponyfill-DN7pwmHT.js → browser-ponyfill-D8y0Ty7C.js} +1 -1
- package/frontend/dist/assets/index-CFJLS0dk.css +32 -0
- package/frontend/dist/assets/{index-LSy71uwm.js → index-D91UGNMG.js} +1880 -1881
- package/frontend/dist/index.html +2 -2
- package/frontend/dist/locales/en/bots.json +4 -1
- package/frontend/dist/locales/en/common.json +7 -1
- package/frontend/dist/locales/en/login.json +2 -0
- package/frontend/dist/locales/en/management.json +79 -1
- package/frontend/dist/locales/en/nodes.json +59 -4
- package/frontend/dist/locales/en/plugin-detail.json +24 -4
- package/frontend/dist/locales/en/plugins.json +226 -7
- package/frontend/dist/locales/en/setup.json +2 -0
- package/frontend/dist/locales/en/sidebar.json +171 -3
- package/frontend/dist/locales/en/visual-editor.json +230 -31
- package/frontend/dist/locales/ru/bots.json +4 -1
- package/frontend/dist/locales/ru/login.json +2 -0
- package/frontend/dist/locales/ru/management.json +79 -1
- package/frontend/dist/locales/ru/minecraft-viewer.json +3 -0
- package/frontend/dist/locales/ru/nodes.json +105 -51
- package/frontend/dist/locales/ru/plugins.json +103 -4
- package/frontend/dist/locales/ru/setup.json +2 -0
- package/frontend/dist/locales/ru/sidebar.json +171 -3
- package/frontend/dist/locales/ru/visual-editor.json +232 -33
- package/frontend/package.json +2 -0
- package/nul +12 -0
- package/package.json +3 -3
- package/backend/package-lock.json +0 -6801
- package/backend/src/core/node-registries/actions.js +0 -202
- package/backend/src/core/node-registries/arrays.js +0 -155
- package/backend/src/core/node-registries/bot.js +0 -23
- package/backend/src/core/node-registries/container.js +0 -162
- package/backend/src/core/node-registries/data.js +0 -290
- package/backend/src/core/node-registries/debug.js +0 -26
- package/backend/src/core/node-registries/events.js +0 -201
- package/backend/src/core/node-registries/flow.js +0 -139
- package/backend/src/core/node-registries/furnace.js +0 -143
- package/backend/src/core/node-registries/logic.js +0 -62
- package/backend/src/core/node-registries/math.js +0 -42
- package/backend/src/core/node-registries/navigation.js +0 -111
- package/backend/src/core/node-registries/objects.js +0 -98
- package/backend/src/core/node-registries/strings.js +0 -187
- package/backend/src/core/node-registries/time.js +0 -113
- package/backend/src/core/node-registries/type.js +0 -25
- package/backend/src/core/node-registries/users.js +0 -79
- package/frontend/dist/assets/index-SfhKxI4-.css +0 -32
|
@@ -1,29 +1,42 @@
|
|
|
1
1
|
const { LRUCache } = require('lru-cache');
|
|
2
2
|
|
|
3
|
+
const DEFAULT_MEMORY_THRESHOLD = 500 * 1024 * 1024;
|
|
4
|
+
|
|
3
5
|
class CacheManager {
|
|
4
6
|
constructor({ commandRepository, permissionRepository, logger } = {}) {
|
|
5
|
-
// Кеш с TTL и максимальным размером
|
|
6
7
|
this.tokenCache = new LRUCache({
|
|
7
8
|
max: 500,
|
|
8
|
-
ttl: 1000 * 60 * 5,
|
|
9
|
+
ttl: 1000 * 60 * 5,
|
|
9
10
|
});
|
|
10
11
|
|
|
11
12
|
this.playerListCache = new LRUCache({
|
|
12
13
|
max: 100,
|
|
13
|
-
ttl: 1000 * 2,
|
|
14
|
+
ttl: 1000 * 2,
|
|
14
15
|
});
|
|
15
16
|
|
|
16
17
|
this.botConfigsCache = new Map();
|
|
17
|
-
|
|
18
|
-
// Зависимости для автозагрузки конфигурации
|
|
18
|
+
|
|
19
19
|
this.commandRepository = commandRepository;
|
|
20
20
|
this.permissionRepository = permissionRepository;
|
|
21
21
|
this.logger = logger;
|
|
22
|
+
|
|
23
|
+
this.memoryThreshold = DEFAULT_MEMORY_THRESHOLD;
|
|
24
|
+
|
|
25
|
+
this._metrics = {
|
|
26
|
+
tokenCache: { hits: 0, misses: 0 },
|
|
27
|
+
playerListCache: { hits: 0, misses: 0 },
|
|
28
|
+
botConfigsCache: { hits: 0, misses: 0 },
|
|
29
|
+
};
|
|
22
30
|
}
|
|
23
31
|
|
|
24
|
-
// Token cache
|
|
25
32
|
getToken(token) {
|
|
26
|
-
|
|
33
|
+
const value = this.tokenCache.get(token);
|
|
34
|
+
if (value !== undefined) {
|
|
35
|
+
this._metrics.tokenCache.hits++;
|
|
36
|
+
} else {
|
|
37
|
+
this._metrics.tokenCache.misses++;
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
27
40
|
}
|
|
28
41
|
|
|
29
42
|
setToken(token, data) {
|
|
@@ -34,18 +47,28 @@ class CacheManager {
|
|
|
34
47
|
this.tokenCache.delete(token);
|
|
35
48
|
}
|
|
36
49
|
|
|
37
|
-
// Player list cache
|
|
38
50
|
getPlayerList(botId) {
|
|
39
|
-
|
|
51
|
+
const value = this.playerListCache.get(botId);
|
|
52
|
+
if (value !== undefined) {
|
|
53
|
+
this._metrics.playerListCache.hits++;
|
|
54
|
+
} else {
|
|
55
|
+
this._metrics.playerListCache.misses++;
|
|
56
|
+
}
|
|
57
|
+
return value;
|
|
40
58
|
}
|
|
41
59
|
|
|
42
60
|
setPlayerList(botId, players) {
|
|
43
61
|
this.playerListCache.set(botId, players);
|
|
44
62
|
}
|
|
45
63
|
|
|
46
|
-
// Bot config cache
|
|
47
64
|
getBotConfig(botId) {
|
|
48
|
-
|
|
65
|
+
const value = this.botConfigsCache.get(botId);
|
|
66
|
+
if (value !== undefined) {
|
|
67
|
+
this._metrics.botConfigsCache.hits++;
|
|
68
|
+
} else {
|
|
69
|
+
this._metrics.botConfigsCache.misses++;
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
49
72
|
}
|
|
50
73
|
|
|
51
74
|
setBotConfig(botId, config) {
|
|
@@ -56,27 +79,64 @@ class CacheManager {
|
|
|
56
79
|
this.botConfigsCache.delete(botId);
|
|
57
80
|
}
|
|
58
81
|
|
|
59
|
-
// Очистка всего кеша для бота
|
|
60
82
|
clearBotCache(botId) {
|
|
61
83
|
this.deleteBotConfig(botId);
|
|
62
84
|
this.playerListCache.delete(botId);
|
|
63
85
|
}
|
|
64
86
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
87
|
+
getMetrics() {
|
|
88
|
+
const calc = ({ hits, misses }) => {
|
|
89
|
+
const total = hits + misses;
|
|
90
|
+
return {
|
|
91
|
+
hits,
|
|
92
|
+
misses,
|
|
93
|
+
hitRate: total === 0 ? 0 : hits / total,
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
return {
|
|
98
|
+
tokenCache: calc(this._metrics.tokenCache),
|
|
99
|
+
playerListCache: calc(this._metrics.playerListCache),
|
|
100
|
+
botConfigsCache: calc(this._metrics.botConfigsCache),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
resetMetrics() {
|
|
105
|
+
this._metrics = {
|
|
106
|
+
tokenCache: { hits: 0, misses: 0 },
|
|
107
|
+
playerListCache: { hits: 0, misses: 0 },
|
|
108
|
+
botConfigsCache: { hits: 0, misses: 0 },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
checkMemoryUsage() {
|
|
113
|
+
const { heapUsed } = process.memoryUsage();
|
|
114
|
+
const threshold = this.memoryThreshold;
|
|
115
|
+
const exceeded = heapUsed > threshold;
|
|
116
|
+
|
|
117
|
+
if (exceeded) {
|
|
118
|
+
if (this.logger) {
|
|
119
|
+
this.logger.warn(
|
|
120
|
+
{ heapUsed, threshold },
|
|
121
|
+
'Memory threshold exceeded, clearing playerListCache'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
this.playerListCache.clear();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return { heapUsed, threshold, exceeded };
|
|
128
|
+
}
|
|
129
|
+
|
|
70
130
|
async getOrLoadBotConfig(botId) {
|
|
71
131
|
let config = this.botConfigsCache.get(botId);
|
|
72
|
-
|
|
132
|
+
|
|
73
133
|
if (!config) {
|
|
74
134
|
if (this.logger) {
|
|
75
|
-
this.logger.debug({ botId }, '
|
|
135
|
+
this.logger.debug({ botId }, 'Cache miss, loading from DB');
|
|
76
136
|
}
|
|
77
|
-
|
|
137
|
+
|
|
78
138
|
if (!this.commandRepository || !this.permissionRepository) {
|
|
79
|
-
throw new Error('CacheManager
|
|
139
|
+
throw new Error('CacheManager does not have access to repositories for loading configuration');
|
|
80
140
|
}
|
|
81
141
|
|
|
82
142
|
const [commands, permissions] = await Promise.all([
|
|
@@ -98,9 +158,9 @@ class CacheManager {
|
|
|
98
158
|
}
|
|
99
159
|
|
|
100
160
|
this.botConfigsCache.set(botId, config);
|
|
101
|
-
|
|
161
|
+
|
|
102
162
|
if (this.logger) {
|
|
103
|
-
this.logger.debug({ botId }, '
|
|
163
|
+
this.logger.debug({ botId }, 'Configuration loaded and cached');
|
|
104
164
|
}
|
|
105
165
|
}
|
|
106
166
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
class CrashRestartManager {
|
|
2
|
+
constructor(maxRestarts = 3, restartWindowMs = 60000) {
|
|
3
|
+
this.maxRestarts = maxRestarts;
|
|
4
|
+
this.restartWindowMs = restartWindowMs;
|
|
5
|
+
this.crashHistory = new Map();
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
handleCrash(botId, botConfig, appendLog) {
|
|
9
|
+
const history = this.crashHistory.get(botId) || { count: 0, firstCrash: Date.now(), crashes: [] };
|
|
10
|
+
|
|
11
|
+
history.count++;
|
|
12
|
+
history.crashes.push(Date.now());
|
|
13
|
+
|
|
14
|
+
history.crashes = history.crashes.filter(t => Date.now() - t < this.restartWindowMs);
|
|
15
|
+
history.count = history.crashes.length;
|
|
16
|
+
history.firstCrash = history.crashes[0] || Date.now();
|
|
17
|
+
|
|
18
|
+
this.crashHistory.set(botId, history);
|
|
19
|
+
|
|
20
|
+
const withinWindow = Date.now() - history.firstCrash < this.restartWindowMs;
|
|
21
|
+
|
|
22
|
+
if (history.count > this.maxRestarts && withinWindow) {
|
|
23
|
+
appendLog(`[CRASH] Бот превысил лимит рестартов (${this.maxRestarts}). Авто-рестарт отключен.`);
|
|
24
|
+
return { shouldRestart: false, reason: 'max_restarts_exceeded' };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const delay = Math.min(1000 * Math.pow(2, history.count - 1), 30000);
|
|
28
|
+
appendLog(`[CRASH] Перезапуск через ${delay / 1000} сек... (попытка ${history.count}/${this.maxRestarts})`);
|
|
29
|
+
|
|
30
|
+
return { shouldRestart: true, delay };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
resetCounter(botId) {
|
|
34
|
+
this.crashHistory.delete(botId);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getCrashCount(botId) {
|
|
38
|
+
return this.crashHistory.get(botId)?.count || 0;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
module.exports = CrashRestartManager;
|
|
@@ -25,6 +25,12 @@ class GraphDebugState {
|
|
|
25
25
|
// Режим пошагового выполнения (step over)
|
|
26
26
|
this.stepMode = false; // Если true, остановится на следующей ноде
|
|
27
27
|
this.stepFromNodeId = null; // ID ноды, с которой начали step over (чтобы её пропустить)
|
|
28
|
+
|
|
29
|
+
this.testMode = false;
|
|
30
|
+
this.history = [];
|
|
31
|
+
this.historyLimit = 200;
|
|
32
|
+
this.rewindTargetIndex = null;
|
|
33
|
+
this.replayState = null;
|
|
28
34
|
}
|
|
29
35
|
|
|
30
36
|
/**
|
|
@@ -53,7 +59,6 @@ class GraphDebugState {
|
|
|
53
59
|
* Возвращает Promise, который разрешится когда пользователь нажмет Continue
|
|
54
60
|
*/
|
|
55
61
|
async pause(state) {
|
|
56
|
-
// Генерируем sessionId для этой паузы
|
|
57
62
|
const sessionId = randomUUID();
|
|
58
63
|
|
|
59
64
|
this.activeExecution = {
|
|
@@ -62,10 +67,14 @@ class GraphDebugState {
|
|
|
62
67
|
pausedAt: Date.now()
|
|
63
68
|
};
|
|
64
69
|
|
|
70
|
+
this.pushSnapshot(state);
|
|
65
71
|
|
|
66
72
|
this.broadcast('debug:paused', {
|
|
67
73
|
sessionId,
|
|
68
|
-
...state
|
|
74
|
+
...state,
|
|
75
|
+
historyLength: this.history.length,
|
|
76
|
+
canStepBack: this.history.length > 1,
|
|
77
|
+
testMode: this.testMode
|
|
69
78
|
});
|
|
70
79
|
|
|
71
80
|
|
|
@@ -86,18 +95,18 @@ class GraphDebugState {
|
|
|
86
95
|
* @param {boolean} stepMode - Если true, остановится на следующей ноде
|
|
87
96
|
*/
|
|
88
97
|
resume(overrides = null, stepMode = false) {
|
|
98
|
+
const effectiveStepMode = this.testMode ? true : stepMode;
|
|
99
|
+
|
|
89
100
|
if (this.resumeCallback) {
|
|
90
|
-
// Объединяем pendingOverrides с переданными overrides
|
|
91
101
|
const finalOverrides = {
|
|
92
102
|
...this.pendingOverrides,
|
|
93
103
|
...(overrides || {})
|
|
94
104
|
};
|
|
95
105
|
|
|
96
|
-
console.log('[GraphDebugState] Resuming with overrides:', finalOverrides, 'stepMode:',
|
|
106
|
+
console.log('[GraphDebugState] Resuming with overrides:', finalOverrides, 'stepMode:', effectiveStepMode, 'testMode:', this.testMode);
|
|
97
107
|
|
|
98
|
-
|
|
99
|
-
this.
|
|
100
|
-
if (stepMode && this.activeExecution) {
|
|
108
|
+
this.stepMode = effectiveStepMode;
|
|
109
|
+
if (effectiveStepMode && this.activeExecution) {
|
|
101
110
|
this.stepFromNodeId = this.activeExecution.nodeId;
|
|
102
111
|
console.log('[GraphDebugState] Step mode: will skip current node', this.stepFromNodeId);
|
|
103
112
|
}
|
|
@@ -107,15 +116,14 @@ class GraphDebugState {
|
|
|
107
116
|
this.pausePromise = null;
|
|
108
117
|
}
|
|
109
118
|
|
|
110
|
-
|
|
111
|
-
if (!stepMode) {
|
|
119
|
+
if (!effectiveStepMode) {
|
|
112
120
|
this.activeExecution = null;
|
|
113
121
|
this.stepFromNodeId = null;
|
|
114
122
|
}
|
|
115
123
|
|
|
116
|
-
this.pendingOverrides = {};
|
|
124
|
+
this.pendingOverrides = {};
|
|
117
125
|
|
|
118
|
-
this.broadcast('debug:resumed', { stepMode });
|
|
126
|
+
this.broadcast('debug:resumed', { stepMode: effectiveStepMode, testMode: this.testMode });
|
|
119
127
|
}
|
|
120
128
|
|
|
121
129
|
/**
|
|
@@ -133,6 +141,14 @@ class GraphDebugState {
|
|
|
133
141
|
this.stepMode = false; // Сбрасываем step mode
|
|
134
142
|
this.stepFromNodeId = null; // Очищаем запомненную ноду
|
|
135
143
|
|
|
144
|
+
if (this.testMode) {
|
|
145
|
+
this.testMode = false;
|
|
146
|
+
this.history = [];
|
|
147
|
+
this.rewindTargetIndex = null;
|
|
148
|
+
this.replayState = null;
|
|
149
|
+
this.broadcast('debug:test-mode-stopped', {});
|
|
150
|
+
}
|
|
151
|
+
|
|
136
152
|
this.broadcast('debug:stopped', {});
|
|
137
153
|
}
|
|
138
154
|
|
|
@@ -217,6 +233,89 @@ class GraphDebugState {
|
|
|
217
233
|
console.log(`[GraphDebugState] Set override: ${key} =`, value);
|
|
218
234
|
}
|
|
219
235
|
|
|
236
|
+
pushSnapshot(state) {
|
|
237
|
+
if (!state) return;
|
|
238
|
+
const snapshot = {
|
|
239
|
+
index: this.history.length,
|
|
240
|
+
nodeId: state.nodeId,
|
|
241
|
+
nodeType: state.nodeType,
|
|
242
|
+
inputs: this._cloneSafely(state.inputs),
|
|
243
|
+
variables: this._cloneSafely(state.context?.variables),
|
|
244
|
+
commandArguments: this._cloneSafely(state.context?.commandArguments),
|
|
245
|
+
executedSteps: this._cloneSafely(state.executedSteps),
|
|
246
|
+
capturedAt: Date.now()
|
|
247
|
+
};
|
|
248
|
+
this.history.push(snapshot);
|
|
249
|
+
if (this.history.length > this.historyLimit) {
|
|
250
|
+
this.history.shift();
|
|
251
|
+
this.history.forEach((s, i) => { s.index = i; });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_cloneSafely(value) {
|
|
256
|
+
if (value === undefined || value === null) return value;
|
|
257
|
+
try {
|
|
258
|
+
return JSON.parse(JSON.stringify(value));
|
|
259
|
+
} catch (e) {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
enableTestMode(initialPayload = {}) {
|
|
265
|
+
this.testMode = true;
|
|
266
|
+
this.stepMode = true;
|
|
267
|
+
this.stepFromNodeId = null;
|
|
268
|
+
this.history = [];
|
|
269
|
+
this.rewindTargetIndex = null;
|
|
270
|
+
this.replayState = null;
|
|
271
|
+
this.broadcast('debug:test-mode-started', {
|
|
272
|
+
...initialPayload,
|
|
273
|
+
testMode: true
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
disableTestMode() {
|
|
278
|
+
this.testMode = false;
|
|
279
|
+
this.history = [];
|
|
280
|
+
this.rewindTargetIndex = null;
|
|
281
|
+
this.replayState = null;
|
|
282
|
+
this.broadcast('debug:test-mode-stopped', {});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
consumeRewindTarget(nodeId) {
|
|
286
|
+
if (this.rewindTargetIndex === null) return false;
|
|
287
|
+
const target = this.history[this.rewindTargetIndex];
|
|
288
|
+
if (target && target.nodeId === nodeId) {
|
|
289
|
+
this.rewindTargetIndex = null;
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
return false;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
stepBack() {
|
|
296
|
+
if (this.history.length < 2) {
|
|
297
|
+
this.broadcast('debug:rewind-failed', { reason: 'no-history' });
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const target = this.history[this.history.length - 2];
|
|
301
|
+
this.history = this.history.slice(0, this.history.length - 1);
|
|
302
|
+
this.rewindTargetIndex = this.history.length - 1;
|
|
303
|
+
this.replayState = {
|
|
304
|
+
variables: target.variables,
|
|
305
|
+
commandArguments: target.commandArguments,
|
|
306
|
+
targetNodeId: target.nodeId
|
|
307
|
+
};
|
|
308
|
+
if (this.resumeCallback) {
|
|
309
|
+
this.resumeCallback({ __rewind: true, target });
|
|
310
|
+
this.resumeCallback = null;
|
|
311
|
+
this.pausePromise = null;
|
|
312
|
+
}
|
|
313
|
+
this.activeExecution = null;
|
|
314
|
+
this.pendingOverrides = {};
|
|
315
|
+
this.broadcast('debug:rewound', { target });
|
|
316
|
+
return target;
|
|
317
|
+
}
|
|
318
|
+
|
|
220
319
|
/**
|
|
221
320
|
* Получить текущее состояние (для отправки новым пользователям)
|
|
222
321
|
*/
|
|
@@ -224,7 +323,10 @@ class GraphDebugState {
|
|
|
224
323
|
return {
|
|
225
324
|
breakpoints: Array.from(this.breakpoints.values()),
|
|
226
325
|
activeExecution: this.activeExecution,
|
|
227
|
-
connectedUsers: Array.from(this.connectedUsers.values())
|
|
326
|
+
connectedUsers: Array.from(this.connectedUsers.values()),
|
|
327
|
+
testMode: this.testMode,
|
|
328
|
+
historyLength: this.history.length,
|
|
329
|
+
canStepBack: this.history.length > 1
|
|
228
330
|
};
|
|
229
331
|
}
|
|
230
332
|
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const GraphValidator = require('../domain/services/GraphValidator');
|
|
2
|
+
const { GraphExecutionError, ValidationError } = require('../errors');
|
|
3
|
+
|
|
4
|
+
const graphValidator = new GraphValidator();
|
|
5
|
+
|
|
6
|
+
class EventGraphService {
|
|
7
|
+
constructor({ eventGraphManager, eventGraphRepository, cacheManager, logger } = {}) {
|
|
8
|
+
this.eventGraphManager = eventGraphManager;
|
|
9
|
+
this.eventGraphRepository = eventGraphRepository;
|
|
10
|
+
this.cache = cacheManager;
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
this._graphCache = new Map();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async loadGraphsForBot(botId) {
|
|
16
|
+
this.logger.info({ botId }, 'eventGraph.load.start');
|
|
17
|
+
try {
|
|
18
|
+
await this.eventGraphManager.loadGraphsForBot(botId);
|
|
19
|
+
this.logger.info({ botId }, 'eventGraph.load.success');
|
|
20
|
+
} catch (error) {
|
|
21
|
+
this.logger.error({ botId, error }, 'eventGraph.load.error');
|
|
22
|
+
throw new GraphExecutionError('eventGraph.errors.loadFailed', { cause: error, context: { botId } });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
unloadGraphsForBot(botId) {
|
|
27
|
+
this.eventGraphManager.unloadGraphsForBot(botId);
|
|
28
|
+
this._graphCache.delete(botId);
|
|
29
|
+
this.logger.info({ botId }, 'eventGraph.unload.success');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async handleEvent(botId, eventType, args) {
|
|
33
|
+
try {
|
|
34
|
+
await this.eventGraphManager.handleEvent(botId, eventType, args);
|
|
35
|
+
} catch (error) {
|
|
36
|
+
this.logger.error({ botId, eventType, error }, 'eventGraph.event.error');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async validateGraph(graphJson) {
|
|
41
|
+
let graph;
|
|
42
|
+
try {
|
|
43
|
+
graph = typeof graphJson === 'string' ? JSON.parse(graphJson) : graphJson;
|
|
44
|
+
} catch {
|
|
45
|
+
throw new ValidationError('eventGraph.errors.invalidJson');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const result = graphValidator.validate(graph);
|
|
49
|
+
if (!result.valid) {
|
|
50
|
+
throw new ValidationError('eventGraph.errors.invalidStructure', { context: { errors: result.errors } });
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async getGraphsForBot(botId) {
|
|
56
|
+
if (this._graphCache.has(botId)) {
|
|
57
|
+
return this._graphCache.get(botId);
|
|
58
|
+
}
|
|
59
|
+
const graphs = await this.eventGraphRepository.findByBotId(botId);
|
|
60
|
+
this._graphCache.set(botId, graphs);
|
|
61
|
+
return graphs;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
invalidateCache(botId) {
|
|
65
|
+
this._graphCache.delete(botId);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
module.exports = EventGraphService;
|
|
@@ -88,7 +88,15 @@ class MinecraftBotManager {
|
|
|
88
88
|
});
|
|
89
89
|
|
|
90
90
|
bot.on('kicked', (reason) => {
|
|
91
|
-
|
|
91
|
+
let reasonText;
|
|
92
|
+
if (typeof reason === 'string') {
|
|
93
|
+
try { reasonText = JSON.parse(reason).text || reason; } catch (e) { reasonText = reason; }
|
|
94
|
+
} else if (reason && typeof reason === 'object') {
|
|
95
|
+
reasonText = reason.text || reason.message || reason.reason || JSON.stringify(reason);
|
|
96
|
+
} else {
|
|
97
|
+
reasonText = String(reason);
|
|
98
|
+
}
|
|
99
|
+
this.logger.warn(`[Minecraft] Bot kicked for session ${sessionId}: ${reasonText}`);
|
|
92
100
|
});
|
|
93
101
|
}
|
|
94
102
|
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
const DependencyResolver = require('../domain/services/DependencyResolver');
|
|
2
|
+
const { validatePluginManifest } = require('../validation/InputValidator');
|
|
3
|
+
const { PluginError, ValidationError } = require('../errors');
|
|
4
|
+
|
|
5
|
+
const dependencyResolver = new DependencyResolver();
|
|
6
|
+
|
|
7
|
+
class PluginManagementService {
|
|
8
|
+
constructor({ pluginManager, pluginRepository, logger } = {}) {
|
|
9
|
+
this.pluginManager = pluginManager;
|
|
10
|
+
this.pluginRepository = pluginRepository;
|
|
11
|
+
this.logger = logger;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async installFromGithub(botId, repoUrl, tag = null) {
|
|
15
|
+
this.logger.info({ botId, repoUrl, tag }, 'plugin.install.github.start');
|
|
16
|
+
try {
|
|
17
|
+
const result = await this.pluginManager.installFromGithub(botId, repoUrl, undefined, false, tag);
|
|
18
|
+
this.logger.info({ botId, pluginName: result.name }, 'plugin.install.github.success');
|
|
19
|
+
return result;
|
|
20
|
+
} catch (error) {
|
|
21
|
+
this.logger.error({ botId, repoUrl, error }, 'plugin.install.github.error');
|
|
22
|
+
throw new PluginError('plugin.errors.installFailed', { cause: error, context: { repoUrl } });
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async installFromLocalPath(botId, directoryPath) {
|
|
27
|
+
this.logger.info({ botId, directoryPath }, 'plugin.install.local.start');
|
|
28
|
+
try {
|
|
29
|
+
const result = await this.pluginManager.installFromLocalPath(botId, directoryPath);
|
|
30
|
+
this.logger.info({ botId, pluginName: result.name }, 'plugin.install.local.success');
|
|
31
|
+
return result;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
this.logger.error({ botId, directoryPath, error }, 'plugin.install.local.error');
|
|
34
|
+
throw new PluginError('plugin.errors.installFailed', { cause: error, context: { directoryPath } });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async updatePlugin(pluginId, targetTag = null, targetRepoUrl = null) {
|
|
39
|
+
this.logger.info({ pluginId, targetTag }, 'plugin.update.start');
|
|
40
|
+
try {
|
|
41
|
+
const result = await this.pluginManager.updatePlugin(pluginId, targetTag, targetRepoUrl);
|
|
42
|
+
this.logger.info({ pluginId, newVersion: result.version }, 'plugin.update.success');
|
|
43
|
+
return result;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
this.logger.error({ pluginId, error }, 'plugin.update.error');
|
|
46
|
+
throw new PluginError('plugin.errors.updateFailed', { cause: error, context: { pluginId } });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async deletePlugin(pluginId) {
|
|
51
|
+
this.logger.info({ pluginId }, 'plugin.delete.start');
|
|
52
|
+
try {
|
|
53
|
+
await this.pluginManager.deletePlugin(pluginId);
|
|
54
|
+
this.logger.info({ pluginId }, 'plugin.delete.success');
|
|
55
|
+
} catch (error) {
|
|
56
|
+
this.logger.error({ pluginId, error }, 'plugin.delete.error');
|
|
57
|
+
throw new PluginError('plugin.errors.deleteFailed', { cause: error, context: { pluginId } });
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async validateManifest(manifest) {
|
|
62
|
+
const result = validatePluginManifest(manifest);
|
|
63
|
+
if (!result.valid) {
|
|
64
|
+
throw new ValidationError('plugin.errors.invalidManifest', { context: { errors: result.errors } });
|
|
65
|
+
}
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async checkDependencies(botId, manifest) {
|
|
70
|
+
const installedPlugins = await this.pluginRepository.findByBotId(botId);
|
|
71
|
+
const fakePlugin = { manifest: JSON.stringify(manifest) };
|
|
72
|
+
return dependencyResolver.checkCompatibility(fakePlugin, installedPlugins);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async checkForUpdates(botId, catalog) {
|
|
76
|
+
return this.pluginManager.checkForUpdates(botId, catalog);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async getInstalledPlugins(botId) {
|
|
80
|
+
return this.pluginRepository.findByBotId(botId);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
module.exports = PluginManagementService;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
function createMockHandler(label) {
|
|
2
|
+
const handler = {
|
|
3
|
+
get(target, prop) {
|
|
4
|
+
if (prop === 'then') return undefined;
|
|
5
|
+
if (prop === Symbol.toPrimitive) return () => `[mock:${label}]`;
|
|
6
|
+
if (prop === 'toString') return () => `[mock:${label}]`;
|
|
7
|
+
if (prop in target) return target[prop];
|
|
8
|
+
const child = function (...args) {
|
|
9
|
+
console.log(`[TestMode] mock call: ${label}.${String(prop)}(${args.length} args)`);
|
|
10
|
+
return undefined;
|
|
11
|
+
};
|
|
12
|
+
child.__mock = true;
|
|
13
|
+
const proxied = new Proxy(child, createMockHandler(`${label}.${String(prop)}`));
|
|
14
|
+
target[prop] = proxied;
|
|
15
|
+
return proxied;
|
|
16
|
+
},
|
|
17
|
+
apply(target, thisArg, args) {
|
|
18
|
+
console.log(`[TestMode] mock invoke: ${label}(${args.length} args)`);
|
|
19
|
+
return undefined;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
return handler;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function createTestBotMock() {
|
|
26
|
+
const base = function () {};
|
|
27
|
+
base.username = 'TestBot';
|
|
28
|
+
base.entity = { position: { x: 0, y: 64, z: 0 }, yaw: 0, pitch: 0 };
|
|
29
|
+
base.players = {};
|
|
30
|
+
base.entities = {};
|
|
31
|
+
base.health = 20;
|
|
32
|
+
base.food = 20;
|
|
33
|
+
base.sendMessage = (...args) => {
|
|
34
|
+
console.log('[TestMode] bot.sendMessage:', ...args);
|
|
35
|
+
};
|
|
36
|
+
base.lookAt = () => {};
|
|
37
|
+
base.chat = (msg) => console.log('[TestMode] bot.chat:', msg);
|
|
38
|
+
return new Proxy(base, createMockHandler('bot'));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function createTestApiMock() {
|
|
42
|
+
const base = function () {};
|
|
43
|
+
return new Proxy(base, createMockHandler('api'));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function buildTestContext({ botId, graphId, eventArgs }) {
|
|
47
|
+
return {
|
|
48
|
+
botId,
|
|
49
|
+
graphId,
|
|
50
|
+
eventArgs,
|
|
51
|
+
user: eventArgs?.user || (eventArgs?.username ? { username: eventArgs.username } : { username: 'TestUser' }),
|
|
52
|
+
commandArguments: eventArgs?.commandArguments || {},
|
|
53
|
+
bot: createTestBotMock(),
|
|
54
|
+
api: createTestApiMock(),
|
|
55
|
+
services: new Proxy({}, createMockHandler('services')),
|
|
56
|
+
typeChat: 'chat',
|
|
57
|
+
__testMode: true
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
module.exports = {
|
|
62
|
+
createTestBotMock,
|
|
63
|
+
createTestApiMock,
|
|
64
|
+
buildTestContext
|
|
65
|
+
};
|