@swizzy_ai/kit 1.0.0 β 1.0.2
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/README.md +835 -439
- package/dist/core/wizard/bungee/executor.d.ts +15 -0
- package/dist/core/wizard/bungee/executor.d.ts.map +1 -0
- package/dist/core/wizard/context-manager.d.ts +9 -0
- package/dist/core/wizard/context-manager.d.ts.map +1 -0
- package/dist/core/wizard/logger.d.ts +8 -0
- package/dist/core/wizard/logger.d.ts.map +1 -0
- package/dist/core/wizard/schema-utils.d.ts +16 -0
- package/dist/core/wizard/schema-utils.d.ts.map +1 -0
- package/dist/core/wizard/step-data-generator.d.ts +24 -0
- package/dist/core/wizard/step-data-generator.d.ts.map +1 -0
- package/dist/core/wizard/steps/base.d.ts +4 -3
- package/dist/core/wizard/steps/base.d.ts.map +1 -1
- package/dist/core/wizard/steps/compute.d.ts +2 -2
- package/dist/core/wizard/steps/compute.d.ts.map +1 -1
- package/dist/core/wizard/steps/text.d.ts +2 -2
- package/dist/core/wizard/steps/text.d.ts.map +1 -1
- package/dist/core/wizard/usage-tracker.d.ts +25 -0
- package/dist/core/wizard/usage-tracker.d.ts.map +1 -0
- package/dist/core/wizard/visualization-manager.d.ts +34 -0
- package/dist/core/wizard/visualization-manager.d.ts.map +1 -0
- package/dist/core/wizard/wizard.d.ts +39 -40
- package/dist/core/wizard/wizard.d.ts.map +1 -1
- package/dist/index.d.ts +46 -46
- package/dist/index.js +1207 -626
- package/dist/index.js.map +1 -1
- package/dist/services/client/index.d.ts +1 -0
- package/dist/services/client/index.d.ts.map +1 -1
- package/dist/services/client/providers.d.ts.map +1 -1
- package/dist/ui/wizard-visualizer.html +570 -385
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -3,15 +3,15 @@
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
5
|
var zod = require('zod');
|
|
6
|
-
var fs = require('fs');
|
|
7
|
-
var path = require('path');
|
|
8
|
-
var http = require('http');
|
|
9
|
-
var WebSocket = require('ws');
|
|
10
6
|
var openai = require('@ai-sdk/openai');
|
|
11
7
|
var anthropic = require('@ai-sdk/anthropic');
|
|
12
8
|
var google = require('@ai-sdk/google');
|
|
13
9
|
var xai = require('@ai-sdk/xai');
|
|
14
10
|
var ai = require('ai');
|
|
11
|
+
var http = require('http');
|
|
12
|
+
var WebSocket = require('ws');
|
|
13
|
+
var fs = require('fs');
|
|
14
|
+
var path = require('path');
|
|
15
15
|
|
|
16
16
|
function _interopNamespaceDefault(e) {
|
|
17
17
|
var n = Object.create(null);
|
|
@@ -30,18 +30,19 @@ function _interopNamespaceDefault(e) {
|
|
|
30
30
|
return Object.freeze(n);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
34
|
-
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
35
33
|
var http__namespace = /*#__PURE__*/_interopNamespaceDefault(http);
|
|
36
34
|
var WebSocket__namespace = /*#__PURE__*/_interopNamespaceDefault(WebSocket);
|
|
35
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
36
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
37
37
|
|
|
38
38
|
class Step {
|
|
39
39
|
constructor(config) {
|
|
40
|
+
this.executionCount = 0;
|
|
40
41
|
this.id = config.id;
|
|
41
42
|
this.instruction = config.instruction;
|
|
42
43
|
this.schema = config.schema;
|
|
43
44
|
this.update = config.update;
|
|
44
|
-
this.
|
|
45
|
+
this.context = config.context;
|
|
45
46
|
this.contextType = config.contextType || 'xml'; // Default to xml
|
|
46
47
|
this.beforeRun = config.beforeRun;
|
|
47
48
|
this.afterRun = config.afterRun;
|
|
@@ -55,7 +56,7 @@ class Step {
|
|
|
55
56
|
return result.data;
|
|
56
57
|
}
|
|
57
58
|
getContext(workflowContext) {
|
|
58
|
-
return this.
|
|
59
|
+
return this.context ? this.context(workflowContext) : workflowContext;
|
|
59
60
|
}
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -142,7 +143,7 @@ class SwizzyProvider extends BaseProvider {
|
|
|
142
143
|
this.config = { baseURL, apiKey };
|
|
143
144
|
}
|
|
144
145
|
async complete(options) {
|
|
145
|
-
const endpoint =
|
|
146
|
+
const endpoint = '/completions';
|
|
146
147
|
const response = await fetch(`${this.config.baseURL}${endpoint}`, {
|
|
147
148
|
method: 'POST',
|
|
148
149
|
headers: {
|
|
@@ -153,6 +154,7 @@ class SwizzyProvider extends BaseProvider {
|
|
|
153
154
|
prompt: options.prompt,
|
|
154
155
|
max_tokens: options.maxTokens || 1000,
|
|
155
156
|
temperature: options.temperature || 0.7,
|
|
157
|
+
stream: options.stream || false,
|
|
156
158
|
}),
|
|
157
159
|
});
|
|
158
160
|
if (!response.ok) {
|
|
@@ -173,13 +175,43 @@ class SwizzyProvider extends BaseProvider {
|
|
|
173
175
|
if (!reader)
|
|
174
176
|
throw new Error('No response body for streaming');
|
|
175
177
|
let fullText = '';
|
|
178
|
+
let buffer = '';
|
|
176
179
|
const decoder = new TextDecoder();
|
|
177
180
|
while (true) {
|
|
178
181
|
const { done, value } = await reader.read();
|
|
179
182
|
if (done)
|
|
180
183
|
break;
|
|
181
|
-
|
|
182
|
-
|
|
184
|
+
buffer += decoder.decode(value, { stream: true });
|
|
185
|
+
const events = buffer.split('\n\n');
|
|
186
|
+
buffer = events.pop() || '';
|
|
187
|
+
for (const event of events) {
|
|
188
|
+
if (event.startsWith('data: ')) {
|
|
189
|
+
const data = event.slice(6);
|
|
190
|
+
if (data === '[DONE]') {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
try {
|
|
194
|
+
const parsed = JSON.parse(data);
|
|
195
|
+
if (parsed.response !== undefined) {
|
|
196
|
+
if (options.onChunk) {
|
|
197
|
+
options.onChunk(parsed.response);
|
|
198
|
+
}
|
|
199
|
+
fullText += parsed.response;
|
|
200
|
+
}
|
|
201
|
+
if (parsed.usage && options.onUsage) {
|
|
202
|
+
const usage = {
|
|
203
|
+
promptTokens: parsed.usage.prompt_tokens || 0,
|
|
204
|
+
completionTokens: parsed.usage.completion_tokens || 0,
|
|
205
|
+
totalTokens: parsed.usage.total_tokens || 0,
|
|
206
|
+
};
|
|
207
|
+
options.onUsage(usage, this.getProviderName());
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
catch (e) {
|
|
211
|
+
console.error('Failed to parse SSE data:', data, e);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
}
|
|
183
215
|
}
|
|
184
216
|
return { text: fullText };
|
|
185
217
|
}
|
|
@@ -386,297 +418,918 @@ class BungeeBuilder {
|
|
|
386
418
|
}
|
|
387
419
|
}
|
|
388
420
|
|
|
389
|
-
class
|
|
390
|
-
|
|
391
|
-
this.
|
|
392
|
-
|
|
393
|
-
// Bungee state tracking
|
|
394
|
-
this.bungeeWorkers = new Map();
|
|
395
|
-
this.pendingReentry = new Set();
|
|
396
|
-
// Performance optimizations
|
|
397
|
-
this.stepIndexMap = new Map();
|
|
398
|
-
this.schemaDescriptions = new Map();
|
|
399
|
-
this.maxCacheSize = 100;
|
|
400
|
-
this.connectedClients = new Set();
|
|
401
|
-
this.maxWebSocketConnections = 10;
|
|
402
|
-
this.wsIntervals = new WeakMap();
|
|
403
|
-
// WebSocket messages sent immediately for real-time UI updates
|
|
404
|
-
// Token tracking
|
|
405
|
-
this.totalTokens = 0;
|
|
406
|
-
this.stepTokens = 0;
|
|
407
|
-
this.currentStepIndex = 0;
|
|
408
|
-
this.isPaused = false;
|
|
409
|
-
this.isRunning = false;
|
|
410
|
-
this.isStepMode = false;
|
|
411
|
-
this.id = config.id;
|
|
412
|
-
const registry = new ProviderRegistry();
|
|
413
|
-
this.llmClient = new LLMClient(registry);
|
|
414
|
-
this.systemPrompt = config.systemPrompt;
|
|
415
|
-
// Set up token tracking
|
|
416
|
-
if (config.onUsage) {
|
|
417
|
-
const originalOnUsage = config.onUsage;
|
|
418
|
-
config.onUsage = (usage, provider) => {
|
|
419
|
-
this.totalTokens += usage.totalTokens;
|
|
420
|
-
this.stepTokens = usage.totalTokens; // Last step tokens
|
|
421
|
-
this.sendToClients({
|
|
422
|
-
type: 'token_update',
|
|
423
|
-
totalTokens: this.totalTokens,
|
|
424
|
-
stepTokens: this.stepTokens
|
|
425
|
-
});
|
|
426
|
-
originalOnUsage(usage, provider);
|
|
427
|
-
};
|
|
421
|
+
class SchemaUtils {
|
|
422
|
+
static describeSchema(schema, stepId) {
|
|
423
|
+
if (stepId && this.schemaDescriptions.has(stepId)) {
|
|
424
|
+
return this.schemaDescriptions.get(stepId);
|
|
428
425
|
}
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
426
|
+
let description;
|
|
427
|
+
if (schema instanceof zod.z.ZodObject) {
|
|
428
|
+
const shape = schema._def.shape();
|
|
429
|
+
const fields = Object.entries(shape).map(([key, fieldSchema]) => {
|
|
430
|
+
const type = this.getSchemaType(fieldSchema);
|
|
431
|
+
const xmlExample = this.getXmlExample(key, type);
|
|
432
|
+
return `${key}: ${type} - ${xmlExample}`;
|
|
433
|
+
});
|
|
434
|
+
description = `Object with fields:\n${fields.join('\n')}`;
|
|
433
435
|
}
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
log(messageOrFn) {
|
|
437
|
-
if (!this.logFilePath)
|
|
438
|
-
return; // Early exit if logging disabled
|
|
439
|
-
const message = typeof messageOrFn === 'function' ? messageOrFn() : messageOrFn;
|
|
440
|
-
const content = `${new Date().toISOString()}: ${message}\n`;
|
|
441
|
-
this.appendToFile(content);
|
|
442
|
-
}
|
|
443
|
-
appendToFile(content) {
|
|
444
|
-
if (!this.logFilePath)
|
|
445
|
-
return;
|
|
446
|
-
try {
|
|
447
|
-
fs__namespace.appendFileSync(this.logFilePath, content, 'utf8');
|
|
436
|
+
else {
|
|
437
|
+
description = 'Unknown schema type';
|
|
448
438
|
}
|
|
449
|
-
|
|
450
|
-
|
|
439
|
+
if (stepId) {
|
|
440
|
+
// Implement simple cache eviction if needed
|
|
441
|
+
if (this.schemaDescriptions.size >= this.maxCacheSize) {
|
|
442
|
+
const firstKey = this.schemaDescriptions.keys().next().value;
|
|
443
|
+
this.schemaDescriptions.delete(firstKey || '');
|
|
444
|
+
}
|
|
445
|
+
this.schemaDescriptions.set(stepId, description);
|
|
451
446
|
}
|
|
447
|
+
return description;
|
|
452
448
|
}
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
// Add all parallel steps to index with same position
|
|
468
|
-
parallelSteps.forEach(step => this.stepIndexMap.set(step.id, index));
|
|
469
|
-
return this;
|
|
470
|
-
}
|
|
471
|
-
addTextStep(config) {
|
|
472
|
-
const step = new TextStep(config);
|
|
473
|
-
const index = this.steps.length;
|
|
474
|
-
this.steps.push(step);
|
|
475
|
-
this.stepIndexMap.set(step.id, index);
|
|
476
|
-
return this;
|
|
477
|
-
}
|
|
478
|
-
addComputeStep(config) {
|
|
479
|
-
const step = new ComputeStep(config);
|
|
480
|
-
const index = this.steps.length;
|
|
481
|
-
this.steps.push(step);
|
|
482
|
-
this.stepIndexMap.set(step.id, index);
|
|
483
|
-
return this;
|
|
484
|
-
}
|
|
485
|
-
goto(stepId) { return `GOTO ${stepId}`; }
|
|
486
|
-
next() { return 'NEXT'; }
|
|
487
|
-
stop() { return 'STOP'; }
|
|
488
|
-
retry() { return 'RETRY'; }
|
|
489
|
-
wait() { return 'WAIT'; }
|
|
490
|
-
clearStepError(stepId) {
|
|
491
|
-
delete this.workflowContext[`${stepId}_error`];
|
|
492
|
-
delete this.workflowContext[`${stepId}_retryCount`];
|
|
493
|
-
}
|
|
494
|
-
isStringSignal(signal) {
|
|
495
|
-
return typeof signal === 'string';
|
|
496
|
-
}
|
|
497
|
-
isBungeeJumpSignal(signal) {
|
|
498
|
-
return typeof signal === 'object' && signal !== null && signal.type === 'BUNGEE_JUMP';
|
|
449
|
+
static getSchemaType(schema) {
|
|
450
|
+
if (schema instanceof zod.z.ZodOptional)
|
|
451
|
+
return this.getSchemaType(schema._def.innerType);
|
|
452
|
+
if (schema instanceof zod.z.ZodString)
|
|
453
|
+
return 'string';
|
|
454
|
+
if (schema instanceof zod.z.ZodNumber)
|
|
455
|
+
return 'number';
|
|
456
|
+
if (schema instanceof zod.z.ZodBoolean)
|
|
457
|
+
return 'boolean';
|
|
458
|
+
if (schema instanceof zod.z.ZodArray)
|
|
459
|
+
return 'array';
|
|
460
|
+
if (schema instanceof zod.z.ZodEnum)
|
|
461
|
+
return `enum: ${schema._def.values.join(', ')}`;
|
|
462
|
+
return 'object';
|
|
499
463
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
const
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
// Clean up completed workers
|
|
512
|
-
for (const promise of activeWorkers) {
|
|
513
|
-
if (promise !== workerPromise) {
|
|
514
|
-
activeWorkers.delete(promise);
|
|
515
|
-
}
|
|
516
|
-
}
|
|
464
|
+
static extractSchemaFields(schema) {
|
|
465
|
+
if (!(schema instanceof zod.z.ZodObject))
|
|
466
|
+
return [];
|
|
467
|
+
const shape = schema._def.shape();
|
|
468
|
+
const fields = [];
|
|
469
|
+
for (const [key, fieldSchema] of Object.entries(shape)) {
|
|
470
|
+
const type = this.getSchemaType(fieldSchema);
|
|
471
|
+
const field = { key, type };
|
|
472
|
+
if (type.startsWith('enum:')) {
|
|
473
|
+
field.type = 'enum';
|
|
474
|
+
field.enumValues = type.substring(5).split(', ');
|
|
517
475
|
}
|
|
476
|
+
fields.push(field);
|
|
518
477
|
}
|
|
519
|
-
|
|
520
|
-
await Promise.all(activeWorkers);
|
|
521
|
-
console.log(`β
Bungee plan ${plan.id} completed, returning to anchor ${plan.anchorId}`);
|
|
478
|
+
return fields;
|
|
522
479
|
}
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
this.bungeeWorkers.get(plan.id).set(workerId, {
|
|
534
|
-
planId: plan.id,
|
|
535
|
-
workerId,
|
|
536
|
-
promise,
|
|
537
|
-
telescope
|
|
538
|
-
});
|
|
539
|
-
try {
|
|
540
|
-
await promise;
|
|
541
|
-
}
|
|
542
|
-
catch (error) {
|
|
543
|
-
console.error(`Bungee worker ${workerId} failed:`, error);
|
|
544
|
-
this.workflowContext[`${workerId}_error`] = error.message;
|
|
545
|
-
}
|
|
546
|
-
finally {
|
|
547
|
-
// Clean up
|
|
548
|
-
const planWorkers = this.bungeeWorkers.get(plan.id);
|
|
549
|
-
if (planWorkers) {
|
|
550
|
-
planWorkers.delete(workerId);
|
|
551
|
-
if (planWorkers.size === 0) {
|
|
552
|
-
this.bungeeWorkers.delete(plan.id);
|
|
553
|
-
// Trigger reentry to anchor
|
|
554
|
-
this.pendingReentry.add(plan.anchorId);
|
|
480
|
+
static getXmlExample(key, type) {
|
|
481
|
+
switch (type) {
|
|
482
|
+
case 'string': return `<${key} tag-category="wizard" type="string">example`;
|
|
483
|
+
case 'number': return `<${key} tag-category="wizard" type="number">123`;
|
|
484
|
+
case 'boolean': return `<${key} tag-category="wizard" type="boolean">true`;
|
|
485
|
+
case 'array': return `<${key} tag-category="wizard" type="array">["item1", "item2"]`;
|
|
486
|
+
default:
|
|
487
|
+
if (type.startsWith('enum:')) {
|
|
488
|
+
const values = type.split(': ')[1].split(', ');
|
|
489
|
+
return `<${key} tag-category="wizard" type="string">${values[0]}`;
|
|
555
490
|
}
|
|
556
|
-
|
|
491
|
+
return `<${key} tag-category="wizard" type="object"><subfield type="string">value</subfield>`;
|
|
557
492
|
}
|
|
558
493
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
494
|
+
static objectToXml(obj, rootName = 'context') {
|
|
495
|
+
const buildXml = (data, tagName) => {
|
|
496
|
+
let type = typeof data;
|
|
497
|
+
if (data === null)
|
|
498
|
+
type = 'null';
|
|
499
|
+
else if (Array.isArray(data))
|
|
500
|
+
type = 'array';
|
|
501
|
+
const attr = ` type="${type}"`;
|
|
502
|
+
if (data === null || data === undefined)
|
|
503
|
+
return `<${tagName}${attr}></${tagName}>`;
|
|
504
|
+
if (type === 'string')
|
|
505
|
+
return `<${tagName}${attr}>${this.escapeXml(data)}</${tagName}>`;
|
|
506
|
+
if (type === 'number' || type === 'boolean')
|
|
507
|
+
return `<${tagName}${attr}>${data}</${tagName}>`;
|
|
508
|
+
if (type === 'array')
|
|
509
|
+
return `<${tagName}${attr}>${JSON.stringify(data)}</${tagName}>`;
|
|
510
|
+
if (type === 'object') {
|
|
511
|
+
const children = Object.entries(data).map(([k, v]) => buildXml(v, k)).join('');
|
|
512
|
+
return `<${tagName}${attr}>${children}</${tagName}>`;
|
|
570
513
|
}
|
|
514
|
+
return `<${tagName}${attr}>${String(data)}</${tagName}>`;
|
|
571
515
|
};
|
|
516
|
+
return buildXml(obj, rootName);
|
|
572
517
|
}
|
|
573
|
-
|
|
574
|
-
return
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
next: () => 'STOP',
|
|
581
|
-
stop: () => 'STOP',
|
|
582
|
-
retry: () => 'STOP',
|
|
583
|
-
wait: () => 'STOP',
|
|
584
|
-
bungee: {
|
|
585
|
-
init: () => {
|
|
586
|
-
throw new Error('Bungee not allowed in worker context');
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
};
|
|
518
|
+
static escapeXml(unsafe) {
|
|
519
|
+
return unsafe
|
|
520
|
+
.replace(/&/g, '&')
|
|
521
|
+
.replace(/</g, '<')
|
|
522
|
+
.replace(/>/g, '>')
|
|
523
|
+
.replace(/"/g, '"')
|
|
524
|
+
.replace(/'/g, "'");
|
|
590
525
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
526
|
+
}
|
|
527
|
+
SchemaUtils.schemaDescriptions = new Map();
|
|
528
|
+
SchemaUtils.maxCacheSize = 100;
|
|
529
|
+
|
|
530
|
+
class VisualizationManager {
|
|
531
|
+
constructor(wizard) {
|
|
532
|
+
this.wizard = wizard;
|
|
533
|
+
this.connectedClients = new Set();
|
|
534
|
+
this.maxWebSocketConnections = 10;
|
|
535
|
+
this.wsIntervals = new WeakMap();
|
|
536
|
+
} // Wizard instance for callbacks
|
|
537
|
+
getStepsInfo() {
|
|
538
|
+
return this.wizard.steps.map((item) => {
|
|
539
|
+
if (Array.isArray(item)) {
|
|
540
|
+
return item.map((step) => ({
|
|
541
|
+
id: step.id,
|
|
542
|
+
instruction: step.instruction,
|
|
543
|
+
fields: SchemaUtils.extractSchemaFields(step.schema),
|
|
544
|
+
status: 'pending'
|
|
545
|
+
}));
|
|
546
|
+
}
|
|
547
|
+
else {
|
|
548
|
+
return {
|
|
549
|
+
id: item.id,
|
|
550
|
+
instruction: item.instruction,
|
|
551
|
+
fields: SchemaUtils.extractSchemaFields(item.schema),
|
|
552
|
+
status: 'pending'
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
}).flat();
|
|
556
|
+
}
|
|
557
|
+
async visualize(port = 3000) {
|
|
558
|
+
return new Promise((resolve, reject) => {
|
|
559
|
+
const server = http__namespace.createServer((req, res) => {
|
|
560
|
+
if (req.url === '/') {
|
|
561
|
+
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
562
|
+
res.end(this.getVisualizationHtml());
|
|
563
|
+
}
|
|
564
|
+
else if (req.url === '/api/steps') {
|
|
565
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
566
|
+
res.end(JSON.stringify(this.getStepsInfo()));
|
|
567
|
+
}
|
|
568
|
+
else if (req.url === '/api/context') {
|
|
569
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
570
|
+
res.end(JSON.stringify(this.wizard.getContext()));
|
|
571
|
+
}
|
|
572
|
+
else if (req.url === '/api/log') {
|
|
573
|
+
this.wizard.logger.getLog().then((log) => {
|
|
574
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
575
|
+
res.end(log);
|
|
576
|
+
}).catch(() => {
|
|
577
|
+
res.writeHead(200, { 'Content-Type': 'text/plain' });
|
|
578
|
+
res.end('');
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
res.writeHead(404);
|
|
583
|
+
res.end('Not found');
|
|
584
|
+
}
|
|
585
|
+
});
|
|
586
|
+
this.wss = new WebSocket__namespace.Server({ server });
|
|
587
|
+
this.setupWebSocketHandlers();
|
|
588
|
+
server.listen(port, 'localhost', () => {
|
|
589
|
+
this.visualizationPort = port;
|
|
590
|
+
this.visualizationServer = server;
|
|
591
|
+
const url = `http://localhost:${port}`;
|
|
592
|
+
console.log(`π― Wizard visualization available at: ${url}`);
|
|
593
|
+
resolve({ server, url });
|
|
594
|
+
});
|
|
595
|
+
server.on('error', (err) => {
|
|
596
|
+
if (err.code === 'EADDRINUSE') {
|
|
597
|
+
this.visualize(port + 1).then(resolve).catch(reject);
|
|
598
|
+
}
|
|
599
|
+
else {
|
|
600
|
+
reject(err);
|
|
601
|
+
}
|
|
602
|
+
});
|
|
603
|
+
});
|
|
604
|
+
}
|
|
605
|
+
setupWebSocketHandlers() {
|
|
606
|
+
if (!this.wss)
|
|
607
|
+
return;
|
|
608
|
+
this.wss.on('connection', (ws) => {
|
|
609
|
+
if (this.connectedClients.size >= this.maxWebSocketConnections) {
|
|
610
|
+
console.log('π WebSocket connection rejected: max connections reached');
|
|
611
|
+
ws.close(1008, 'Max connections reached');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
console.log('π WebSocket client connected');
|
|
615
|
+
this.connectedClients.add(ws);
|
|
616
|
+
const pingInterval = setInterval(() => {
|
|
617
|
+
if (ws.readyState === WebSocket__namespace.OPEN) {
|
|
618
|
+
ws.ping();
|
|
619
|
+
}
|
|
620
|
+
else {
|
|
621
|
+
clearInterval(pingInterval);
|
|
622
|
+
}
|
|
623
|
+
}, 30000);
|
|
624
|
+
this.wsIntervals.set(ws, pingInterval);
|
|
625
|
+
ws.on('message', (message) => {
|
|
626
|
+
try {
|
|
627
|
+
const data = JSON.parse(message.toString());
|
|
628
|
+
this.handleWebSocketMessage(data, ws);
|
|
629
|
+
}
|
|
630
|
+
catch (error) {
|
|
631
|
+
console.error('Invalid WebSocket message:', error);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
ws.on('close', () => {
|
|
635
|
+
console.log('π WebSocket client disconnected');
|
|
636
|
+
this.connectedClients.delete(ws);
|
|
637
|
+
const interval = this.wsIntervals.get(ws);
|
|
638
|
+
if (interval) {
|
|
639
|
+
clearInterval(interval);
|
|
640
|
+
this.wsIntervals.delete(ws);
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
ws.on('error', () => {
|
|
644
|
+
this.connectedClients.delete(ws);
|
|
645
|
+
const interval = this.wsIntervals.get(ws);
|
|
646
|
+
if (interval) {
|
|
647
|
+
clearInterval(interval);
|
|
648
|
+
this.wsIntervals.delete(ws);
|
|
649
|
+
}
|
|
650
|
+
});
|
|
651
|
+
this.sendToClients({ type: 'status_update', status: { waitingForStart: true, isRunning: false, isPaused: false } });
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
handleWebSocketMessage(data, ws) {
|
|
655
|
+
switch (data.type) {
|
|
656
|
+
case 'control':
|
|
657
|
+
switch (data.action) {
|
|
658
|
+
case 'start':
|
|
659
|
+
console.log('π Starting wizard execution from UI');
|
|
660
|
+
this.wizard.isRunning = true;
|
|
661
|
+
this.sendToClients({ type: 'status_update', status: { isRunning: true, isPaused: false, isStepMode: false } });
|
|
662
|
+
if (this.runResolver) {
|
|
663
|
+
this.runResolver();
|
|
664
|
+
this.runResolver = undefined;
|
|
665
|
+
}
|
|
666
|
+
break;
|
|
667
|
+
case 'pause':
|
|
668
|
+
this.wizard.isPaused = true;
|
|
669
|
+
console.log('βΈοΈ Wizard execution paused');
|
|
670
|
+
this.sendToClients({ type: 'status_update', status: { isPaused: true, isStepMode: this.wizard.isStepMode } });
|
|
671
|
+
break;
|
|
672
|
+
case 'resume':
|
|
673
|
+
this.wizard.isPaused = false;
|
|
674
|
+
this.wizard.isStepMode = false;
|
|
675
|
+
console.log('βΆοΈ Wizard execution resumed');
|
|
676
|
+
if (this.pauseResolver) {
|
|
677
|
+
this.pauseResolver();
|
|
678
|
+
this.pauseResolver = undefined;
|
|
679
|
+
}
|
|
680
|
+
this.sendToClients({ type: 'status_update', status: { isPaused: false, isStepMode: false } });
|
|
681
|
+
break;
|
|
682
|
+
case 'step_forward':
|
|
683
|
+
if (this.wizard.isPaused) {
|
|
684
|
+
this.wizard.isStepMode = true;
|
|
685
|
+
console.log('βοΈ Stepping forward');
|
|
686
|
+
if (this.pauseResolver) {
|
|
687
|
+
this.pauseResolver();
|
|
688
|
+
this.pauseResolver = undefined;
|
|
689
|
+
}
|
|
690
|
+
this.sendToClients({ type: 'status_update', status: { isPaused: true, isStepMode: true } });
|
|
691
|
+
}
|
|
692
|
+
break;
|
|
693
|
+
case 'stop':
|
|
694
|
+
console.log('π Stopping wizard execution');
|
|
695
|
+
this.wizard.isRunning = false;
|
|
696
|
+
this.wizard.isPaused = false;
|
|
697
|
+
this.wizard.isStepMode = false;
|
|
698
|
+
this.sendToClients({ type: 'status_update', status: { isRunning: false, isPaused: false, isStepMode: false } });
|
|
699
|
+
break;
|
|
700
|
+
case 'replay':
|
|
701
|
+
console.log('π Replaying wizard - resetting state');
|
|
702
|
+
this.wizard.isRunning = false;
|
|
703
|
+
this.wizard.isPaused = false;
|
|
704
|
+
this.wizard.workflowContext = {};
|
|
705
|
+
this.sendToClients({ type: 'status_update', status: { isRunning: false, isPaused: false, waitingForStart: true } });
|
|
706
|
+
break;
|
|
707
|
+
}
|
|
708
|
+
break;
|
|
709
|
+
case 'run':
|
|
710
|
+
console.log('π Starting wizard execution from UI (run command)');
|
|
711
|
+
this.wizard.isRunning = true;
|
|
712
|
+
this.sendToClients({ type: 'status_update', status: { isRunning: true, isPaused: false } });
|
|
713
|
+
if (this.runResolver) {
|
|
714
|
+
this.runResolver();
|
|
715
|
+
this.runResolver = undefined;
|
|
716
|
+
}
|
|
717
|
+
break;
|
|
718
|
+
case 'form_submit':
|
|
719
|
+
this.wizard.userOverrideData = data.data;
|
|
720
|
+
console.log('π User override data received:', data.data);
|
|
721
|
+
if (this.pauseResolver) {
|
|
722
|
+
this.pauseResolver();
|
|
723
|
+
this.pauseResolver = undefined;
|
|
724
|
+
}
|
|
725
|
+
break;
|
|
726
|
+
case 'update_step_data':
|
|
727
|
+
this.wizard.updateContext(data.data);
|
|
728
|
+
console.log('π Step data updated:', data.data);
|
|
729
|
+
break;
|
|
730
|
+
case 'goto':
|
|
731
|
+
const index = this.wizard.findStepIndex(data.stepId);
|
|
732
|
+
if (index !== -1) {
|
|
733
|
+
const wasRunning = this.wizard.isRunning;
|
|
734
|
+
if (!wasRunning) {
|
|
735
|
+
this.wizard.startFrom(data.stepId);
|
|
736
|
+
}
|
|
737
|
+
else {
|
|
738
|
+
this.wizard.currentStepIndex = index;
|
|
739
|
+
}
|
|
740
|
+
console.log(`π Going to step ${data.stepId}`);
|
|
741
|
+
this.sendToClients({ type: 'status_update', status: { isRunning: true, isPaused: false, isStepMode: false } });
|
|
742
|
+
}
|
|
743
|
+
break;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
sendToClients(message) {
|
|
747
|
+
const messageStr = JSON.stringify(message);
|
|
748
|
+
this.connectedClients.forEach(client => {
|
|
749
|
+
if (client.readyState === WebSocket__namespace.OPEN) {
|
|
750
|
+
client.send(messageStr);
|
|
751
|
+
}
|
|
752
|
+
});
|
|
753
|
+
}
|
|
754
|
+
async waitForRunCommand() {
|
|
755
|
+
return new Promise(resolve => {
|
|
756
|
+
this.runResolver = resolve;
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
async waitForResume() {
|
|
760
|
+
return new Promise(resolve => {
|
|
761
|
+
this.pauseResolver = resolve;
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
sendStatusUpdate(status) {
|
|
765
|
+
this.sendToClients({ type: 'status_update', status });
|
|
766
|
+
}
|
|
767
|
+
sendWizardStart(steps) {
|
|
768
|
+
this.sendToClients({ type: 'wizard_start', steps });
|
|
769
|
+
}
|
|
770
|
+
sendStepUpdate(update) {
|
|
771
|
+
this.sendToClients({ type: 'step_update', ...update });
|
|
772
|
+
}
|
|
773
|
+
sendContextUpdate(context) {
|
|
774
|
+
this.sendToClients({ type: 'context_update', context });
|
|
775
|
+
}
|
|
776
|
+
sendTokenUpdate(totalTokens, rate) {
|
|
777
|
+
this.sendToClients({
|
|
778
|
+
type: 'token_update',
|
|
779
|
+
totalTokens,
|
|
780
|
+
rate
|
|
781
|
+
});
|
|
782
|
+
}
|
|
783
|
+
getVisualizationHtml() {
|
|
784
|
+
const fs = require('fs');
|
|
785
|
+
const path = require('path');
|
|
786
|
+
// Read the HTML file (now self-contained with inline CSS and JS)
|
|
787
|
+
const htmlPath = path.join(__dirname, 'ui/wizard-visualizer.html');
|
|
788
|
+
return fs.readFileSync(htmlPath, 'utf-8');
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
class BungeeExecutor {
|
|
793
|
+
constructor(wizard) {
|
|
794
|
+
this.wizard = wizard;
|
|
795
|
+
this.bungeeWorkers = new Map();
|
|
796
|
+
this.pendingReentry = new Set();
|
|
797
|
+
} // Wizard instance
|
|
798
|
+
async executeBungeePlan(plan) {
|
|
799
|
+
console.log(`πͺ Executing Bungee plan ${plan.id} with ${plan.destinations.length} destinations`);
|
|
800
|
+
// Track active workers for this plan
|
|
801
|
+
const activeWorkers = new Set();
|
|
802
|
+
for (let i = 0; i < plan.destinations.length; i++) {
|
|
803
|
+
// Launch worker
|
|
804
|
+
const workerPromise = this.launchBungeeWorker(plan, i);
|
|
805
|
+
activeWorkers.add(workerPromise);
|
|
806
|
+
// Respect concurrency limit
|
|
807
|
+
if (activeWorkers.size >= plan.concurrency) {
|
|
808
|
+
await Promise.race(activeWorkers);
|
|
809
|
+
// Clean up completed workers
|
|
810
|
+
for (const promise of activeWorkers) {
|
|
811
|
+
if (promise !== workerPromise) {
|
|
812
|
+
activeWorkers.delete(promise);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
// Wait for all workers to complete
|
|
818
|
+
await Promise.all(activeWorkers);
|
|
819
|
+
console.log(`β
Bungee plan ${plan.id} completed, returning to anchor ${plan.anchorId}`);
|
|
820
|
+
}
|
|
821
|
+
async launchBungeeWorker(plan, index) {
|
|
822
|
+
const destination = plan.destinations[index];
|
|
823
|
+
const telescope = plan.configFn ? plan.configFn(index) : {};
|
|
824
|
+
const workerId = `${plan.id}_${destination.targetId}_${index}_${Date.now()}`;
|
|
825
|
+
const telescopeContext = this.createTelescopeContext(this.wizard.workflowContext, telescope);
|
|
826
|
+
const promise = this.executeWorkerStep(destination.targetId, telescopeContext);
|
|
827
|
+
// Track this worker
|
|
828
|
+
if (!this.bungeeWorkers.has(plan.id)) {
|
|
829
|
+
this.bungeeWorkers.set(plan.id, new Map());
|
|
830
|
+
}
|
|
831
|
+
this.bungeeWorkers.get(plan.id).set(workerId, {
|
|
832
|
+
planId: plan.id,
|
|
833
|
+
workerId,
|
|
834
|
+
promise,
|
|
835
|
+
telescope
|
|
836
|
+
});
|
|
837
|
+
try {
|
|
838
|
+
await promise;
|
|
839
|
+
}
|
|
840
|
+
catch (error) {
|
|
841
|
+
console.error(`Bungee worker ${workerId} failed:`, error);
|
|
842
|
+
this.wizard.workflowContext[`${workerId}_error`] = error.message;
|
|
843
|
+
}
|
|
844
|
+
finally {
|
|
845
|
+
// Clean up
|
|
846
|
+
const planWorkers = this.bungeeWorkers.get(plan.id);
|
|
847
|
+
if (planWorkers) {
|
|
848
|
+
planWorkers.delete(workerId);
|
|
849
|
+
if (planWorkers.size === 0) {
|
|
850
|
+
this.bungeeWorkers.delete(plan.id);
|
|
851
|
+
// Trigger reentry to anchor
|
|
852
|
+
this.pendingReentry.add(plan.anchorId);
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
createTelescopeContext(baseContext, telescope) {
|
|
858
|
+
return {
|
|
859
|
+
...baseContext,
|
|
594
860
|
...telescope,
|
|
595
861
|
_telescope: telescope,
|
|
596
862
|
_anchorId: null
|
|
597
863
|
};
|
|
598
864
|
}
|
|
599
|
-
async executeWorkerStep(stepId, telescopeContext) {
|
|
600
|
-
const step = this.findStep(stepId);
|
|
601
|
-
if (!step)
|
|
602
|
-
return;
|
|
603
|
-
const stepContext = step.getContext(telescopeContext);
|
|
604
|
-
const stepData = await this.generateStepData(step, stepContext);
|
|
605
|
-
const actions = this.createWorkerActions(telescopeContext._telescope);
|
|
606
|
-
return await step.update(stepData, telescopeContext, actions);
|
|
865
|
+
async executeWorkerStep(stepId, telescopeContext) {
|
|
866
|
+
const step = this.wizard.findStep(stepId);
|
|
867
|
+
if (!step)
|
|
868
|
+
return;
|
|
869
|
+
const stepContext = step.getContext(telescopeContext);
|
|
870
|
+
const stepData = await this.wizard.generateStepData(step, stepContext);
|
|
871
|
+
const actions = this.wizard.createWorkerActions(telescopeContext._telescope);
|
|
872
|
+
return await step.update(stepData, telescopeContext, actions);
|
|
873
|
+
}
|
|
874
|
+
mergeWorkerResults(updates, telescope) {
|
|
875
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
876
|
+
this.wizard.workflowContext[key] = value;
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
async retriggerAnchor(anchorId) {
|
|
880
|
+
const anchorStep = this.wizard.findStep(anchorId);
|
|
881
|
+
if (anchorStep) {
|
|
882
|
+
await this.wizard.executeStep(anchorStep);
|
|
883
|
+
}
|
|
884
|
+
}
|
|
885
|
+
async processReentries() {
|
|
886
|
+
const anchorsToRetrigger = Array.from(this.pendingReentry);
|
|
887
|
+
this.pendingReentry.clear();
|
|
888
|
+
for (const anchorId of anchorsToRetrigger) {
|
|
889
|
+
await this.retriggerAnchor(anchorId);
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
class Logger {
|
|
895
|
+
constructor(id) {
|
|
896
|
+
// Create logs directory if it doesn't exist
|
|
897
|
+
const logsDir = path__namespace.join(process.cwd(), '.wizard');
|
|
898
|
+
if (!fs__namespace.existsSync(logsDir)) {
|
|
899
|
+
fs__namespace.mkdirSync(logsDir, { recursive: true });
|
|
900
|
+
}
|
|
901
|
+
this.logFilePath = path__namespace.join(logsDir, `${id}.log`);
|
|
902
|
+
}
|
|
903
|
+
log(messageOrFn) {
|
|
904
|
+
if (!this.logFilePath)
|
|
905
|
+
return; // Early exit if logging disabled
|
|
906
|
+
const message = typeof messageOrFn === 'function' ? messageOrFn() : messageOrFn;
|
|
907
|
+
const content = `${new Date().toISOString()}: ${message}\n`;
|
|
908
|
+
this.appendToFile(content);
|
|
909
|
+
}
|
|
910
|
+
appendToFile(content) {
|
|
911
|
+
if (!this.logFilePath)
|
|
912
|
+
return;
|
|
913
|
+
try {
|
|
914
|
+
fs__namespace.appendFileSync(this.logFilePath, content, 'utf8');
|
|
915
|
+
}
|
|
916
|
+
catch (error) {
|
|
917
|
+
console.log('Wizard log:', content.trim());
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
async getLog() {
|
|
921
|
+
if (!this.logFilePath || !fs__namespace.existsSync(this.logFilePath))
|
|
922
|
+
return '';
|
|
923
|
+
try {
|
|
924
|
+
return await fs__namespace.promises.readFile(this.logFilePath, 'utf8');
|
|
925
|
+
}
|
|
926
|
+
catch {
|
|
927
|
+
return '';
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
class UsageTracker {
|
|
933
|
+
constructor(onUsage, onUpdate) {
|
|
934
|
+
this.totalTokens = 0;
|
|
935
|
+
this.stepTokens = 0;
|
|
936
|
+
this.onUsage = onUsage;
|
|
937
|
+
this.onUpdate = onUpdate;
|
|
938
|
+
if (onUsage) {
|
|
939
|
+
const originalOnUsage = onUsage;
|
|
940
|
+
this.onUsage = (usage, provider) => {
|
|
941
|
+
this.totalTokens += usage.totalTokens;
|
|
942
|
+
this.stepTokens = usage.totalTokens; // Last step tokens
|
|
943
|
+
originalOnUsage(usage, provider);
|
|
944
|
+
this.notifyUpdate();
|
|
945
|
+
};
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
setStartTime(time) {
|
|
949
|
+
this.startTime = time;
|
|
950
|
+
}
|
|
951
|
+
notifyUpdate() {
|
|
952
|
+
if (this.onUpdate && this.startTime) {
|
|
953
|
+
const elapsed = (Date.now() - this.startTime) / 1000; // seconds
|
|
954
|
+
const rate = elapsed > 0 ? this.totalTokens / elapsed : 0;
|
|
955
|
+
this.onUpdate(this.totalTokens, rate);
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
getTotalTokens() {
|
|
959
|
+
return this.totalTokens;
|
|
960
|
+
}
|
|
961
|
+
getStepTokens() {
|
|
962
|
+
return this.stepTokens;
|
|
963
|
+
}
|
|
964
|
+
updateUsage(usage, provider) {
|
|
965
|
+
if (this.onUsage) {
|
|
966
|
+
this.onUsage(usage, provider);
|
|
967
|
+
}
|
|
968
|
+
else {
|
|
969
|
+
// If no onUsage, still update totals and notify
|
|
970
|
+
this.totalTokens += usage.totalTokens;
|
|
971
|
+
this.stepTokens = usage.totalTokens;
|
|
972
|
+
this.notifyUpdate();
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
class ContextManager {
|
|
978
|
+
constructor() {
|
|
979
|
+
this.workflowContext = {};
|
|
980
|
+
}
|
|
981
|
+
setContext(context) {
|
|
982
|
+
this.workflowContext = { ...this.workflowContext, ...context };
|
|
983
|
+
}
|
|
984
|
+
getContext() {
|
|
985
|
+
return this.workflowContext;
|
|
986
|
+
}
|
|
987
|
+
updateContext(updates) {
|
|
988
|
+
this.workflowContext = { ...this.workflowContext, ...updates };
|
|
989
|
+
}
|
|
990
|
+
getWorkflowContext() {
|
|
991
|
+
return this.workflowContext;
|
|
992
|
+
}
|
|
993
|
+
setWorkflowContext(context) {
|
|
994
|
+
this.workflowContext = context;
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Simple EventEmitter for wizard events
|
|
999
|
+
class EventEmitter {
|
|
1000
|
+
constructor() {
|
|
1001
|
+
this.events = new Map();
|
|
1002
|
+
}
|
|
1003
|
+
on(event, callback) {
|
|
1004
|
+
if (!this.events.has(event)) {
|
|
1005
|
+
this.events.set(event, []);
|
|
1006
|
+
}
|
|
1007
|
+
this.events.get(event).push(callback);
|
|
1008
|
+
}
|
|
1009
|
+
emit(event, data) {
|
|
1010
|
+
const callbacks = this.events.get(event);
|
|
1011
|
+
if (callbacks) {
|
|
1012
|
+
callbacks.forEach(cb => cb(data));
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
class Wizard {
|
|
1017
|
+
debounce(func, wait) {
|
|
1018
|
+
let timeout;
|
|
1019
|
+
return (...args) => {
|
|
1020
|
+
clearTimeout(timeout);
|
|
1021
|
+
timeout = setTimeout(() => func.apply(this, args), wait);
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
// Getters for manager methods
|
|
1025
|
+
get workflowContext() {
|
|
1026
|
+
return this.contextManager.getContext();
|
|
1027
|
+
}
|
|
1028
|
+
set workflowContext(value) {
|
|
1029
|
+
this.contextManager.setWorkflowContext(value);
|
|
1030
|
+
}
|
|
1031
|
+
get log() {
|
|
1032
|
+
return this.isLoggingEnabled ? this.logger.log.bind(this.logger) : () => { };
|
|
1033
|
+
}
|
|
1034
|
+
get sendToClients() {
|
|
1035
|
+
return this.visualizationManager.sendToClients.bind(this.visualizationManager);
|
|
1036
|
+
}
|
|
1037
|
+
get visualizationServer() {
|
|
1038
|
+
return this.visualizationManager.visualizationServer;
|
|
1039
|
+
}
|
|
1040
|
+
constructor(config) {
|
|
1041
|
+
this.steps = [];
|
|
1042
|
+
this.stepIndexMap = new Map();
|
|
1043
|
+
this.currentStepIndex = 0;
|
|
1044
|
+
this.isPaused = false;
|
|
1045
|
+
this.isRunning = false;
|
|
1046
|
+
this.isStepMode = false;
|
|
1047
|
+
this.skipStartWait = false;
|
|
1048
|
+
this.isLoggingEnabled = process.env.NODE_ENV === 'development' || !process.env.NODE_ENV;
|
|
1049
|
+
this.debouncedSendContextUpdate = this.debounce(() => {
|
|
1050
|
+
this.visualizationManager.sendContextUpdate(this.contextManager.getContext());
|
|
1051
|
+
}, 100);
|
|
1052
|
+
this.id = config.id;
|
|
1053
|
+
const registry = new ProviderRegistry();
|
|
1054
|
+
this.llmClient = new LLMClient(registry);
|
|
1055
|
+
this.systemPrompt = config.systemPrompt;
|
|
1056
|
+
// Initialize managers
|
|
1057
|
+
this.logger = new Logger(this.id);
|
|
1058
|
+
this.contextManager = new ContextManager();
|
|
1059
|
+
this.visualizationManager = new VisualizationManager(this);
|
|
1060
|
+
this.usageTracker = new UsageTracker(config.onUsage, (totalTokens, rate) => {
|
|
1061
|
+
this.visualizationManager.sendTokenUpdate(totalTokens, rate);
|
|
1062
|
+
});
|
|
1063
|
+
this.bungeeExecutor = new BungeeExecutor(this);
|
|
1064
|
+
this.events = new EventEmitter();
|
|
1065
|
+
}
|
|
1066
|
+
addStep(config) {
|
|
1067
|
+
const step = new Step(config);
|
|
1068
|
+
const index = this.steps.length;
|
|
1069
|
+
this.steps.push(step);
|
|
1070
|
+
this.stepIndexMap.set(step.id, index);
|
|
1071
|
+
return this;
|
|
1072
|
+
}
|
|
1073
|
+
addParallelSteps(callback) {
|
|
1074
|
+
const configs = [];
|
|
1075
|
+
const addStep = (config) => configs.push(config);
|
|
1076
|
+
callback(addStep);
|
|
1077
|
+
const parallelSteps = configs.map(c => new Step(c));
|
|
1078
|
+
const index = this.steps.length;
|
|
1079
|
+
this.steps.push(parallelSteps);
|
|
1080
|
+
// Add all parallel steps to index with same position
|
|
1081
|
+
parallelSteps.forEach(step => this.stepIndexMap.set(step.id, index));
|
|
1082
|
+
return this;
|
|
1083
|
+
}
|
|
1084
|
+
addTextStep(config) {
|
|
1085
|
+
const step = new TextStep(config);
|
|
1086
|
+
const index = this.steps.length;
|
|
1087
|
+
this.steps.push(step);
|
|
1088
|
+
this.stepIndexMap.set(step.id, index);
|
|
1089
|
+
return this;
|
|
607
1090
|
}
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
1091
|
+
addComputeStep(config) {
|
|
1092
|
+
const step = new ComputeStep(config);
|
|
1093
|
+
const index = this.steps.length;
|
|
1094
|
+
this.steps.push(step);
|
|
1095
|
+
this.stepIndexMap.set(step.id, index);
|
|
1096
|
+
return this;
|
|
612
1097
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
1098
|
+
goto(stepId) { return `GOTO ${stepId}`; }
|
|
1099
|
+
next() { return Wizard.NEXT; }
|
|
1100
|
+
stop() { return Wizard.STOP; }
|
|
1101
|
+
retry() { return Wizard.RETRY; }
|
|
1102
|
+
wait() { return Wizard.WAIT; }
|
|
1103
|
+
on(event, callback) {
|
|
1104
|
+
this.events.on(event, callback);
|
|
1105
|
+
return this;
|
|
1106
|
+
}
|
|
1107
|
+
clearStepError(stepId) {
|
|
1108
|
+
const context = this.contextManager.getContext();
|
|
1109
|
+
delete context[`${stepId}_error`];
|
|
1110
|
+
delete context[`${stepId}_retryCount`];
|
|
1111
|
+
this.contextManager.setWorkflowContext(context);
|
|
1112
|
+
}
|
|
1113
|
+
isStringSignal(signal) {
|
|
1114
|
+
return typeof signal === 'string';
|
|
1115
|
+
}
|
|
1116
|
+
isBungeeJumpSignal(signal) {
|
|
1117
|
+
return typeof signal === 'object' && signal !== null && signal.type === 'BUNGEE_JUMP';
|
|
1118
|
+
}
|
|
1119
|
+
async handleFlowControlSignal(signal) {
|
|
1120
|
+
switch (signal) {
|
|
1121
|
+
case Wizard.NEXT:
|
|
1122
|
+
this.currentStepIndex++;
|
|
1123
|
+
return true;
|
|
1124
|
+
case Wizard.STOP:
|
|
1125
|
+
return false;
|
|
1126
|
+
case Wizard.RETRY:
|
|
1127
|
+
return true;
|
|
1128
|
+
case Wizard.WAIT:
|
|
1129
|
+
await new Promise(resolve => setTimeout(resolve, 10 * 1000));
|
|
1130
|
+
this.currentStepIndex++;
|
|
1131
|
+
return true;
|
|
1132
|
+
default:
|
|
1133
|
+
if (this.isBungeeJumpSignal(signal)) {
|
|
1134
|
+
await this.bungeeExecutor.executeBungeePlan(signal.plan);
|
|
1135
|
+
return true;
|
|
1136
|
+
}
|
|
1137
|
+
else if (this.isStringSignal(signal) && signal.startsWith('GOTO ')) {
|
|
1138
|
+
const targetStepId = signal.substring(5);
|
|
1139
|
+
const targetIndex = this.findStepIndex(targetStepId);
|
|
1140
|
+
if (targetIndex !== -1) {
|
|
1141
|
+
this.currentStepIndex = targetIndex;
|
|
1142
|
+
}
|
|
1143
|
+
else {
|
|
1144
|
+
throw new Error(`Unknown step ID for GOTO: ${targetStepId}`);
|
|
1145
|
+
}
|
|
1146
|
+
return true;
|
|
1147
|
+
}
|
|
617
1148
|
}
|
|
1149
|
+
return true;
|
|
618
1150
|
}
|
|
619
|
-
async
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
await this.
|
|
1151
|
+
async initializeRun() {
|
|
1152
|
+
if (this.visualizationServer) {
|
|
1153
|
+
console.log('π― Waiting for UI to start wizard execution...');
|
|
1154
|
+
this.sendToClients({ type: 'status_update', status: { waitingForStart: true, isStepMode: false } });
|
|
1155
|
+
await this.visualizationManager.waitForRunCommand();
|
|
1156
|
+
console.log('π Starting wizard execution from UI command');
|
|
1157
|
+
// Send all steps info
|
|
1158
|
+
const stepsInfo = this.steps.map(item => {
|
|
1159
|
+
if (Array.isArray(item)) {
|
|
1160
|
+
return item.map(step => ({
|
|
1161
|
+
id: step.id,
|
|
1162
|
+
instruction: step.instruction,
|
|
1163
|
+
fields: SchemaUtils.extractSchemaFields(step.schema),
|
|
1164
|
+
status: 'pending'
|
|
1165
|
+
}));
|
|
1166
|
+
}
|
|
1167
|
+
else {
|
|
1168
|
+
return {
|
|
1169
|
+
id: item.id,
|
|
1170
|
+
instruction: item.instruction,
|
|
1171
|
+
fields: SchemaUtils.extractSchemaFields(item.schema),
|
|
1172
|
+
status: 'pending'
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1175
|
+
}).flat();
|
|
1176
|
+
this.sendToClients({ type: 'wizard_start', steps: stepsInfo });
|
|
624
1177
|
}
|
|
1178
|
+
this.log('Wizard session started');
|
|
1179
|
+
this.currentStepIndex = 0;
|
|
1180
|
+
this.usageTracker.setStartTime(Date.now());
|
|
1181
|
+
this.isRunning = true;
|
|
1182
|
+
// Emit start event
|
|
1183
|
+
this.events.emit('start', {
|
|
1184
|
+
wizardId: this.id,
|
|
1185
|
+
timestamp: Date.now(),
|
|
1186
|
+
steps: this.steps.map(item => {
|
|
1187
|
+
if (Array.isArray(item)) {
|
|
1188
|
+
return item.map(step => ({
|
|
1189
|
+
id: step.id,
|
|
1190
|
+
instruction: step.instruction,
|
|
1191
|
+
fields: SchemaUtils.extractSchemaFields(step.schema)
|
|
1192
|
+
}));
|
|
1193
|
+
}
|
|
1194
|
+
else {
|
|
1195
|
+
return {
|
|
1196
|
+
id: item.id,
|
|
1197
|
+
instruction: item.instruction,
|
|
1198
|
+
fields: SchemaUtils.extractSchemaFields(item.schema)
|
|
1199
|
+
};
|
|
1200
|
+
}
|
|
1201
|
+
}).flat()
|
|
1202
|
+
});
|
|
625
1203
|
}
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
1204
|
+
async executeParallelSteps(parallelSteps) {
|
|
1205
|
+
console.log(`Starting parallel steps: ${parallelSteps.map(s => s.id).join(', ')}`);
|
|
1206
|
+
this.log(`Starting parallel steps: ${parallelSteps.map(s => s.id).join(', ')}`);
|
|
1207
|
+
const promises = parallelSteps.map(step => this.executeStep(step));
|
|
1208
|
+
const signals = await Promise.all(promises);
|
|
1209
|
+
let nextSignal = Wizard.NEXT;
|
|
1210
|
+
for (const signal of signals) {
|
|
1211
|
+
if (signal === Wizard.STOP) {
|
|
1212
|
+
return Wizard.STOP;
|
|
632
1213
|
}
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
1214
|
+
if (this.isStringSignal(signal) && signal.startsWith('GOTO ')) {
|
|
1215
|
+
nextSignal = signal;
|
|
1216
|
+
break;
|
|
1217
|
+
}
|
|
1218
|
+
if (signal === Wizard.RETRY) {
|
|
1219
|
+
nextSignal = Wizard.RETRY;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
return nextSignal;
|
|
1223
|
+
}
|
|
1224
|
+
async executeSequentialStep(step) {
|
|
1225
|
+
const signal = await this.executeStep(step);
|
|
1226
|
+
if (!(await this.handleFlowControlSignal(signal)))
|
|
1227
|
+
return;
|
|
1228
|
+
}
|
|
1229
|
+
finalizeRun(startTime) {
|
|
1230
|
+
const endTime = Date.now();
|
|
1231
|
+
const duration = endTime - startTime;
|
|
1232
|
+
this.isRunning = false;
|
|
1233
|
+
this.sendToClients({ type: 'status_update', status: { completed: true } });
|
|
1234
|
+
console.log(`β
Wizard completed in ${duration}ms`);
|
|
1235
|
+
// Emit complete event
|
|
1236
|
+
this.events.emit('complete', {
|
|
1237
|
+
duration,
|
|
1238
|
+
totalSteps: this.steps.length,
|
|
1239
|
+
timestamp: endTime
|
|
1240
|
+
});
|
|
1241
|
+
}
|
|
1242
|
+
createBaseActions() {
|
|
1243
|
+
return {
|
|
1244
|
+
updateContext: (updates) => this.updateContext(updates),
|
|
1245
|
+
llmClient: this.llmClient,
|
|
1246
|
+
goto: (stepId) => this.goto(stepId),
|
|
1247
|
+
next: () => this.next(),
|
|
1248
|
+
stop: () => this.stop(),
|
|
1249
|
+
retry: () => this.retry(),
|
|
1250
|
+
wait: () => this.wait(),
|
|
1251
|
+
};
|
|
1252
|
+
}
|
|
1253
|
+
createWizardActions(anchorStepId = '') {
|
|
1254
|
+
return {
|
|
1255
|
+
...this.createBaseActions(),
|
|
1256
|
+
bungee: {
|
|
1257
|
+
init: () => new BungeeBuilder(anchorStepId)
|
|
636
1258
|
}
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
createWorkerActions(telescope) {
|
|
1262
|
+
return {
|
|
1263
|
+
updateContext: (updates) => {
|
|
1264
|
+
this.bungeeExecutor.mergeWorkerResults(updates, telescope);
|
|
1265
|
+
},
|
|
1266
|
+
llmClient: this.llmClient,
|
|
1267
|
+
goto: () => Wizard.STOP,
|
|
1268
|
+
next: () => Wizard.STOP,
|
|
1269
|
+
stop: () => Wizard.STOP,
|
|
1270
|
+
retry: () => Wizard.STOP,
|
|
1271
|
+
wait: () => Wizard.STOP,
|
|
1272
|
+
bungee: {
|
|
1273
|
+
init: () => {
|
|
1274
|
+
throw new Error('Bungee not allowed in worker context');
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
findStep(stepId) {
|
|
1280
|
+
const index = this.stepIndexMap.get(stepId);
|
|
1281
|
+
if (index === undefined)
|
|
1282
|
+
return null;
|
|
1283
|
+
const item = this.steps[index];
|
|
1284
|
+
if (Array.isArray(item)) {
|
|
1285
|
+
return item.find(s => s.id === stepId) || null;
|
|
1286
|
+
}
|
|
1287
|
+
else {
|
|
1288
|
+
return item.id === stepId ? item : null;
|
|
637
1289
|
}
|
|
638
|
-
return null;
|
|
639
1290
|
}
|
|
640
1291
|
findStepIndex(stepId) {
|
|
641
1292
|
return this.stepIndexMap.get(stepId) ?? -1;
|
|
642
1293
|
}
|
|
643
1294
|
async executeStep(step) {
|
|
644
1295
|
console.log(`Starting step ${step.id}`);
|
|
645
|
-
this.log(`Starting step ${step.id}`);
|
|
646
|
-
const
|
|
1296
|
+
this.logger.log(`Starting step ${step.id}`);
|
|
1297
|
+
const stepStartTime = Date.now();
|
|
1298
|
+
// Emit step:start event
|
|
1299
|
+
this.events.emit('step:start', {
|
|
1300
|
+
stepId: step.id,
|
|
1301
|
+
instruction: step.instruction,
|
|
1302
|
+
timestamp: stepStartTime
|
|
1303
|
+
});
|
|
1304
|
+
const stepContext = step.getContext(this.contextManager.getContext());
|
|
647
1305
|
let processedInstruction = step.instruction;
|
|
648
1306
|
if (step.contextType === 'template' || step.contextType === 'both') {
|
|
649
1307
|
processedInstruction = this.applyTemplate(step.instruction, stepContext);
|
|
650
1308
|
}
|
|
651
|
-
this.
|
|
652
|
-
type: 'step_update',
|
|
1309
|
+
this.visualizationManager.sendStepUpdate({
|
|
653
1310
|
stepId: step.id,
|
|
654
1311
|
status: 'current',
|
|
655
1312
|
instruction: processedInstruction,
|
|
656
1313
|
context: stepContext,
|
|
657
|
-
fields:
|
|
658
|
-
});
|
|
659
|
-
this.sendToClients({
|
|
660
|
-
type: 'context_update',
|
|
661
|
-
context: this.workflowContext
|
|
1314
|
+
fields: SchemaUtils.extractSchemaFields(step.schema)
|
|
662
1315
|
});
|
|
1316
|
+
this.debouncedSendContextUpdate();
|
|
663
1317
|
try {
|
|
664
1318
|
if (step.beforeRun) {
|
|
665
1319
|
await step.beforeRun();
|
|
666
1320
|
}
|
|
667
|
-
this.log(() => `Context for step ${step.id}: ${JSON.stringify(stepContext)}`);
|
|
1321
|
+
this.logger.log(() => `Context for step ${step.id}: ${JSON.stringify(stepContext)}`);
|
|
668
1322
|
// Skip LLM data generation for compute steps
|
|
669
1323
|
const stepData = step.isComputeStep ? null : await this.generateStepData(step, stepContext);
|
|
670
1324
|
if (this.isPaused) {
|
|
671
1325
|
console.log('βΈοΈ Paused before LLM call, waiting for user input...');
|
|
672
|
-
await this.waitForResume();
|
|
1326
|
+
await this.visualizationManager.waitForResume();
|
|
673
1327
|
console.log('βΆοΈ Resumed, checking for user override...');
|
|
674
1328
|
if (this.userOverrideData) {
|
|
675
1329
|
console.log('π Using user override data');
|
|
676
1330
|
try {
|
|
677
1331
|
const validatedData = step.validate(this.userOverrideData);
|
|
678
|
-
this.
|
|
679
|
-
type: 'step_update',
|
|
1332
|
+
this.visualizationManager.sendStepUpdate({
|
|
680
1333
|
stepId: step.id,
|
|
681
1334
|
status: 'completed',
|
|
682
1335
|
data: validatedData
|
|
@@ -692,26 +1345,41 @@ class Wizard {
|
|
|
692
1345
|
}
|
|
693
1346
|
if (stepData && stepData.__validationFailed) {
|
|
694
1347
|
console.log(`π Validation failed for step ${step.id}, retrying...`, stepData);
|
|
1348
|
+
// Emit step:retry event
|
|
1349
|
+
this.events.emit('step:retry', {
|
|
1350
|
+
stepId: step.id,
|
|
1351
|
+
attempt: (this.workflowContext[`${step.id}_retryCount`] || 0) + 1,
|
|
1352
|
+
error: stepData.error,
|
|
1353
|
+
timestamp: Date.now()
|
|
1354
|
+
});
|
|
695
1355
|
return 'RETRY';
|
|
696
1356
|
}
|
|
697
1357
|
const actions = this.createWizardActions(step.id);
|
|
698
1358
|
const signal = await step.update(stepData, this.workflowContext, actions);
|
|
699
|
-
this.
|
|
700
|
-
type: 'step_update',
|
|
1359
|
+
this.visualizationManager.sendStepUpdate({
|
|
701
1360
|
stepId: step.id,
|
|
702
1361
|
status: 'completed',
|
|
703
1362
|
data: stepData
|
|
704
1363
|
});
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1364
|
+
this.logger.log(() => `Step ${step.id} completed with data: ${JSON.stringify(stepData)}`);
|
|
1365
|
+
// Emit step:complete event
|
|
1366
|
+
this.events.emit('step:complete', {
|
|
1367
|
+
stepId: step.id,
|
|
1368
|
+
data: stepData,
|
|
1369
|
+
duration: Date.now() - stepStartTime,
|
|
1370
|
+
timestamp: Date.now()
|
|
1371
|
+
});
|
|
1372
|
+
return this.finalizeStepExecution(step, stepData, signal);
|
|
712
1373
|
}
|
|
713
1374
|
catch (error) {
|
|
714
1375
|
console.log('Processing error', error);
|
|
1376
|
+
// Emit step:error event
|
|
1377
|
+
this.events.emit('step:error', {
|
|
1378
|
+
stepId: step.id,
|
|
1379
|
+
error: error,
|
|
1380
|
+
retryCount: (this.workflowContext[`${step.id}_retryCount`] || 0) + 1,
|
|
1381
|
+
timestamp: Date.now()
|
|
1382
|
+
});
|
|
715
1383
|
this.updateContext({
|
|
716
1384
|
[`${step.id}_error`]: error.message,
|
|
717
1385
|
[`${step.id}_retryCount`]: (this.workflowContext[`${step.id}_retryCount`] || 0) + 1
|
|
@@ -719,9 +1387,7 @@ class Wizard {
|
|
|
719
1387
|
return 'RETRY';
|
|
720
1388
|
}
|
|
721
1389
|
}
|
|
722
|
-
async
|
|
723
|
-
const actions = this.createWizardActions(step.id);
|
|
724
|
-
const signal = await step.update(stepData, this.workflowContext, actions);
|
|
1390
|
+
async finalizeStepExecution(step, stepData, signal) {
|
|
725
1391
|
if (this.workflowContext[`${step.id}_error`]) {
|
|
726
1392
|
this.clearStepError(step.id);
|
|
727
1393
|
}
|
|
@@ -730,6 +1396,11 @@ class Wizard {
|
|
|
730
1396
|
}
|
|
731
1397
|
return signal;
|
|
732
1398
|
}
|
|
1399
|
+
async processStepResult(step, stepData) {
|
|
1400
|
+
const actions = this.createWizardActions(step.id);
|
|
1401
|
+
const signal = await step.update(stepData, this.workflowContext, actions);
|
|
1402
|
+
return this.finalizeStepExecution(step, stepData, signal);
|
|
1403
|
+
}
|
|
733
1404
|
setContext(context) {
|
|
734
1405
|
this.workflowContext = { ...this.workflowContext, ...context };
|
|
735
1406
|
return this;
|
|
@@ -742,120 +1413,69 @@ class Wizard {
|
|
|
742
1413
|
return this;
|
|
743
1414
|
}
|
|
744
1415
|
async run() {
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
1416
|
+
await this.initializeRun();
|
|
1417
|
+
this.executionLoop();
|
|
1418
|
+
}
|
|
1419
|
+
startFrom(stepId) {
|
|
1420
|
+
const index = this.findStepIndex(stepId);
|
|
1421
|
+
if (index === -1)
|
|
1422
|
+
return;
|
|
1423
|
+
this.currentStepIndex = index;
|
|
1424
|
+
this.isRunning = true;
|
|
1425
|
+
this.isPaused = false;
|
|
1426
|
+
this.isStepMode = false;
|
|
1427
|
+
this.usageTracker.setStartTime(Date.now());
|
|
1428
|
+
this.events.emit('start', {
|
|
1429
|
+
wizardId: this.id,
|
|
1430
|
+
timestamp: Date.now(),
|
|
1431
|
+
steps: this.steps.map(item => {
|
|
753
1432
|
if (Array.isArray(item)) {
|
|
754
1433
|
return item.map(step => ({
|
|
755
1434
|
id: step.id,
|
|
756
1435
|
instruction: step.instruction,
|
|
757
|
-
fields:
|
|
758
|
-
status: 'pending'
|
|
1436
|
+
fields: SchemaUtils.extractSchemaFields(step.schema)
|
|
759
1437
|
}));
|
|
760
1438
|
}
|
|
761
1439
|
else {
|
|
762
1440
|
return {
|
|
763
1441
|
id: item.id,
|
|
764
1442
|
instruction: item.instruction,
|
|
765
|
-
fields:
|
|
766
|
-
status: 'pending'
|
|
1443
|
+
fields: SchemaUtils.extractSchemaFields(item.schema)
|
|
767
1444
|
};
|
|
768
1445
|
}
|
|
769
|
-
}).flat()
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
1446
|
+
}).flat()
|
|
1447
|
+
});
|
|
1448
|
+
this.executionLoop();
|
|
1449
|
+
}
|
|
1450
|
+
async executionLoop() {
|
|
1451
|
+
const startTime = Date.now();
|
|
775
1452
|
while (this.currentStepIndex < this.steps.length && this.isRunning) {
|
|
776
1453
|
const item = this.steps[this.currentStepIndex];
|
|
777
1454
|
if (!item)
|
|
778
1455
|
break;
|
|
779
1456
|
if (Array.isArray(item)) {
|
|
780
|
-
const
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
const promises = parallelSteps.map(step => this.executeStep(step));
|
|
784
|
-
const signals = await Promise.all(promises);
|
|
785
|
-
let nextSignal = 'NEXT';
|
|
786
|
-
for (const signal of signals) {
|
|
787
|
-
if (signal === 'STOP') {
|
|
788
|
-
return;
|
|
789
|
-
}
|
|
790
|
-
if (this.isStringSignal(signal) && signal.startsWith('GOTO ')) {
|
|
791
|
-
nextSignal = signal;
|
|
792
|
-
break;
|
|
793
|
-
}
|
|
794
|
-
if (signal === 'RETRY') {
|
|
795
|
-
nextSignal = 'RETRY';
|
|
796
|
-
}
|
|
797
|
-
}
|
|
798
|
-
if (nextSignal === 'NEXT') {
|
|
799
|
-
this.currentStepIndex++;
|
|
800
|
-
}
|
|
801
|
-
else if (nextSignal === 'RETRY') ;
|
|
802
|
-
else if (this.isStringSignal(nextSignal) && nextSignal.startsWith('GOTO ')) {
|
|
803
|
-
const targetStepId = nextSignal.substring(5);
|
|
804
|
-
const targetIndex = this.findStepIndex(targetStepId);
|
|
805
|
-
if (targetIndex !== -1) {
|
|
806
|
-
this.currentStepIndex = targetIndex;
|
|
807
|
-
}
|
|
808
|
-
else {
|
|
809
|
-
throw new Error(`Unknown step ID for GOTO: ${targetStepId}`);
|
|
810
|
-
}
|
|
811
|
-
}
|
|
1457
|
+
const signal = await this.executeParallelSteps(item);
|
|
1458
|
+
if (!(await this.handleFlowControlSignal(signal)))
|
|
1459
|
+
return;
|
|
812
1460
|
}
|
|
813
1461
|
else {
|
|
814
|
-
|
|
815
|
-
switch (signal) {
|
|
816
|
-
case 'NEXT':
|
|
817
|
-
this.currentStepIndex++;
|
|
818
|
-
break;
|
|
819
|
-
case 'STOP':
|
|
820
|
-
return;
|
|
821
|
-
case 'RETRY':
|
|
822
|
-
break;
|
|
823
|
-
case 'WAIT':
|
|
824
|
-
await new Promise(resolve => setTimeout(resolve, 10 * 1000));
|
|
825
|
-
this.currentStepIndex++;
|
|
826
|
-
break;
|
|
827
|
-
default:
|
|
828
|
-
if (this.isBungeeJumpSignal(signal)) {
|
|
829
|
-
await this.executeBungeePlan(signal.plan);
|
|
830
|
-
}
|
|
831
|
-
else if (this.isStringSignal(signal) && signal.startsWith('GOTO ')) {
|
|
832
|
-
const targetStepId = signal.substring(5);
|
|
833
|
-
const targetIndex = this.findStepIndex(targetStepId);
|
|
834
|
-
if (targetIndex !== -1) {
|
|
835
|
-
this.currentStepIndex = targetIndex;
|
|
836
|
-
}
|
|
837
|
-
else {
|
|
838
|
-
throw new Error(`Unknown step ID for GOTO: ${targetStepId}`);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
}
|
|
1462
|
+
await this.executeSequentialStep(item);
|
|
842
1463
|
}
|
|
843
1464
|
if (this.isStepMode) {
|
|
844
1465
|
this.isPaused = true;
|
|
845
|
-
|
|
1466
|
+
this.events.emit('pause', {
|
|
1467
|
+
timestamp: Date.now(),
|
|
1468
|
+
currentStepId: this.steps[this.currentStepIndex]?.id
|
|
1469
|
+
});
|
|
1470
|
+
await this.visualizationManager.waitForResume();
|
|
1471
|
+
this.events.emit('resume', {
|
|
1472
|
+
timestamp: Date.now(),
|
|
1473
|
+
currentStepId: this.steps[this.currentStepIndex]?.id
|
|
1474
|
+
});
|
|
846
1475
|
}
|
|
847
|
-
await this.processReentries();
|
|
1476
|
+
await this.bungeeExecutor.processReentries();
|
|
848
1477
|
}
|
|
849
|
-
|
|
850
|
-
const duration = endTime - startTime;
|
|
851
|
-
this.isRunning = false;
|
|
852
|
-
this.sendToClients({ type: 'status_update', status: { completed: true } });
|
|
853
|
-
console.log(`β
Wizard completed in ${duration}ms`);
|
|
854
|
-
}
|
|
855
|
-
async waitForRunCommand() {
|
|
856
|
-
return new Promise(resolve => {
|
|
857
|
-
this.runResolver = resolve;
|
|
858
|
-
});
|
|
1478
|
+
this.finalizeRun(startTime);
|
|
859
1479
|
}
|
|
860
1480
|
async generateStepData(step, stepContext) {
|
|
861
1481
|
const systemContext = this.systemPrompt ? `${this.systemPrompt}\n\n` : '';
|
|
@@ -889,7 +1509,10 @@ Generate the text response now.`;
|
|
|
889
1509
|
console.log(`LLM response for step ${step.id}:`, llmResult.text);
|
|
890
1510
|
return llmResult.text;
|
|
891
1511
|
}
|
|
892
|
-
|
|
1512
|
+
// For regular steps, use streaming
|
|
1513
|
+
const parser = this.createStreamingXmlParser();
|
|
1514
|
+
let latestResult = {};
|
|
1515
|
+
const schemaDescription = SchemaUtils.describeSchema(step.schema, step.id);
|
|
893
1516
|
const prompt = `${systemContext}You are executing a wizard step. Generate data for this step.
|
|
894
1517
|
|
|
895
1518
|
STEP: ${step.id}
|
|
@@ -903,6 +1526,13 @@ Return a plain XML response with a root <response> tag.
|
|
|
903
1526
|
CRITICAL: Every field MUST include tag-category="wizard" attribute. This is MANDATORY.
|
|
904
1527
|
Every field MUST also include a type attribute (e.g., type="string", type="number", type="boolean", type="array").
|
|
905
1528
|
|
|
1529
|
+
ARRAY FORMATTING RULES (CRITICAL):
|
|
1530
|
+
- Arrays MUST be valid JSON on a SINGLE line
|
|
1531
|
+
- Use double quotes, not single quotes: ["a", "b"] NOT ['a', 'b']
|
|
1532
|
+
- NO trailing commas: ["a", "b"] NOT ["a", "b",]
|
|
1533
|
+
- NO line breaks inside arrays
|
|
1534
|
+
- Example: <items tag-category="wizard" type="array">["apple", "banana", "orange"]
|
|
1535
|
+
|
|
906
1536
|
IMPORTANT PARSING RULES:
|
|
907
1537
|
- Fields with tag-category="wizard" do NOT need closing tags
|
|
908
1538
|
- Content ends when the next tag with tag-category="wizard" begins, OR when </response> is reached
|
|
@@ -913,35 +1543,45 @@ Example:
|
|
|
913
1543
|
<response>
|
|
914
1544
|
<name tag-category="wizard" type="string">John Smith
|
|
915
1545
|
<age tag-category="wizard" type="number">25
|
|
916
|
-
<code tag-category="wizard" type="string">
|
|
917
|
-
function example() {
|
|
918
|
-
const x = <div>Hello</div>;
|
|
919
|
-
return x;
|
|
920
|
-
}
|
|
921
1546
|
<tags tag-category="wizard" type="array">["a", "b", "c"]
|
|
922
1547
|
</response>
|
|
923
1548
|
|
|
924
|
-
Notice:
|
|
1549
|
+
Notice: Arrays are compact JSON on one line! No closing tags needed for wizard fields.
|
|
925
1550
|
|
|
926
1551
|
Generate the XML response now.`;
|
|
927
1552
|
this.log(() => `Full prompt for step ${step.id}: ${prompt}`);
|
|
928
|
-
|
|
1553
|
+
await this.llmClient.complete({
|
|
929
1554
|
prompt,
|
|
930
1555
|
model: step.model,
|
|
931
1556
|
maxTokens: 1000,
|
|
932
1557
|
temperature: 0.3,
|
|
1558
|
+
stream: true,
|
|
1559
|
+
onChunk: (chunk) => {
|
|
1560
|
+
const parseResult = parser.push(chunk);
|
|
1561
|
+
if (parseResult && !parseResult.done) {
|
|
1562
|
+
latestResult = parseResult.result;
|
|
1563
|
+
// Optionally: Send partial results to UI
|
|
1564
|
+
this.visualizationManager.sendStepUpdate({
|
|
1565
|
+
stepId: step.id,
|
|
1566
|
+
status: 'streaming',
|
|
1567
|
+
data: latestResult
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
else if (parseResult?.done) {
|
|
1571
|
+
latestResult = parseResult.result;
|
|
1572
|
+
}
|
|
1573
|
+
},
|
|
1574
|
+
onUsage: this.usageTracker.updateUsage.bind(this.usageTracker)
|
|
933
1575
|
});
|
|
934
|
-
this.log(() => `
|
|
935
|
-
|
|
936
|
-
const jsonData = this.parseXmlToJson(llmResult.text);
|
|
937
|
-
this.log(() => `Parsed JSON data for step ${step.id}: ${JSON.stringify(jsonData)}`);
|
|
1576
|
+
this.log(() => `Final parsed data: ${JSON.stringify(latestResult)}`);
|
|
1577
|
+
this.log(() => `Parsed JSON data for step ${step.id}: ${JSON.stringify(latestResult)}`);
|
|
938
1578
|
try {
|
|
939
|
-
return step.validate(
|
|
1579
|
+
return step.validate(latestResult);
|
|
940
1580
|
}
|
|
941
1581
|
catch (validationError) {
|
|
942
1582
|
this.log(() => `Validation failed for step ${step.id}: ${validationError.message}`);
|
|
943
1583
|
try {
|
|
944
|
-
const repairedData = await this.repairSchemaData(
|
|
1584
|
+
const repairedData = await this.repairSchemaData(latestResult, step.schema, validationError.message, step.id);
|
|
945
1585
|
this.log(() => `Repaired data for step ${step.id}: ${JSON.stringify(repairedData)}`);
|
|
946
1586
|
return step.validate(repairedData);
|
|
947
1587
|
}
|
|
@@ -955,7 +1595,7 @@ Generate the XML response now.`;
|
|
|
955
1595
|
const step = this.findStep(stepId);
|
|
956
1596
|
if (!step)
|
|
957
1597
|
throw new Error(`Step ${stepId} not found`);
|
|
958
|
-
const schemaDescription =
|
|
1598
|
+
const schemaDescription = SchemaUtils.describeSchema(schema, stepId);
|
|
959
1599
|
const prompt = `You are repairing invalid data for a wizard step. The data failed validation and needs to be fixed to match the schema.
|
|
960
1600
|
|
|
961
1601
|
INVALID DATA: ${JSON.stringify(invalidData, null, 2)}
|
|
@@ -984,81 +1624,62 @@ Fix the data to match the schema and generate the XML response now.`;
|
|
|
984
1624
|
model: step.model,
|
|
985
1625
|
maxTokens: 10000,
|
|
986
1626
|
temperature: 0.3,
|
|
987
|
-
});
|
|
988
|
-
const repairedJsonData = this.parseXmlToJson(llmResult.text);
|
|
989
|
-
return repairedJsonData;
|
|
990
|
-
}
|
|
991
|
-
describeSchema(schema, stepId) {
|
|
992
|
-
if (stepId && this.schemaDescriptions.has(stepId)) {
|
|
993
|
-
return this.schemaDescriptions.get(stepId);
|
|
994
|
-
}
|
|
995
|
-
let description;
|
|
996
|
-
if (schema instanceof zod.z.ZodObject) {
|
|
997
|
-
const shape = schema._def.shape();
|
|
998
|
-
const fields = Object.entries(shape).map(([key, fieldSchema]) => {
|
|
999
|
-
const type = this.getSchemaType(fieldSchema);
|
|
1000
|
-
const xmlExample = this.getXmlExample(key, type);
|
|
1001
|
-
return `${key}: ${type} - ${xmlExample}`;
|
|
1002
|
-
});
|
|
1003
|
-
description = `Object with fields:\n${fields.join('\n')}`;
|
|
1004
|
-
}
|
|
1005
|
-
else {
|
|
1006
|
-
description = 'Unknown schema type';
|
|
1007
|
-
}
|
|
1008
|
-
if (stepId) {
|
|
1009
|
-
// Implement simple cache eviction if needed
|
|
1010
|
-
if (this.schemaDescriptions.size >= this.maxCacheSize) {
|
|
1011
|
-
const firstKey = this.schemaDescriptions.keys().next().value;
|
|
1012
|
-
this.schemaDescriptions.delete(firstKey || '');
|
|
1013
|
-
}
|
|
1014
|
-
this.schemaDescriptions.set(stepId, description);
|
|
1015
|
-
}
|
|
1016
|
-
return description;
|
|
1627
|
+
});
|
|
1628
|
+
const repairedJsonData = this.parseXmlToJson(llmResult.text);
|
|
1629
|
+
return repairedJsonData;
|
|
1017
1630
|
}
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1631
|
+
createStreamingXmlParser() {
|
|
1632
|
+
let buffer = '';
|
|
1633
|
+
let inResponse = false;
|
|
1634
|
+
const result = {};
|
|
1635
|
+
let currentField = null;
|
|
1636
|
+
return {
|
|
1637
|
+
push: (chunk) => {
|
|
1638
|
+
buffer += chunk;
|
|
1639
|
+
// Detect <response> start
|
|
1640
|
+
if (!inResponse && buffer.includes('<response>')) {
|
|
1641
|
+
inResponse = true;
|
|
1642
|
+
buffer = buffer.slice(buffer.indexOf('<response>') + 10);
|
|
1028
1643
|
}
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1644
|
+
if (!inResponse)
|
|
1645
|
+
return null;
|
|
1646
|
+
// Parse wizard-tagged fields incrementally
|
|
1647
|
+
const tagMatch = buffer.match(Wizard.WIZARD_TAG_PATTERN);
|
|
1648
|
+
if (tagMatch) {
|
|
1649
|
+
// If we have a current field, finalize it
|
|
1650
|
+
if (currentField) {
|
|
1651
|
+
result[currentField.name] = this.parseValueByType(currentField.content.trim(), currentField.type);
|
|
1652
|
+
}
|
|
1653
|
+
// Start new field
|
|
1654
|
+
const typeMatch = tagMatch[2].match(/type=["']([^"']+)["']/);
|
|
1655
|
+
currentField = {
|
|
1656
|
+
name: tagMatch[1],
|
|
1657
|
+
type: typeMatch?.[1]?.toLowerCase() || 'string',
|
|
1658
|
+
content: ''
|
|
1659
|
+
};
|
|
1660
|
+
buffer = buffer.slice(tagMatch.index + tagMatch[0].length);
|
|
1661
|
+
}
|
|
1662
|
+
// Accumulate content for current field
|
|
1663
|
+
if (currentField) {
|
|
1664
|
+
const nextTagIndex = buffer.search(/<\w+\s+[^>]*tag-category=["']wizard["']/);
|
|
1665
|
+
if (nextTagIndex !== -1) {
|
|
1666
|
+
currentField.content += buffer.slice(0, nextTagIndex);
|
|
1667
|
+
buffer = buffer.slice(nextTagIndex);
|
|
1668
|
+
}
|
|
1669
|
+
else if (buffer.includes('</response>')) {
|
|
1670
|
+
const endIndex = buffer.indexOf('</response>');
|
|
1671
|
+
currentField.content += buffer.slice(0, endIndex);
|
|
1672
|
+
result[currentField.name] = this.parseValueByType(currentField.content.trim(), currentField.type);
|
|
1673
|
+
return { done: true, result };
|
|
1674
|
+
}
|
|
1675
|
+
else {
|
|
1676
|
+
currentField.content += buffer;
|
|
1677
|
+
buffer = '';
|
|
1678
|
+
}
|
|
1679
|
+
}
|
|
1680
|
+
return { done: false, result: { ...result } };
|
|
1058
1681
|
}
|
|
1059
|
-
|
|
1060
|
-
}
|
|
1061
|
-
return fields;
|
|
1682
|
+
};
|
|
1062
1683
|
}
|
|
1063
1684
|
parseXmlToJson(xml) {
|
|
1064
1685
|
const responseMatch = xml.match(/<response\s*>([\s\S]*?)(?:<\/response\s*>|$)/i);
|
|
@@ -1142,6 +1763,17 @@ Fix the data to match the schema and generate the XML response now.`;
|
|
|
1142
1763
|
}
|
|
1143
1764
|
return result;
|
|
1144
1765
|
}
|
|
1766
|
+
parseValueByType(content, type) {
|
|
1767
|
+
switch (type) {
|
|
1768
|
+
case 'string': return content;
|
|
1769
|
+
case 'number': return this.parseNumber(content);
|
|
1770
|
+
case 'boolean': return this.parseBoolean(content);
|
|
1771
|
+
case 'array': return this.parseArray(content);
|
|
1772
|
+
case 'object': return this.parseXmlElementWithTagCategory(content);
|
|
1773
|
+
case 'null': return null;
|
|
1774
|
+
default: return this.inferAndParseValue(content);
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1145
1777
|
parseNumber(value) {
|
|
1146
1778
|
const num = Number(value);
|
|
1147
1779
|
if (isNaN(num)) {
|
|
@@ -1158,16 +1790,146 @@ Fix the data to match the schema and generate the XML response now.`;
|
|
|
1158
1790
|
throw new Error(`Invalid boolean value: "${value}" (expected "true" or "false")`);
|
|
1159
1791
|
}
|
|
1160
1792
|
parseArray(value) {
|
|
1793
|
+
const trimmed = value.trim();
|
|
1794
|
+
// Strategy 1: Try direct JSON parse
|
|
1161
1795
|
try {
|
|
1162
|
-
const parsed = JSON.parse(
|
|
1163
|
-
if (
|
|
1164
|
-
|
|
1796
|
+
const parsed = JSON.parse(trimmed);
|
|
1797
|
+
if (Array.isArray(parsed))
|
|
1798
|
+
return parsed;
|
|
1799
|
+
}
|
|
1800
|
+
catch (e) {
|
|
1801
|
+
// Continue to fallback strategies
|
|
1802
|
+
}
|
|
1803
|
+
// Strategy 2: Fix common JSON issues and retry
|
|
1804
|
+
try {
|
|
1805
|
+
let fixed = trimmed
|
|
1806
|
+
// Remove trailing commas before closing bracket
|
|
1807
|
+
.replace(/,(\s*[}\]])/g, '$1')
|
|
1808
|
+
// Normalize quotes (convert single to double)
|
|
1809
|
+
.replace(/'/g, '"')
|
|
1810
|
+
// Remove comments if any
|
|
1811
|
+
.replace(/\/\/.*$/gm, '')
|
|
1812
|
+
.replace(/\/\*[\s\S]*?\*\//g, '');
|
|
1813
|
+
const parsed = JSON.parse(fixed);
|
|
1814
|
+
if (Array.isArray(parsed))
|
|
1815
|
+
return parsed;
|
|
1816
|
+
}
|
|
1817
|
+
catch (e) {
|
|
1818
|
+
// Continue to manual parsing
|
|
1819
|
+
}
|
|
1820
|
+
// Strategy 3: Manual extraction (most robust)
|
|
1821
|
+
// Extract content between first [ and last ]
|
|
1822
|
+
const bracketMatch = trimmed.match(/\[([\s\S]*)\]/);
|
|
1823
|
+
if (bracketMatch) {
|
|
1824
|
+
const content = bracketMatch[1].trim();
|
|
1825
|
+
// Empty array
|
|
1826
|
+
if (!content)
|
|
1827
|
+
return [];
|
|
1828
|
+
// Try to parse as JSON array one more time
|
|
1829
|
+
try {
|
|
1830
|
+
const parsed = JSON.parse(`[${content}]`);
|
|
1831
|
+
if (Array.isArray(parsed))
|
|
1832
|
+
return parsed;
|
|
1833
|
+
}
|
|
1834
|
+
catch (e) {
|
|
1835
|
+
// Fall through to string splitting
|
|
1165
1836
|
}
|
|
1166
|
-
|
|
1837
|
+
// Manual string splitting for simple arrays
|
|
1838
|
+
const items = [];
|
|
1839
|
+
let current = '';
|
|
1840
|
+
let inString = false;
|
|
1841
|
+
let escapeNext = false;
|
|
1842
|
+
let depth = 0;
|
|
1843
|
+
for (let i = 0; i < content.length; i++) {
|
|
1844
|
+
const char = content[i];
|
|
1845
|
+
if (escapeNext) {
|
|
1846
|
+
current += char;
|
|
1847
|
+
escapeNext = false;
|
|
1848
|
+
continue;
|
|
1849
|
+
}
|
|
1850
|
+
if (char === '\\') {
|
|
1851
|
+
escapeNext = true;
|
|
1852
|
+
current += char;
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1855
|
+
if (char === '"' && depth === 0) {
|
|
1856
|
+
inString = !inString;
|
|
1857
|
+
current += char;
|
|
1858
|
+
continue;
|
|
1859
|
+
}
|
|
1860
|
+
if (inString) {
|
|
1861
|
+
current += char;
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
// Track nested structures
|
|
1865
|
+
if (char === '{' || char === '[') {
|
|
1866
|
+
depth++;
|
|
1867
|
+
current += char;
|
|
1868
|
+
continue;
|
|
1869
|
+
}
|
|
1870
|
+
if (char === '}' || char === ']') {
|
|
1871
|
+
depth--;
|
|
1872
|
+
current += char;
|
|
1873
|
+
continue;
|
|
1874
|
+
}
|
|
1875
|
+
// Split on comma at depth 0
|
|
1876
|
+
if (char === ',' && depth === 0) {
|
|
1877
|
+
const item = current.trim();
|
|
1878
|
+
if (item) {
|
|
1879
|
+
items.push(this.parseArrayItem(item));
|
|
1880
|
+
}
|
|
1881
|
+
current = '';
|
|
1882
|
+
continue;
|
|
1883
|
+
}
|
|
1884
|
+
current += char;
|
|
1885
|
+
}
|
|
1886
|
+
// Don't forget the last item
|
|
1887
|
+
if (current.trim()) {
|
|
1888
|
+
items.push(this.parseArrayItem(current.trim()));
|
|
1889
|
+
}
|
|
1890
|
+
return items;
|
|
1167
1891
|
}
|
|
1168
|
-
|
|
1169
|
-
|
|
1892
|
+
// Strategy 4: Newline-separated values (last resort)
|
|
1893
|
+
const lines = trimmed
|
|
1894
|
+
.split('\n')
|
|
1895
|
+
.map(l => l.trim())
|
|
1896
|
+
.filter(l => l && l !== '[' && l !== ']');
|
|
1897
|
+
if (lines.length > 0) {
|
|
1898
|
+
return lines.map(line => {
|
|
1899
|
+
// Remove trailing comma
|
|
1900
|
+
const cleaned = line.replace(/,\s*$/, '');
|
|
1901
|
+
return this.parseArrayItem(cleaned);
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
throw new Error(`Could not parse array from: "${value.substring(0, 100)}..."`);
|
|
1905
|
+
}
|
|
1906
|
+
parseArrayItem(item) {
|
|
1907
|
+
const trimmed = item.trim();
|
|
1908
|
+
// Try JSON parse first
|
|
1909
|
+
try {
|
|
1910
|
+
return JSON.parse(trimmed);
|
|
1911
|
+
}
|
|
1912
|
+
catch (e) {
|
|
1913
|
+
// Not valid JSON, continue
|
|
1170
1914
|
}
|
|
1915
|
+
// Remove quotes if present
|
|
1916
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
1917
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
1918
|
+
return trimmed.slice(1, -1);
|
|
1919
|
+
}
|
|
1920
|
+
// Try as number
|
|
1921
|
+
if (!isNaN(Number(trimmed))) {
|
|
1922
|
+
return Number(trimmed);
|
|
1923
|
+
}
|
|
1924
|
+
// Boolean
|
|
1925
|
+
if (trimmed === 'true')
|
|
1926
|
+
return true;
|
|
1927
|
+
if (trimmed === 'false')
|
|
1928
|
+
return false;
|
|
1929
|
+
if (trimmed === 'null')
|
|
1930
|
+
return null;
|
|
1931
|
+
// Return as string
|
|
1932
|
+
return trimmed;
|
|
1171
1933
|
}
|
|
1172
1934
|
inferAndParseValue(content) {
|
|
1173
1935
|
const trimmed = content.trim();
|
|
@@ -1244,197 +2006,16 @@ Fix the data to match the schema and generate the XML response now.`;
|
|
|
1244
2006
|
});
|
|
1245
2007
|
}
|
|
1246
2008
|
async visualize(port = 3000) {
|
|
1247
|
-
return
|
|
1248
|
-
const server = http__namespace.createServer((req, res) => {
|
|
1249
|
-
if (req.url === '/') {
|
|
1250
|
-
res.writeHead(200, { 'Content-Type': 'text/html' });
|
|
1251
|
-
res.end(this.getVisualizationHtml());
|
|
1252
|
-
}
|
|
1253
|
-
else {
|
|
1254
|
-
res.writeHead(404);
|
|
1255
|
-
res.end('Not found');
|
|
1256
|
-
}
|
|
1257
|
-
});
|
|
1258
|
-
this.wss = new WebSocket__namespace.Server({ server });
|
|
1259
|
-
this.setupWebSocketHandlers();
|
|
1260
|
-
server.listen(port, 'localhost', () => {
|
|
1261
|
-
this.visualizationPort = port;
|
|
1262
|
-
this.visualizationServer = server;
|
|
1263
|
-
const url = `http://localhost:${port}`;
|
|
1264
|
-
console.log(`π― Wizard visualization available at: ${url}`);
|
|
1265
|
-
resolve({ server, url });
|
|
1266
|
-
});
|
|
1267
|
-
server.on('error', (err) => {
|
|
1268
|
-
if (err.code === 'EADDRINUSE') {
|
|
1269
|
-
this.visualize(port + 1).then(resolve).catch(reject);
|
|
1270
|
-
}
|
|
1271
|
-
else {
|
|
1272
|
-
reject(err);
|
|
1273
|
-
}
|
|
1274
|
-
});
|
|
1275
|
-
});
|
|
1276
|
-
}
|
|
1277
|
-
setupWebSocketHandlers() {
|
|
1278
|
-
if (!this.wss)
|
|
1279
|
-
return;
|
|
1280
|
-
this.wss.on('connection', (ws) => {
|
|
1281
|
-
if (this.connectedClients.size >= this.maxWebSocketConnections) {
|
|
1282
|
-
console.log('π WebSocket connection rejected: max connections reached');
|
|
1283
|
-
ws.close(1008, 'Max connections reached');
|
|
1284
|
-
return;
|
|
1285
|
-
}
|
|
1286
|
-
console.log('π WebSocket client connected');
|
|
1287
|
-
this.connectedClients.add(ws);
|
|
1288
|
-
const pingInterval = setInterval(() => {
|
|
1289
|
-
if (ws.readyState === WebSocket__namespace.OPEN) {
|
|
1290
|
-
ws.ping();
|
|
1291
|
-
}
|
|
1292
|
-
else {
|
|
1293
|
-
clearInterval(pingInterval);
|
|
1294
|
-
}
|
|
1295
|
-
}, 30000);
|
|
1296
|
-
this.wsIntervals.set(ws, pingInterval);
|
|
1297
|
-
ws.on('message', (message) => {
|
|
1298
|
-
try {
|
|
1299
|
-
const data = JSON.parse(message.toString());
|
|
1300
|
-
this.handleWebSocketMessage(data, ws);
|
|
1301
|
-
}
|
|
1302
|
-
catch (error) {
|
|
1303
|
-
console.error('Invalid WebSocket message:', error);
|
|
1304
|
-
}
|
|
1305
|
-
});
|
|
1306
|
-
ws.on('close', () => {
|
|
1307
|
-
console.log('π WebSocket client disconnected');
|
|
1308
|
-
this.connectedClients.delete(ws);
|
|
1309
|
-
const interval = this.wsIntervals.get(ws);
|
|
1310
|
-
if (interval) {
|
|
1311
|
-
clearInterval(interval);
|
|
1312
|
-
this.wsIntervals.delete(ws);
|
|
1313
|
-
}
|
|
1314
|
-
});
|
|
1315
|
-
ws.on('error', () => {
|
|
1316
|
-
this.connectedClients.delete(ws);
|
|
1317
|
-
const interval = this.wsIntervals.get(ws);
|
|
1318
|
-
if (interval) {
|
|
1319
|
-
clearInterval(interval);
|
|
1320
|
-
this.wsIntervals.delete(ws);
|
|
1321
|
-
}
|
|
1322
|
-
});
|
|
1323
|
-
this.sendToClients({ type: 'status_update', status: { waitingForStart: true, isRunning: false, isPaused: false } });
|
|
1324
|
-
});
|
|
1325
|
-
}
|
|
1326
|
-
handleWebSocketMessage(data, ws) {
|
|
1327
|
-
switch (data.type) {
|
|
1328
|
-
case 'control':
|
|
1329
|
-
switch (data.action) {
|
|
1330
|
-
case 'start':
|
|
1331
|
-
console.log('π Starting wizard execution from UI');
|
|
1332
|
-
this.isRunning = true;
|
|
1333
|
-
this.sendToClients({ type: 'status_update', status: { isRunning: true, isPaused: false, isStepMode: false } });
|
|
1334
|
-
if (this.runResolver) {
|
|
1335
|
-
this.runResolver();
|
|
1336
|
-
this.runResolver = undefined;
|
|
1337
|
-
}
|
|
1338
|
-
break;
|
|
1339
|
-
case 'pause':
|
|
1340
|
-
this.isPaused = true;
|
|
1341
|
-
console.log('βΈοΈ Wizard execution paused');
|
|
1342
|
-
this.sendToClients({ type: 'status_update', status: { isPaused: true, isStepMode: this.isStepMode } });
|
|
1343
|
-
break;
|
|
1344
|
-
case 'resume':
|
|
1345
|
-
this.isPaused = false;
|
|
1346
|
-
this.isStepMode = false;
|
|
1347
|
-
console.log('βΆοΈ Wizard execution resumed');
|
|
1348
|
-
if (this.pauseResolver) {
|
|
1349
|
-
this.pauseResolver();
|
|
1350
|
-
this.pauseResolver = undefined;
|
|
1351
|
-
}
|
|
1352
|
-
this.sendToClients({ type: 'status_update', status: { isPaused: false, isStepMode: false } });
|
|
1353
|
-
break;
|
|
1354
|
-
case 'step_forward':
|
|
1355
|
-
if (this.isPaused) {
|
|
1356
|
-
this.isStepMode = true;
|
|
1357
|
-
console.log('βοΈ Stepping forward');
|
|
1358
|
-
if (this.pauseResolver) {
|
|
1359
|
-
this.pauseResolver();
|
|
1360
|
-
this.pauseResolver = undefined;
|
|
1361
|
-
}
|
|
1362
|
-
this.sendToClients({ type: 'status_update', status: { isPaused: true, isStepMode: true } });
|
|
1363
|
-
}
|
|
1364
|
-
break;
|
|
1365
|
-
case 'stop':
|
|
1366
|
-
console.log('π Stopping wizard execution');
|
|
1367
|
-
this.isRunning = false;
|
|
1368
|
-
this.isPaused = false;
|
|
1369
|
-
this.isStepMode = false;
|
|
1370
|
-
this.sendToClients({ type: 'status_update', status: { isRunning: false, isPaused: false, isStepMode: false } });
|
|
1371
|
-
break;
|
|
1372
|
-
case 'replay':
|
|
1373
|
-
console.log('π Replaying wizard - resetting state');
|
|
1374
|
-
this.isRunning = false;
|
|
1375
|
-
this.isPaused = false;
|
|
1376
|
-
this.workflowContext = {};
|
|
1377
|
-
this.sendToClients({ type: 'status_update', status: { isRunning: false, isPaused: false, waitingForStart: true } });
|
|
1378
|
-
break;
|
|
1379
|
-
}
|
|
1380
|
-
break;
|
|
1381
|
-
case 'run':
|
|
1382
|
-
console.log('π Starting wizard execution from UI (run command)');
|
|
1383
|
-
this.isRunning = true;
|
|
1384
|
-
this.sendToClients({ type: 'status_update', status: { isRunning: true, isPaused: false } });
|
|
1385
|
-
if (this.runResolver) {
|
|
1386
|
-
this.runResolver();
|
|
1387
|
-
this.runResolver = undefined;
|
|
1388
|
-
}
|
|
1389
|
-
break;
|
|
1390
|
-
case 'form_submit':
|
|
1391
|
-
this.userOverrideData = data.data;
|
|
1392
|
-
console.log('π User override data received:', data.data);
|
|
1393
|
-
if (this.pauseResolver) {
|
|
1394
|
-
this.pauseResolver();
|
|
1395
|
-
this.pauseResolver = undefined;
|
|
1396
|
-
}
|
|
1397
|
-
break;
|
|
1398
|
-
case 'update_step_data':
|
|
1399
|
-
this.updateContext(data.data);
|
|
1400
|
-
console.log('π Step data updated:', data.data);
|
|
1401
|
-
break;
|
|
1402
|
-
case 'goto':
|
|
1403
|
-
const index = this.findStepIndex(data.stepId);
|
|
1404
|
-
if (index !== -1) {
|
|
1405
|
-
this.currentStepIndex = index;
|
|
1406
|
-
this.isRunning = true;
|
|
1407
|
-
this.isPaused = false;
|
|
1408
|
-
this.isStepMode = false;
|
|
1409
|
-
console.log(`π Going to step ${data.stepId}`);
|
|
1410
|
-
this.sendToClients({ type: 'status_update', status: { isRunning: true, isPaused: false, isStepMode: false } });
|
|
1411
|
-
}
|
|
1412
|
-
break;
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
sendToClients(message) {
|
|
1416
|
-
const messageStr = JSON.stringify(message);
|
|
1417
|
-
this.connectedClients.forEach(client => {
|
|
1418
|
-
if (client.readyState === WebSocket__namespace.OPEN) {
|
|
1419
|
-
client.send(messageStr);
|
|
1420
|
-
}
|
|
1421
|
-
});
|
|
1422
|
-
}
|
|
1423
|
-
async waitForResume() {
|
|
1424
|
-
return new Promise(resolve => {
|
|
1425
|
-
this.pauseResolver = resolve;
|
|
1426
|
-
});
|
|
1427
|
-
}
|
|
1428
|
-
getVisualizationHtml() {
|
|
1429
|
-
const fs = require('fs');
|
|
1430
|
-
const path = require('path');
|
|
1431
|
-
// Read the HTML file (now self-contained with inline CSS and JS)
|
|
1432
|
-
const htmlPath = path.join(__dirname, 'ui/wizard-visualizer.html');
|
|
1433
|
-
return fs.readFileSync(htmlPath, 'utf-8');
|
|
2009
|
+
return this.visualizationManager.visualize(port);
|
|
1434
2010
|
}
|
|
1435
2011
|
}
|
|
1436
2012
|
Wizard.TEMPLATE_REGEX = /\{\{(\w+(?:\.\w+)*)\}\}/g;
|
|
1437
2013
|
Wizard.WIZARD_TAG_PATTERN = /<(\w+)\s+([^>]*tag-category=["']wizard["'][^>]*)>/gi;
|
|
2014
|
+
// Flow control signals
|
|
2015
|
+
Wizard.NEXT = 'NEXT';
|
|
2016
|
+
Wizard.STOP = 'STOP';
|
|
2017
|
+
Wizard.RETRY = 'RETRY';
|
|
2018
|
+
Wizard.WAIT = 'WAIT';
|
|
1438
2019
|
|
|
1439
2020
|
exports.BaseProvider = BaseProvider;
|
|
1440
2021
|
exports.BungeeBuilder = BungeeBuilder;
|