ultravisor 1.0.3 → 1.0.8

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 (248) hide show
  1. package/.claude/launch.json +11 -0
  2. package/.claude/ultravisor-dev-config.json +3 -0
  3. package/card-audit.json +1545 -0
  4. package/docs/_sidebar.md +17 -0
  5. package/docs/css/docuserve.css +73 -0
  6. package/docs/features/beacon-authentication.md +209 -0
  7. package/docs/features/beacon-providers.md +1299 -0
  8. package/docs/features/beacons.md +530 -0
  9. package/docs/features/capabilities.md +193 -0
  10. package/docs/features/llm-model-setup.md +505 -0
  11. package/docs/features/llm.md +262 -0
  12. package/docs/features/tasks-content-system.md +110 -0
  13. package/docs/features/tasks-data-transform.md +233 -0
  14. package/docs/features/tasks-extension.md +36 -0
  15. package/docs/features/tasks-file-system.md +203 -0
  16. package/docs/features/tasks-flow-control.md +125 -0
  17. package/docs/features/tasks-http-client.md +119 -0
  18. package/docs/features/tasks-llm.md +118 -0
  19. package/docs/features/tasks-meadow-api.md +163 -0
  20. package/docs/features/tasks-user-interaction.md +49 -0
  21. package/docs/features/tasks.md +14 -0
  22. package/docs/index.html +1 -1
  23. package/docs/retold-catalog.json +1 -1
  24. package/docs/retold-keyword-index.json +17374 -5
  25. package/operation-library/api-data-pipeline.json +143 -0
  26. package/operation-library/beacon-remote-command.json +72 -0
  27. package/operation-library/conditional-file-backup.json +143 -0
  28. package/operation-library/config-processor.json +136 -0
  29. package/operation-library/csv-data-analysis.json +228 -0
  30. package/operation-library/csv-to-json.json +160 -0
  31. package/operation-library/directory-inventory.json +128 -0
  32. package/operation-library/expression-calculator.json +114 -0
  33. package/operation-library/file-search-replace.json +175 -0
  34. package/operation-library/git-status-report.json +368 -0
  35. package/operation-library/health-check-ping.json +329 -0
  36. package/operation-library/json-config-merge.json +313 -0
  37. package/operation-library/llm-embedding-tool-use.json +314 -0
  38. package/operation-library/llm-multi-turn-analysis.json +322 -0
  39. package/operation-library/llm-summarize.json +264 -0
  40. package/operation-library/log-line-counter.json +185 -0
  41. package/operation-library/meadow-crud-lifecycle.json +264 -0
  42. package/operation-library/npm-project-validator.json +193 -0
  43. package/operation-library/rest-api-orchestrator.json +218 -0
  44. package/operation-library/shell-system-info.json +137 -0
  45. package/operation-library/simple-echo.json +95 -0
  46. package/operation-library/template-processor.json +299 -0
  47. package/operation-library/text-sanitizer.json +176 -0
  48. package/package.json +12 -8
  49. package/source/Ultravisor.cjs +30 -3
  50. package/source/beacon/Ultravisor-Beacon-CLI.cjs +143 -0
  51. package/source/beacon/Ultravisor-Beacon-CapabilityProvider.cjs +129 -0
  52. package/source/beacon/Ultravisor-Beacon-Client.cjs +568 -0
  53. package/source/beacon/Ultravisor-Beacon-Executor.cjs +500 -0
  54. package/source/beacon/Ultravisor-Beacon-ProviderRegistry.cjs +330 -0
  55. package/source/beacon/providers/Ultravisor-Beacon-Provider-FileSystem.cjs +331 -0
  56. package/source/beacon/providers/Ultravisor-Beacon-Provider-LLM.cjs +966 -0
  57. package/source/beacon/providers/Ultravisor-Beacon-Provider-Shell.cjs +95 -0
  58. package/source/cli/Ultravisor-CLIProgram.cjs +44 -22
  59. package/source/cli/commands/Ultravisor-Command-SingleOperation.cjs +29 -18
  60. package/source/cli/commands/Ultravisor-Command-SingleTask.cjs +62 -19
  61. package/source/cli/commands/Ultravisor-Command-UpdateTask.cjs +27 -15
  62. package/source/config/Ultravisor-Default-Command-Configuration.cjs +12 -3
  63. package/source/services/Ultravisor-Beacon-Coordinator.cjs +966 -0
  64. package/source/services/Ultravisor-ExecutionEngine.cjs +1093 -0
  65. package/source/services/Ultravisor-ExecutionManifest.cjs +629 -0
  66. package/source/services/Ultravisor-Hypervisor-State.cjs +270 -97
  67. package/source/services/Ultravisor-Hypervisor.cjs +185 -75
  68. package/source/services/Ultravisor-Schedule-Persistence-Base.cjs +45 -0
  69. package/source/services/Ultravisor-StateManager.cjs +253 -0
  70. package/source/services/Ultravisor-TaskTypeRegistry.cjs +213 -0
  71. package/source/services/persistence/Ultravisor-Schedule-Persistence-JSONFile.cjs +140 -0
  72. package/source/services/tasks/Ultravisor-BuiltIn-TaskConfigs.cjs +19 -0
  73. package/source/services/tasks/Ultravisor-TaskHelper-BeaconDispatch.cjs +107 -0
  74. package/source/services/tasks/Ultravisor-TaskType-Base.cjs +125 -0
  75. package/source/services/tasks/content-system/Ultravisor-TaskConfigs-ContentSystem.cjs +90 -0
  76. package/source/services/tasks/content-system/definitions/content-create-folder.json +24 -0
  77. package/source/services/tasks/content-system/definitions/content-list-files.json +27 -0
  78. package/source/services/tasks/content-system/definitions/content-read-file.json +26 -0
  79. package/source/services/tasks/content-system/definitions/content-save-file.json +26 -0
  80. package/source/services/tasks/data-transform/Ultravisor-TaskConfigs-DataTransform.cjs +577 -0
  81. package/source/services/tasks/data-transform/Ultravisor-TaskType-ReplaceString.cjs +80 -0
  82. package/source/services/tasks/data-transform/Ultravisor-TaskType-SetValues.cjs +74 -0
  83. package/source/services/tasks/data-transform/Ultravisor-TaskType-StringAppender.cjs +90 -0
  84. package/source/services/tasks/data-transform/definitions/comprehension-intersect.json +24 -0
  85. package/source/services/tasks/data-transform/definitions/csv-transform.json +24 -0
  86. package/source/services/tasks/data-transform/definitions/expression-solver.json +20 -0
  87. package/source/services/tasks/data-transform/definitions/histogram.json +23 -0
  88. package/source/services/tasks/data-transform/definitions/parse-csv.json +27 -0
  89. package/source/services/tasks/data-transform/definitions/replace-string.json +27 -0
  90. package/source/services/tasks/data-transform/definitions/set-values.json +17 -0
  91. package/source/services/tasks/data-transform/definitions/string-appender.json +22 -0
  92. package/source/services/tasks/data-transform/definitions/template-string.json +20 -0
  93. package/source/services/tasks/extension/Ultravisor-TaskConfigs-Extension.cjs +87 -0
  94. package/source/services/tasks/extension/definitions/beacon-dispatch.json +32 -0
  95. package/source/services/tasks/file-system/Ultravisor-TaskConfigs-FileSystem.cjs +509 -0
  96. package/source/services/tasks/file-system/Ultravisor-TaskType-ReadFile.cjs +96 -0
  97. package/source/services/tasks/file-system/Ultravisor-TaskType-ReadFileBuffered.cjs +123 -0
  98. package/source/services/tasks/file-system/Ultravisor-TaskType-WriteFile.cjs +106 -0
  99. package/source/services/tasks/file-system/definitions/copy-file.json +27 -0
  100. package/source/services/tasks/file-system/definitions/list-files.json +27 -0
  101. package/source/services/tasks/file-system/definitions/read-file-buffered.json +31 -0
  102. package/source/services/tasks/file-system/definitions/read-file.json +26 -0
  103. package/source/services/tasks/file-system/definitions/read-json.json +23 -0
  104. package/source/services/tasks/file-system/definitions/write-file.json +29 -0
  105. package/source/services/tasks/file-system/definitions/write-json.json +30 -0
  106. package/source/services/tasks/flow-control/Ultravisor-TaskConfigs-FlowControl.cjs +353 -0
  107. package/source/services/tasks/flow-control/Ultravisor-TaskType-IfConditional.cjs +125 -0
  108. package/source/services/tasks/flow-control/Ultravisor-TaskType-LaunchOperation.cjs +186 -0
  109. package/source/services/tasks/flow-control/Ultravisor-TaskType-SplitExecute.cjs +164 -0
  110. package/source/services/tasks/flow-control/definitions/if-conditional.json +25 -0
  111. package/source/services/tasks/flow-control/definitions/launch-operation.json +27 -0
  112. package/source/services/tasks/flow-control/definitions/split-execute.json +32 -0
  113. package/source/services/tasks/http-client/Ultravisor-TaskConfigs-HttpClient.cjs +281 -0
  114. package/source/services/tasks/http-client/definitions/get-json.json +26 -0
  115. package/source/services/tasks/http-client/definitions/get-text.json +26 -0
  116. package/source/services/tasks/http-client/definitions/rest-request.json +32 -0
  117. package/source/services/tasks/http-client/definitions/send-json.json +28 -0
  118. package/source/services/tasks/llm/Ultravisor-TaskConfigs-LLM.cjs +605 -0
  119. package/source/services/tasks/llm/definitions/llm-chat-completion.json +46 -0
  120. package/source/services/tasks/llm/definitions/llm-embedding.json +31 -0
  121. package/source/services/tasks/llm/definitions/llm-tool-use.json +42 -0
  122. package/source/services/tasks/meadow-api/Ultravisor-TaskConfigs-MeadowApi.cjs +341 -0
  123. package/source/services/tasks/meadow-api/definitions/meadow-count.json +26 -0
  124. package/source/services/tasks/meadow-api/definitions/meadow-create.json +26 -0
  125. package/source/services/tasks/meadow-api/definitions/meadow-delete.json +23 -0
  126. package/source/services/tasks/meadow-api/definitions/meadow-read.json +26 -0
  127. package/source/services/tasks/meadow-api/definitions/meadow-reads.json +29 -0
  128. package/source/services/tasks/meadow-api/definitions/meadow-update.json +26 -0
  129. package/source/services/tasks/shell/Ultravisor-TaskConfigs-Shell.cjs +63 -0
  130. package/source/services/tasks/shell/definitions/command.json +29 -0
  131. package/source/services/tasks/user-interaction/Ultravisor-TaskConfigs-UserInteraction.cjs +64 -0
  132. package/source/services/tasks/user-interaction/Ultravisor-TaskType-ErrorMessage.cjs +55 -0
  133. package/source/services/tasks/user-interaction/Ultravisor-TaskType-ValueInput.cjs +47 -0
  134. package/source/services/tasks/user-interaction/definitions/error-message.json +18 -0
  135. package/source/services/tasks/user-interaction/definitions/value-input.json +23 -0
  136. package/source/web_server/Ultravisor-API-Server.cjs +849 -124
  137. package/test/Ultravisor_browser_tests.js +2226 -0
  138. package/test/Ultravisor_operation_library_tests.js +894 -0
  139. package/test/Ultravisor_tests.js +4946 -5044
  140. package/test/workflows/test-content-concatenate-all.json +339 -0
  141. package/test/workflows/test-content-full-pipeline.json +159 -0
  142. package/test/workflows/test-content-list-files.json +76 -0
  143. package/test/workflows/test-content-save-and-read.json +106 -0
  144. package/webinterface/css/ultravisor-themes.css +668 -0
  145. package/webinterface/css/ultravisor.css +37 -14
  146. package/webinterface/docs/card-help/beacon-dispatch.md +30 -0
  147. package/webinterface/docs/card-help/command.md +27 -0
  148. package/webinterface/docs/card-help/comprehension-intersect.md +24 -0
  149. package/webinterface/docs/card-help/content-create-folder.md +22 -0
  150. package/webinterface/docs/card-help/content-list-files.md +25 -0
  151. package/webinterface/docs/card-help/content-read-file.md +24 -0
  152. package/webinterface/docs/card-help/content-save-file.md +24 -0
  153. package/webinterface/docs/card-help/copy-file.md +25 -0
  154. package/webinterface/docs/card-help/csv-transform.md +24 -0
  155. package/webinterface/docs/card-help/end.md +11 -0
  156. package/webinterface/docs/card-help/error-message.md +16 -0
  157. package/webinterface/docs/card-help/expression-solver.md +21 -0
  158. package/webinterface/docs/card-help/get-json.md +24 -0
  159. package/webinterface/docs/card-help/get-text.md +24 -0
  160. package/webinterface/docs/card-help/histogram.md +23 -0
  161. package/webinterface/docs/card-help/if-conditional.md +23 -0
  162. package/webinterface/docs/card-help/launch-operation.md +25 -0
  163. package/webinterface/docs/card-help/list-files.md +25 -0
  164. package/webinterface/docs/card-help/llm-chat-completion.md +43 -0
  165. package/webinterface/docs/card-help/llm-embedding.md +24 -0
  166. package/webinterface/docs/card-help/llm-tool-use.md +39 -0
  167. package/webinterface/docs/card-help/meadow-count.md +24 -0
  168. package/webinterface/docs/card-help/meadow-create.md +24 -0
  169. package/webinterface/docs/card-help/meadow-delete.md +19 -0
  170. package/webinterface/docs/card-help/meadow-read.md +24 -0
  171. package/webinterface/docs/card-help/meadow-reads.md +27 -0
  172. package/webinterface/docs/card-help/meadow-update.md +24 -0
  173. package/webinterface/docs/card-help/parse-csv.md +27 -0
  174. package/webinterface/docs/card-help/read-file-buffered.md +29 -0
  175. package/webinterface/docs/card-help/read-file.md +24 -0
  176. package/webinterface/docs/card-help/read-json.md +21 -0
  177. package/webinterface/docs/card-help/replace-string.md +25 -0
  178. package/webinterface/docs/card-help/rest-request.md +30 -0
  179. package/webinterface/docs/card-help/send-json.md +26 -0
  180. package/webinterface/docs/card-help/set-values.md +16 -0
  181. package/webinterface/docs/card-help/split-execute.md +35 -0
  182. package/webinterface/docs/card-help/start.md +11 -0
  183. package/webinterface/docs/card-help/string-appender.md +22 -0
  184. package/webinterface/docs/card-help/template-string.md +21 -0
  185. package/webinterface/docs/card-help/value-input.md +24 -0
  186. package/webinterface/docs/card-help/write-file.md +27 -0
  187. package/webinterface/docs/card-help/write-json.md +28 -0
  188. package/webinterface/html/index.html +5 -0
  189. package/webinterface/package.json +13 -4
  190. package/webinterface/source/Pict-Application-Ultravisor.js +564 -70
  191. package/webinterface/source/Ultravisor-TimingUtils.js +99 -0
  192. package/webinterface/source/card-help-content.js +46 -0
  193. package/webinterface/source/cards/Ultravisor-BuiltIn-CardConfigs.js +174 -0
  194. package/webinterface/source/cards/Ultravisor-CardConfigGenerator.js +431 -0
  195. package/webinterface/source/data/ExampleFlow-CSVPipeline.js +231 -0
  196. package/webinterface/source/data/ExampleFlow-FileProcessor.js +315 -0
  197. package/webinterface/source/data/ExampleFlow-MeadowPipeline.js +328 -0
  198. package/webinterface/source/panels/Ultravisor-CardSettingsPanel.js +902 -0
  199. package/webinterface/source/providers/PictRouter-Ultravisor-Configuration.json +20 -8
  200. package/webinterface/source/views/PictView-Ultravisor-BeaconList.js +817 -0
  201. package/webinterface/source/views/PictView-Ultravisor-BottomBar.js +5 -5
  202. package/webinterface/source/views/PictView-Ultravisor-Dashboard.js +25 -25
  203. package/webinterface/source/views/PictView-Ultravisor-Documentation.js +856 -0
  204. package/webinterface/source/views/PictView-Ultravisor-FlowEditor.js +777 -0
  205. package/webinterface/source/views/PictView-Ultravisor-ManifestList.js +321 -58
  206. package/webinterface/source/views/PictView-Ultravisor-OperationEdit.js +36 -91
  207. package/webinterface/source/views/PictView-Ultravisor-OperationList.js +388 -16
  208. package/webinterface/source/views/PictView-Ultravisor-PendingInput.js +314 -0
  209. package/webinterface/source/views/PictView-Ultravisor-Schedule.js +521 -65
  210. package/webinterface/source/views/PictView-Ultravisor-TimingView.js +333 -71
  211. package/webinterface/source/views/PictView-Ultravisor-TopBar.js +257 -21
  212. package/webinterface/theme-sampler.html +645 -0
  213. package/debug/Harness.js +0 -6
  214. package/source/services/Ultravisor-Operation-Manifest.cjs +0 -160
  215. package/source/services/Ultravisor-Operation.cjs +0 -200
  216. package/source/services/Ultravisor-Task.cjs +0 -349
  217. package/source/services/events/Ultravisor-Hypervisor-Event-Solver.cjs +0 -11
  218. package/source/services/tasks/Ultravisor-Task-Base.cjs +0 -264
  219. package/source/services/tasks/Ultravisor-Task-CollectValues.cjs +0 -188
  220. package/source/services/tasks/Ultravisor-Task-Command.cjs +0 -65
  221. package/source/services/tasks/Ultravisor-Task-CommandEach.cjs +0 -190
  222. package/source/services/tasks/Ultravisor-Task-Conditional.cjs +0 -104
  223. package/source/services/tasks/Ultravisor-Task-DateWindow.cjs +0 -72
  224. package/source/services/tasks/Ultravisor-Task-GeneratePagedOperation.cjs +0 -336
  225. package/source/services/tasks/Ultravisor-Task-LaunchOperation.cjs +0 -143
  226. package/source/services/tasks/Ultravisor-Task-LaunchTask.cjs +0 -146
  227. package/source/services/tasks/Ultravisor-Task-LineMatch.cjs +0 -158
  228. package/source/services/tasks/Ultravisor-Task-Request.cjs +0 -56
  229. package/source/services/tasks/Ultravisor-Task-Solver.cjs +0 -89
  230. package/source/services/tasks/Ultravisor-Task-TemplateString.cjs +0 -93
  231. package/source/services/tasks/rest/Ultravisor-Task-GetBinary.cjs +0 -127
  232. package/source/services/tasks/rest/Ultravisor-Task-GetJSON.cjs +0 -119
  233. package/source/services/tasks/rest/Ultravisor-Task-GetText.cjs +0 -109
  234. package/source/services/tasks/rest/Ultravisor-Task-GetXML.cjs +0 -112
  235. package/source/services/tasks/rest/Ultravisor-Task-RestRequest.cjs +0 -499
  236. package/source/services/tasks/rest/Ultravisor-Task-SendJSON.cjs +0 -150
  237. package/source/services/tasks/stagingfiles/Ultravisor-Task-CopyFile.cjs +0 -110
  238. package/source/services/tasks/stagingfiles/Ultravisor-Task-ListFiles.cjs +0 -89
  239. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadBinary.cjs +0 -87
  240. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadJSON.cjs +0 -67
  241. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadText.cjs +0 -66
  242. package/source/services/tasks/stagingfiles/Ultravisor-Task-ReadXML.cjs +0 -69
  243. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteBinary.cjs +0 -95
  244. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteJSON.cjs +0 -96
  245. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteText.cjs +0 -99
  246. package/source/services/tasks/stagingfiles/Ultravisor-Task-WriteXML.cjs +0 -102
  247. package/webinterface/source/views/PictView-Ultravisor-TaskEdit.js +0 -220
  248. package/webinterface/source/views/PictView-Ultravisor-TaskList.js +0 -248
@@ -1,8 +1,10 @@
1
1
  const libPictService = require(`pict-serviceproviderbase`);
2
2
 
3
+ const libFS = require('fs');
3
4
  const libPath = require('path');
4
5
  const libOrator = require('orator');
5
6
  const libOratorServiceServerRestify = require(`orator-serviceserver-restify`);
7
+ const libOratorAuthentication = require('orator-authentication');
6
8
 
7
9
  class UltravisorAPIServer extends libPictService
8
10
  {
@@ -16,6 +18,37 @@ class UltravisorAPIServer extends libPictService
16
18
 
17
19
  // Add Orator as a service
18
20
  this.fable.addServiceTypeIfNotExists('Orator', libOrator);
21
+
22
+ this._OratorAuth = null;
23
+ }
24
+
25
+ /**
26
+ * Get a service instance from the fable services map.
27
+ */
28
+ _getService(pTypeName)
29
+ {
30
+ return this.fable.servicesMap[pTypeName]
31
+ ? Object.values(this.fable.servicesMap[pTypeName])[0]
32
+ : null;
33
+ }
34
+
35
+ _requireSession(pRequest, pResponse, fNext)
36
+ {
37
+ if (!this._OratorAuth)
38
+ {
39
+ return {};
40
+ }
41
+
42
+ let tmpSession = this._OratorAuth.getSessionForRequest(pRequest);
43
+
44
+ if (!tmpSession)
45
+ {
46
+ pResponse.send(401, { Error: 'Authentication required.', LoggedIn: false });
47
+ fNext();
48
+ return null;
49
+ }
50
+
51
+ return tmpSession;
19
52
  }
20
53
 
21
54
  wireEndpoints(fCallback)
@@ -41,11 +74,11 @@ class UltravisorAPIServer extends libPictService
41
74
  '/status',
42
75
  function (pRequest, pResponse, fNext)
43
76
  {
44
- let tmpHypervisor = this.fable['Ultravisor-Hypervisor'];
77
+ let tmpHypervisor = this._getService('UltravisorHypervisor');
45
78
  pResponse.send({
46
79
  Status: 'Running',
47
- ScheduleEntries: tmpHypervisor.getSchedule().length,
48
- ScheduleRunning: tmpHypervisor._Running
80
+ ScheduleEntries: tmpHypervisor ? tmpHypervisor.getSchedule().length : 0,
81
+ ScheduleRunning: tmpHypervisor ? tmpHypervisor._Running : false
49
82
  });
50
83
  return fNext();
51
84
  }.bind(this)
@@ -57,29 +90,33 @@ class UltravisorAPIServer extends libPictService
57
90
  function (pRequest, pResponse, fNext)
58
91
  {
59
92
  this.log.info(`Ultravisor API Server: Received stop request via API; stopping server.`);
60
- let tmpHypervisor = this.fable['Ultravisor-Hypervisor'];
61
- tmpHypervisor.stopSchedule();
93
+ let tmpHypervisor = this._getService('UltravisorHypervisor');
94
+ if (tmpHypervisor)
95
+ {
96
+ tmpHypervisor.stopSchedule();
97
+ }
62
98
  pResponse.send({ "Status": "STOPPING" });
63
99
  pResponse.end();
64
100
  return this._Orator.stopService(fNext);
65
101
  }.bind(this)
66
102
  );
67
103
 
68
- // --- Task CRUD ---
104
+ // --- Node Template CRUD ---
69
105
  this._OratorServer.get
70
106
  (
71
- '/Task',
107
+ '/NodeTemplate',
72
108
  function (pRequest, pResponse, fNext)
73
109
  {
74
- this.fable['Ultravisor-Hypervisor-State'].getTaskList({},
75
- function (pError, pTasks)
110
+ let tmpState = this._getService('UltravisorHypervisorState');
111
+ tmpState.getNodeTemplateList(
112
+ function (pError, pTemplates)
76
113
  {
77
114
  if (pError)
78
115
  {
79
116
  pResponse.send(500, { Error: pError.message });
80
117
  return fNext();
81
118
  }
82
- pResponse.send(pTasks);
119
+ pResponse.send(pTemplates);
83
120
  return fNext();
84
121
  });
85
122
  }.bind(this)
@@ -87,18 +124,19 @@ class UltravisorAPIServer extends libPictService
87
124
 
88
125
  this._OratorServer.get
89
126
  (
90
- '/Task/:GUIDTask',
127
+ '/NodeTemplate/:Hash',
91
128
  function (pRequest, pResponse, fNext)
92
129
  {
93
- this.fable['Ultravisor-Hypervisor-State'].getTask(pRequest.params.GUIDTask,
94
- function (pError, pTask)
130
+ let tmpState = this._getService('UltravisorHypervisorState');
131
+ tmpState.getNodeTemplate(pRequest.params.Hash,
132
+ function (pError, pTemplate)
95
133
  {
96
134
  if (pError)
97
135
  {
98
136
  pResponse.send(404, { Error: pError.message });
99
137
  return fNext();
100
138
  }
101
- pResponse.send(pTask);
139
+ pResponse.send(pTemplate);
102
140
  return fNext();
103
141
  });
104
142
  }.bind(this)
@@ -106,18 +144,19 @@ class UltravisorAPIServer extends libPictService
106
144
 
107
145
  this._OratorServer.post
108
146
  (
109
- '/Task',
147
+ '/NodeTemplate',
110
148
  function (pRequest, pResponse, fNext)
111
149
  {
112
- this.fable['Ultravisor-Hypervisor-State'].updateTask(pRequest.body,
113
- function (pError, pTask)
150
+ let tmpState = this._getService('UltravisorHypervisorState');
151
+ tmpState.updateNodeTemplate(pRequest.body,
152
+ function (pError, pTemplate)
114
153
  {
115
154
  if (pError)
116
155
  {
117
156
  pResponse.send(400, { Error: pError.message });
118
157
  return fNext();
119
158
  }
120
- pResponse.send(pTask);
159
+ pResponse.send(pTemplate);
121
160
  return fNext();
122
161
  });
123
162
  }.bind(this)
@@ -125,20 +164,21 @@ class UltravisorAPIServer extends libPictService
125
164
 
126
165
  this._OratorServer.put
127
166
  (
128
- '/Task/:GUIDTask',
167
+ '/NodeTemplate/:Hash',
129
168
  function (pRequest, pResponse, fNext)
130
169
  {
131
- let tmpTaskData = pRequest.body || {};
132
- tmpTaskData.GUIDTask = pRequest.params.GUIDTask;
133
- this.fable['Ultravisor-Hypervisor-State'].updateTask(tmpTaskData,
134
- function (pError, pTask)
170
+ let tmpTemplateData = pRequest.body || {};
171
+ tmpTemplateData.Hash = pRequest.params.Hash;
172
+ let tmpState = this._getService('UltravisorHypervisorState');
173
+ tmpState.updateNodeTemplate(tmpTemplateData,
174
+ function (pError, pTemplate)
135
175
  {
136
176
  if (pError)
137
177
  {
138
178
  pResponse.send(400, { Error: pError.message });
139
179
  return fNext();
140
180
  }
141
- pResponse.send(pTask);
181
+ pResponse.send(pTemplate);
142
182
  return fNext();
143
183
  });
144
184
  }.bind(this)
@@ -146,21 +186,21 @@ class UltravisorAPIServer extends libPictService
146
186
 
147
187
  this._OratorServer.del
148
188
  (
149
- '/Task/:GUIDTask',
189
+ '/NodeTemplate/:Hash',
150
190
  function (pRequest, pResponse, fNext)
151
191
  {
152
- let tmpState = this.fable['Ultravisor-Hypervisor-State'];
153
- if (tmpState._Tasks.hasOwnProperty(pRequest.params.GUIDTask))
154
- {
155
- delete tmpState._Tasks[pRequest.params.GUIDTask];
156
- tmpState.persistState();
157
- pResponse.send({ Status: 'Deleted', GUIDTask: pRequest.params.GUIDTask });
158
- }
159
- else
160
- {
161
- pResponse.send(404, { Error: `Task ${pRequest.params.GUIDTask} not found.` });
162
- }
163
- return fNext();
192
+ let tmpState = this._getService('UltravisorHypervisorState');
193
+ tmpState.deleteNodeTemplate(pRequest.params.Hash,
194
+ function (pError)
195
+ {
196
+ if (pError)
197
+ {
198
+ pResponse.send(404, { Error: pError.message });
199
+ return fNext();
200
+ }
201
+ pResponse.send({ Status: 'Deleted', Hash: pRequest.params.Hash });
202
+ return fNext();
203
+ });
164
204
  }.bind(this)
165
205
  );
166
206
 
@@ -170,7 +210,8 @@ class UltravisorAPIServer extends libPictService
170
210
  '/Operation',
171
211
  function (pRequest, pResponse, fNext)
172
212
  {
173
- this.fable['Ultravisor-Hypervisor-State'].getOperationList({},
213
+ let tmpState = this._getService('UltravisorHypervisorState');
214
+ tmpState.getOperationList(
174
215
  function (pError, pOperations)
175
216
  {
176
217
  if (pError)
@@ -186,10 +227,11 @@ class UltravisorAPIServer extends libPictService
186
227
 
187
228
  this._OratorServer.get
188
229
  (
189
- '/Operation/:GUIDOperation',
230
+ '/Operation/:Hash',
190
231
  function (pRequest, pResponse, fNext)
191
232
  {
192
- this.fable['Ultravisor-Hypervisor-State'].getOperation(pRequest.params.GUIDOperation,
233
+ let tmpState = this._getService('UltravisorHypervisorState');
234
+ tmpState.getOperation(pRequest.params.Hash,
193
235
  function (pError, pOperation)
194
236
  {
195
237
  if (pError)
@@ -208,7 +250,8 @@ class UltravisorAPIServer extends libPictService
208
250
  '/Operation',
209
251
  function (pRequest, pResponse, fNext)
210
252
  {
211
- this.fable['Ultravisor-Hypervisor-State'].updateOperation(pRequest.body,
253
+ let tmpState = this._getService('UltravisorHypervisorState');
254
+ tmpState.updateOperation(pRequest.body,
212
255
  function (pError, pOperation)
213
256
  {
214
257
  if (pError)
@@ -224,12 +267,13 @@ class UltravisorAPIServer extends libPictService
224
267
 
225
268
  this._OratorServer.put
226
269
  (
227
- '/Operation/:GUIDOperation',
270
+ '/Operation/:Hash',
228
271
  function (pRequest, pResponse, fNext)
229
272
  {
230
273
  let tmpOperationData = pRequest.body || {};
231
- tmpOperationData.GUIDOperation = pRequest.params.GUIDOperation;
232
- this.fable['Ultravisor-Hypervisor-State'].updateOperation(tmpOperationData,
274
+ tmpOperationData.Hash = pRequest.params.Hash;
275
+ let tmpState = this._getService('UltravisorHypervisorState');
276
+ tmpState.updateOperation(tmpOperationData,
233
277
  function (pError, pOperation)
234
278
  {
235
279
  if (pError)
@@ -245,69 +289,34 @@ class UltravisorAPIServer extends libPictService
245
289
 
246
290
  this._OratorServer.del
247
291
  (
248
- '/Operation/:GUIDOperation',
249
- function (pRequest, pResponse, fNext)
250
- {
251
- let tmpState = this.fable['Ultravisor-Hypervisor-State'];
252
- if (tmpState._Operations.hasOwnProperty(pRequest.params.GUIDOperation))
253
- {
254
- delete tmpState._Operations[pRequest.params.GUIDOperation];
255
- tmpState.persistState();
256
- pResponse.send({ Status: 'Deleted', GUIDOperation: pRequest.params.GUIDOperation });
257
- }
258
- else
259
- {
260
- pResponse.send(404, { Error: `Operation ${pRequest.params.GUIDOperation} not found.` });
261
- }
262
- return fNext();
263
- }.bind(this)
264
- );
265
-
266
- // --- Task Execution ---
267
- this._OratorServer.get
268
- (
269
- '/Task/:GUIDTask/Execute',
292
+ '/Operation/:Hash',
270
293
  function (pRequest, pResponse, fNext)
271
294
  {
272
- let tmpState = this.fable['Ultravisor-Hypervisor-State'];
273
- let tmpTaskService = this.fable['Ultravisor-Task'];
274
-
275
- tmpState.getTask(pRequest.params.GUIDTask,
276
- function (pError, pTask)
295
+ let tmpState = this._getService('UltravisorHypervisorState');
296
+ tmpState.deleteOperation(pRequest.params.Hash,
297
+ function (pError)
277
298
  {
278
299
  if (pError)
279
300
  {
280
301
  pResponse.send(404, { Error: pError.message });
281
302
  return fNext();
282
303
  }
283
- tmpTaskService.executeTask(pTask, {},
284
- function (pExecError, pManifestEntry)
285
- {
286
- if (pExecError)
287
- {
288
- pResponse.send(500, { Error: pExecError.message });
289
- return fNext();
290
- }
291
- // Store the task result as a manifest so it appears in /Manifest
292
- let tmpManifestService = this.fable['Ultravisor-Operation-Manifest'];
293
- tmpManifestService.createTaskManifest(pManifestEntry);
294
- pResponse.send(pManifestEntry);
295
- return fNext();
296
- }.bind(this));
297
- }.bind(this));
304
+ pResponse.send({ Status: 'Deleted', Hash: pRequest.params.Hash });
305
+ return fNext();
306
+ });
298
307
  }.bind(this)
299
308
  );
300
309
 
301
310
  // --- Operation Execution ---
302
311
  this._OratorServer.get
303
312
  (
304
- '/Operation/:GUIDOperation/Execute',
313
+ '/Operation/:Hash/Execute',
305
314
  function (pRequest, pResponse, fNext)
306
315
  {
307
- let tmpState = this.fable['Ultravisor-Hypervisor-State'];
308
- let tmpOperationService = this.fable['Ultravisor-Operation'];
316
+ let tmpState = this._getService('UltravisorHypervisorState');
317
+ let tmpEngine = this._getService('UltravisorExecutionEngine');
309
318
 
310
- tmpState.getOperation(pRequest.params.GUIDOperation,
319
+ tmpState.getOperation(pRequest.params.Hash,
311
320
  function (pError, pOperation)
312
321
  {
313
322
  if (pError)
@@ -315,51 +324,69 @@ class UltravisorAPIServer extends libPictService
315
324
  pResponse.send(404, { Error: pError.message });
316
325
  return fNext();
317
326
  }
318
- tmpOperationService.executeOperation(pOperation,
319
- function (pExecError, pManifest)
327
+
328
+ let tmpRunMode = (pRequest.query && pRequest.query.RunMode) || undefined;
329
+ let tmpInitialState = tmpRunMode ? { RunMode: tmpRunMode } : {};
330
+
331
+ tmpEngine.executeOperation(pOperation, tmpInitialState,
332
+ function (pExecError, pContext)
320
333
  {
321
334
  if (pExecError)
322
335
  {
323
336
  pResponse.send(500, { Error: pExecError.message });
324
337
  return fNext();
325
338
  }
326
- pResponse.send(pManifest);
339
+ pResponse.send({
340
+ Status: pContext.Status,
341
+ Hash: pContext.Hash,
342
+ OperationHash: pContext.OperationHash,
343
+ RunMode: pContext.RunMode,
344
+ Output: pContext.Output,
345
+ TaskOutputs: pContext.TaskOutputs,
346
+ Log: pContext.Log,
347
+ Errors: pContext.Errors,
348
+ StartTime: pContext.StartTime,
349
+ StopTime: pContext.StopTime,
350
+ ElapsedMs: pContext.ElapsedMs,
351
+ TaskManifests: pContext.TaskManifests,
352
+ TimingSummary: pContext.TimingSummary,
353
+ EventLog: pContext.EventLog,
354
+ WaitingTasks: pContext.WaitingTasks
355
+ });
327
356
  return fNext();
328
357
  });
329
358
  });
330
359
  }.bind(this)
331
360
  );
332
361
 
333
- // --- Schedule ---
362
+ // --- Task Types ---
334
363
  this._OratorServer.get
335
364
  (
336
- '/Schedule',
365
+ '/TaskType',
337
366
  function (pRequest, pResponse, fNext)
338
367
  {
339
- pResponse.send(this.fable['Ultravisor-Hypervisor'].getSchedule());
368
+ let tmpRegistry = this._getService('UltravisorTaskTypeRegistry');
369
+ if (tmpRegistry)
370
+ {
371
+ pResponse.send(tmpRegistry.listDefinitions());
372
+ }
373
+ else
374
+ {
375
+ pResponse.send([]);
376
+ }
340
377
  return fNext();
341
378
  }.bind(this)
342
379
  );
343
380
 
344
- this._OratorServer.post
381
+ // --- Schedule ---
382
+ this._OratorServer.get
345
383
  (
346
- '/Schedule/Task',
384
+ '/Schedule',
347
385
  function (pRequest, pResponse, fNext)
348
386
  {
349
- let tmpBody = pRequest.body || {};
350
- let tmpHypervisor = this.fable['Ultravisor-Hypervisor'];
351
-
352
- tmpHypervisor.scheduleTask(tmpBody.GUIDTask, tmpBody.ScheduleType, tmpBody.Parameters,
353
- function (pError, pEntry)
354
- {
355
- if (pError)
356
- {
357
- pResponse.send(400, { Error: pError.message });
358
- return fNext();
359
- }
360
- pResponse.send(pEntry);
361
- return fNext();
362
- });
387
+ let tmpHypervisor = this._getService('UltravisorHypervisor');
388
+ pResponse.send(tmpHypervisor ? tmpHypervisor.getSchedule() : []);
389
+ return fNext();
363
390
  }.bind(this)
364
391
  );
365
392
 
@@ -369,9 +396,9 @@ class UltravisorAPIServer extends libPictService
369
396
  function (pRequest, pResponse, fNext)
370
397
  {
371
398
  let tmpBody = pRequest.body || {};
372
- let tmpHypervisor = this.fable['Ultravisor-Hypervisor'];
399
+ let tmpHypervisor = this._getService('UltravisorHypervisor');
373
400
 
374
- tmpHypervisor.scheduleOperation(tmpBody.GUIDOperation, tmpBody.ScheduleType, tmpBody.Parameters,
401
+ tmpHypervisor.scheduleOperation(tmpBody.Hash, tmpBody.ScheduleType, tmpBody.Parameters,
375
402
  function (pError, pEntry)
376
403
  {
377
404
  if (pError)
@@ -390,7 +417,8 @@ class UltravisorAPIServer extends libPictService
390
417
  '/Schedule/:GUID',
391
418
  function (pRequest, pResponse, fNext)
392
419
  {
393
- this.fable['Ultravisor-Hypervisor'].removeScheduleEntry(pRequest.params.GUID,
420
+ let tmpHypervisor = this._getService('UltravisorHypervisor');
421
+ tmpHypervisor.removeScheduleEntry(pRequest.params.GUID,
394
422
  function (pError, pResult)
395
423
  {
396
424
  if (pError)
@@ -409,7 +437,8 @@ class UltravisorAPIServer extends libPictService
409
437
  '/Schedule/Start',
410
438
  function (pRequest, pResponse, fNext)
411
439
  {
412
- this.fable['Ultravisor-Hypervisor'].startSchedule(
440
+ let tmpHypervisor = this._getService('UltravisorHypervisor');
441
+ tmpHypervisor.startSchedule(
413
442
  function ()
414
443
  {
415
444
  pResponse.send({ Status: 'Schedule Started' });
@@ -418,12 +447,33 @@ class UltravisorAPIServer extends libPictService
418
447
  }.bind(this)
419
448
  );
420
449
 
450
+ this._OratorServer.get
451
+ (
452
+ '/Schedule/Start/:GUID',
453
+ function (pRequest, pResponse, fNext)
454
+ {
455
+ let tmpHypervisor = this._getService('UltravisorHypervisor');
456
+ tmpHypervisor.startScheduleEntry(pRequest.params.GUID,
457
+ function (pError, pEntry)
458
+ {
459
+ if (pError)
460
+ {
461
+ pResponse.send(404, { Error: pError.message });
462
+ return fNext();
463
+ }
464
+ pResponse.send({ Status: 'Entry Started', Entry: pEntry });
465
+ return fNext();
466
+ });
467
+ }.bind(this)
468
+ );
469
+
421
470
  this._OratorServer.get
422
471
  (
423
472
  '/Schedule/Stop',
424
473
  function (pRequest, pResponse, fNext)
425
474
  {
426
- this.fable['Ultravisor-Hypervisor'].stopSchedule(
475
+ let tmpHypervisor = this._getService('UltravisorHypervisor');
476
+ tmpHypervisor.stopSchedule(
427
477
  function ()
428
478
  {
429
479
  pResponse.send({ Status: 'Schedule Stopped' });
@@ -432,35 +482,694 @@ class UltravisorAPIServer extends libPictService
432
482
  }.bind(this)
433
483
  );
434
484
 
485
+ this._OratorServer.get
486
+ (
487
+ '/Schedule/Stop/:GUID',
488
+ function (pRequest, pResponse, fNext)
489
+ {
490
+ let tmpHypervisor = this._getService('UltravisorHypervisor');
491
+ tmpHypervisor.stopScheduleEntry(pRequest.params.GUID,
492
+ function (pError, pEntry)
493
+ {
494
+ if (pError)
495
+ {
496
+ pResponse.send(404, { Error: pError.message });
497
+ return fNext();
498
+ }
499
+ pResponse.send({ Status: 'Entry Stopped', Entry: pEntry });
500
+ return fNext();
501
+ });
502
+ }.bind(this)
503
+ );
504
+
435
505
  // --- Manifests ---
436
506
  this._OratorServer.get
437
507
  (
438
508
  '/Manifest',
439
509
  function (pRequest, pResponse, fNext)
440
510
  {
441
- pResponse.send(this.fable['Ultravisor-Operation-Manifest'].getManifestList());
511
+ let tmpManifest = this._getService('UltravisorExecutionManifest');
512
+ pResponse.send(tmpManifest ? tmpManifest.listRuns() : []);
442
513
  return fNext();
443
514
  }.bind(this)
444
515
  );
445
516
 
446
517
  this._OratorServer.get
447
518
  (
448
- '/Manifest/:GUIDRun',
519
+ '/Manifest/:RunHash',
449
520
  function (pRequest, pResponse, fNext)
450
521
  {
451
- let tmpManifest = this.fable['Ultravisor-Operation-Manifest'].getManifest(pRequest.params.GUIDRun);
452
- if (tmpManifest)
522
+ let tmpManifest = this._getService('UltravisorExecutionManifest');
523
+ let tmpRun = tmpManifest ? tmpManifest.getRun(pRequest.params.RunHash) : null;
524
+ if (tmpRun)
453
525
  {
454
- pResponse.send(tmpManifest);
526
+ pResponse.send(tmpRun);
455
527
  }
456
528
  else
457
529
  {
458
- pResponse.send(404, { Error: `Manifest ${pRequest.params.GUIDRun} not found.` });
530
+ pResponse.send(404, { Error: `Manifest ${pRequest.params.RunHash} not found.` });
531
+ }
532
+ return fNext();
533
+ }.bind(this)
534
+ );
535
+
536
+ // --- Pending Inputs ---
537
+ this._OratorServer.get
538
+ (
539
+ '/PendingInput',
540
+ function (pRequest, pResponse, fNext)
541
+ {
542
+ let tmpManifest = this._getService('UltravisorExecutionManifest');
543
+ if (!tmpManifest)
544
+ {
545
+ pResponse.send([]);
546
+ return fNext();
547
+ }
548
+
549
+ let tmpRuns = tmpManifest.listRuns();
550
+ let tmpPending = [];
551
+
552
+ for (let i = 0; i < tmpRuns.length; i++)
553
+ {
554
+ if (tmpRuns[i].Status === 'WaitingForInput')
555
+ {
556
+ let tmpFullRun = tmpManifest.getRun(tmpRuns[i].Hash);
557
+ if (tmpFullRun)
558
+ {
559
+ tmpPending.push({
560
+ RunHash: tmpFullRun.Hash,
561
+ OperationHash: tmpFullRun.OperationHash,
562
+ OperationName: tmpFullRun.OperationName,
563
+ StartTime: tmpFullRun.StartTime,
564
+ WaitingTasks: tmpFullRun.WaitingTasks
565
+ });
566
+ }
567
+ }
568
+ }
569
+
570
+ pResponse.send(tmpPending);
571
+ return fNext();
572
+ }.bind(this)
573
+ );
574
+
575
+ this._OratorServer.post
576
+ (
577
+ '/PendingInput/:RunHash',
578
+ function (pRequest, pResponse, fNext)
579
+ {
580
+ let tmpBody = pRequest.body || {};
581
+ let tmpEngine = this._getService('UltravisorExecutionEngine');
582
+ let tmpRunHash = pRequest.params.RunHash;
583
+
584
+ if (!tmpBody.NodeHash)
585
+ {
586
+ pResponse.send(400, { Error: 'NodeHash is required.' });
587
+ return fNext();
588
+ }
589
+
590
+ tmpEngine.resumeOperation(tmpRunHash, tmpBody.NodeHash, tmpBody.Value,
591
+ function (pError, pContext)
592
+ {
593
+ if (pError)
594
+ {
595
+ pResponse.send(400, { Error: pError.message });
596
+ return fNext();
597
+ }
598
+ pResponse.send({
599
+ Status: pContext.Status,
600
+ Hash: pContext.Hash,
601
+ TaskOutputs: pContext.TaskOutputs,
602
+ Log: pContext.Log,
603
+ Errors: pContext.Errors,
604
+ WaitingTasks: pContext.WaitingTasks
605
+ });
606
+ return fNext();
607
+ });
608
+ }.bind(this)
609
+ );
610
+
611
+ // --- Operation Resume (for value-input tasks) ---
612
+ this._OratorServer.post
613
+ (
614
+ '/Operation/Resume',
615
+ function (pRequest, pResponse, fNext)
616
+ {
617
+ let tmpBody = pRequest.body || {};
618
+ let tmpEngine = this._getService('UltravisorExecutionEngine');
619
+
620
+ if (!tmpBody.RunHash || !tmpBody.NodeHash)
621
+ {
622
+ pResponse.send(400, { Error: 'RunHash and NodeHash are required.' });
623
+ return fNext();
624
+ }
625
+
626
+ tmpEngine.resumeOperation(tmpBody.RunHash, tmpBody.NodeHash, tmpBody.Value,
627
+ function (pError, pContext)
628
+ {
629
+ if (pError)
630
+ {
631
+ pResponse.send(400, { Error: pError.message });
632
+ return fNext();
633
+ }
634
+ pResponse.send({
635
+ Status: pContext.Status,
636
+ Hash: pContext.Hash,
637
+ TaskOutputs: pContext.TaskOutputs,
638
+ Log: pContext.Log,
639
+ Errors: pContext.Errors,
640
+ WaitingTasks: pContext.WaitingTasks
641
+ });
642
+ return fNext();
643
+ });
644
+ }.bind(this)
645
+ );
646
+
647
+ // --- Operation Library ---
648
+ this._OratorServer.get
649
+ (
650
+ '/OperationLibrary',
651
+ function (pRequest, pResponse, fNext)
652
+ {
653
+ let tmpLibraryPath = this.fable?.ProgramConfiguration?.UltravisorOperationLibraryPath;
654
+ if (!tmpLibraryPath)
655
+ {
656
+ pResponse.send([]);
657
+ return fNext();
658
+ }
659
+
660
+ let tmpResolvedPath = libPath.resolve(process.cwd(), tmpLibraryPath);
661
+
662
+ let tmpFiles;
663
+ try
664
+ {
665
+ tmpFiles = libFS.readdirSync(tmpResolvedPath);
666
+ }
667
+ catch (pError)
668
+ {
669
+ this.log.warn(`OperationLibrary: could not read directory [${tmpResolvedPath}]: ${pError.message}`);
670
+ pResponse.send([]);
671
+ return fNext();
672
+ }
673
+
674
+ let tmpLibraryItems = [];
675
+
676
+ for (let i = 0; i < tmpFiles.length; i++)
677
+ {
678
+ let tmpFileName = tmpFiles[i];
679
+ if (!tmpFileName.endsWith('.json'))
680
+ {
681
+ continue;
682
+ }
683
+
684
+ try
685
+ {
686
+ let tmpFilePath = libPath.join(tmpResolvedPath, tmpFileName);
687
+ let tmpContent = libFS.readFileSync(tmpFilePath, 'utf8');
688
+ let tmpOperation = JSON.parse(tmpContent);
689
+
690
+ tmpLibraryItems.push({
691
+ FileName: tmpFileName,
692
+ Name: tmpOperation.Name || tmpFileName,
693
+ Description: tmpOperation.Description || '',
694
+ Tags: tmpOperation.Tags || [],
695
+ Author: tmpOperation.Author || '',
696
+ Version: tmpOperation.Version || '',
697
+ NodeCount: (tmpOperation.Graph && tmpOperation.Graph.Nodes) ? tmpOperation.Graph.Nodes.length : 0
698
+ });
699
+ }
700
+ catch (pError)
701
+ {
702
+ this.log.warn(`OperationLibrary: could not parse [${tmpFileName}]: ${pError.message}`);
703
+ }
704
+ }
705
+
706
+ tmpLibraryItems.sort(
707
+ function (a, b)
708
+ {
709
+ return a.Name.localeCompare(b.Name);
710
+ });
711
+
712
+ pResponse.send(tmpLibraryItems);
713
+ return fNext();
714
+ }.bind(this)
715
+ );
716
+
717
+ this._OratorServer.get
718
+ (
719
+ '/OperationLibrary/:FileName',
720
+ function (pRequest, pResponse, fNext)
721
+ {
722
+ let tmpFileName = pRequest.params.FileName;
723
+
724
+ // Security: prevent directory traversal
725
+ if (!tmpFileName || !tmpFileName.endsWith('.json') ||
726
+ tmpFileName.indexOf('/') >= 0 || tmpFileName.indexOf('\\') >= 0 ||
727
+ tmpFileName.indexOf('..') >= 0)
728
+ {
729
+ pResponse.send(400, { Error: 'Invalid file name.' });
730
+ return fNext();
731
+ }
732
+
733
+ let tmpLibraryPath = this.fable?.ProgramConfiguration?.UltravisorOperationLibraryPath;
734
+ if (!tmpLibraryPath)
735
+ {
736
+ pResponse.send(404, { Error: 'Operation library not configured.' });
737
+ return fNext();
738
+ }
739
+
740
+ let tmpResolvedPath = libPath.resolve(process.cwd(), tmpLibraryPath);
741
+ let tmpFilePath = libPath.join(tmpResolvedPath, tmpFileName);
742
+
743
+ try
744
+ {
745
+ let tmpContent = libFS.readFileSync(tmpFilePath, 'utf8');
746
+ let tmpOperation = JSON.parse(tmpContent);
747
+ pResponse.send(tmpOperation);
748
+ }
749
+ catch (pError)
750
+ {
751
+ pResponse.send(404, { Error: `Library operation [${tmpFileName}] not found.` });
752
+ }
753
+ return fNext();
754
+ }.bind(this)
755
+ );
756
+
757
+ // --- Operation Export ---
758
+ this._OratorServer.get
759
+ (
760
+ '/Operation/:Hash/Export',
761
+ function (pRequest, pResponse, fNext)
762
+ {
763
+ let tmpState = this._getService('UltravisorHypervisorState');
764
+ tmpState.getOperation(pRequest.params.Hash,
765
+ function (pError, pOperation)
766
+ {
767
+ if (pError)
768
+ {
769
+ pResponse.send(404, { Error: pError.message });
770
+ return fNext();
771
+ }
772
+
773
+ let tmpExport = {
774
+ Hash: pOperation.Hash,
775
+ Name: pOperation.Name || '',
776
+ Description: pOperation.Description || '',
777
+ Graph: pOperation.Graph || { Nodes: [], Connections: [], ViewState: {} },
778
+ SavedLayouts: pOperation.SavedLayouts || [],
779
+ InitialGlobalState: pOperation.InitialGlobalState || {},
780
+ InitialOperationState: pOperation.InitialOperationState || {},
781
+ ExportedAt: new Date().toISOString()
782
+ };
783
+
784
+ pResponse.send(tmpExport);
785
+ return fNext();
786
+ });
787
+ }.bind(this)
788
+ );
789
+
790
+ // ===================================================================
791
+ // Beacon Worker Endpoints
792
+ // ===================================================================
793
+
794
+ // --- Beacon Registration ---
795
+ this._OratorServer.post
796
+ (
797
+ '/Beacon/Register',
798
+ function (pRequest, pResponse, fNext)
799
+ {
800
+ let tmpSession = this._requireSession(pRequest, pResponse, fNext);
801
+ if (!tmpSession) { return; }
802
+
803
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
804
+ if (!tmpCoordinator)
805
+ {
806
+ pResponse.send(500, { Error: 'BeaconCoordinator service not available.' });
807
+ return fNext();
808
+ }
809
+
810
+ let tmpBody = pRequest.body || {};
811
+ if (!tmpBody.Name || !tmpBody.Capabilities)
812
+ {
813
+ pResponse.send(400, { Error: 'Name and Capabilities are required.' });
814
+ return fNext();
815
+ }
816
+
817
+ let tmpBeacon = tmpCoordinator.registerBeacon(tmpBody, tmpSession.SessionID);
818
+ pResponse.send(tmpBeacon);
819
+ return fNext();
820
+ }.bind(this)
821
+ );
822
+
823
+ // --- List Beacons (no auth – management UI) ---
824
+ this._OratorServer.get
825
+ (
826
+ '/Beacon',
827
+ function (pRequest, pResponse, fNext)
828
+ {
829
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
830
+ if (!tmpCoordinator)
831
+ {
832
+ pResponse.send([]);
833
+ return fNext();
834
+ }
835
+
836
+ pResponse.send(tmpCoordinator.listBeacons());
837
+ return fNext();
838
+ }.bind(this)
839
+ );
840
+
841
+ // --- List Work Items (no auth – management UI) ---
842
+ this._OratorServer.get
843
+ (
844
+ '/Beacon/Work',
845
+ function (pRequest, pResponse, fNext)
846
+ {
847
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
848
+ if (!tmpCoordinator)
849
+ {
850
+ pResponse.send([]);
851
+ return fNext();
852
+ }
853
+
854
+ pResponse.send(tmpCoordinator.listWorkItems());
855
+ return fNext();
856
+ }.bind(this)
857
+ );
858
+
859
+ // --- List Affinity Bindings (no auth – management UI) ---
860
+ this._OratorServer.get
861
+ (
862
+ '/Beacon/Affinity',
863
+ function (pRequest, pResponse, fNext)
864
+ {
865
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
866
+ if (!tmpCoordinator)
867
+ {
868
+ pResponse.send([]);
869
+ return fNext();
870
+ }
871
+
872
+ pResponse.send(tmpCoordinator.listAffinityBindings());
873
+ return fNext();
874
+ }.bind(this)
875
+ );
876
+
877
+ // --- Get Specific Beacon (no auth – management UI) ---
878
+ this._OratorServer.get
879
+ (
880
+ '/Beacon/:BeaconID',
881
+ function (pRequest, pResponse, fNext)
882
+ {
883
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
884
+ if (!tmpCoordinator)
885
+ {
886
+ pResponse.send(404, { Error: 'BeaconCoordinator service not available.' });
887
+ return fNext();
888
+ }
889
+
890
+ let tmpBeacon = tmpCoordinator.getBeacon(pRequest.params.BeaconID);
891
+ if (!tmpBeacon)
892
+ {
893
+ pResponse.send(404, { Error: `Beacon [${pRequest.params.BeaconID}] not found.` });
894
+ return fNext();
895
+ }
896
+
897
+ pResponse.send(tmpBeacon);
898
+ return fNext();
899
+ }.bind(this)
900
+ );
901
+
902
+ // --- Deregister Beacon (no auth – management UI) ---
903
+ this._OratorServer.del
904
+ (
905
+ '/Beacon/:BeaconID',
906
+ function (pRequest, pResponse, fNext)
907
+ {
908
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
909
+ if (!tmpCoordinator)
910
+ {
911
+ pResponse.send(500, { Error: 'BeaconCoordinator service not available.' });
912
+ return fNext();
913
+ }
914
+
915
+ let tmpRemoved = tmpCoordinator.deregisterBeacon(pRequest.params.BeaconID);
916
+ if (!tmpRemoved)
917
+ {
918
+ pResponse.send(404, { Error: `Beacon [${pRequest.params.BeaconID}] not found.` });
919
+ return fNext();
920
+ }
921
+
922
+ pResponse.send({ Status: 'Deregistered', BeaconID: pRequest.params.BeaconID });
923
+ return fNext();
924
+ }.bind(this)
925
+ );
926
+
927
+ // --- Beacon Heartbeat ---
928
+ this._OratorServer.post
929
+ (
930
+ '/Beacon/:BeaconID/Heartbeat',
931
+ function (pRequest, pResponse, fNext)
932
+ {
933
+ let tmpSession = this._requireSession(pRequest, pResponse, fNext);
934
+ if (!tmpSession) { return; }
935
+
936
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
937
+ if (!tmpCoordinator)
938
+ {
939
+ pResponse.send(500, { Error: 'BeaconCoordinator service not available.' });
940
+ return fNext();
941
+ }
942
+
943
+ let tmpBeacon = tmpCoordinator.heartbeat(pRequest.params.BeaconID);
944
+ if (!tmpBeacon)
945
+ {
946
+ pResponse.send(404, { Error: `Beacon [${pRequest.params.BeaconID}] not found.` });
947
+ return fNext();
459
948
  }
949
+
950
+ pResponse.send(tmpBeacon);
460
951
  return fNext();
461
952
  }.bind(this)
462
953
  );
463
954
 
955
+ // --- Poll for Work ---
956
+ this._OratorServer.post
957
+ (
958
+ '/Beacon/Work/Poll',
959
+ function (pRequest, pResponse, fNext)
960
+ {
961
+ let tmpSession = this._requireSession(pRequest, pResponse, fNext);
962
+ if (!tmpSession) { return; }
963
+
964
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
965
+ if (!tmpCoordinator)
966
+ {
967
+ pResponse.send(500, { Error: 'BeaconCoordinator service not available.' });
968
+ return fNext();
969
+ }
970
+
971
+ let tmpBody = pRequest.body || {};
972
+ if (!tmpBody.BeaconID)
973
+ {
974
+ pResponse.send(400, { Error: 'BeaconID is required.' });
975
+ return fNext();
976
+ }
977
+
978
+ let tmpWorkItem = tmpCoordinator.pollForWork(tmpBody.BeaconID);
979
+ pResponse.send({ WorkItem: tmpWorkItem });
980
+ return fNext();
981
+ }.bind(this)
982
+ );
983
+
984
+ // --- Complete Work Item ---
985
+ this._OratorServer.post
986
+ (
987
+ '/Beacon/Work/:WorkItemHash/Complete',
988
+ function (pRequest, pResponse, fNext)
989
+ {
990
+ let tmpSession = this._requireSession(pRequest, pResponse, fNext);
991
+ if (!tmpSession) { return; }
992
+
993
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
994
+ if (!tmpCoordinator)
995
+ {
996
+ pResponse.send(500, { Error: 'BeaconCoordinator service not available.' });
997
+ return fNext();
998
+ }
999
+
1000
+ let tmpBody = pRequest.body || {};
1001
+ tmpCoordinator.completeWorkItem(pRequest.params.WorkItemHash,
1002
+ { Outputs: tmpBody.Outputs || {}, Log: tmpBody.Log || [] },
1003
+ function (pError)
1004
+ {
1005
+ if (pError)
1006
+ {
1007
+ pResponse.send(400, { Error: pError.message });
1008
+ return fNext();
1009
+ }
1010
+
1011
+ pResponse.send({ Status: 'Completed', WorkItemHash: pRequest.params.WorkItemHash });
1012
+ return fNext();
1013
+ });
1014
+ }.bind(this)
1015
+ );
1016
+
1017
+ // --- Report Progress ---
1018
+ this._OratorServer.post
1019
+ (
1020
+ '/Beacon/Work/:WorkItemHash/Progress',
1021
+ function (pRequest, pResponse, fNext)
1022
+ {
1023
+ let tmpSession = this._requireSession(pRequest, pResponse, fNext);
1024
+ if (!tmpSession) { return; }
1025
+
1026
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
1027
+ if (!tmpCoordinator)
1028
+ {
1029
+ pResponse.send(500, { Error: 'BeaconCoordinator service not available.' });
1030
+ return fNext();
1031
+ }
1032
+
1033
+ let tmpBody = pRequest.body || {};
1034
+ let tmpUpdated = tmpCoordinator.updateProgress(pRequest.params.WorkItemHash, tmpBody);
1035
+
1036
+ if (!tmpUpdated)
1037
+ {
1038
+ pResponse.send(404, { Error: `Work item [${pRequest.params.WorkItemHash}] not found or not running.` });
1039
+ return fNext();
1040
+ }
1041
+
1042
+ pResponse.send({ Success: true, WorkItemHash: pRequest.params.WorkItemHash });
1043
+ return fNext();
1044
+ }.bind(this)
1045
+ );
1046
+
1047
+ // --- Direct Dispatch (synchronous) ---
1048
+ this._OratorServer.post
1049
+ (
1050
+ '/Beacon/Work/Dispatch',
1051
+ function (pRequest, pResponse, fNext)
1052
+ {
1053
+ let tmpSession = this._requireSession(pRequest, pResponse, fNext);
1054
+ if (!tmpSession) { return; }
1055
+
1056
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
1057
+ if (!tmpCoordinator)
1058
+ {
1059
+ pResponse.send(500, { Success: false, Error: 'BeaconCoordinator service not available.' });
1060
+ return fNext();
1061
+ }
1062
+
1063
+ let tmpBody = pRequest.body || {};
1064
+ if (!tmpBody.Capability)
1065
+ {
1066
+ pResponse.send(400, { Success: false, Error: 'Capability is required.' });
1067
+ return fNext();
1068
+ }
1069
+
1070
+ // Check if any Beacons are registered
1071
+ let tmpBeacons = tmpCoordinator.listBeacons();
1072
+ if (tmpBeacons.length === 0)
1073
+ {
1074
+ pResponse.send(503, { Success: false, Error: 'No Beacon workers are registered.' });
1075
+ return fNext();
1076
+ }
1077
+
1078
+ // Disable request timeout for long-running dispatch
1079
+ if (pRequest.connection)
1080
+ {
1081
+ pRequest.connection.setTimeout(0);
1082
+ }
1083
+
1084
+ let tmpWorkItemInfo = {
1085
+ Capability: tmpBody.Capability || 'Shell',
1086
+ Action: tmpBody.Action || 'Execute',
1087
+ Settings: tmpBody.Settings || {},
1088
+ AffinityKey: tmpBody.AffinityKey || '',
1089
+ TimeoutMs: tmpBody.TimeoutMs || 300000
1090
+ };
1091
+
1092
+ tmpCoordinator.dispatchAndWait(tmpWorkItemInfo,
1093
+ (pError, pResult) =>
1094
+ {
1095
+ if (pError)
1096
+ {
1097
+ pResponse.send(500, { Success: false, Error: pError.message });
1098
+ return fNext();
1099
+ }
1100
+
1101
+ pResponse.send(pResult);
1102
+ return fNext();
1103
+ });
1104
+ }.bind(this)
1105
+ );
1106
+
1107
+ // --- Beacon Capabilities (no auth – management UI) ---
1108
+ this._OratorServer.get
1109
+ (
1110
+ '/Beacon/Capabilities',
1111
+ function (pRequest, pResponse, fNext)
1112
+ {
1113
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
1114
+ if (!tmpCoordinator)
1115
+ {
1116
+ pResponse.send({ Capabilities: [] });
1117
+ return fNext();
1118
+ }
1119
+
1120
+ let tmpBeacons = tmpCoordinator.listBeacons();
1121
+ let tmpCapabilitySet = {};
1122
+
1123
+ for (let i = 0; i < tmpBeacons.length; i++)
1124
+ {
1125
+ let tmpCaps = tmpBeacons[i].Capabilities || [];
1126
+ for (let j = 0; j < tmpCaps.length; j++)
1127
+ {
1128
+ tmpCapabilitySet[tmpCaps[j]] = true;
1129
+ }
1130
+ }
1131
+
1132
+ pResponse.send({
1133
+ Capabilities: Object.keys(tmpCapabilitySet),
1134
+ BeaconCount: tmpBeacons.length
1135
+ });
1136
+ return fNext();
1137
+ }.bind(this)
1138
+ );
1139
+
1140
+ // --- Fail Work Item ---
1141
+ this._OratorServer.post
1142
+ (
1143
+ '/Beacon/Work/:WorkItemHash/Error',
1144
+ function (pRequest, pResponse, fNext)
1145
+ {
1146
+ let tmpSession = this._requireSession(pRequest, pResponse, fNext);
1147
+ if (!tmpSession) { return; }
1148
+
1149
+ let tmpCoordinator = this._getService('UltravisorBeaconCoordinator');
1150
+ if (!tmpCoordinator)
1151
+ {
1152
+ pResponse.send(500, { Error: 'BeaconCoordinator service not available.' });
1153
+ return fNext();
1154
+ }
1155
+
1156
+ let tmpBody = pRequest.body || {};
1157
+ tmpCoordinator.failWorkItem(pRequest.params.WorkItemHash,
1158
+ { ErrorMessage: tmpBody.ErrorMessage || 'Unknown error', Log: tmpBody.Log || [] },
1159
+ function (pError)
1160
+ {
1161
+ if (pError)
1162
+ {
1163
+ pResponse.send(400, { Error: pError.message });
1164
+ return fNext();
1165
+ }
1166
+
1167
+ pResponse.send({ Status: 'Failed', WorkItemHash: pRequest.params.WorkItemHash });
1168
+ return fNext();
1169
+ });
1170
+ }.bind(this)
1171
+ );
1172
+
464
1173
  return fCallback();
465
1174
  }
466
1175
 
@@ -513,6 +1222,22 @@ class UltravisorAPIServer extends libPictService
513
1222
  return fNext();
514
1223
  }.bind(this));
515
1224
 
1225
+ tmpAnticipate.anticipate(
1226
+ function (fNext)
1227
+ {
1228
+ this.fable.addServiceTypeIfNotExists('OratorAuthentication', libOratorAuthentication);
1229
+ this._OratorAuth = this.fable.instantiateServiceProvider('OratorAuthentication',
1230
+ {
1231
+ RoutePrefix: '/1.0/',
1232
+ SessionTTL: this.fable.settings.UltravisorBeaconSessionTTLMs || 86400000,
1233
+ CookieHttpOnly: true,
1234
+ CookieSecure: false
1235
+ });
1236
+ this._OratorAuth.connectRoutes();
1237
+ this.log.info('Ultravisor: OratorAuthentication routes registered.');
1238
+ return fNext();
1239
+ }.bind(this));
1240
+
516
1241
  tmpAnticipate.anticipate(
517
1242
  function (fNext)
518
1243
  {
@@ -576,4 +1301,4 @@ class UltravisorAPIServer extends libPictService
576
1301
  }
577
1302
  }
578
1303
 
579
- module.exports = UltravisorAPIServer;
1304
+ module.exports = UltravisorAPIServer;