ultravisor 1.0.14 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/source/beacon/Ultravisor-Beacon-Client.cjs +3 -0
- package/source/services/Ultravisor-Beacon-Coordinator.cjs +17 -0
- package/source/services/Ultravisor-ExecutionEngine.cjs +14 -0
- package/source/services/tasks/flow-control/Ultravisor-TaskConfigs-FlowControl.cjs +116 -0
- package/source/services/tasks/flow-control/Ultravisor-TaskType-ParameterSweep.cjs +197 -0
- package/source/services/tasks/flow-control/definitions/parameter-sweep.json +36 -0
- package/test/Ultravisor_tests.js +3 -3
- package/webinterface/package.json +0 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ultravisor",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.16",
|
|
4
4
|
"description": "Cyclic process execution with ai integration.",
|
|
5
5
|
"main": "source/Ultravisor.cjs",
|
|
6
6
|
"bin": {
|
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
"cron": "^4.4.0",
|
|
31
31
|
"orator": "^6.0.4",
|
|
32
32
|
"orator-authentication": "^1.0.0",
|
|
33
|
-
"orator-serviceserver-restify": "^2.0.
|
|
34
|
-
"pict": "^1.0.
|
|
33
|
+
"orator-serviceserver-restify": "^2.0.10",
|
|
34
|
+
"pict": "^1.0.361",
|
|
35
35
|
"pict-service-commandlineutility": "^1.0.19",
|
|
36
36
|
"pict-serviceproviderbase": "^1.0.4",
|
|
37
|
-
"ultravisor-beacon": "^0.0.
|
|
37
|
+
"ultravisor-beacon": "^0.0.8",
|
|
38
38
|
"ws": "^8.20.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
@@ -40,6 +40,9 @@ class UltravisorBeaconClient
|
|
|
40
40
|
this._ActiveWorkItems = 0;
|
|
41
41
|
this._SessionCookie = null;
|
|
42
42
|
this._Authenticating = false;
|
|
43
|
+
this._ReconnectPending = false;
|
|
44
|
+
this._ReconnectAttempts = 0;
|
|
45
|
+
this._MaxReconnectDelayMs = 300000; // Cap at 5 minutes
|
|
43
46
|
|
|
44
47
|
this._Executor = new libBeaconExecutor({
|
|
45
48
|
StagingPath: this._Config.StagingPath
|
|
@@ -989,6 +989,23 @@ class UltravisorBeaconCoordinator extends libPictService
|
|
|
989
989
|
{
|
|
990
990
|
this._tryPushToWebSocketBeacon(tmpWorkItem);
|
|
991
991
|
}
|
|
992
|
+
else if (tmpWorkItem.Status === 'Assigned' && tmpWorkItem.AssignedBeaconID && this._WorkItemPushHandler)
|
|
993
|
+
{
|
|
994
|
+
// Affinity pre-assigned — push directly to the assigned beacon via WebSocket
|
|
995
|
+
tmpWorkItem.Status = 'Running';
|
|
996
|
+
let tmpPushed = this._WorkItemPushHandler(tmpWorkItem.AssignedBeaconID,
|
|
997
|
+
this._sanitizeWorkItemForBeacon(tmpWorkItem));
|
|
998
|
+
|
|
999
|
+
if (tmpPushed)
|
|
1000
|
+
{
|
|
1001
|
+
this.log.info(`BeaconCoordinator: pushed affinity-assigned work item [${tmpWorkItemHash}] to WebSocket beacon [${tmpWorkItem.AssignedBeaconID}].`);
|
|
1002
|
+
}
|
|
1003
|
+
else
|
|
1004
|
+
{
|
|
1005
|
+
// WebSocket push failed — revert to Assigned for HTTP poll pickup
|
|
1006
|
+
tmpWorkItem.Status = 'Assigned';
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
992
1009
|
|
|
993
1010
|
return tmpWorkItem;
|
|
994
1011
|
}
|
|
@@ -1005,6 +1005,20 @@ class UltravisorExecutionEngine extends libPictService
|
|
|
1005
1005
|
|
|
1006
1006
|
if (typeof(tmpVal) === 'string' && tmpVal.indexOf('{~') >= 0)
|
|
1007
1007
|
{
|
|
1008
|
+
// When the entire value is a single {~D:Record.X~} expression,
|
|
1009
|
+
// resolve via StateManager to preserve non-scalar types
|
|
1010
|
+
// (arrays, objects). parseTemplate always returns strings.
|
|
1011
|
+
let tmpDataMatch = tmpVal.match(/^\{~D:Record\.(.+?)~\}$/);
|
|
1012
|
+
if (tmpDataMatch)
|
|
1013
|
+
{
|
|
1014
|
+
let tmpAddress = tmpDataMatch[1];
|
|
1015
|
+
let tmpResolved = tmpStateManager.resolveAddress(tmpAddress, pContext);
|
|
1016
|
+
if (tmpResolved !== undefined)
|
|
1017
|
+
{
|
|
1018
|
+
tmpSettings[tmpKey] = tmpResolved;
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1008
1022
|
tmpSettings[tmpKey] = this._resolveTemplate(tmpVal, tmpTemplateContext);
|
|
1009
1023
|
}
|
|
1010
1024
|
}
|
|
@@ -174,6 +174,24 @@ function _handleStepComplete(pContext, fCallback)
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Flatten a parameter set object into string-valued output fields.
|
|
179
|
+
* { seed: 42, guidance: 5.0 } → { seed: "42", guidance: "5.0" }
|
|
180
|
+
* This allows direct state wiring: ParameterSweep.seed → Denoise.seed
|
|
181
|
+
*/
|
|
182
|
+
function _flattenParams(pObj)
|
|
183
|
+
{
|
|
184
|
+
let tmpResult = {};
|
|
185
|
+
let tmpKeys = Object.keys(pObj || {});
|
|
186
|
+
for (let i = 0; i < tmpKeys.length; i++)
|
|
187
|
+
{
|
|
188
|
+
let tmpVal = pObj[tmpKeys[i]];
|
|
189
|
+
tmpResult[tmpKeys[i]] = (tmpVal === null || tmpVal === undefined) ? '' : String(tmpVal);
|
|
190
|
+
}
|
|
191
|
+
return tmpResult;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
177
195
|
// ═══════════════════════════════════════════════════════════════════
|
|
178
196
|
// FLOW CONTROL TASK CONFIGS
|
|
179
197
|
// ═══════════════════════════════════════════════════════════════════
|
|
@@ -244,6 +262,104 @@ module.exports =
|
|
|
244
262
|
}
|
|
245
263
|
},
|
|
246
264
|
|
|
265
|
+
// ── parameter-sweep ───────────────────────────────────────
|
|
266
|
+
{
|
|
267
|
+
Definition: require('./definitions/parameter-sweep.json'),
|
|
268
|
+
Execute: function (pTask, pResolvedSettings, pExecutionContext, fCallback)
|
|
269
|
+
{
|
|
270
|
+
if (pExecutionContext.TriggeringEventName === 'StepComplete')
|
|
271
|
+
{
|
|
272
|
+
// Read stored iteration state from prior invocations
|
|
273
|
+
let tmpStoredState = pExecutionContext.TaskOutputs[pExecutionContext.NodeHash] || {};
|
|
274
|
+
let tmpSets = tmpStoredState._ParameterSets;
|
|
275
|
+
|
|
276
|
+
if (!Array.isArray(tmpSets))
|
|
277
|
+
{
|
|
278
|
+
return fCallback(null, {
|
|
279
|
+
EventToFire: 'Error',
|
|
280
|
+
Outputs: { CurrentParameters: '{}', CurrentIndex: 0, TotalCount: 0, CompletedCount: 0 },
|
|
281
|
+
Log: ['StepComplete fired but no stored parameter sets found. Was BeginSweep called?']
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
let tmpCurrentIndex = tmpStoredState.CurrentIndex || 0;
|
|
286
|
+
let tmpCompletedCount = (tmpStoredState.CompletedCount || 0) + 1;
|
|
287
|
+
let tmpNextIndex = tmpCurrentIndex + 1;
|
|
288
|
+
let tmpTotalCount = tmpSets.length;
|
|
289
|
+
|
|
290
|
+
if (tmpNextIndex >= tmpTotalCount)
|
|
291
|
+
{
|
|
292
|
+
let tmpLast = tmpSets[tmpTotalCount - 1] || {};
|
|
293
|
+
let tmpOutputs = Object.assign(
|
|
294
|
+
{ _ParameterSets: tmpSets, CurrentParameters: JSON.stringify(tmpLast), CurrentIndex: tmpTotalCount - 1, TotalCount: tmpTotalCount, CompletedCount: tmpCompletedCount },
|
|
295
|
+
_flattenParams(tmpLast));
|
|
296
|
+
return fCallback(null, {
|
|
297
|
+
EventToFire: 'SweepComplete',
|
|
298
|
+
Outputs: tmpOutputs,
|
|
299
|
+
Log: ['Parameter sweep complete. Processed ' + tmpCompletedCount + '/' + tmpTotalCount + ' set(s).']
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
let tmpNext = tmpSets[tmpNextIndex] || {};
|
|
304
|
+
let tmpOutputs = Object.assign(
|
|
305
|
+
{ _ParameterSets: tmpSets, CurrentParameters: JSON.stringify(tmpNext), CurrentIndex: tmpNextIndex, TotalCount: tmpTotalCount, CompletedCount: tmpCompletedCount },
|
|
306
|
+
_flattenParams(tmpNext));
|
|
307
|
+
return fCallback(null, {
|
|
308
|
+
EventToFire: 'ParameterSetReady',
|
|
309
|
+
Outputs: tmpOutputs,
|
|
310
|
+
Log: ['Emitting parameter set ' + (tmpNextIndex + 1) + '/' + tmpTotalCount + '.']
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// BeginSweep — parse the array and emit the first set
|
|
315
|
+
let tmpRawSets = pResolvedSettings.ParameterSets;
|
|
316
|
+
let tmpSets;
|
|
317
|
+
if (typeof tmpRawSets === 'string')
|
|
318
|
+
{
|
|
319
|
+
try { tmpSets = JSON.parse(tmpRawSets); }
|
|
320
|
+
catch (pErr)
|
|
321
|
+
{
|
|
322
|
+
return fCallback(null, {
|
|
323
|
+
EventToFire: 'Error',
|
|
324
|
+
Outputs: { CurrentParameters: '{}', CurrentIndex: 0, TotalCount: 0, CompletedCount: 0 },
|
|
325
|
+
Log: ['ParameterSets is not valid JSON: ' + pErr.message]
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
else if (Array.isArray(tmpRawSets))
|
|
330
|
+
{
|
|
331
|
+
tmpSets = tmpRawSets;
|
|
332
|
+
}
|
|
333
|
+
else
|
|
334
|
+
{
|
|
335
|
+
return fCallback(null, {
|
|
336
|
+
EventToFire: 'Error',
|
|
337
|
+
Outputs: { CurrentParameters: '{}', CurrentIndex: 0, TotalCount: 0, CompletedCount: 0 },
|
|
338
|
+
Log: ['ParameterSets must be a JSON array.']
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
if (!Array.isArray(tmpSets) || tmpSets.length === 0)
|
|
343
|
+
{
|
|
344
|
+
return fCallback(null, {
|
|
345
|
+
EventToFire: 'SweepComplete',
|
|
346
|
+
Outputs: { CurrentParameters: '{}', CurrentIndex: 0, TotalCount: 0, CompletedCount: 0 },
|
|
347
|
+
Log: ['ParameterSets is empty. Nothing to sweep.']
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
let tmpFirst = tmpSets[0] || {};
|
|
352
|
+
let tmpOutputs = Object.assign(
|
|
353
|
+
{ _ParameterSets: tmpSets, CurrentParameters: JSON.stringify(tmpFirst), CurrentIndex: 0, TotalCount: tmpSets.length, CompletedCount: 0 },
|
|
354
|
+
_flattenParams(tmpFirst));
|
|
355
|
+
return fCallback(null, {
|
|
356
|
+
EventToFire: 'ParameterSetReady',
|
|
357
|
+
Outputs: tmpOutputs,
|
|
358
|
+
Log: ['Parameter sweep started: ' + tmpSets.length + ' set(s). Emitting set 1/' + tmpSets.length + '.']
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
|
|
247
363
|
// ── launch-operation ───────────────────────────────────────
|
|
248
364
|
{
|
|
249
365
|
Definition: require('./definitions/launch-operation.json'),
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parameter Sweep — Iterates over a JSON array of parameter sets.
|
|
3
|
+
*
|
|
4
|
+
* Each entry in the array becomes one execution of downstream tasks.
|
|
5
|
+
* Individual fields from each parameter set are exposed as state outputs
|
|
6
|
+
* so they can be wired into downstream card settings.
|
|
7
|
+
*
|
|
8
|
+
* Graph pattern (identical to split-execute):
|
|
9
|
+
* ParameterSweep.ParameterSetReady → [downstream tasks] → ParameterSweep.StepComplete
|
|
10
|
+
* ParameterSweep.SweepComplete → [whatever follows the loop]
|
|
11
|
+
*
|
|
12
|
+
* Example ParameterSets input:
|
|
13
|
+
* [{"seed": 42, "guidance": 5.0}, {"seed": 123, "guidance": 7.0}]
|
|
14
|
+
*
|
|
15
|
+
* State outputs per iteration:
|
|
16
|
+
* CurrentParameters: '{"seed": 42, "guidance": 5.0}' (full object as JSON string)
|
|
17
|
+
* CurrentIndex: 0
|
|
18
|
+
* TotalCount: 2
|
|
19
|
+
* CompletedCount: 0
|
|
20
|
+
* Plus: each key from the current parameter set is also exposed directly
|
|
21
|
+
* e.g., "seed" = "42", "guidance" = "5.0" (all coerced to strings)
|
|
22
|
+
*
|
|
23
|
+
* @license MIT
|
|
24
|
+
* @author Steven Velozo <steven@velozo.com>
|
|
25
|
+
*/
|
|
26
|
+
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
const libTaskTypeBase = require('../Ultravisor-TaskType-Base.cjs');
|
|
30
|
+
const libPath = require('path');
|
|
31
|
+
|
|
32
|
+
class TaskTypeParameterSweep extends libTaskTypeBase
|
|
33
|
+
{
|
|
34
|
+
constructor(pFable, pOptions, pServiceHash)
|
|
35
|
+
{
|
|
36
|
+
super(pFable, pOptions, pServiceHash);
|
|
37
|
+
this.serviceType = 'TaskType-ParameterSweep';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
get definition()
|
|
41
|
+
{
|
|
42
|
+
return require('./definitions/parameter-sweep.json');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
execute(pResolvedSettings, pExecutionContext, fCallback, fFireIntermediateEvent)
|
|
46
|
+
{
|
|
47
|
+
let tmpTrigger = pExecutionContext.TriggeringEventName || 'BeginSweep';
|
|
48
|
+
|
|
49
|
+
if (tmpTrigger === 'StepComplete')
|
|
50
|
+
{
|
|
51
|
+
return this._handleStepComplete(pResolvedSettings, pExecutionContext, fCallback);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return this._handleBeginSweep(pResolvedSettings, pExecutionContext, fCallback);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
_handleBeginSweep(pSettings, pContext, fCallback)
|
|
58
|
+
{
|
|
59
|
+
let tmpRawSets = pSettings.ParameterSets;
|
|
60
|
+
|
|
61
|
+
// Parse the parameter array
|
|
62
|
+
let tmpSets;
|
|
63
|
+
if (typeof tmpRawSets === 'string')
|
|
64
|
+
{
|
|
65
|
+
try
|
|
66
|
+
{
|
|
67
|
+
tmpSets = JSON.parse(tmpRawSets);
|
|
68
|
+
}
|
|
69
|
+
catch (pError)
|
|
70
|
+
{
|
|
71
|
+
return fCallback(null,
|
|
72
|
+
{
|
|
73
|
+
EventToFire: 'Error',
|
|
74
|
+
Outputs: { CurrentParameters: '{}', CurrentIndex: 0, TotalCount: 0, CompletedCount: 0 },
|
|
75
|
+
Log: ['ParameterSets is not valid JSON: ' + pError.message]
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
else if (Array.isArray(tmpRawSets))
|
|
80
|
+
{
|
|
81
|
+
tmpSets = tmpRawSets;
|
|
82
|
+
}
|
|
83
|
+
else
|
|
84
|
+
{
|
|
85
|
+
return fCallback(null,
|
|
86
|
+
{
|
|
87
|
+
EventToFire: 'Error',
|
|
88
|
+
Outputs: { CurrentParameters: '{}', CurrentIndex: 0, TotalCount: 0, CompletedCount: 0 },
|
|
89
|
+
Log: ['ParameterSets must be a JSON array string or an array.']
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (!Array.isArray(tmpSets) || tmpSets.length === 0)
|
|
94
|
+
{
|
|
95
|
+
return fCallback(null,
|
|
96
|
+
{
|
|
97
|
+
EventToFire: 'Error',
|
|
98
|
+
Outputs: { CurrentParameters: '{}', CurrentIndex: 0, TotalCount: 0, CompletedCount: 0 },
|
|
99
|
+
Log: ['ParameterSets is empty or not an array.']
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// Emit the first parameter set
|
|
104
|
+
let tmpFirst = tmpSets[0] || {};
|
|
105
|
+
let tmpOutputs = this._buildOutputs(tmpSets, tmpFirst, 0, 0);
|
|
106
|
+
|
|
107
|
+
return fCallback(null,
|
|
108
|
+
{
|
|
109
|
+
EventToFire: 'ParameterSetReady',
|
|
110
|
+
Outputs: tmpOutputs,
|
|
111
|
+
Log: ['Parameter sweep started: ' + tmpSets.length + ' set(s). Emitting set 1/' + tmpSets.length + '.']
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
_handleStepComplete(pSettings, pContext, fCallback)
|
|
116
|
+
{
|
|
117
|
+
// Read stored iteration state from prior invocations
|
|
118
|
+
let tmpStoredState = pContext.TaskOutputs[pContext.NodeHash] || {};
|
|
119
|
+
let tmpSets = tmpStoredState._ParameterSets;
|
|
120
|
+
|
|
121
|
+
if (!tmpSets || !Array.isArray(tmpSets))
|
|
122
|
+
{
|
|
123
|
+
return fCallback(null,
|
|
124
|
+
{
|
|
125
|
+
EventToFire: 'Error',
|
|
126
|
+
Outputs: { CurrentParameters: '{}', CurrentIndex: 0, TotalCount: 0, CompletedCount: 0 },
|
|
127
|
+
Log: ['StepComplete fired but no stored parameter sets found. Was BeginSweep called?']
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
let tmpCurrentIndex = (tmpStoredState.CurrentIndex || 0);
|
|
132
|
+
let tmpCompletedCount = (tmpStoredState.CompletedCount || 0) + 1;
|
|
133
|
+
let tmpNextIndex = tmpCurrentIndex + 1;
|
|
134
|
+
let tmpTotalCount = tmpSets.length;
|
|
135
|
+
|
|
136
|
+
if (tmpNextIndex >= tmpTotalCount)
|
|
137
|
+
{
|
|
138
|
+
// All parameter sets have been processed
|
|
139
|
+
let tmpLast = tmpSets[tmpTotalCount - 1] || {};
|
|
140
|
+
let tmpOutputs = this._buildOutputs(tmpSets, tmpLast, tmpTotalCount - 1, tmpCompletedCount);
|
|
141
|
+
|
|
142
|
+
return fCallback(null,
|
|
143
|
+
{
|
|
144
|
+
EventToFire: 'SweepComplete',
|
|
145
|
+
Outputs: tmpOutputs,
|
|
146
|
+
Log: ['Parameter sweep complete. Processed ' + tmpCompletedCount + '/' + tmpTotalCount + ' set(s).']
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Emit the next parameter set
|
|
151
|
+
let tmpNext = tmpSets[tmpNextIndex] || {};
|
|
152
|
+
let tmpOutputs = this._buildOutputs(tmpSets, tmpNext, tmpNextIndex, tmpCompletedCount);
|
|
153
|
+
|
|
154
|
+
return fCallback(null,
|
|
155
|
+
{
|
|
156
|
+
EventToFire: 'ParameterSetReady',
|
|
157
|
+
Outputs: tmpOutputs,
|
|
158
|
+
Log: ['Emitting parameter set ' + (tmpNextIndex + 1) + '/' + tmpTotalCount + '.']
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Build the output object for a given parameter set.
|
|
164
|
+
* Includes internal state (_ParameterSets), iteration counters,
|
|
165
|
+
* the full current set as JSON, and each individual field flattened.
|
|
166
|
+
*/
|
|
167
|
+
_buildOutputs(pSets, pCurrentSet, pIndex, pCompletedCount)
|
|
168
|
+
{
|
|
169
|
+
let tmpOutputs =
|
|
170
|
+
{
|
|
171
|
+
// Internal state (prefixed with _ so it doesn't show in the flow editor)
|
|
172
|
+
_ParameterSets: pSets,
|
|
173
|
+
|
|
174
|
+
// Iteration counters
|
|
175
|
+
CurrentParameters: JSON.stringify(pCurrentSet),
|
|
176
|
+
CurrentIndex: pIndex,
|
|
177
|
+
TotalCount: pSets.length,
|
|
178
|
+
CompletedCount: pCompletedCount
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Flatten each field from the current parameter set as a top-level output.
|
|
182
|
+
// This allows direct state wiring: ParameterSweep.seed → Denoise.seed
|
|
183
|
+
// without the downstream card needing to parse JSON.
|
|
184
|
+
let tmpKeys = Object.keys(pCurrentSet);
|
|
185
|
+
for (let i = 0; i < tmpKeys.length; i++)
|
|
186
|
+
{
|
|
187
|
+
let tmpKey = tmpKeys[i];
|
|
188
|
+
let tmpVal = pCurrentSet[tmpKey];
|
|
189
|
+
// Coerce to string for consistent state wiring (the engine handles type coercion downstream)
|
|
190
|
+
tmpOutputs[tmpKey] = (tmpVal === null || tmpVal === undefined) ? '' : String(tmpVal);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return tmpOutputs;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
module.exports = TaskTypeParameterSweep;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"Hash": "parameter-sweep",
|
|
3
|
+
"Type": "parameter-sweep",
|
|
4
|
+
"Name": "Parameter Sweep",
|
|
5
|
+
"Description": "Iterates over a JSON array of parameter sets, firing one execution per entry. Each entry's fields are exposed as state outputs for downstream cards. Use for batch experiments with different seeds, models, LoRAs, guidance scales, etc.",
|
|
6
|
+
"Category": "flow-control",
|
|
7
|
+
"Capability": "Flow Control",
|
|
8
|
+
"Action": "ParameterSweep",
|
|
9
|
+
"Tier": "Engine",
|
|
10
|
+
|
|
11
|
+
"EventInputs": [
|
|
12
|
+
{ "Name": "BeginSweep", "Description": "Start iterating through the parameter array" },
|
|
13
|
+
{ "Name": "StepComplete", "Description": "Signal that the current parameter set has been fully processed by downstream tasks" }
|
|
14
|
+
],
|
|
15
|
+
|
|
16
|
+
"EventOutputs": [
|
|
17
|
+
{ "Name": "ParameterSetReady", "Description": "Fired for each parameter set in the array. Downstream tasks receive the current parameters via state connections." },
|
|
18
|
+
{ "Name": "SweepComplete", "Description": "Fired after all parameter sets have been processed." },
|
|
19
|
+
{ "Name": "Error", "Description": "Fired if the parameter array is invalid or empty.", "IsError": true }
|
|
20
|
+
],
|
|
21
|
+
|
|
22
|
+
"SettingsInputs": [
|
|
23
|
+
{ "Name": "ParameterSets", "DataType": "String", "Required": true, "Default": "[{\"seed\": 42}, {\"seed\": 123}, {\"seed\": 456}]", "Description": "JSON array of objects. Each object is one set of parameters to sweep through. Example: [{\"seed\": 42, \"guidance\": 5.0}, {\"seed\": 123, \"guidance\": 7.0}]" }
|
|
24
|
+
],
|
|
25
|
+
|
|
26
|
+
"StateOutputs": [
|
|
27
|
+
{ "Name": "CurrentParameters", "DataType": "String", "Description": "JSON string of the current parameter set object (e.g., {\"seed\": 42, \"guidance\": 5.0})" },
|
|
28
|
+
{ "Name": "CurrentIndex", "DataType": "Number", "Description": "0-based index of the current parameter set" },
|
|
29
|
+
{ "Name": "TotalCount", "DataType": "Number", "Description": "Total number of parameter sets in the array" },
|
|
30
|
+
{ "Name": "CompletedCount", "DataType": "Number", "Description": "Number of parameter sets fully processed so far" }
|
|
31
|
+
],
|
|
32
|
+
|
|
33
|
+
"DefaultSettings": {
|
|
34
|
+
"ParameterSets": "[{\"seed\": 42}, {\"seed\": 123}, {\"seed\": 456}]"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/test/Ultravisor_tests.js
CHANGED
|
@@ -190,7 +190,7 @@ suite
|
|
|
190
190
|
Expect(tmpInstance.definition.Hash).to.equal('read-file');
|
|
191
191
|
|
|
192
192
|
let tmpDefs = tmpRegistry.listDefinitions();
|
|
193
|
-
Expect(tmpDefs.length).to.equal(
|
|
193
|
+
Expect(tmpDefs.length).to.equal(54);
|
|
194
194
|
|
|
195
195
|
// Verify all registered definitions have Capability, Action, and Tier
|
|
196
196
|
for (let i = 0; i < tmpDefs.length; i++)
|
|
@@ -1806,7 +1806,7 @@ suite
|
|
|
1806
1806
|
let tmpBuiltInConfigs = require('../source/services/tasks/Ultravisor-BuiltIn-TaskConfigs.cjs');
|
|
1807
1807
|
let tmpCount = tmpRegistry.registerTaskTypesFromConfigArray(tmpBuiltInConfigs);
|
|
1808
1808
|
|
|
1809
|
-
Expect(tmpCount).to.equal(
|
|
1809
|
+
Expect(tmpCount).to.equal(54);
|
|
1810
1810
|
|
|
1811
1811
|
// Spot-check a few
|
|
1812
1812
|
Expect(tmpRegistry.hasTaskType('error-message')).to.equal(true);
|
|
@@ -1991,7 +1991,7 @@ suite
|
|
|
1991
1991
|
|
|
1992
1992
|
// Configs already registered by createTestFable — verify all present
|
|
1993
1993
|
let tmpDefs = tmpRegistry.listDefinitions();
|
|
1994
|
-
Expect(tmpDefs.length).to.equal(
|
|
1994
|
+
Expect(tmpDefs.length).to.equal(54);
|
|
1995
1995
|
}
|
|
1996
1996
|
);
|
|
1997
1997
|
}
|