blockmine 1.25.0 → 1.27.1
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 -1
- 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/scripts/postinstall.js +38 -0
- 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,1012 +1,579 @@
|
|
|
1
|
-
const prismaService = require('./PrismaService');
|
|
2
1
|
const { parseVariables } = require('./utils/variableParser');
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const prisma = prismaService.getClient();
|
|
7
|
-
|
|
8
|
-
const BreakLoopSignal = require('./BreakLoopSignal');
|
|
2
|
+
const GraphValidation = require('./GraphValidation');
|
|
3
|
+
const GraphDebugHandler = require('./GraphDebugHandler');
|
|
4
|
+
const { attachDebugIpcHandler } = require('./GraphDebugIPC');
|
|
9
5
|
const { getTraceCollector } = require('./services/TraceCollectorService');
|
|
10
6
|
const { getGlobalDebugManager } = require('./services/DebugSessionManager');
|
|
7
|
+
const { MessageTypes } = require('./ipc/ipcMessageTypes');
|
|
8
|
+
const RewindSignal = require('./RewindSignal');
|
|
9
|
+
const BreakLoopSignal = require('./BreakLoopSignal');
|
|
11
10
|
|
|
12
11
|
class GraphExecutionEngine {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
async execute(graph, context, eventType) {
|
|
44
|
-
if (!graph || graph === 'null') return context;
|
|
45
|
-
|
|
46
|
-
const parsedGraph = validationService.parseGraph(graph, 'GraphExecutionEngine.execute');
|
|
47
|
-
if (!parsedGraph) {
|
|
48
|
-
return context;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const validation = validationService.validateGraphStructure(parsedGraph, 'GraphExecutionEngine');
|
|
52
|
-
if (validation.shouldSkip) {
|
|
53
|
-
return context;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const graphId = context.graphId || null;
|
|
57
|
-
const botId = context.botId || null;
|
|
58
|
-
|
|
59
|
-
if (graphId && botId) {
|
|
60
|
-
this.currentTraceId = await this.traceCollector.startTrace(
|
|
61
|
-
botId,
|
|
62
|
-
graphId,
|
|
63
|
-
eventType || 'command',
|
|
64
|
-
context.eventArgs || {}
|
|
65
|
-
);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
try {
|
|
69
|
-
this.activeGraph = parsedGraph;
|
|
70
|
-
this.context = context;
|
|
71
|
-
|
|
72
|
-
if (!this.context.variables) {
|
|
73
|
-
this.context.variables = parseVariables(
|
|
74
|
-
this.activeGraph.variables,
|
|
75
|
-
'GraphExecutionEngine'
|
|
12
|
+
constructor(nodeRegistry, botManagerOrApi = null) {
|
|
13
|
+
if (!nodeRegistry || typeof nodeRegistry.getNodeConfig !== 'function') {
|
|
14
|
+
throw new Error('GraphExecutionEngine requires a valid NodeRegistry instance.');
|
|
15
|
+
}
|
|
16
|
+
this.nodeRegistry = nodeRegistry;
|
|
17
|
+
this.botManager = botManagerOrApi;
|
|
18
|
+
this.activeGraph = null;
|
|
19
|
+
this.context = null;
|
|
20
|
+
this.memo = new Map();
|
|
21
|
+
this.traceCollector = getTraceCollector();
|
|
22
|
+
this.currentTraceId = null;
|
|
23
|
+
this.debugHandler = null;
|
|
24
|
+
this._inputOverrides = new Map();
|
|
25
|
+
|
|
26
|
+
attachDebugIpcHandler();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async execute(graph, context, eventType) {
|
|
30
|
+
const validation = GraphValidation.validateGraphForExecution(graph, 'GraphExecutionEngine.execute');
|
|
31
|
+
if (validation.shouldSkip) {
|
|
32
|
+
return context;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const parsedGraph = validation.graph;
|
|
36
|
+
const graphId = context.graphId || null;
|
|
37
|
+
const botId = context.botId || null;
|
|
38
|
+
|
|
39
|
+
if (graphId && botId) {
|
|
40
|
+
this.currentTraceId = await this.traceCollector.startTrace(
|
|
41
|
+
botId, graphId, eventType || 'command', context.eventArgs || {}
|
|
76
42
|
);
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
this.activeGraph = parsedGraph;
|
|
47
|
+
this.context = context;
|
|
48
|
+
|
|
49
|
+
if (!this.context.variables) {
|
|
50
|
+
this.context.variables = parseVariables(this.activeGraph.variables, 'GraphExecutionEngine');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!this.context.persistenceIntent) {
|
|
54
|
+
this.context.persistenceIntent = new Map();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
this.debugHandler = new GraphDebugHandler(this.context);
|
|
58
|
+
this._inputOverrides = new Map();
|
|
59
|
+
|
|
60
|
+
let rewindAttempts = 0;
|
|
61
|
+
const MAX_REWINDS = 100;
|
|
62
|
+
|
|
63
|
+
while (true) {
|
|
64
|
+
this.memo.clear();
|
|
65
|
+
|
|
66
|
+
await this._applyReplayState(graphId);
|
|
67
|
+
|
|
68
|
+
const startNode = GraphValidation.findStartNode(this.activeGraph, eventType);
|
|
69
|
+
if (!startNode) {
|
|
70
|
+
if (!eventType) {
|
|
71
|
+
throw new Error(`Не найдена стартовая нода события (event:command) в графе.`);
|
|
72
|
+
}
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (this.currentTraceId) {
|
|
77
|
+
this.traceCollector.recordStep(this.currentTraceId, {
|
|
78
|
+
nodeId: startNode.id,
|
|
79
|
+
nodeType: startNode.type,
|
|
80
|
+
status: 'executed',
|
|
81
|
+
duration: 0,
|
|
82
|
+
inputs: {},
|
|
83
|
+
outputs: await this._captureNodeOutputs(startNode),
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
await this.traverse(startNode, 'exec');
|
|
89
|
+
break;
|
|
90
|
+
} catch (err) {
|
|
91
|
+
if (err instanceof RewindSignal) {
|
|
92
|
+
rewindAttempts++;
|
|
93
|
+
if (rewindAttempts > MAX_REWINDS) {
|
|
94
|
+
console.error('[Debug] Too many rewinds, aborting');
|
|
95
|
+
break;
|
|
96
|
+
}
|
|
97
|
+
console.log(`[Debug] Rewind requested, restarting graph (attempt ${rewindAttempts})`);
|
|
98
|
+
|
|
99
|
+
if (this.currentTraceId && graphId && botId) {
|
|
100
|
+
try {
|
|
101
|
+
await this.traceCollector.completeTrace(this.currentTraceId);
|
|
102
|
+
} catch (e) {}
|
|
103
|
+
this.currentTraceId = await this.traceCollector.startTrace(
|
|
104
|
+
botId, graphId, eventType || 'command', context.eventArgs || {}
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await this._finalizeTrace(graphId);
|
|
114
|
+
|
|
115
|
+
} catch (error) {
|
|
116
|
+
await this._handleExecutionError(error, graphId);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return this.context;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async _applyReplayState(graphId) {
|
|
123
|
+
if (!graphId) return;
|
|
124
|
+
|
|
125
|
+
try {
|
|
126
|
+
const debugManager = getGlobalDebugManager();
|
|
127
|
+
const debugState = debugManager.get(graphId);
|
|
128
|
+
if (debugState && debugState.replayState) {
|
|
129
|
+
if (debugState.replayState.variables) {
|
|
130
|
+
this.context.variables = JSON.parse(JSON.stringify(debugState.replayState.variables));
|
|
131
|
+
}
|
|
132
|
+
if (debugState.replayState.commandArguments) {
|
|
133
|
+
this.context.commandArguments = JSON.parse(JSON.stringify(debugState.replayState.commandArguments));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (e) {}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async _finalizeTrace(graphId) {
|
|
140
|
+
if (!this.currentTraceId) return;
|
|
141
|
+
|
|
142
|
+
await this._captureAllDataNodeOutputs();
|
|
143
|
+
|
|
144
|
+
const trace = await this.traceCollector.completeTrace(this.currentTraceId);
|
|
145
|
+
|
|
146
|
+
if (trace && process.send) {
|
|
147
|
+
process.send({
|
|
148
|
+
type: MessageTypes.GRAPH.TRACE_COMPLETED,
|
|
149
|
+
trace: {
|
|
150
|
+
...trace,
|
|
151
|
+
steps: trace.steps,
|
|
152
|
+
eventArgs: typeof trace.eventArgs === 'string' ? trace.eventArgs : JSON.stringify(trace.eventArgs)
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (graphId) {
|
|
158
|
+
try {
|
|
159
|
+
const debugManager = getGlobalDebugManager();
|
|
160
|
+
const debugState = debugManager.get(graphId);
|
|
161
|
+
if (debugState && debugState.activeExecution) {
|
|
162
|
+
debugState.broadcast('debug:completed', {
|
|
163
|
+
trace: await this.traceCollector.getTrace(this.currentTraceId)
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
this.currentTraceId = null;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
async _handleExecutionError(error, graphId) {
|
|
173
|
+
if (this.currentTraceId) {
|
|
174
|
+
try {
|
|
175
|
+
await this._captureAllDataNodeOutputs();
|
|
176
|
+
} catch (captureError) {
|
|
177
|
+
console.error(`[GraphExecutor] Error capturing outputs on failure:`, captureError);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const trace = await this.traceCollector.failTrace(this.currentTraceId, error);
|
|
181
|
+
|
|
182
|
+
if (trace && process.send) {
|
|
183
|
+
process.send({
|
|
184
|
+
type: MessageTypes.GRAPH.TRACE_COMPLETED,
|
|
185
|
+
trace: {
|
|
186
|
+
...trace,
|
|
187
|
+
steps: trace.steps,
|
|
188
|
+
eventArgs: typeof trace.eventArgs === 'string' ? trace.eventArgs : JSON.stringify(trace.eventArgs)
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
this.currentTraceId = null;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const isStoppedByDebugger = error?.message === 'Execution stopped by debugger';
|
|
197
|
+
if (!(error instanceof BreakLoopSignal) && !isStoppedByDebugger) {
|
|
167
198
|
console.error(`[GraphExecutionEngine] Критическая ошибка выполнения графа: ${error.stack}`);
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
return this.context;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
async traverse(node, fromPinId) {
|
|
175
|
-
// Находим соединение к СУЩЕСТВУЮЩЕЙ ноде (пропускаем мусорные соединения к удалённым нодам)
|
|
176
|
-
const connection = this.activeGraph.connections.find(c => {
|
|
177
|
-
if (c.sourceNodeId !== node.id || c.sourcePinId !== fromPinId) return false;
|
|
178
|
-
const targetExists = this.activeGraph.nodes.some(n => n.id === c.targetNodeId);
|
|
179
|
-
return targetExists;
|
|
180
|
-
});
|
|
181
|
-
if (!connection) {
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const nextNode = this.activeGraph.nodes.find(n => n.id === connection.targetNodeId);
|
|
186
|
-
if (!nextNode) return;
|
|
187
|
-
|
|
188
|
-
if (this.currentTraceId) {
|
|
189
|
-
this.traceCollector.recordTraversal(
|
|
190
|
-
this.currentTraceId,
|
|
191
|
-
node.id,
|
|
192
|
-
fromPinId,
|
|
193
|
-
nextNode.id
|
|
194
|
-
);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
await this.executeNode(nextNode);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
async executeNode(node) {
|
|
201
|
-
const startTime = Date.now();
|
|
202
|
-
|
|
203
|
-
// Записываем шаг ДО проверки брейкпоинта, чтобы в trace были данные о предыдущих нодах
|
|
204
|
-
if (this.currentTraceId) {
|
|
205
|
-
this.traceCollector.recordStep(this.currentTraceId, {
|
|
206
|
-
nodeId: node.id,
|
|
207
|
-
nodeType: node.type,
|
|
208
|
-
status: 'executed',
|
|
209
|
-
duration: null,
|
|
210
|
-
inputs: await this._captureNodeInputs(node),
|
|
211
|
-
outputs: {},
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
// Проверка брейкпоинта ПЕРЕД выполнением (но ПОСЛЕ записи в trace)
|
|
216
|
-
await this.checkBreakpoint(node);
|
|
217
|
-
|
|
218
|
-
// Проверка step mode ПЕРЕД выполнением (важно делать ДО executor, чтобы поймать ноду до traverse)
|
|
219
|
-
await this._checkStepMode(node);
|
|
220
|
-
|
|
221
|
-
const nodeConfig = this.nodeRegistry.getNodeConfig(node.type);
|
|
222
|
-
if (nodeConfig && typeof nodeConfig.executor === 'function') {
|
|
223
|
-
const helpers = {
|
|
224
|
-
resolvePinValue: this.resolvePinValue.bind(this),
|
|
225
|
-
traverse: this.traverse.bind(this),
|
|
226
|
-
memo: this.memo,
|
|
227
|
-
clearLoopBodyMemo: this.clearLoopBodyMemo.bind(this),
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
try {
|
|
231
|
-
await nodeConfig.executor.call(this, node, this.context, helpers);
|
|
232
|
-
|
|
233
|
-
const executionTime = Date.now() - startTime;
|
|
234
|
-
|
|
235
|
-
if (this.currentTraceId) {
|
|
236
|
-
this.traceCollector.updateStepOutputs(this.currentTraceId, node.id, await this._captureNodeOutputs(node));
|
|
237
|
-
this.traceCollector.updateStepDuration(this.currentTraceId, node.id, executionTime);
|
|
238
|
-
}
|
|
239
|
-
} catch (error) {
|
|
240
|
-
if (this.currentTraceId) {
|
|
241
|
-
this.traceCollector.updateStepError(
|
|
242
|
-
this.currentTraceId,
|
|
243
|
-
node.id,
|
|
244
|
-
error.message,
|
|
245
|
-
Date.now() - startTime
|
|
246
|
-
);
|
|
247
|
-
}
|
|
248
|
-
throw error;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
const execCacheKey = `${node.id}_executed`;
|
|
255
|
-
if (this.memo.has(execCacheKey)) {
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
this.memo.set(execCacheKey, true);
|
|
259
|
-
|
|
260
|
-
try {
|
|
261
|
-
await this._executeLegacyNode(node);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const executionTime = Date.now() - startTime;
|
|
265
|
-
|
|
266
|
-
if (this.currentTraceId) {
|
|
267
|
-
this.traceCollector.updateStepOutputs(this.currentTraceId, node.id, await this._captureNodeOutputs(node));
|
|
268
|
-
this.traceCollector.updateStepDuration(this.currentTraceId, node.id, executionTime);
|
|
269
|
-
}
|
|
270
|
-
} catch (error) {
|
|
271
|
-
if (this.currentTraceId) {
|
|
272
|
-
this.traceCollector.updateStepError(
|
|
273
|
-
this.currentTraceId,
|
|
274
|
-
node.id,
|
|
275
|
-
error.message,
|
|
276
|
-
Date.now() - startTime
|
|
277
|
-
);
|
|
278
|
-
}
|
|
279
|
-
throw error;
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async _checkStepMode(node) {
|
|
284
|
-
try {
|
|
285
|
-
// Если работаем в дочернем процессе, используем IPC
|
|
286
|
-
if (process.send) {
|
|
287
|
-
return await this._checkStepModeViaIpc(node);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
// Иначе используем прямой доступ к DebugManager
|
|
291
|
-
const { getGlobalDebugManager } = require('./services/DebugSessionManager');
|
|
292
|
-
const debugManager = getGlobalDebugManager();
|
|
293
|
-
const graphId = this.activeGraph?.id;
|
|
294
|
-
|
|
295
|
-
if (graphId) {
|
|
296
|
-
const debugState = debugManager.get(graphId);
|
|
297
|
-
if (debugState && debugState.shouldStepPause(node.id)) {
|
|
298
|
-
console.log(`[Debug] Step mode: pausing after executing node ${node.id}`);
|
|
299
|
-
|
|
300
|
-
// Получаем inputs для отправки в debug session
|
|
301
|
-
const inputs = await this._captureNodeInputs(node);
|
|
302
|
-
|
|
303
|
-
// Получаем текущую трассировку для отображения выполненных шагов
|
|
304
|
-
const executedSteps = this.currentTraceId
|
|
305
|
-
? await this.traceCollector.getTrace(this.currentTraceId)
|
|
306
|
-
: null;
|
|
307
|
-
|
|
308
|
-
// Паузим выполнение
|
|
309
|
-
await debugState.pause({
|
|
310
|
-
nodeId: node.id,
|
|
311
|
-
nodeType: node.type,
|
|
312
|
-
inputs,
|
|
313
|
-
executedSteps,
|
|
314
|
-
context: {
|
|
315
|
-
user: this.context.user,
|
|
316
|
-
variables: this.context.variables,
|
|
317
|
-
commandArguments: this.context.commandArguments,
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
} catch (error) {
|
|
323
|
-
if (error.message !== 'DebugSessionManager not initialized! Call initializeDebugManager(io) first.') {
|
|
324
|
-
throw error;
|
|
325
|
-
}
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
async _checkStepModeViaIpc(node) {
|
|
330
|
-
// Step mode работает аналогично breakpoint, просто отправляем тип 'step'
|
|
331
|
-
const { randomUUID } = require('crypto');
|
|
332
|
-
const requestId = randomUUID();
|
|
333
|
-
|
|
334
|
-
const inputs = await this._captureNodeInputs(node);
|
|
335
|
-
const executedSteps = this.currentTraceId
|
|
336
|
-
? await this.traceCollector.getTrace(this.currentTraceId)
|
|
337
|
-
: null;
|
|
338
|
-
|
|
339
|
-
process.send({
|
|
340
|
-
type: 'debug:check_step_mode',
|
|
341
|
-
requestId,
|
|
342
|
-
payload: {
|
|
343
|
-
graphId: this.context.graphId,
|
|
344
|
-
nodeId: node.id,
|
|
345
|
-
nodeType: node.type,
|
|
346
|
-
inputs,
|
|
347
|
-
executedSteps,
|
|
348
|
-
context: {
|
|
349
|
-
user: this.context.user,
|
|
350
|
-
variables: this.context.variables,
|
|
351
|
-
commandArguments: this.context.commandArguments,
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
});
|
|
355
|
-
|
|
356
|
-
// Ждём ответа (если step mode не активен, ответ придёт сразу)
|
|
357
|
-
await new Promise((resolve) => {
|
|
358
|
-
this.pendingDebugRequests.set(requestId, resolve);
|
|
359
|
-
|
|
360
|
-
// Таймаут на случай, если ответ не придёт
|
|
361
|
-
const timeoutId = setTimeout(() => {
|
|
362
|
-
if (this.pendingDebugRequests.has(requestId)) {
|
|
363
|
-
this.pendingDebugRequests.delete(requestId);
|
|
364
|
-
resolve(null);
|
|
365
|
-
}
|
|
366
|
-
}, debugConfig.STEP_MODE_TIMEOUT);
|
|
367
|
-
|
|
368
|
-
// Сохраняем timeoutId для возможной отмены
|
|
369
|
-
this.pendingDebugRequests.set(`${requestId}_timeout`, timeoutId);
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
async _executeLegacyNode(node) {
|
|
374
|
-
switch (node.type) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
case 'string:contains':
|
|
383
|
-
case 'string:matches':
|
|
384
|
-
case 'string:equals': {
|
|
385
|
-
await this.traverse(node, 'exec');
|
|
386
|
-
break;
|
|
387
|
-
}
|
|
388
|
-
case 'array:get_random_element': {
|
|
389
|
-
await this.traverse(node, 'element');
|
|
390
|
-
break;
|
|
391
|
-
}
|
|
392
|
-
case 'array:add_element':
|
|
393
|
-
case 'array:remove_by_index':
|
|
394
|
-
case 'array:get_by_index':
|
|
395
|
-
case 'array:find_index':
|
|
396
|
-
case 'array:contains': {
|
|
397
|
-
await this.traverse(node, 'result');
|
|
398
|
-
break;
|
|
399
|
-
}
|
|
400
|
-
case 'data:array_literal':
|
|
401
|
-
case 'data:make_object':
|
|
402
|
-
case 'data:get_variable':
|
|
403
|
-
case 'data:get_argument':
|
|
404
|
-
case 'data:length':
|
|
405
|
-
case 'data:get_entity_field':
|
|
406
|
-
case 'data:cast':
|
|
407
|
-
case 'data:string_literal':
|
|
408
|
-
case 'data:get_user_field':
|
|
409
|
-
case 'data:get_server_players':
|
|
410
|
-
case 'data:get_bot_look':
|
|
411
|
-
case 'math:operation':
|
|
412
|
-
case 'math:random_number':
|
|
413
|
-
case 'logic:operation':
|
|
414
|
-
case 'string:concat':
|
|
415
|
-
case 'object:create':
|
|
416
|
-
case 'object:get':
|
|
417
|
-
case 'object:set':
|
|
418
|
-
case 'object:delete':
|
|
419
|
-
case 'object:has_key': {
|
|
420
|
-
await this.traverse(node, 'value');
|
|
421
|
-
break;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
clearLoopBodyMemo(loopNode) {
|
|
427
|
-
const nodesToClear = new Set();
|
|
428
|
-
const queue = [];
|
|
429
|
-
|
|
430
|
-
const initialConnection = this.activeGraph.connections.find(
|
|
431
|
-
c => c.sourceNodeId === loopNode.id && c.sourcePinId === 'loop_body'
|
|
432
|
-
);
|
|
433
|
-
if (initialConnection) {
|
|
434
|
-
const firstNode = this.activeGraph.nodes.find(n => n.id === initialConnection.targetNodeId);
|
|
435
|
-
if (firstNode) {
|
|
436
|
-
queue.push(firstNode);
|
|
199
|
+
} else if (isStoppedByDebugger) {
|
|
200
|
+
console.log('[GraphExecutionEngine] Execution stopped by user from debugger');
|
|
437
201
|
}
|
|
438
202
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
if (
|
|
452
|
-
|
|
203
|
+
|
|
204
|
+
async traverse(node, fromPinId) {
|
|
205
|
+
const connection = this.activeGraph.connections.find(c => {
|
|
206
|
+
if (c.sourceNodeId !== node.id || c.sourcePinId !== fromPinId) return false;
|
|
207
|
+
const targetExists = this.activeGraph.nodes.some(n => n.id === c.targetNodeId);
|
|
208
|
+
return targetExists;
|
|
209
|
+
});
|
|
210
|
+
if (!connection) return;
|
|
211
|
+
|
|
212
|
+
const nextNode = this.activeGraph.nodes.find(n => n.id === connection.targetNodeId);
|
|
213
|
+
if (!nextNode) return;
|
|
214
|
+
|
|
215
|
+
if (this.currentTraceId) {
|
|
216
|
+
this.traceCollector.recordTraversal(this.currentTraceId, node.id, fromPinId, nextNode.id);
|
|
453
217
|
}
|
|
454
|
-
|
|
218
|
+
|
|
219
|
+
await this.executeNode(nextNode);
|
|
455
220
|
}
|
|
456
221
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
222
|
+
async executeNode(node) {
|
|
223
|
+
const startTime = Date.now();
|
|
224
|
+
|
|
225
|
+
if (this.currentTraceId) {
|
|
226
|
+
this.traceCollector.recordStep(this.currentTraceId, {
|
|
227
|
+
nodeId: node.id,
|
|
228
|
+
nodeType: node.type,
|
|
229
|
+
status: 'executed',
|
|
230
|
+
duration: null,
|
|
231
|
+
inputs: await this._captureNodeInputs(node),
|
|
232
|
+
outputs: {},
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (this.debugHandler) {
|
|
237
|
+
const bpResult = await this.debugHandler.checkBreakpoint(
|
|
238
|
+
node,
|
|
239
|
+
this._captureNodeInputs.bind(this),
|
|
240
|
+
this._captureNodeOutputs.bind(this),
|
|
241
|
+
this.currentTraceId, this.traceCollector
|
|
242
|
+
);
|
|
243
|
+
|
|
244
|
+
await this.debugHandler.checkStepMode(
|
|
245
|
+
node,
|
|
246
|
+
this._captureNodeInputs.bind(this),
|
|
247
|
+
this.currentTraceId, this.traceCollector
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const nodeConfig = this.nodeRegistry.getNodeConfig(node.type);
|
|
252
|
+
if (nodeConfig && typeof nodeConfig.executor === 'function') {
|
|
253
|
+
const helpers = {
|
|
254
|
+
resolvePinValue: this.resolvePinValue.bind(this),
|
|
255
|
+
traverse: this.traverse.bind(this),
|
|
256
|
+
memo: this.memo,
|
|
257
|
+
clearLoopBodyMemo: this.clearLoopBodyMemo.bind(this),
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
try {
|
|
261
|
+
await nodeConfig.executor.call(this, node, this.context, helpers);
|
|
262
|
+
|
|
263
|
+
const executionTime = Date.now() - startTime;
|
|
264
|
+
if (this.currentTraceId) {
|
|
265
|
+
this.traceCollector.updateStepOutputs(this.currentTraceId, node.id, await this._captureNodeOutputs(node));
|
|
266
|
+
this.traceCollector.updateStepDuration(this.currentTraceId, node.id, executionTime);
|
|
267
|
+
}
|
|
268
|
+
} catch (error) {
|
|
269
|
+
if (this.currentTraceId) {
|
|
270
|
+
this.traceCollector.updateStepError(this.currentTraceId, node.id, error.message, Date.now() - startTime);
|
|
271
|
+
}
|
|
272
|
+
throw error;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const execCacheKey = `${node.id}_executed`;
|
|
279
|
+
if (this.memo.has(execCacheKey)) return;
|
|
280
|
+
this.memo.set(execCacheKey, true);
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
await this._executeLegacyNode(node);
|
|
284
|
+
|
|
285
|
+
const executionTime = Date.now() - startTime;
|
|
286
|
+
if (this.currentTraceId) {
|
|
287
|
+
this.traceCollector.updateStepOutputs(this.currentTraceId, node.id, await this._captureNodeOutputs(node));
|
|
288
|
+
this.traceCollector.updateStepDuration(this.currentTraceId, node.id, executionTime);
|
|
289
|
+
}
|
|
290
|
+
} catch (error) {
|
|
291
|
+
if (this.currentTraceId) {
|
|
292
|
+
this.traceCollector.updateStepError(this.currentTraceId, node.id, error.message, Date.now() - startTime);
|
|
461
293
|
}
|
|
294
|
+
throw error;
|
|
462
295
|
}
|
|
463
296
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const nodeConfig = this.nodeRegistry.getNodeConfig(node.type);
|
|
511
|
-
if (nodeConfig && typeof nodeConfig.evaluator === 'function') {
|
|
512
|
-
const helpers = {
|
|
513
|
-
resolvePinValue: this.resolvePinValue.bind(this),
|
|
514
|
-
memo: this.memo,
|
|
515
|
-
};
|
|
516
|
-
|
|
517
|
-
// Записываем data ноду в трассировку перед вычислением (только один раз)
|
|
518
|
-
const traceKey = `trace_recorded:${node.id}`;
|
|
519
|
-
const isDataNode = !nodeConfig.pins.inputs.some(p => p.type === 'Exec');
|
|
520
|
-
|
|
521
|
-
if (this.currentTraceId && isDataNode && !this.memo.has(traceKey)) {
|
|
522
|
-
// Это data нода (нет exec пинов) и она ещё не записана - записываем её inputs
|
|
523
|
-
const inputs = await this._captureNodeInputs(node);
|
|
524
|
-
|
|
525
|
-
this.traceCollector.recordStep(this.currentTraceId, {
|
|
526
|
-
nodeId: node.id,
|
|
527
|
-
nodeType: node.type,
|
|
528
|
-
status: 'executed',
|
|
529
|
-
duration: 0,
|
|
530
|
-
inputs,
|
|
531
|
-
outputs: {},
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
// Помечаем, что нода уже записана в трассировку
|
|
535
|
-
this.memo.set(traceKey, true);
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
const result = await nodeConfig.evaluator.call(this, node, pinId, this.context, helpers);
|
|
539
|
-
|
|
540
|
-
const isVolatile = this.isNodeVolatile(node);
|
|
541
|
-
if (!isVolatile) {
|
|
542
|
-
this.memo.set(cacheKey, result);
|
|
543
|
-
}
|
|
544
|
-
|
|
545
|
-
// Обновляем outputs для data ноды после вычисления И после записи в memo
|
|
546
|
-
// Обновляем только один раз для всей ноды (не для каждого пина отдельно)
|
|
547
|
-
const traceOutputsKey = `trace_outputs_recorded:${node.id}`;
|
|
548
|
-
if (this.currentTraceId && isDataNode && this.memo.has(traceKey) && !this.memo.has(traceOutputsKey)) {
|
|
549
|
-
const outputs = {};
|
|
550
|
-
for (const outputPin of nodeConfig.pins.outputs) {
|
|
551
|
-
if (outputPin.type !== 'Exec') {
|
|
552
|
-
const outKey = `${node.id}:${outputPin.id}`;
|
|
553
|
-
if (this.memo.has(outKey)) {
|
|
554
|
-
outputs[outputPin.id] = this.memo.get(outKey);
|
|
555
|
-
}
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
this.traceCollector.updateStepOutputs(this.currentTraceId, node.id, outputs);
|
|
559
|
-
this.memo.set(traceOutputsKey, true); // Помечаем, что outputs уже записаны
|
|
560
|
-
}
|
|
561
|
-
|
|
562
|
-
return result;
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
let result;
|
|
566
|
-
|
|
567
|
-
switch (node.type) {
|
|
568
|
-
case 'user:set_blacklist':
|
|
569
|
-
result = this.memo.get(`${node.id}:updated_user`);
|
|
570
|
-
break;
|
|
571
|
-
case 'event:command':
|
|
572
|
-
if (pinId === 'args') result = this.context.eventArgs?.args || {};
|
|
573
|
-
else if (pinId === 'user') result = this.context.eventArgs?.user || {};
|
|
574
|
-
else if (pinId === 'chat_type') result = this.context.eventArgs?.typeChat || 'chat';
|
|
575
|
-
else if (pinId === 'command_name') result = this.context.eventArgs?.commandName;
|
|
576
|
-
else if (pinId === 'success') result = this.context.success !== undefined ? this.context.success : true;
|
|
577
|
-
else result = this.context.eventArgs?.[pinId];
|
|
578
|
-
break;
|
|
579
|
-
case 'event:chat':
|
|
580
|
-
if (pinId === 'username') result = this.context.eventArgs?.username || this.context.username;
|
|
581
|
-
else if (pinId === 'message') result = this.context.eventArgs?.message || this.context.message;
|
|
582
|
-
else if (pinId === 'chatType') result = this.context.eventArgs?.chatType || this.context.chat_type;
|
|
583
|
-
else result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
584
|
-
break;
|
|
585
|
-
case 'event:raw_message':
|
|
586
|
-
if (pinId === 'rawText') result = this.context.eventArgs?.rawText || this.context.rawText;
|
|
587
|
-
else result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
588
|
-
break;
|
|
589
|
-
case 'event:playerJoined':
|
|
590
|
-
case 'event:playerLeft':
|
|
591
|
-
if (pinId === 'user') result = this.context.eventArgs?.user || this.context.user;
|
|
592
|
-
else result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
593
|
-
break;
|
|
594
|
-
case 'event:entitySpawn':
|
|
595
|
-
case 'event:entityMoved':
|
|
596
|
-
case 'event:entityGone':
|
|
597
|
-
if (pinId === 'entity') result = this.context.eventArgs?.entity || this.context.entity;
|
|
598
|
-
else result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
599
|
-
break;
|
|
600
|
-
case 'event:health':
|
|
601
|
-
case 'event:botDied':
|
|
602
|
-
case 'event:botStartup':
|
|
603
|
-
result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
604
|
-
break;
|
|
605
|
-
case 'event:websocket_call':
|
|
606
|
-
if (pinId === 'graphName') result = this.context.eventArgs?.graphName || this.context.graphName;
|
|
607
|
-
else if (pinId === 'data') result = this.context.eventArgs?.data || this.context.data;
|
|
608
|
-
else if (pinId === 'socketId') result = this.context.eventArgs?.socketId || this.context.socketId;
|
|
609
|
-
else if (pinId === 'keyPrefix') result = this.context.eventArgs?.keyPrefix || this.context.keyPrefix;
|
|
610
|
-
else result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
611
|
-
break;
|
|
612
|
-
|
|
613
|
-
case 'flow:for_each': {
|
|
614
|
-
if (pinId === 'element') {
|
|
615
|
-
result = this.memo.get(`${node.id}:element`);
|
|
616
|
-
} else if (pinId === 'index') {
|
|
617
|
-
result = this.memo.get(`${node.id}:index`);
|
|
297
|
+
|
|
298
|
+
async _executeLegacyNode(node) {
|
|
299
|
+
const legacyMap = {
|
|
300
|
+
'string:contains': 'exec', 'string:matches': 'exec', 'string:equals': 'exec',
|
|
301
|
+
'array:get_random_element': 'element',
|
|
302
|
+
'array:add_element': 'result', 'array:remove_by_index': 'result',
|
|
303
|
+
'array:get_by_index': 'result', 'array:find_index': 'result', 'array:contains': 'result',
|
|
304
|
+
'data:array_literal': 'value', 'data:make_object': 'value', 'data:get_variable': 'value',
|
|
305
|
+
'data:get_argument': 'value', 'data:length': 'value', 'data:get_entity_field': 'value',
|
|
306
|
+
'data:cast': 'value', 'data:string_literal': 'value', 'data:get_user_field': 'value',
|
|
307
|
+
'data:get_server_players': 'value', 'data:get_bot_look': 'value',
|
|
308
|
+
'math:operation': 'value', 'math:random_number': 'value',
|
|
309
|
+
'logic:operation': 'value', 'string:concat': 'value',
|
|
310
|
+
'object:create': 'value', 'object:get': 'value', 'object:set': 'value',
|
|
311
|
+
'object:delete': 'value', 'object:has_key': 'value',
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
const pin = legacyMap[node.type];
|
|
315
|
+
if (pin) {
|
|
316
|
+
await this.traverse(node, pin);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
clearLoopBodyMemo(loopNode) {
|
|
321
|
+
const nodesToClear = new Set();
|
|
322
|
+
const queue = [];
|
|
323
|
+
|
|
324
|
+
const initialConnection = this.activeGraph.connections.find(
|
|
325
|
+
c => c.sourceNodeId === loopNode.id && c.sourcePinId === 'loop_body'
|
|
326
|
+
);
|
|
327
|
+
if (initialConnection) {
|
|
328
|
+
const firstNode = this.activeGraph.nodes.find(n => n.id === initialConnection.targetNodeId);
|
|
329
|
+
if (firstNode) queue.push(firstNode);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const visited = new Set();
|
|
333
|
+
while (queue.length > 0) {
|
|
334
|
+
const currentNode = queue.shift();
|
|
335
|
+
if (visited.has(currentNode.id)) continue;
|
|
336
|
+
visited.add(currentNode.id);
|
|
337
|
+
nodesToClear.add(currentNode.id);
|
|
338
|
+
|
|
339
|
+
const connections = this.activeGraph.connections.filter(c => c.sourceNodeId === currentNode.id);
|
|
340
|
+
for (const conn of connections) {
|
|
341
|
+
const nextNode = this.activeGraph.nodes.find(n => n.id === conn.targetNodeId);
|
|
342
|
+
if (nextNode) queue.push(nextNode);
|
|
618
343
|
}
|
|
619
|
-
|
|
620
|
-
}
|
|
344
|
+
}
|
|
621
345
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
346
|
+
for (const nodeId of nodesToClear) {
|
|
347
|
+
for (const key of this.memo.keys()) {
|
|
348
|
+
if (key.startsWith(nodeId)) this.memo.delete(key);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
626
352
|
|
|
627
|
-
|
|
353
|
+
async resolvePinValue(node, pinId, defaultValue = null) {
|
|
354
|
+
const overrideValue = this.debugHandler?.getInputOverride(node.id, pinId);
|
|
355
|
+
if (overrideValue !== undefined) return overrideValue;
|
|
628
356
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
357
|
+
const connection = this.activeGraph.connections.find(
|
|
358
|
+
c => c.targetNodeId === node.id && c.targetPinId === pinId
|
|
359
|
+
);
|
|
360
|
+
if (connection) {
|
|
361
|
+
const sourceNode = this.activeGraph.nodes.find(n => n.id === connection.sourceNodeId);
|
|
362
|
+
return await this.evaluateOutputPin(sourceNode, connection.sourcePinId, defaultValue);
|
|
363
|
+
}
|
|
632
364
|
|
|
633
|
-
|
|
634
|
-
}
|
|
365
|
+
let value = node.data && node.data[pinId] !== undefined ? node.data[pinId] : defaultValue;
|
|
635
366
|
|
|
636
|
-
|
|
637
|
-
|
|
367
|
+
if (typeof value === 'string' && value.includes('{')) {
|
|
368
|
+
value = await this._replaceVariablesInString(value, node);
|
|
369
|
+
}
|
|
638
370
|
|
|
639
|
-
|
|
640
|
-
console.warn(`[GraphExecutionEngine] isNodeVolatile достиг максимальной глубины рекурсии (${MAX_RECURSION_DEPTH})`);
|
|
641
|
-
return false;
|
|
371
|
+
return value;
|
|
642
372
|
}
|
|
643
373
|
|
|
644
|
-
|
|
645
|
-
|
|
374
|
+
async _replaceVariablesInString(text, node) {
|
|
375
|
+
const variablePattern = /\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
|
|
376
|
+
const matches = [...text.matchAll(variablePattern)];
|
|
377
|
+
let result = text;
|
|
378
|
+
|
|
379
|
+
for (const match of matches) {
|
|
380
|
+
const varName = match[1];
|
|
381
|
+
const varValue = await this.resolvePinValue(node, `var_${varName}`, '');
|
|
382
|
+
result = result.replace(match[0], String(varValue));
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
return result;
|
|
646
386
|
}
|
|
647
|
-
visited.add(node.id);
|
|
648
387
|
|
|
649
|
-
|
|
650
|
-
return
|
|
388
|
+
async evaluateOutputPin(node, pinId, defaultValue = null) {
|
|
389
|
+
if (!node) return defaultValue;
|
|
390
|
+
|
|
391
|
+
const cacheKey = `${node.id}:${pinId}`;
|
|
392
|
+
if (this.memo.has(cacheKey)) return this.memo.get(cacheKey);
|
|
393
|
+
|
|
394
|
+
const nodeConfig = this.nodeRegistry.getNodeConfig(node.type);
|
|
395
|
+
if (nodeConfig && typeof nodeConfig.evaluator === 'function') {
|
|
396
|
+
return await this._evaluateWithConfig(node, pinId, nodeConfig, cacheKey, defaultValue);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const result = this._evaluateLegacyNode(node, pinId, defaultValue);
|
|
400
|
+
|
|
401
|
+
if (!GraphDebugHandler.checkVolatility(node, this.activeGraph)) {
|
|
402
|
+
this.memo.set(cacheKey, result);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
return result;
|
|
651
406
|
}
|
|
652
407
|
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
408
|
+
async _evaluateWithConfig(node, pinId, nodeConfig, cacheKey, defaultValue) {
|
|
409
|
+
const helpers = { resolvePinValue: this.resolvePinValue.bind(this), memo: this.memo };
|
|
410
|
+
|
|
411
|
+
const traceKey = `trace_recorded:${node.id}`;
|
|
412
|
+
const inputPins = GraphValidation.getNodeInputPins(nodeConfig, node.data);
|
|
413
|
+
const outputPins = GraphValidation.getNodeOutputPins(nodeConfig, node.data);
|
|
414
|
+
const isDataNode = !inputPins.some(p => p && p.type === 'Exec');
|
|
415
|
+
|
|
416
|
+
if (this.currentTraceId && isDataNode && !this.memo.has(traceKey)) {
|
|
417
|
+
this.traceCollector.recordStep(this.currentTraceId, {
|
|
418
|
+
nodeId: node.id, nodeType: node.type, status: 'executed', duration: 0,
|
|
419
|
+
inputs: await this._captureNodeInputs(node), outputs: {},
|
|
420
|
+
});
|
|
421
|
+
this.memo.set(traceKey, true);
|
|
658
422
|
}
|
|
423
|
+
|
|
424
|
+
const result = await nodeConfig.evaluator.call(this, node, pinId, this.context, helpers);
|
|
425
|
+
|
|
426
|
+
if (!GraphDebugHandler.checkVolatility(node, this.activeGraph)) {
|
|
427
|
+
this.memo.set(cacheKey, result);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const traceOutputsKey = `trace_outputs_recorded:${node.id}`;
|
|
431
|
+
if (this.currentTraceId && isDataNode && this.memo.has(traceKey) && !this.memo.has(traceOutputsKey)) {
|
|
432
|
+
const outputs = {};
|
|
433
|
+
for (const outputPin of outputPins) {
|
|
434
|
+
if (outputPin && outputPin.type !== 'Exec') {
|
|
435
|
+
const outKey = `${node.id}:${outputPin.id}`;
|
|
436
|
+
if (this.memo.has(outKey)) outputs[outputPin.id] = this.memo.get(outKey);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
this.traceCollector.updateStepOutputs(this.currentTraceId, node.id, outputs);
|
|
440
|
+
this.memo.set(traceOutputsKey, true);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
return result;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
_evaluateLegacyNode(node, pinId, defaultValue) {
|
|
447
|
+
let result;
|
|
448
|
+
|
|
449
|
+
switch (node.type) {
|
|
450
|
+
case 'user:set_blacklist':
|
|
451
|
+
result = this.memo.get(`${node.id}:updated_user`);
|
|
452
|
+
break;
|
|
453
|
+
case 'event:command':
|
|
454
|
+
if (pinId === 'args') result = this.context.eventArgs?.args || {};
|
|
455
|
+
else if (pinId === 'user') result = this.context.eventArgs?.user || {};
|
|
456
|
+
else if (pinId === 'chat_type') result = this.context.eventArgs?.typeChat || 'chat';
|
|
457
|
+
else if (pinId === 'command_name') result = this.context.eventArgs?.commandName;
|
|
458
|
+
else if (pinId === 'success') result = this.context.success !== undefined ? this.context.success : true;
|
|
459
|
+
else result = this.context.eventArgs?.[pinId];
|
|
460
|
+
break;
|
|
461
|
+
case 'event:chat':
|
|
462
|
+
if (pinId === 'username') result = this.context.eventArgs?.username || this.context.username;
|
|
463
|
+
else if (pinId === 'message') result = this.context.eventArgs?.message || this.context.message;
|
|
464
|
+
else if (pinId === 'chatType') result = this.context.eventArgs?.chatType || this.context.chat_type;
|
|
465
|
+
else result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
466
|
+
break;
|
|
467
|
+
case 'event:raw_message':
|
|
468
|
+
if (pinId === 'rawText') result = this.context.eventArgs?.rawText || this.context.rawText;
|
|
469
|
+
else result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
470
|
+
break;
|
|
471
|
+
case 'event:playerJoined':
|
|
472
|
+
case 'event:playerLeft':
|
|
473
|
+
if (pinId === 'user') result = this.context.eventArgs?.user || this.context.user;
|
|
474
|
+
else result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
475
|
+
break;
|
|
476
|
+
case 'event:entitySpawn':
|
|
477
|
+
case 'event:entityMoved':
|
|
478
|
+
case 'event:entityGone':
|
|
479
|
+
if (pinId === 'entity') result = this.context.eventArgs?.entity || this.context.entity;
|
|
480
|
+
else result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
481
|
+
break;
|
|
482
|
+
case 'event:health':
|
|
483
|
+
case 'event:botDied':
|
|
484
|
+
case 'event:botStartup':
|
|
485
|
+
result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
486
|
+
break;
|
|
487
|
+
case 'event:websocket_call':
|
|
488
|
+
if (pinId === 'graphName') result = this.context.eventArgs?.graphName || this.context.graphName;
|
|
489
|
+
else if (pinId === 'data') result = this.context.eventArgs?.data || this.context.data;
|
|
490
|
+
else if (pinId === 'socketId') result = this.context.eventArgs?.socketId || this.context.socketId;
|
|
491
|
+
else if (pinId === 'keyPrefix') result = this.context.eventArgs?.keyPrefix || this.context.keyPrefix;
|
|
492
|
+
else result = this.context.eventArgs?.[pinId] || this.context[pinId];
|
|
493
|
+
break;
|
|
494
|
+
case 'flow:for_each':
|
|
495
|
+
if (pinId === 'element') result = this.memo.get(`${node.id}:element`);
|
|
496
|
+
else if (pinId === 'index') result = this.memo.get(`${node.id}:index`);
|
|
497
|
+
break;
|
|
498
|
+
default:
|
|
499
|
+
result = defaultValue;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
return result;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
hasConnection(node, pinId) {
|
|
506
|
+
if (!this.activeGraph || !this.activeGraph.connections) return false;
|
|
507
|
+
return this.activeGraph.connections.some(conn =>
|
|
508
|
+
conn.targetNodeId === node.id && conn.targetPinId === pinId
|
|
509
|
+
);
|
|
659
510
|
}
|
|
660
511
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
for (const inputPin of nodeConfig.pins.inputs) {
|
|
727
|
-
if (inputPin.type === 'Exec') continue; // Пропускаем exec пины
|
|
728
|
-
|
|
729
|
-
try {
|
|
730
|
-
// Используем resolvePinValue для получения актуального значения
|
|
731
|
-
const value = await this.resolvePinValue(node, inputPin.id);
|
|
732
|
-
// Записываем все значения, включая undefined и null
|
|
733
|
-
inputs[inputPin.id] = value;
|
|
734
|
-
} catch (error) {
|
|
735
|
-
inputs[inputPin.id] = '<error capturing>';
|
|
736
|
-
}
|
|
737
|
-
}
|
|
738
|
-
return inputs;
|
|
739
|
-
}
|
|
740
|
-
|
|
741
|
-
/**
|
|
742
|
-
* Захватить значения выходов ноды для трассировки
|
|
743
|
-
*/
|
|
744
|
-
async _captureNodeOutputs(node) {
|
|
745
|
-
const outputs = {};
|
|
746
|
-
|
|
747
|
-
// Получаем конфигурацию ноды
|
|
748
|
-
const nodeConfig = this.nodeRegistry.getNodeConfig(node.type);
|
|
749
|
-
if (!nodeConfig || !nodeConfig.pins || !nodeConfig.pins.outputs) {
|
|
750
|
-
return outputs;
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
// Захватываем значения всех выходов
|
|
754
|
-
for (const outputPin of nodeConfig.pins.outputs) {
|
|
755
|
-
if (outputPin.type === 'Exec') continue; // Пропускаем exec пины
|
|
756
|
-
|
|
757
|
-
try {
|
|
758
|
-
// Активно вызываем evaluateOutputPin вместо чтения из memo
|
|
759
|
-
// Это необходимо для event нод, которые используют switch-case и не пишут в memo
|
|
760
|
-
const value = await this.evaluateOutputPin(node, outputPin.id);
|
|
761
|
-
// Записываем все значения, включая undefined и null
|
|
762
|
-
outputs[outputPin.id] = value;
|
|
763
|
-
} catch (error) {
|
|
764
|
-
outputs[outputPin.id] = '<error capturing>';
|
|
765
|
-
}
|
|
766
|
-
}
|
|
767
|
-
return outputs;
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
/**
|
|
771
|
-
* Проверить брейкпоинт перед выполнением ноды
|
|
772
|
-
*/
|
|
773
|
-
async checkBreakpoint(node) {
|
|
774
|
-
try {
|
|
775
|
-
// Если работаем в дочернем процессе, используем IPC
|
|
776
|
-
if (process.send) {
|
|
777
|
-
return await this._checkBreakpointViaIpc(node);
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
// Иначе используем прямой доступ к DebugManager
|
|
781
|
-
const debugManager = getGlobalDebugManager();
|
|
782
|
-
const graphId = this.context.graphId;
|
|
783
|
-
|
|
784
|
-
if (!graphId) return;
|
|
785
|
-
|
|
786
|
-
const debugState = debugManager.get(graphId);
|
|
787
|
-
if (!debugState) return;
|
|
788
|
-
|
|
789
|
-
const breakpoint = debugState.breakpoints.get(node.id);
|
|
790
|
-
if (!breakpoint || !breakpoint.enabled) return;
|
|
791
|
-
|
|
792
|
-
const shouldPause = await this.evaluateBreakpointCondition(breakpoint);
|
|
793
|
-
|
|
794
|
-
if (shouldPause) {
|
|
795
|
-
console.log(`[Debug] Hit breakpoint at node ${node.id}, pausing execution`);
|
|
796
|
-
|
|
797
|
-
breakpoint.hitCount++;
|
|
798
|
-
|
|
799
|
-
const inputs = await this._captureNodeInputs(node);
|
|
800
|
-
|
|
801
|
-
const executedSteps = this.currentTraceId
|
|
802
|
-
? await this.traceCollector.getTrace(this.currentTraceId)
|
|
803
|
-
: null;
|
|
804
|
-
|
|
805
|
-
const overrides = await debugState.pause({
|
|
806
|
-
nodeId: node.id,
|
|
807
|
-
nodeType: node.type,
|
|
808
|
-
inputs,
|
|
809
|
-
executedSteps, // Добавляем выполненные шаги
|
|
810
|
-
context: {
|
|
811
|
-
user: this.context.user,
|
|
812
|
-
variables: this.context.variables,
|
|
813
|
-
commandArguments: this.context.commandArguments,
|
|
814
|
-
},
|
|
815
|
-
breakpoint: {
|
|
816
|
-
condition: breakpoint.condition,
|
|
817
|
-
hitCount: breakpoint.hitCount
|
|
818
|
-
}
|
|
819
|
-
});
|
|
820
|
-
|
|
821
|
-
if (overrides && overrides.__stopped) {
|
|
822
|
-
throw new Error('Execution stopped by debugger');
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
if (overrides) {
|
|
826
|
-
this.applyWhatIfOverrides(node, overrides);
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
} catch (error) {
|
|
830
|
-
console.error(`[checkBreakpoint] ERROR:`, error.message, error.stack);
|
|
831
|
-
if (error.message === 'DebugSessionManager not initialized! Call initializeDebugManager(io) first.') {
|
|
832
|
-
return;
|
|
833
|
-
}
|
|
834
|
-
// НЕ пробрасываем ошибку дальше, чтобы не сломать выполнение
|
|
835
|
-
console.error(`[checkBreakpoint] Ignoring error to continue execution`);
|
|
836
|
-
}
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
/**
|
|
840
|
-
* Оценить условие брейкпоинта
|
|
841
|
-
*/
|
|
842
|
-
async evaluateBreakpointCondition(breakpoint) {
|
|
843
|
-
// Если нет условия, всегда срабатывает
|
|
844
|
-
if (!breakpoint.condition || breakpoint.condition.trim() === '') {
|
|
845
|
-
return true;
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
try {
|
|
849
|
-
// Создаем sandbox для безопасного выполнения условия
|
|
850
|
-
const sandbox = {
|
|
851
|
-
user: this.context.user || {},
|
|
852
|
-
args: this.context.commandArguments || {},
|
|
853
|
-
variables: this.context.variables || {},
|
|
854
|
-
context: this.context
|
|
855
|
-
};
|
|
856
|
-
|
|
857
|
-
// Используем Function constructor для безопасного выполнения
|
|
858
|
-
const fn = new Function(
|
|
859
|
-
...Object.keys(sandbox),
|
|
860
|
-
`return (${breakpoint.condition})`
|
|
861
|
-
);
|
|
862
|
-
|
|
863
|
-
const result = fn(...Object.values(sandbox));
|
|
864
|
-
|
|
865
|
-
return Boolean(result);
|
|
866
|
-
} catch (error) {
|
|
867
|
-
console.error(`[Debug] Error evaluating breakpoint condition: ${error.message}`);
|
|
868
|
-
console.error(`[Debug] Condition was: ${breakpoint.condition}`);
|
|
869
|
-
return false;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
|
|
873
|
-
/**
|
|
874
|
-
* Применить what-if overrides к ноде
|
|
875
|
-
*/
|
|
876
|
-
/**
|
|
877
|
-
* Проверить брейкпоинт через IPC (для дочерних процессов)
|
|
878
|
-
*/
|
|
879
|
-
async _checkBreakpointViaIpc(node) {
|
|
880
|
-
try {
|
|
881
|
-
const { randomUUID } = require('crypto');
|
|
882
|
-
const requestId = randomUUID();
|
|
883
|
-
|
|
884
|
-
const inputs = await this._captureNodeInputs(node);
|
|
885
|
-
const executedSteps = this.currentTraceId
|
|
886
|
-
? await this.traceCollector.getTrace(this.currentTraceId)
|
|
887
|
-
: null;
|
|
888
|
-
|
|
889
|
-
// Отправляем запрос в главный процесс
|
|
890
|
-
process.send({
|
|
891
|
-
type: 'debug:check_breakpoint',
|
|
892
|
-
requestId,
|
|
893
|
-
payload: {
|
|
894
|
-
graphId: this.context.graphId,
|
|
895
|
-
nodeId: node.id,
|
|
896
|
-
nodeType: node.type,
|
|
897
|
-
inputs,
|
|
898
|
-
executedSteps,
|
|
899
|
-
context: {
|
|
900
|
-
user: this.context.user,
|
|
901
|
-
variables: this.context.variables,
|
|
902
|
-
commandArguments: this.context.commandArguments,
|
|
903
|
-
}
|
|
904
|
-
}
|
|
905
|
-
});
|
|
906
|
-
|
|
907
|
-
// Ждём ответа
|
|
908
|
-
const overrides = await new Promise((resolve) => {
|
|
909
|
-
this.pendingDebugRequests.set(requestId, resolve);
|
|
910
|
-
|
|
911
|
-
// Таймаут на случай, если ответ не придёт
|
|
912
|
-
const timeoutId = setTimeout(() => {
|
|
913
|
-
if (this.pendingDebugRequests.has(requestId)) {
|
|
914
|
-
this.pendingDebugRequests.delete(requestId);
|
|
915
|
-
resolve(null);
|
|
916
|
-
}
|
|
917
|
-
}, debugConfig.BREAKPOINT_TIMEOUT);
|
|
918
|
-
|
|
919
|
-
// Сохраняем timeoutId для возможной отмены
|
|
920
|
-
this.pendingDebugRequests.set(`${requestId}_timeout`, timeoutId);
|
|
921
|
-
});
|
|
922
|
-
|
|
923
|
-
if (overrides && overrides.__stopped) {
|
|
924
|
-
throw new Error('Execution stopped by debugger');
|
|
925
|
-
}
|
|
926
|
-
|
|
927
|
-
if (overrides) {
|
|
928
|
-
this.applyWhatIfOverrides(node, overrides);
|
|
929
|
-
}
|
|
930
|
-
} catch (error) {
|
|
931
|
-
if (error.message === 'Execution stopped by debugger') {
|
|
932
|
-
throw error;
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
/**
|
|
938
|
-
* Статический обработчик IPC ответов (глобальный для всех instances)
|
|
939
|
-
*/
|
|
940
|
-
static _handleGlobalDebugResponse(message) {
|
|
941
|
-
const { requestId, overrides } = message;
|
|
942
|
-
const resolve = GraphExecutionEngine._allPendingRequests.get(requestId);
|
|
943
|
-
|
|
944
|
-
if (resolve) {
|
|
945
|
-
// Отменяем таймаут
|
|
946
|
-
const timeoutId = GraphExecutionEngine._allPendingRequests.get(`${requestId}_timeout`);
|
|
947
|
-
if (timeoutId) {
|
|
948
|
-
clearTimeout(timeoutId);
|
|
949
|
-
GraphExecutionEngine._allPendingRequests.delete(`${requestId}_timeout`);
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
GraphExecutionEngine._allPendingRequests.delete(requestId);
|
|
953
|
-
resolve(overrides);
|
|
954
|
-
}
|
|
955
|
-
}
|
|
956
|
-
|
|
957
|
-
/**
|
|
958
|
-
* Обработать IPC ответ от главного процесса (legacy wrapper)
|
|
959
|
-
*/
|
|
960
|
-
_handleDebugResponse(message) {
|
|
961
|
-
GraphExecutionEngine._handleGlobalDebugResponse(message);
|
|
962
|
-
}
|
|
963
|
-
|
|
964
|
-
applyWhatIfOverrides(node, overrides) {
|
|
965
|
-
if (!overrides || typeof overrides !== 'object') return;
|
|
966
|
-
|
|
967
|
-
console.log(`[Debug] Applying what-if overrides to node ${node.id}:`, overrides);
|
|
968
|
-
|
|
969
|
-
for (const [key, value] of Object.entries(overrides)) {
|
|
970
|
-
if (key === '__stopped') continue;
|
|
971
|
-
|
|
972
|
-
// Парсим ключ для определения типа изменения
|
|
973
|
-
// Форматы:
|
|
974
|
-
// - "var.varName" - переменная графа
|
|
975
|
-
// - "nodeId.out.pinName" - выходной пин ноды
|
|
976
|
-
// - "nodeId.in.pinName" - входной пин ноды
|
|
977
|
-
// - "pinName" - входной пин текущей ноды
|
|
978
|
-
|
|
979
|
-
if (key.startsWith('var.')) {
|
|
980
|
-
// Изменение переменной графа
|
|
981
|
-
const varName = key.substring(4);
|
|
982
|
-
if (!this.context.variables) {
|
|
983
|
-
this.context.variables = {};
|
|
984
|
-
}
|
|
985
|
-
this.context.variables[varName] = value;
|
|
986
|
-
console.log(`[Debug] Variable override: ${varName} = ${JSON.stringify(value)}`);
|
|
987
|
-
}
|
|
988
|
-
else if (key.includes('.out.')) {
|
|
989
|
-
// Изменение выходного пина ноды
|
|
990
|
-
const [nodeId, , pinName] = key.split('.');
|
|
991
|
-
const memoKey = `${nodeId}:${pinName}`;
|
|
992
|
-
this.memo.set(memoKey, value);
|
|
993
|
-
console.log(`[Debug] Output override: ${memoKey} = ${JSON.stringify(value)}`);
|
|
994
|
-
}
|
|
995
|
-
else if (key.includes('.in.')) {
|
|
996
|
-
// Изменение входного пина ноды (пока не применяется, но можно расширить)
|
|
997
|
-
const [nodeId, , pinName] = key.split('.');
|
|
998
|
-
console.log(`[Debug] Input override (informational): ${nodeId}.${pinName} = ${JSON.stringify(value)}`);
|
|
999
|
-
// Входы можно изменить через изменение outputs предыдущих нод или переменных
|
|
1000
|
-
}
|
|
1001
|
-
else {
|
|
1002
|
-
// Изменение входного пина текущей ноды
|
|
1003
|
-
if (!node.data) {
|
|
1004
|
-
node.data = {};
|
|
1005
|
-
}
|
|
1006
|
-
node.data[key] = value;
|
|
1007
|
-
}
|
|
1008
|
-
}
|
|
1009
|
-
}
|
|
512
|
+
async _captureAllDataNodeOutputs() {
|
|
513
|
+
if (!this.currentTraceId) return;
|
|
514
|
+
|
|
515
|
+
const trace = await this.traceCollector.getTrace(this.currentTraceId);
|
|
516
|
+
if (!trace || !trace.steps) return;
|
|
517
|
+
|
|
518
|
+
for (const step of trace.steps) {
|
|
519
|
+
if (step.type === 'traversal') continue;
|
|
520
|
+
if (step.outputs && Object.keys(step.outputs).length > 0) continue;
|
|
521
|
+
|
|
522
|
+
const node = this.activeGraph.nodes.find(n => n.id === step.nodeId);
|
|
523
|
+
if (!node) continue;
|
|
524
|
+
|
|
525
|
+
const nodeConfig = this.nodeRegistry.getNodeConfig(node.type);
|
|
526
|
+
if (!nodeConfig) continue;
|
|
527
|
+
|
|
528
|
+
const inputPins = GraphValidation.getNodeInputPins(nodeConfig, node.data);
|
|
529
|
+
if (inputPins.some(p => p && p.type === 'Exec')) continue;
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
const outputs = await this._captureNodeOutputs(node);
|
|
533
|
+
if (outputs && Object.keys(outputs).length > 0) {
|
|
534
|
+
this.traceCollector.updateStepOutputs(this.currentTraceId, node.id, outputs);
|
|
535
|
+
}
|
|
536
|
+
} catch (error) {
|
|
537
|
+
console.error(`[GraphExecutor] Error capturing outputs for data node ${node.id}:`, error);
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
async _captureNodeInputs(node) {
|
|
543
|
+
const inputs = {};
|
|
544
|
+
const nodeConfig = this.nodeRegistry.getNodeConfig(node.type);
|
|
545
|
+
if (!nodeConfig) return inputs;
|
|
546
|
+
|
|
547
|
+
const inputPins = GraphValidation.getNodeInputPins(nodeConfig, node.data);
|
|
548
|
+
|
|
549
|
+
for (const inputPin of inputPins) {
|
|
550
|
+
if (!inputPin || inputPin.type === 'Exec') continue;
|
|
551
|
+
try {
|
|
552
|
+
inputs[inputPin.id] = await this.resolvePinValue(node, inputPin.id);
|
|
553
|
+
} catch (error) {
|
|
554
|
+
inputs[inputPin.id] = '<error capturing>';
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
return inputs;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
async _captureNodeOutputs(node) {
|
|
561
|
+
const outputs = {};
|
|
562
|
+
const nodeConfig = this.nodeRegistry.getNodeConfig(node.type);
|
|
563
|
+
if (!nodeConfig) return outputs;
|
|
564
|
+
|
|
565
|
+
const outputPins = GraphValidation.getNodeOutputPins(nodeConfig, node.data);
|
|
566
|
+
|
|
567
|
+
for (const outputPin of outputPins) {
|
|
568
|
+
if (!outputPin || outputPin.type === 'Exec') continue;
|
|
569
|
+
try {
|
|
570
|
+
outputs[outputPin.id] = await this.evaluateOutputPin(node, outputPin.id);
|
|
571
|
+
} catch (error) {
|
|
572
|
+
outputs[outputPin.id] = '<error capturing>';
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
return outputs;
|
|
576
|
+
}
|
|
1010
577
|
}
|
|
1011
578
|
|
|
1012
579
|
module.exports = GraphExecutionEngine;
|