ultravisor 1.0.2 → 1.0.4

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.
Files changed (121) hide show
  1. package/.claude/launch.json +11 -0
  2. package/.claude/ultravisor-dev-config.json +3 -0
  3. package/.ultravisor.json +426 -0
  4. package/docs/README.md +63 -0
  5. package/package.json +12 -8
  6. package/source/Ultravisor.cjs +22 -3
  7. package/source/cli/Ultravisor-CLIProgram.cjs +35 -23
  8. package/source/cli/commands/Ultravisor-Command-SingleOperation.cjs +29 -18
  9. package/source/cli/commands/Ultravisor-Command-SingleTask.cjs +62 -19
  10. package/source/cli/commands/Ultravisor-Command-UpdateTask.cjs +27 -15
  11. package/source/config/Ultravisor-Default-Command-Configuration.cjs +5 -3
  12. package/source/services/Ultravisor-ExecutionEngine.cjs +1039 -0
  13. package/source/services/Ultravisor-ExecutionManifest.cjs +399 -0
  14. package/source/services/Ultravisor-Hypervisor-State.cjs +270 -97
  15. package/source/services/Ultravisor-Hypervisor.cjs +38 -83
  16. package/source/services/Ultravisor-StateManager.cjs +241 -0
  17. package/source/services/Ultravisor-TaskTypeRegistry.cjs +143 -0
  18. package/source/services/tasks/Ultravisor-TaskType-Base.cjs +105 -0
  19. package/source/services/tasks/control/Ultravisor-TaskType-IfConditional.cjs +148 -0
  20. package/source/services/tasks/control/Ultravisor-TaskType-LaunchOperation.cjs +187 -0
  21. package/source/services/tasks/control/Ultravisor-TaskType-SplitExecute.cjs +184 -0
  22. package/source/services/tasks/data/Ultravisor-TaskType-ReplaceString.cjs +82 -0
  23. package/source/services/tasks/data/Ultravisor-TaskType-SetValues.cjs +81 -0
  24. package/source/services/tasks/data/Ultravisor-TaskType-StringAppender.cjs +101 -0
  25. package/source/services/tasks/file-io/Ultravisor-TaskType-ReadFile.cjs +103 -0
  26. package/source/services/tasks/file-io/Ultravisor-TaskType-WriteFile.cjs +117 -0
  27. package/source/services/tasks/interaction/Ultravisor-TaskType-ErrorMessage.cjs +54 -0
  28. package/source/services/tasks/interaction/Ultravisor-TaskType-ValueInput.cjs +62 -0
  29. package/source/web_server/Ultravisor-API-Server.cjs +237 -124
  30. package/test/Ultravisor_browser_tests.js +2226 -0
  31. package/test/Ultravisor_tests.js +1143 -5830
  32. package/webinterface/css/ultravisor.css +23 -0
  33. package/webinterface/package.json +6 -3
  34. package/webinterface/source/Pict-Application-Ultravisor.js +93 -73
  35. package/webinterface/source/cards/FlowCard-CSVTransform.js +43 -0
  36. package/webinterface/source/cards/FlowCard-Command.js +86 -0
  37. package/webinterface/source/cards/FlowCard-ComprehensionIntersect.js +40 -0
  38. package/webinterface/source/cards/FlowCard-Conditional.js +87 -0
  39. package/webinterface/source/cards/FlowCard-CopyFile.js +55 -0
  40. package/webinterface/source/cards/FlowCard-End.js +29 -0
  41. package/webinterface/source/cards/FlowCard-GetJSON.js +55 -0
  42. package/webinterface/source/cards/FlowCard-GetText.js +54 -0
  43. package/webinterface/source/cards/FlowCard-Histogram.js +176 -0
  44. package/webinterface/source/cards/FlowCard-LaunchOperation.js +82 -0
  45. package/webinterface/source/cards/FlowCard-ListFiles.js +55 -0
  46. package/webinterface/source/cards/FlowCard-MeadowCount.js +44 -0
  47. package/webinterface/source/cards/FlowCard-MeadowCreate.js +44 -0
  48. package/webinterface/source/cards/FlowCard-MeadowDelete.js +45 -0
  49. package/webinterface/source/cards/FlowCard-MeadowRead.js +46 -0
  50. package/webinterface/source/cards/FlowCard-MeadowReads.js +46 -0
  51. package/webinterface/source/cards/FlowCard-MeadowUpdate.js +44 -0
  52. package/webinterface/source/cards/FlowCard-ParseCSV.js +85 -0
  53. package/webinterface/source/cards/FlowCard-ReadJSON.js +54 -0
  54. package/webinterface/source/cards/FlowCard-ReadText.js +54 -0
  55. package/webinterface/source/cards/FlowCard-RestRequest.js +59 -0
  56. package/webinterface/source/cards/FlowCard-SendJSON.js +57 -0
  57. package/webinterface/source/cards/FlowCard-Solver.js +77 -0
  58. package/webinterface/source/cards/FlowCard-Start.js +29 -0
  59. package/webinterface/source/cards/FlowCard-TemplateString.js +77 -0
  60. package/webinterface/source/cards/FlowCard-WriteJSON.js +54 -0
  61. package/webinterface/source/cards/FlowCard-WriteText.js +54 -0
  62. package/webinterface/source/data/ExampleFlow-CSVPipeline.js +231 -0
  63. package/webinterface/source/data/ExampleFlow-FileProcessor.js +315 -0
  64. package/webinterface/source/data/ExampleFlow-MeadowPipeline.js +328 -0
  65. package/webinterface/source/providers/PictRouter-Ultravisor-Configuration.json +8 -8
  66. package/webinterface/source/views/PictView-Ultravisor-Dashboard.js +6 -6
  67. package/webinterface/source/views/PictView-Ultravisor-FlowEditor.js +436 -0
  68. package/webinterface/source/views/PictView-Ultravisor-ManifestList.js +45 -43
  69. package/webinterface/source/views/PictView-Ultravisor-OperationEdit.js +34 -89
  70. package/webinterface/source/views/PictView-Ultravisor-OperationList.js +128 -13
  71. package/webinterface/source/views/PictView-Ultravisor-PendingInput.js +314 -0
  72. package/webinterface/source/views/PictView-Ultravisor-Schedule.js +18 -53
  73. package/webinterface/source/views/PictView-Ultravisor-TimingView.js +27 -14
  74. package/webinterface/source/views/PictView-Ultravisor-TopBar.js +2 -1
  75. package/.babelrc +0 -6
  76. package/.browserslistrc +0 -1
  77. package/.browserslistrc-BACKUP +0 -1
  78. package/.gulpfile-quackage-config.json +0 -7
  79. package/.gulpfile-quackage.js +0 -2
  80. package/debug/Harness.js +0 -5
  81. package/source/services/Ultravisor-Operation-Manifest.cjs +0 -160
  82. package/source/services/Ultravisor-Operation.cjs +0 -200
  83. package/source/services/Ultravisor-Task.cjs +0 -349
  84. package/source/services/events/Ultravisor-Hypervisor-Event-Solver.cjs +0 -11
  85. package/source/services/tasks/Ultravisor-Task-Base.cjs +0 -264
  86. package/source/services/tasks/Ultravisor-Task-CollectValues.cjs +0 -188
  87. package/source/services/tasks/Ultravisor-Task-Command.cjs +0 -65
  88. package/source/services/tasks/Ultravisor-Task-CommandEach.cjs +0 -190
  89. package/source/services/tasks/Ultravisor-Task-Conditional.cjs +0 -104
  90. package/source/services/tasks/Ultravisor-Task-DateWindow.cjs +0 -72
  91. package/source/services/tasks/Ultravisor-Task-GeneratePagedOperation.cjs +0 -336
  92. package/source/services/tasks/Ultravisor-Task-LaunchOperation.cjs +0 -143
  93. package/source/services/tasks/Ultravisor-Task-LaunchTask.cjs +0 -146
  94. package/source/services/tasks/Ultravisor-Task-LineMatch.cjs +0 -158
  95. package/source/services/tasks/Ultravisor-Task-Request.cjs +0 -56
  96. package/source/services/tasks/Ultravisor-Task-Solver.cjs +0 -89
  97. package/source/services/tasks/Ultravisor-Task-TemplateString.cjs +0 -93
  98. package/source/services/tasks/rest/Ultravisor-Task-GetBinary.cjs +0 -127
  99. package/source/services/tasks/rest/Ultravisor-Task-GetJSON.cjs +0 -119
  100. package/source/services/tasks/rest/Ultravisor-Task-GetText.cjs +0 -109
  101. package/source/services/tasks/rest/Ultravisor-Task-GetXML.cjs +0 -112
  102. package/source/services/tasks/rest/Ultravisor-Task-RestRequest.cjs +0 -499
  103. package/source/services/tasks/rest/Ultravisor-Task-SendJSON.cjs +0 -150
  104. package/source/services/tasks/stagingfiles/Ultravisor-Task-CopyFile.cjs +0 -110
  105. package/source/services/tasks/stagingfiles/Ultravisor-Task-ListFiles.cjs +0 -89
  106. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadBinary.cjs +0 -87
  107. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadJSON.cjs +0 -67
  108. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadText.cjs +0 -66
  109. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadXML.cjs +0 -69
  110. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteBinary.cjs +0 -95
  111. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteJSON.cjs +0 -96
  112. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteText.cjs +0 -99
  113. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteXML.cjs +0 -102
  114. package/webinterface/.babelrc +0 -6
  115. package/webinterface/.browserslistrc +0 -1
  116. package/webinterface/.browserslistrc-BACKUP +0 -1
  117. package/webinterface/.gulpfile-quackage-config.json +0 -7
  118. package/webinterface/.gulpfile-quackage.js +0 -2
  119. package/webinterface/source/views/PictView-Ultravisor-TaskEdit.js +0 -220
  120. package/webinterface/source/views/PictView-Ultravisor-TaskList.js +0 -248
  121. /package/docs/{cover.md → _cover.md} +0 -0
@@ -0,0 +1,1039 @@
1
+ const libPictService = require('pict-serviceproviderbase');
2
+
3
+ /**
4
+ * Event-driven graph executor for Ultravisor operations.
5
+ *
6
+ * Replaces the old sequential task runner. Processes an operation's directed
7
+ * graph by following event connections between task nodes. State connections
8
+ * are resolved just-in-time when a task is triggered.
9
+ */
10
+ class UltravisorExecutionEngine extends libPictService
11
+ {
12
+ constructor(pPict, pOptions, pServiceHash)
13
+ {
14
+ super(pPict, pOptions, pServiceHash);
15
+
16
+ this.serviceType = 'UltravisorExecutionEngine';
17
+ }
18
+
19
+ /**
20
+ * Append a timestamped entry to the execution context log and the fable logger.
21
+ *
22
+ * @param {object} pContext - The execution context.
23
+ * @param {string} pMessage - The log message.
24
+ * @param {string} [pLevel] - Log level: 'info' (default), 'warn', 'error', 'trace'.
25
+ */
26
+ _log(pContext, pMessage, pLevel)
27
+ {
28
+ let tmpLevel = pLevel || 'info';
29
+ pContext.Log.push(`[${new Date().toISOString()}] ${pMessage}`);
30
+ this.log[tmpLevel](`ExecutionEngine [${pContext.OperationHash || '?'}]: ${pMessage}`);
31
+ }
32
+
33
+ /**
34
+ * Execute an operation by its definition.
35
+ *
36
+ * @param {object} pOperationDefinition - The operation definition with Graph.
37
+ * @param {object} [pInitialState] - Optional initial state overrides:
38
+ * GlobalState {object} - seed values for global state
39
+ * OperationState {object} - seed values for operation state
40
+ * RunMode {string} - 'production' | 'standard' | 'debug'
41
+ * @param {function} fCallback - function(pError, pExecutionContext)
42
+ */
43
+ executeOperation(pOperationDefinition, pInitialState, fCallback)
44
+ {
45
+ if (typeof(pInitialState) === 'function')
46
+ {
47
+ fCallback = pInitialState;
48
+ pInitialState = {};
49
+ }
50
+
51
+ if (!pOperationDefinition || !pOperationDefinition.Graph)
52
+ {
53
+ return fCallback(new Error('ExecutionEngine: operation definition must have a Graph.'));
54
+ }
55
+
56
+ let tmpInitialState = pInitialState || {};
57
+
58
+ // Get services
59
+ let tmpManifestService = this.fable.servicesMap['UltravisorExecutionManifest']
60
+ ? Object.values(this.fable.servicesMap['UltravisorExecutionManifest'])[0]
61
+ : null;
62
+
63
+ if (!tmpManifestService)
64
+ {
65
+ return fCallback(new Error('ExecutionEngine: UltravisorExecutionManifest service not found.'));
66
+ }
67
+
68
+ // Create execution context with staging folder
69
+ let tmpContext = tmpManifestService.createExecutionContext(
70
+ pOperationDefinition, tmpInitialState.RunMode);
71
+
72
+ // Seed initial state
73
+ if (tmpInitialState.GlobalState && typeof(tmpInitialState.GlobalState) === 'object')
74
+ {
75
+ Object.assign(tmpContext.GlobalState, tmpInitialState.GlobalState);
76
+ }
77
+ if (tmpInitialState.OperationState && typeof(tmpInitialState.OperationState) === 'object')
78
+ {
79
+ Object.assign(tmpContext.OperationState, tmpInitialState.OperationState);
80
+ }
81
+ if (pOperationDefinition.InitialGlobalState && typeof(pOperationDefinition.InitialGlobalState) === 'object')
82
+ {
83
+ // Operation-level defaults (overridden by runtime initial state)
84
+ let tmpKeys = Object.keys(pOperationDefinition.InitialGlobalState);
85
+ for (let i = 0; i < tmpKeys.length; i++)
86
+ {
87
+ if (!tmpContext.GlobalState.hasOwnProperty(tmpKeys[i]))
88
+ {
89
+ tmpContext.GlobalState[tmpKeys[i]] = pOperationDefinition.InitialGlobalState[tmpKeys[i]];
90
+ }
91
+ }
92
+ }
93
+ if (pOperationDefinition.InitialOperationState && typeof(pOperationDefinition.InitialOperationState) === 'object')
94
+ {
95
+ let tmpKeys = Object.keys(pOperationDefinition.InitialOperationState);
96
+ for (let i = 0; i < tmpKeys.length; i++)
97
+ {
98
+ if (!tmpContext.OperationState.hasOwnProperty(tmpKeys[i]))
99
+ {
100
+ tmpContext.OperationState[tmpKeys[i]] = pOperationDefinition.InitialOperationState[tmpKeys[i]];
101
+ }
102
+ }
103
+ }
104
+
105
+ // Store the graph for lookup
106
+ tmpContext._Graph = pOperationDefinition.Graph;
107
+ tmpContext._NodeMap = this._buildNodeMap(pOperationDefinition.Graph);
108
+ tmpContext._PortLabelMap = this._buildPortLabelMap(pOperationDefinition.Graph);
109
+ tmpContext._ConnectionMap = this._buildConnectionMap(
110
+ pOperationDefinition.Graph, tmpContext._NodeMap, tmpContext._PortLabelMap);
111
+
112
+ // Mark as running
113
+ tmpContext.Status = 'Running';
114
+ tmpContext.StartTime = new Date().toISOString();
115
+ this._log(tmpContext, `Operation [${pOperationDefinition.Hash}] started.`);
116
+
117
+ // Find Start node and enqueue its output events
118
+ let tmpStartNode = this._findStartNode(tmpContext);
119
+
120
+ if (!tmpStartNode)
121
+ {
122
+ tmpContext.Status = 'Error';
123
+ this._log(tmpContext, 'No Start node found in the graph.');
124
+ tmpManifestService.finalizeExecution(tmpContext);
125
+ return fCallback(new Error('No Start node found in the graph.'), tmpContext);
126
+ }
127
+
128
+ // Fire all outgoing event connections from the Start node.
129
+ // Start nodes have a single output, so we enqueue all downstream targets
130
+ // without filtering on port name (avoids label vs hash naming mismatches).
131
+ this._enqueueAllDownstreamEvents(tmpStartNode.Hash, tmpContext);
132
+
133
+ // Process the event queue
134
+ this._processEventQueue(tmpContext,
135
+ (pError) =>
136
+ {
137
+ if (pError)
138
+ {
139
+ this._log(tmpContext, `Execution error: ${pError.message}`, 'error');
140
+ tmpContext.Errors.push({
141
+ NodeHash: null,
142
+ Message: pError.message,
143
+ Timestamp: new Date().toISOString()
144
+ });
145
+ }
146
+
147
+ tmpManifestService.finalizeExecution(tmpContext);
148
+ return fCallback(null, tmpContext);
149
+ });
150
+ }
151
+
152
+ /**
153
+ * Resume a paused operation after a value-input task receives input.
154
+ *
155
+ * @param {string} pRunHash - The execution run hash.
156
+ * @param {string} pNodeHash - The waiting task node hash.
157
+ * @param {*} pValue - The provided value.
158
+ * @param {function} fCallback - function(pError, pExecutionContext)
159
+ */
160
+ resumeOperation(pRunHash, pNodeHash, pValue, fCallback)
161
+ {
162
+ let tmpManifestService = this.fable.servicesMap['UltravisorExecutionManifest']
163
+ ? Object.values(this.fable.servicesMap['UltravisorExecutionManifest'])[0]
164
+ : null;
165
+
166
+ if (!tmpManifestService)
167
+ {
168
+ return fCallback(new Error('ExecutionEngine: UltravisorExecutionManifest service not found.'));
169
+ }
170
+
171
+ let tmpContext = tmpManifestService.getRun(pRunHash);
172
+
173
+ if (!tmpContext)
174
+ {
175
+ return fCallback(new Error(`ExecutionEngine: run [${pRunHash}] not found.`));
176
+ }
177
+
178
+ if (tmpContext.Status !== 'WaitingForInput')
179
+ {
180
+ return fCallback(new Error(`ExecutionEngine: run [${pRunHash}] is not waiting for input (status: ${tmpContext.Status}).`));
181
+ }
182
+
183
+ let tmpWaitingInfo = tmpContext.WaitingTasks[pNodeHash];
184
+
185
+ if (!tmpWaitingInfo)
186
+ {
187
+ return fCallback(new Error(`ExecutionEngine: node [${pNodeHash}] is not waiting for input.`));
188
+ }
189
+
190
+ // Get the StateManager to write the value
191
+ let tmpStateManager = this._getStateManager();
192
+
193
+ // Write the value to the specified output address
194
+ if (tmpWaitingInfo.OutputAddress)
195
+ {
196
+ tmpStateManager.setAddress(tmpWaitingInfo.OutputAddress, pValue, tmpContext, pNodeHash);
197
+ }
198
+
199
+ // Also store in task outputs
200
+ if (!tmpContext.TaskOutputs[pNodeHash])
201
+ {
202
+ tmpContext.TaskOutputs[pNodeHash] = {};
203
+ }
204
+ tmpContext.TaskOutputs[pNodeHash].InputValue = pValue;
205
+
206
+ // Remove from waiting list
207
+ delete tmpContext.WaitingTasks[pNodeHash];
208
+
209
+ // Fire the completion event
210
+ tmpContext.Status = 'Running';
211
+ this._log(tmpContext, `Value input received for node [${pNodeHash}], resuming execution.`);
212
+
213
+ this._enqueueDownstreamEvents(pNodeHash, 'ValueInputComplete', tmpContext);
214
+
215
+ // Process the event queue
216
+ this._processEventQueue(tmpContext,
217
+ (pError) =>
218
+ {
219
+ if (pError)
220
+ {
221
+ this._log(tmpContext, `Execution error after resume: ${pError.message}`, 'error');
222
+ }
223
+ tmpManifestService.finalizeExecution(tmpContext);
224
+ return fCallback(null, tmpContext);
225
+ });
226
+ }
227
+
228
+ // ====================================================================
229
+ // Internal: Event Queue Processing
230
+ // ====================================================================
231
+
232
+ /**
233
+ * Process events from the queue until it is empty.
234
+ *
235
+ * @param {object} pContext - The execution context.
236
+ * @param {function} fCallback - Called when queue is empty or operation is paused.
237
+ */
238
+ _processEventQueue(pContext, fCallback)
239
+ {
240
+ if (pContext.PendingEvents.length === 0)
241
+ {
242
+ // Check if we're waiting for input
243
+ if (Object.keys(pContext.WaitingTasks).length > 0)
244
+ {
245
+ pContext.Status = 'WaitingForInput';
246
+ this._log(pContext, 'Operation paused: waiting for user input.');
247
+ }
248
+ return fCallback(null);
249
+ }
250
+
251
+ // Dequeue the next event
252
+ let tmpEvent = pContext.PendingEvents.shift();
253
+
254
+ this._executeTaskForEvent(tmpEvent.TargetNodeHash, tmpEvent.EventName, pContext,
255
+ (pError) =>
256
+ {
257
+ if (pError)
258
+ {
259
+ this._log(pContext, `Error processing event [${tmpEvent.EventName}] on node [${tmpEvent.TargetNodeHash}]: ${pError.message}`, 'error');
260
+ // Continue processing other events despite errors
261
+ }
262
+
263
+ // Recurse to process next event
264
+ this._processEventQueue(pContext, fCallback);
265
+ });
266
+ }
267
+
268
+ /**
269
+ * Execute a task node in response to an incoming event.
270
+ *
271
+ * @param {string} pNodeHash - The target node hash.
272
+ * @param {string} pEventName - The event that triggered this execution.
273
+ * @param {object} pContext - The execution context.
274
+ * @param {function} fCallback - Called when task execution is complete.
275
+ */
276
+ _executeTaskForEvent(pNodeHash, pEventName, pContext, fCallback)
277
+ {
278
+ let tmpNode = pContext._NodeMap[pNodeHash];
279
+
280
+ if (!tmpNode)
281
+ {
282
+ this._log(pContext, `Node [${pNodeHash}] not found in graph.`, 'error');
283
+ return fCallback(null);
284
+ }
285
+
286
+ // Handle built-in End node
287
+ if (tmpNode.Type === 'end')
288
+ {
289
+ this._log(pContext, `Reached End node [${pNodeHash}].`);
290
+ return fCallback(null);
291
+ }
292
+
293
+ // Handle built-in Start node (shouldn't be a target, but handle gracefully)
294
+ if (tmpNode.Type === 'start')
295
+ {
296
+ return fCallback(null);
297
+ }
298
+
299
+ // Find the task type
300
+ let tmpRegistry = this._getTaskTypeRegistry();
301
+
302
+ if (!tmpRegistry)
303
+ {
304
+ return fCallback(new Error('TaskTypeRegistry service not found.'));
305
+ }
306
+
307
+ let tmpDefinitionHash = tmpNode.DefinitionHash || tmpNode.Type;
308
+ let tmpDefinition = tmpRegistry.getDefinition(tmpDefinitionHash);
309
+
310
+ if (!tmpDefinition)
311
+ {
312
+ this._log(pContext, `Unknown task type [${tmpDefinitionHash}] for node [${pNodeHash}].`, 'error');
313
+ return fCallback(null);
314
+ }
315
+
316
+ // Resolve incoming state connections
317
+ let tmpResolvedSettings = this._resolveStateConnections(pNodeHash, tmpNode, pContext);
318
+
319
+ // Get the manifest service for recording
320
+ let tmpManifestService = this._getManifestService();
321
+ if (tmpManifestService)
322
+ {
323
+ tmpManifestService.recordTaskStart(pContext, pNodeHash, pEventName);
324
+ }
325
+
326
+ this._log(pContext, `Executing node [${pNodeHash}] (${tmpDefinition.Name}) triggered by [${pEventName}]`);
327
+
328
+ // Create task instance and execute
329
+ let tmpTaskInstance = tmpRegistry.instantiateTaskType(tmpDefinitionHash);
330
+
331
+ if (!tmpTaskInstance)
332
+ {
333
+ this._log(pContext, `Failed to instantiate task type [${tmpDefinitionHash}].`, 'error');
334
+ return fCallback(null);
335
+ }
336
+
337
+ // Build the per-task execution context
338
+ let tmpTaskContext = {
339
+ GlobalState: pContext.GlobalState,
340
+ OperationState: pContext.OperationState,
341
+ TaskOutputs: pContext.TaskOutputs,
342
+ StagingPath: pContext.StagingPath,
343
+ OperationHash: pContext.OperationHash,
344
+ NodeHash: pNodeHash,
345
+ RunHash: pContext.Hash,
346
+ RunMode: pContext.RunMode,
347
+ StateManager: this._getStateManager(),
348
+ TriggeringEventName: pEventName
349
+ };
350
+
351
+ // Build the fFireIntermediateEvent function for re-entrant tasks
352
+ let fFireIntermediateEvent = (pIntermediateEventName, pIntermediateOutputs, fResumeCallback) =>
353
+ {
354
+ // Store the intermediate outputs
355
+ if (!pContext.TaskOutputs[pNodeHash])
356
+ {
357
+ pContext.TaskOutputs[pNodeHash] = {};
358
+ }
359
+ Object.assign(pContext.TaskOutputs[pNodeHash], pIntermediateOutputs);
360
+
361
+ // Find downstream nodes for this intermediate event
362
+ let tmpDownstreamEvents = this._getDownstreamEvents(pNodeHash, pIntermediateEventName, pContext);
363
+
364
+ if (tmpDownstreamEvents.length === 0)
365
+ {
366
+ // No downstream connections for this event
367
+ return fResumeCallback();
368
+ }
369
+
370
+ // Process the downstream sub-graph synchronously
371
+ let tmpSubIndex = 0;
372
+
373
+ let fProcessNextDownstream = () =>
374
+ {
375
+ if (tmpSubIndex >= tmpDownstreamEvents.length)
376
+ {
377
+ return fResumeCallback();
378
+ }
379
+
380
+ let tmpDownstreamEvent = tmpDownstreamEvents[tmpSubIndex];
381
+ tmpSubIndex++;
382
+
383
+ this._executeTaskForEvent(tmpDownstreamEvent.TargetNodeHash, tmpDownstreamEvent.EventName, pContext,
384
+ (pError) =>
385
+ {
386
+ if (pError)
387
+ {
388
+ this._log(pContext, `Error in sub-graph: ${pError.message}`, 'error');
389
+ }
390
+
391
+ // Also process any events that were enqueued during sub-graph execution
392
+ this._drainEventsForSubgraph(pContext, () =>
393
+ {
394
+ fProcessNextDownstream();
395
+ });
396
+ });
397
+ };
398
+
399
+ fProcessNextDownstream();
400
+ };
401
+
402
+ // Execute the task
403
+ tmpTaskInstance.execute(tmpResolvedSettings, tmpTaskContext, (pError, pResult) =>
404
+ {
405
+ if (pError)
406
+ {
407
+ this._log(pContext, `Task [${pNodeHash}] error: ${pError.message}`, 'error');
408
+ if (tmpManifestService)
409
+ {
410
+ tmpManifestService.recordTaskError(pContext, pNodeHash, pError);
411
+ }
412
+
413
+ // Fire error event if the task has one
414
+ this._enqueueDownstreamEvents(pNodeHash, 'Error', pContext);
415
+ return fCallback(null);
416
+ }
417
+
418
+ if (!pResult)
419
+ {
420
+ this._log(pContext, `Task [${pNodeHash}] returned no result.`, 'warn');
421
+ return fCallback(null);
422
+ }
423
+
424
+ // Check for WaitingForInput (value-input task)
425
+ if (pResult.WaitingForInput)
426
+ {
427
+ pContext.WaitingTasks[pNodeHash] = {
428
+ PromptMessage: pResult.PromptMessage || '',
429
+ OutputAddress: pResult.OutputAddress || '',
430
+ Timestamp: new Date().toISOString()
431
+ };
432
+ this._log(pContext, `Task [${pNodeHash}] is waiting for user input.`);
433
+ if (tmpManifestService)
434
+ {
435
+ tmpManifestService.recordTaskComplete(pContext, pNodeHash, pResult);
436
+ }
437
+ return fCallback(null);
438
+ }
439
+
440
+ // Store outputs in TaskOutputs
441
+ if (pResult.Outputs && typeof(pResult.Outputs) === 'object')
442
+ {
443
+ if (!pContext.TaskOutputs[pNodeHash])
444
+ {
445
+ pContext.TaskOutputs[pNodeHash] = {};
446
+ }
447
+ Object.assign(pContext.TaskOutputs[pNodeHash], pResult.Outputs);
448
+ }
449
+
450
+ // Store any state writes from the result
451
+ if (pResult.StateWrites && typeof(pResult.StateWrites) === 'object')
452
+ {
453
+ let tmpStateManager = this._getStateManager();
454
+ let tmpWriteKeys = Object.keys(pResult.StateWrites);
455
+ for (let i = 0; i < tmpWriteKeys.length; i++)
456
+ {
457
+ tmpStateManager.setAddress(tmpWriteKeys[i], pResult.StateWrites[tmpWriteKeys[i]],
458
+ pContext, pNodeHash);
459
+ }
460
+ }
461
+
462
+ // Determine if this result is an error event
463
+ let tmpIsErrorResult = false;
464
+ if (pResult.EventToFire && tmpDefinition && Array.isArray(tmpDefinition.EventOutputs))
465
+ {
466
+ for (let e = 0; e < tmpDefinition.EventOutputs.length; e++)
467
+ {
468
+ if (tmpDefinition.EventOutputs[e].Name === pResult.EventToFire
469
+ && tmpDefinition.EventOutputs[e].IsError)
470
+ {
471
+ tmpIsErrorResult = true;
472
+ break;
473
+ }
474
+ }
475
+ }
476
+
477
+ // Log task messages
478
+ if (Array.isArray(pResult.Log))
479
+ {
480
+ let tmpLogLevel = tmpIsErrorResult ? 'error' : 'trace';
481
+ for (let i = 0; i < pResult.Log.length; i++)
482
+ {
483
+ this._log(pContext, ` [${pNodeHash}] ${pResult.Log[i]}`, tmpLogLevel);
484
+ }
485
+ }
486
+
487
+ // Record completion
488
+ if (tmpManifestService)
489
+ {
490
+ tmpManifestService.recordTaskComplete(pContext, pNodeHash, pResult);
491
+ }
492
+
493
+ // Fire the output event (enqueue downstream tasks)
494
+ if (pResult.EventToFire)
495
+ {
496
+ let tmpQueueLenBefore = pContext.PendingEvents.length;
497
+ this._enqueueDownstreamEvents(pNodeHash, pResult.EventToFire, pContext);
498
+ let tmpHandled = pContext.PendingEvents.length > tmpQueueLenBefore;
499
+
500
+ // Record an unhandled error on the context when no downstream
501
+ // error handler is connected.
502
+ if (tmpIsErrorResult && !tmpHandled)
503
+ {
504
+ let tmpErrorMessage = (Array.isArray(pResult.Log) && pResult.Log.length > 0)
505
+ ? pResult.Log.join('; ')
506
+ : `Task [${pNodeHash}] fired error event.`;
507
+ pContext.Errors.push({
508
+ NodeHash: pNodeHash,
509
+ Message: tmpErrorMessage,
510
+ Timestamp: new Date().toISOString()
511
+ });
512
+ }
513
+ }
514
+ else if (tmpIsErrorResult)
515
+ {
516
+ // Error result with no EventToFire — still record the error
517
+ let tmpErrorMessage = (Array.isArray(pResult.Log) && pResult.Log.length > 0)
518
+ ? pResult.Log.join('; ')
519
+ : `Task [${pNodeHash}] fired error event.`;
520
+ pContext.Errors.push({
521
+ NodeHash: pNodeHash,
522
+ Message: tmpErrorMessage,
523
+ Timestamp: new Date().toISOString()
524
+ });
525
+ }
526
+
527
+ return fCallback(null);
528
+ },
529
+ fFireIntermediateEvent);
530
+ }
531
+
532
+ // ====================================================================
533
+ // Internal: State Connection Resolution
534
+ // ====================================================================
535
+
536
+ /**
537
+ * Resolve all incoming state connections for a node, producing merged settings.
538
+ *
539
+ * @param {string} pNodeHash - The target node hash.
540
+ * @param {object} pNode - The node definition from the graph.
541
+ * @param {object} pContext - The execution context.
542
+ * @returns {object} The resolved settings object.
543
+ */
544
+ _resolveStateConnections(pNodeHash, pNode, pContext)
545
+ {
546
+ // Start with a copy of the node's static settings
547
+ let tmpSettings = {};
548
+
549
+ if (pNode.Settings && typeof(pNode.Settings) === 'object')
550
+ {
551
+ tmpSettings = JSON.parse(JSON.stringify(pNode.Settings));
552
+ }
553
+
554
+ // Find all incoming State connections targeting this node
555
+ let tmpStateConnections = pContext._ConnectionMap.stateTargets[pNodeHash] || [];
556
+ let tmpStateManager = this._getStateManager();
557
+ let tmpPortLabelMap = pContext._PortLabelMap;
558
+
559
+ for (let i = 0; i < tmpStateConnections.length; i++)
560
+ {
561
+ let tmpConn = tmpStateConnections[i];
562
+
563
+ // Get the source port name
564
+ let tmpSourcePortName = this._extractPortName(tmpConn.SourcePortHash, tmpPortLabelMap);
565
+ let tmpTargetPortName = this._extractPortName(tmpConn.TargetPortHash, tmpPortLabelMap);
566
+
567
+ // Read the source value from the source node's outputs
568
+ let tmpSourceNodeOutputs = pContext.TaskOutputs[tmpConn.SourceNodeHash] || {};
569
+ let tmpSourceValue = tmpSourceNodeOutputs[tmpSourcePortName];
570
+
571
+ // Apply template if defined
572
+ if (tmpConn.Data && tmpConn.Data.Template && typeof(tmpConn.Data.Template) === 'string')
573
+ {
574
+ let tmpTemplateContext = tmpStateManager.buildTemplateContext(pContext, tmpSourceValue);
575
+ tmpSourceValue = this._resolveTemplate(tmpConn.Data.Template, tmpTemplateContext);
576
+ }
577
+
578
+ // Write the resolved value into settings
579
+ if (tmpTargetPortName && tmpSourceValue !== undefined)
580
+ {
581
+ tmpSettings[tmpTargetPortName] = tmpSourceValue;
582
+ }
583
+ }
584
+
585
+ // Resolve any template expressions in settings values
586
+ // (e.g. "{~D:Record.Operation.InputFilePath~}" -> actual value from state)
587
+ let tmpTemplateContext = tmpStateManager.buildTemplateContext(pContext);
588
+
589
+ let tmpSettingsKeys = Object.keys(tmpSettings);
590
+ for (let i = 0; i < tmpSettingsKeys.length; i++)
591
+ {
592
+ let tmpKey = tmpSettingsKeys[i];
593
+ let tmpVal = tmpSettings[tmpKey];
594
+
595
+ if (typeof(tmpVal) === 'string' && tmpVal.indexOf('{~') >= 0)
596
+ {
597
+ tmpSettings[tmpKey] = this._resolveTemplate(tmpVal, tmpTemplateContext);
598
+ }
599
+ }
600
+
601
+ return tmpSettings;
602
+ }
603
+
604
+ // ====================================================================
605
+ // Internal: Graph Traversal Helpers
606
+ // ====================================================================
607
+
608
+ /**
609
+ * Build a lookup map of nodes keyed by Hash.
610
+ */
611
+ _buildNodeMap(pGraph)
612
+ {
613
+ let tmpMap = {};
614
+ let tmpNodes = pGraph.Nodes || [];
615
+
616
+ for (let i = 0; i < tmpNodes.length; i++)
617
+ {
618
+ let tmpNode = tmpNodes[i];
619
+
620
+ // Normalize flow editor format: "Data" -> "Settings"
621
+ if (!tmpNode.Settings && tmpNode.Data && typeof(tmpNode.Data) === 'object')
622
+ {
623
+ tmpNode.Settings = tmpNode.Data;
624
+ }
625
+
626
+ tmpMap[tmpNode.Hash] = tmpNode;
627
+ }
628
+
629
+ return tmpMap;
630
+ }
631
+
632
+ /**
633
+ * Build connection lookup maps for fast traversal.
634
+ * Creates two indices:
635
+ * eventSources[sourceNodeHash] -> array of connections with ConnectionType='Event'
636
+ * stateTargets[targetNodeHash] -> array of connections with ConnectionType='State'
637
+ *
638
+ * When ConnectionType is not explicitly set (flow editor format), the type is
639
+ * inferred from port hash convention (-eo-/-ei- vs -so-/-si-), node types
640
+ * (start/end are always event), or task type definitions.
641
+ */
642
+ _buildConnectionMap(pGraph, pNodeMap, pPortLabelMap)
643
+ {
644
+ let tmpMap = {
645
+ eventSources: {},
646
+ stateTargets: {}
647
+ };
648
+
649
+ let tmpConnections = pGraph.Connections || [];
650
+
651
+ for (let i = 0; i < tmpConnections.length; i++)
652
+ {
653
+ let tmpConn = tmpConnections[i];
654
+
655
+ // Determine connection type (explicit or inferred)
656
+ let tmpType = tmpConn.ConnectionType
657
+ || this._inferConnectionType(tmpConn, pNodeMap, pPortLabelMap);
658
+
659
+ if (tmpType === 'Event')
660
+ {
661
+ if (!tmpMap.eventSources[tmpConn.SourceNodeHash])
662
+ {
663
+ tmpMap.eventSources[tmpConn.SourceNodeHash] = [];
664
+ }
665
+ tmpMap.eventSources[tmpConn.SourceNodeHash].push(tmpConn);
666
+ }
667
+ else if (tmpType === 'State')
668
+ {
669
+ if (!tmpMap.stateTargets[tmpConn.TargetNodeHash])
670
+ {
671
+ tmpMap.stateTargets[tmpConn.TargetNodeHash] = [];
672
+ }
673
+ tmpMap.stateTargets[tmpConn.TargetNodeHash].push(tmpConn);
674
+ }
675
+ }
676
+
677
+ return tmpMap;
678
+ }
679
+
680
+ /**
681
+ * Find the Start node in the graph.
682
+ */
683
+ _findStartNode(pContext)
684
+ {
685
+ let tmpNodes = pContext._Graph.Nodes || [];
686
+
687
+ for (let i = 0; i < tmpNodes.length; i++)
688
+ {
689
+ if (tmpNodes[i].Type === 'start')
690
+ {
691
+ return tmpNodes[i];
692
+ }
693
+ }
694
+
695
+ return null;
696
+ }
697
+
698
+ /**
699
+ * Enqueue downstream event connections from a source node's output event port.
700
+ *
701
+ * @param {string} pSourceNodeHash - The node firing the event.
702
+ * @param {string} pEventName - The event name (matches the source port name).
703
+ * @param {object} pContext - The execution context.
704
+ */
705
+ /**
706
+ * Enqueue ALL downstream event connections from a source node, ignoring port name.
707
+ * Used for Start nodes which have a single output port.
708
+ */
709
+ _enqueueAllDownstreamEvents(pSourceNodeHash, pContext)
710
+ {
711
+ let tmpConnections = pContext._ConnectionMap.eventSources[pSourceNodeHash] || [];
712
+ let tmpPortLabelMap = pContext._PortLabelMap;
713
+
714
+ for (let i = 0; i < tmpConnections.length; i++)
715
+ {
716
+ let tmpConn = tmpConnections[i];
717
+ let tmpTargetPortName = this._extractPortName(tmpConn.TargetPortHash, tmpPortLabelMap);
718
+ pContext.PendingEvents.push({
719
+ TargetNodeHash: tmpConn.TargetNodeHash,
720
+ EventName: tmpTargetPortName
721
+ });
722
+ }
723
+ }
724
+
725
+ _enqueueDownstreamEvents(pSourceNodeHash, pEventName, pContext)
726
+ {
727
+ let tmpConnections = pContext._ConnectionMap.eventSources[pSourceNodeHash] || [];
728
+ let tmpPortLabelMap = pContext._PortLabelMap;
729
+
730
+ for (let i = 0; i < tmpConnections.length; i++)
731
+ {
732
+ let tmpConn = tmpConnections[i];
733
+ let tmpSourcePortName = this._extractPortName(tmpConn.SourcePortHash, tmpPortLabelMap);
734
+
735
+ if (tmpSourcePortName === pEventName)
736
+ {
737
+ let tmpTargetPortName = this._extractPortName(tmpConn.TargetPortHash, tmpPortLabelMap);
738
+ pContext.PendingEvents.push({
739
+ TargetNodeHash: tmpConn.TargetNodeHash,
740
+ EventName: tmpTargetPortName
741
+ });
742
+ }
743
+ }
744
+ }
745
+
746
+ /**
747
+ * Get downstream event targets for an intermediate event (for sub-graph execution).
748
+ * Returns the targets directly instead of enqueuing them.
749
+ */
750
+ _getDownstreamEvents(pSourceNodeHash, pEventName, pContext)
751
+ {
752
+ let tmpTargets = [];
753
+ let tmpConnections = pContext._ConnectionMap.eventSources[pSourceNodeHash] || [];
754
+ let tmpPortLabelMap = pContext._PortLabelMap;
755
+
756
+ for (let i = 0; i < tmpConnections.length; i++)
757
+ {
758
+ let tmpConn = tmpConnections[i];
759
+ let tmpSourcePortName = this._extractPortName(tmpConn.SourcePortHash, tmpPortLabelMap);
760
+
761
+ if (tmpSourcePortName === pEventName)
762
+ {
763
+ let tmpTargetPortName = this._extractPortName(tmpConn.TargetPortHash, tmpPortLabelMap);
764
+ tmpTargets.push({
765
+ TargetNodeHash: tmpConn.TargetNodeHash,
766
+ EventName: tmpTargetPortName
767
+ });
768
+ }
769
+ }
770
+
771
+ return tmpTargets;
772
+ }
773
+
774
+ /**
775
+ * Drain any events that were enqueued during sub-graph execution.
776
+ * This ensures intermediate event processing completes before
777
+ * the parent task continues.
778
+ */
779
+ _drainEventsForSubgraph(pContext, fCallback)
780
+ {
781
+ if (pContext.PendingEvents.length === 0)
782
+ {
783
+ return fCallback();
784
+ }
785
+
786
+ let tmpEvent = pContext.PendingEvents.shift();
787
+
788
+ this._executeTaskForEvent(tmpEvent.TargetNodeHash, tmpEvent.EventName, pContext,
789
+ (pError) =>
790
+ {
791
+ if (pError)
792
+ {
793
+ this._log(pContext, `Error in sub-graph drain: ${pError.message}`, 'error');
794
+ }
795
+
796
+ this._drainEventsForSubgraph(pContext, fCallback);
797
+ });
798
+ }
799
+
800
+ /**
801
+ * Extract the port name from a port hash.
802
+ *
803
+ * Supports two formats:
804
+ * 1. Programmatic: {NodeHash}-{portTypePrefix}-{Name}
805
+ * e.g. 'TSK-READFILE-001-eo-ReadComplete' -> 'ReadComplete'
806
+ * 2. Flow editor: arbitrary hashes with port Labels stored in the graph
807
+ * e.g. 'fp-read-done' -> looks up Label 'ReadComplete' from PortLabelMap
808
+ *
809
+ * @param {string} pPortHash - The port hash string.
810
+ * @param {object} [pPortLabelMap] - Optional mapping of port hash -> label.
811
+ */
812
+ _extractPortName(pPortHash, pPortLabelMap)
813
+ {
814
+ if (!pPortHash || typeof(pPortHash) !== 'string')
815
+ {
816
+ return '';
817
+ }
818
+
819
+ // First try the standard -eo-/-ei-/-so-/-si- convention
820
+ let tmpPrefixes = ['-ei-', '-eo-', '-si-', '-so-'];
821
+
822
+ for (let i = 0; i < tmpPrefixes.length; i++)
823
+ {
824
+ let tmpIndex = pPortHash.lastIndexOf(tmpPrefixes[i]);
825
+ if (tmpIndex > -1)
826
+ {
827
+ return pPortHash.substring(tmpIndex + tmpPrefixes[i].length);
828
+ }
829
+ }
830
+
831
+ // Then try the port label map (flow editor format)
832
+ if (pPortLabelMap && pPortLabelMap[pPortHash])
833
+ {
834
+ return pPortLabelMap[pPortHash];
835
+ }
836
+
837
+ // Fallback: return the hash as-is
838
+ return pPortHash;
839
+ }
840
+
841
+ // ====================================================================
842
+ // Internal: Template Resolution
843
+ // ====================================================================
844
+
845
+ /**
846
+ * Resolve a template string against a template context object.
847
+ *
848
+ * Uses Pict's parseTemplate for full template support. The template context
849
+ * from StateManager.buildTemplateContext() is passed as the record parameter,
850
+ * which Pict places at `Record` on the root data object.
851
+ *
852
+ * Template addresses use the `Record.` prefix to reach the context:
853
+ * {~D:Record.Value~} -> the source value from the state connection
854
+ * {~D:Record.Global.X~} -> GlobalState.X
855
+ * {~D:Record.Operation.X~} -> OperationState.X
856
+ * {~D:Record.TaskOutput.NodeHash.X~} -> TaskOutputs[NodeHash].X
857
+ * {~D:Record.Staging.Path~} -> StagingPath
858
+ *
859
+ * @param {string} pTemplate - The template string.
860
+ * @param {object} pContext - The template context (from StateManager.buildTemplateContext).
861
+ * @returns {string} The resolved string.
862
+ */
863
+ _resolveTemplate(pTemplate, pContext)
864
+ {
865
+ if (typeof(this.fable.parseTemplate) === 'function')
866
+ {
867
+ return this.fable.parseTemplate(pTemplate, pContext);
868
+ }
869
+
870
+ this.log.warn('ExecutionEngine._resolveTemplate: parseTemplate not available on fable instance. Template expressions will not be resolved.');
871
+ return pTemplate;
872
+ }
873
+
874
+ // ====================================================================
875
+ // Internal: Port and Connection Helpers
876
+ // ====================================================================
877
+
878
+ /**
879
+ * Build a lookup map from port hash -> port Label for all nodes in the graph.
880
+ * Used to resolve port names when hashes don't follow the -eo-/-ei- convention.
881
+ */
882
+ _buildPortLabelMap(pGraph)
883
+ {
884
+ let tmpMap = {};
885
+ let tmpNodes = pGraph.Nodes || [];
886
+
887
+ for (let i = 0; i < tmpNodes.length; i++)
888
+ {
889
+ let tmpPorts = tmpNodes[i].Ports || [];
890
+
891
+ for (let j = 0; j < tmpPorts.length; j++)
892
+ {
893
+ tmpMap[tmpPorts[j].Hash] = tmpPorts[j].Label || tmpPorts[j].Hash;
894
+ }
895
+ }
896
+
897
+ return tmpMap;
898
+ }
899
+
900
+ /**
901
+ * Infer the ConnectionType when not explicitly set on a connection.
902
+ *
903
+ * Uses these heuristics in order:
904
+ * 1. Port hash convention: -eo-/-ei- -> Event, -so-/-si- -> State
905
+ * 2. Start/End node connections are always Event
906
+ * 3. Task type definitions: check if port labels match EventOutputs or StateOutputs
907
+ * 4. Default: Event
908
+ */
909
+ _inferConnectionType(pConn, pNodeMap, pPortLabelMap)
910
+ {
911
+ let tmpSourcePortHash = pConn.SourcePortHash || '';
912
+ let tmpTargetPortHash = pConn.TargetPortHash || '';
913
+
914
+ // Check port hash convention
915
+ if (tmpSourcePortHash.includes('-eo-') || tmpTargetPortHash.includes('-ei-'))
916
+ {
917
+ return 'Event';
918
+ }
919
+ if (tmpSourcePortHash.includes('-so-') || tmpTargetPortHash.includes('-si-'))
920
+ {
921
+ return 'State';
922
+ }
923
+
924
+ // Start and End nodes only have event ports
925
+ let tmpSourceNode = pNodeMap ? pNodeMap[pConn.SourceNodeHash] : null;
926
+ let tmpTargetNode = pNodeMap ? pNodeMap[pConn.TargetNodeHash] : null;
927
+
928
+ if (tmpSourceNode && (tmpSourceNode.Type === 'start' || tmpSourceNode.Type === 'end'))
929
+ {
930
+ return 'Event';
931
+ }
932
+ if (tmpTargetNode && (tmpTargetNode.Type === 'start' || tmpTargetNode.Type === 'end'))
933
+ {
934
+ return 'Event';
935
+ }
936
+
937
+ // Look up port labels and check against task type definitions
938
+ let tmpRegistry = this._getTaskTypeRegistry();
939
+
940
+ if (tmpRegistry && tmpSourceNode)
941
+ {
942
+ let tmpSourceLabel = pPortLabelMap ? (pPortLabelMap[tmpSourcePortHash] || '') : '';
943
+ let tmpDefHash = tmpSourceNode.DefinitionHash || tmpSourceNode.Type;
944
+ let tmpDef = tmpRegistry.getDefinition(tmpDefHash);
945
+
946
+ if (tmpDef)
947
+ {
948
+ if (tmpDef.EventOutputs)
949
+ {
950
+ for (let i = 0; i < tmpDef.EventOutputs.length; i++)
951
+ {
952
+ if (tmpDef.EventOutputs[i].Name === tmpSourceLabel)
953
+ {
954
+ return 'Event';
955
+ }
956
+ }
957
+ }
958
+ if (tmpDef.StateOutputs)
959
+ {
960
+ for (let i = 0; i < tmpDef.StateOutputs.length; i++)
961
+ {
962
+ if (tmpDef.StateOutputs[i].Name === tmpSourceLabel)
963
+ {
964
+ return 'State';
965
+ }
966
+ }
967
+ }
968
+ }
969
+ }
970
+
971
+ // Also check target node to classify
972
+ if (tmpRegistry && tmpTargetNode)
973
+ {
974
+ let tmpTargetLabel = pPortLabelMap ? (pPortLabelMap[tmpTargetPortHash] || '') : '';
975
+ let tmpDefHash = tmpTargetNode.DefinitionHash || tmpTargetNode.Type;
976
+ let tmpDef = tmpRegistry.getDefinition(tmpDefHash);
977
+
978
+ if (tmpDef)
979
+ {
980
+ if (tmpDef.EventInputs)
981
+ {
982
+ for (let i = 0; i < tmpDef.EventInputs.length; i++)
983
+ {
984
+ if (tmpDef.EventInputs[i].Name === tmpTargetLabel)
985
+ {
986
+ return 'Event';
987
+ }
988
+ }
989
+ }
990
+ if (tmpDef.SettingsInputs)
991
+ {
992
+ for (let i = 0; i < tmpDef.SettingsInputs.length; i++)
993
+ {
994
+ if (tmpDef.SettingsInputs[i].Name === tmpTargetLabel)
995
+ {
996
+ return 'State';
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ }
1002
+
1003
+ // Default to Event
1004
+ return 'Event';
1005
+ }
1006
+
1007
+ // ====================================================================
1008
+ // Internal: Service Access
1009
+ // ====================================================================
1010
+
1011
+ _getStateManager()
1012
+ {
1013
+ if (this.fable.servicesMap['UltravisorStateManager'])
1014
+ {
1015
+ return Object.values(this.fable.servicesMap['UltravisorStateManager'])[0];
1016
+ }
1017
+ return null;
1018
+ }
1019
+
1020
+ _getTaskTypeRegistry()
1021
+ {
1022
+ if (this.fable.servicesMap['UltravisorTaskTypeRegistry'])
1023
+ {
1024
+ return Object.values(this.fable.servicesMap['UltravisorTaskTypeRegistry'])[0];
1025
+ }
1026
+ return null;
1027
+ }
1028
+
1029
+ _getManifestService()
1030
+ {
1031
+ if (this.fable.servicesMap['UltravisorExecutionManifest'])
1032
+ {
1033
+ return Object.values(this.fable.servicesMap['UltravisorExecutionManifest'])[0];
1034
+ }
1035
+ return null;
1036
+ }
1037
+ }
1038
+
1039
+ module.exports = UltravisorExecutionEngine;