@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.
Files changed (31) hide show
  1. package/README.md +835 -439
  2. package/dist/core/wizard/bungee/executor.d.ts +15 -0
  3. package/dist/core/wizard/bungee/executor.d.ts.map +1 -0
  4. package/dist/core/wizard/context-manager.d.ts +9 -0
  5. package/dist/core/wizard/context-manager.d.ts.map +1 -0
  6. package/dist/core/wizard/logger.d.ts +8 -0
  7. package/dist/core/wizard/logger.d.ts.map +1 -0
  8. package/dist/core/wizard/schema-utils.d.ts +16 -0
  9. package/dist/core/wizard/schema-utils.d.ts.map +1 -0
  10. package/dist/core/wizard/step-data-generator.d.ts +24 -0
  11. package/dist/core/wizard/step-data-generator.d.ts.map +1 -0
  12. package/dist/core/wizard/steps/base.d.ts +4 -3
  13. package/dist/core/wizard/steps/base.d.ts.map +1 -1
  14. package/dist/core/wizard/steps/compute.d.ts +2 -2
  15. package/dist/core/wizard/steps/compute.d.ts.map +1 -1
  16. package/dist/core/wizard/steps/text.d.ts +2 -2
  17. package/dist/core/wizard/steps/text.d.ts.map +1 -1
  18. package/dist/core/wizard/usage-tracker.d.ts +25 -0
  19. package/dist/core/wizard/usage-tracker.d.ts.map +1 -0
  20. package/dist/core/wizard/visualization-manager.d.ts +34 -0
  21. package/dist/core/wizard/visualization-manager.d.ts.map +1 -0
  22. package/dist/core/wizard/wizard.d.ts +39 -40
  23. package/dist/core/wizard/wizard.d.ts.map +1 -1
  24. package/dist/index.d.ts +46 -46
  25. package/dist/index.js +1207 -626
  26. package/dist/index.js.map +1 -1
  27. package/dist/services/client/index.d.ts +1 -0
  28. package/dist/services/client/index.d.ts.map +1 -1
  29. package/dist/services/client/providers.d.ts.map +1 -1
  30. package/dist/ui/wizard-visualizer.html +570 -385
  31. 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.contextFunction = config.contextFunction;
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.contextFunction ? this.contextFunction(workflowContext) : workflowContext;
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 = options.stream ? '/completions/stream' : '/completions';
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
- const chunk = decoder.decode(value);
182
- fullText += chunk;
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 Wizard {
390
- constructor(config) {
391
- this.steps = [];
392
- this.workflowContext = {};
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
- // Create logs directory if it doesn't exist
430
- const logsDir = path__namespace.join(process.cwd(), '.wizard');
431
- if (!fs__namespace.existsSync(logsDir)) {
432
- fs__namespace.mkdirSync(logsDir, { recursive: true });
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
- this.logFilePath = path__namespace.join(logsDir, `${this.id}.log`);
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
- catch (error) {
450
- console.log('Wizard log:', content.trim());
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
- addStep(config) {
454
- const step = new Step(config);
455
- const index = this.steps.length;
456
- this.steps.push(step);
457
- this.stepIndexMap.set(step.id, index);
458
- return this;
459
- }
460
- addParallelSteps(callback) {
461
- const configs = [];
462
- const addStep = (config) => configs.push(config);
463
- callback(addStep);
464
- const parallelSteps = configs.map(c => new Step(c));
465
- const index = this.steps.length;
466
- this.steps.push(parallelSteps);
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
- async executeBungeePlan(plan) {
501
- console.log(`πŸͺ‚ Executing Bungee plan ${plan.id} with ${plan.destinations.length} destinations`);
502
- // Track active workers for this plan
503
- const activeWorkers = new Set();
504
- for (let i = 0; i < plan.destinations.length; i++) {
505
- // Launch worker
506
- const workerPromise = this.launchBungeeWorker(plan, i);
507
- activeWorkers.add(workerPromise);
508
- // Respect concurrency limit
509
- if (activeWorkers.size >= plan.concurrency) {
510
- await Promise.race(activeWorkers);
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
- // Wait for all workers to complete
520
- await Promise.all(activeWorkers);
521
- console.log(`βœ… Bungee plan ${plan.id} completed, returning to anchor ${plan.anchorId}`);
478
+ return fields;
522
479
  }
523
- async launchBungeeWorker(plan, index) {
524
- const destination = plan.destinations[index];
525
- const telescope = plan.configFn ? plan.configFn(index) : {};
526
- const workerId = `${plan.id}_${destination.targetId}_${index}_${Date.now()}`;
527
- const telescopeContext = this.createTelescopeContext(this.workflowContext, telescope);
528
- const promise = this.executeWorkerStep(destination.targetId, telescopeContext);
529
- // Track this worker
530
- if (!this.bungeeWorkers.has(plan.id)) {
531
- this.bungeeWorkers.set(plan.id, new Map());
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
- createWizardActions(anchorStepId = '') {
560
- return {
561
- updateContext: (updates) => this.updateContext(updates),
562
- llmClient: this.llmClient,
563
- goto: (stepId) => this.goto(stepId),
564
- next: () => this.next(),
565
- stop: () => this.stop(),
566
- retry: () => this.retry(),
567
- wait: () => this.wait(),
568
- bungee: {
569
- init: () => new BungeeBuilder(anchorStepId)
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
- createWorkerActions(telescope) {
574
- return {
575
- updateContext: (updates) => {
576
- this.mergeWorkerResults(updates, telescope);
577
- },
578
- llmClient: this.llmClient,
579
- goto: () => 'STOP',
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
- createTelescopeContext(baseContext, telescope) {
592
- return {
593
- ...baseContext,
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
- mergeWorkerResults(updates, telescope) {
609
- Object.entries(updates).forEach(([key, value]) => {
610
- this.workflowContext[key] = value;
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
- async retriggerAnchor(anchorId) {
614
- const anchorStep = this.findStep(anchorId);
615
- if (anchorStep) {
616
- await this.executeStep(anchorStep);
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 processReentries() {
620
- const anchorsToRetrigger = Array.from(this.pendingReentry);
621
- this.pendingReentry.clear();
622
- for (const anchorId of anchorsToRetrigger) {
623
- await this.retriggerAnchor(anchorId);
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
- findStep(stepId) {
627
- for (const item of this.steps) {
628
- if (Array.isArray(item)) {
629
- const found = item.find(s => s.id === stepId);
630
- if (found)
631
- return found;
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
- else {
634
- if (item.id === stepId)
635
- return item;
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 stepContext = step.getContext(this.workflowContext);
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.sendToClients({
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: this.extractSchemaFields(step.schema)
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.sendToClients({
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.sendToClients({
700
- type: 'step_update',
1359
+ this.visualizationManager.sendStepUpdate({
701
1360
  stepId: step.id,
702
1361
  status: 'completed',
703
1362
  data: stepData
704
1363
  });
705
- if (this.workflowContext[`${step.id}_error`]) {
706
- this.clearStepError(step.id);
707
- }
708
- if (step.afterRun) {
709
- await step.afterRun(stepData);
710
- }
711
- return signal;
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 processStepResult(step, stepData) {
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
- const startTime = Date.now();
746
- if (this.visualizationServer) {
747
- console.log('🎯 Waiting for UI to start wizard execution...');
748
- this.sendToClients({ type: 'status_update', status: { waitingForStart: true, isStepMode: false } });
749
- await this.waitForRunCommand();
750
- console.log('πŸš€ Starting wizard execution from UI command');
751
- // Send all steps info
752
- const stepsInfo = this.steps.map(item => {
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: this.extractSchemaFields(step.schema),
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: this.extractSchemaFields(item.schema),
766
- status: 'pending'
1443
+ fields: SchemaUtils.extractSchemaFields(item.schema)
767
1444
  };
768
1445
  }
769
- }).flat();
770
- this.sendToClients({ type: 'wizard_start', steps: stepsInfo });
771
- }
772
- this.log('Wizard session started');
773
- this.currentStepIndex = 0;
774
- this.isRunning = true;
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 parallelSteps = item;
781
- console.log(`Starting parallel steps: ${parallelSteps.map(s => s.id).join(', ')}`);
782
- this.log(`Starting parallel steps: ${parallelSteps.map(s => s.id).join(', ')}`);
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
- const signal = await this.executeStep(item);
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
- await this.waitForResume();
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
- const endTime = Date.now();
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
- const schemaDescription = this.describeSchema(step.schema, step.id);
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: No closing tags needed for wizard fields! Content naturally ends at the next wizard field or </response>.
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
- const llmResult = await this.llmClient.complete({
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(() => `LLM response for step ${step.id}: ${llmResult.text}`);
935
- console.log(`LLM response for step ${step.id}:`, llmResult.text);
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(jsonData);
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(jsonData, step.schema, validationError.message, step.id);
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 = this.describeSchema(schema, stepId);
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
- getXmlExample(key, type) {
1019
- switch (type) {
1020
- case 'string': return `<${key} tag-category="wizard" type="string">example`;
1021
- case 'number': return `<${key} tag-category="wizard" type="number">123`;
1022
- case 'boolean': return `<${key} tag-category="wizard" type="boolean">true`;
1023
- case 'array': return `<${key} tag-category="wizard" type="array">["item1", "item2"]`;
1024
- default:
1025
- if (type.startsWith('enum:')) {
1026
- const values = type.split(': ')[1].split(', ');
1027
- return `<${key} tag-category="wizard" type="string">${values[0]}`;
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
- return `<${key} tag-category="wizard" type="object"><subfield type="string">value</subfield>`;
1030
- }
1031
- }
1032
- getSchemaType(schema) {
1033
- if (schema instanceof zod.z.ZodOptional)
1034
- return this.getSchemaType(schema._def.innerType);
1035
- if (schema instanceof zod.z.ZodString)
1036
- return 'string';
1037
- if (schema instanceof zod.z.ZodNumber)
1038
- return 'number';
1039
- if (schema instanceof zod.z.ZodBoolean)
1040
- return 'boolean';
1041
- if (schema instanceof zod.z.ZodArray)
1042
- return 'array';
1043
- if (schema instanceof zod.z.ZodEnum)
1044
- return `enum: ${schema._def.values.join(', ')}`;
1045
- return 'object';
1046
- }
1047
- extractSchemaFields(schema) {
1048
- if (!(schema instanceof zod.z.ZodObject))
1049
- return [];
1050
- const shape = schema._def.shape();
1051
- const fields = [];
1052
- for (const [key, fieldSchema] of Object.entries(shape)) {
1053
- const type = this.getSchemaType(fieldSchema);
1054
- const field = { key, type };
1055
- if (type.startsWith('enum:')) {
1056
- field.type = 'enum';
1057
- field.enumValues = type.substring(5).split(', ');
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
- fields.push(field);
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(value);
1163
- if (!Array.isArray(parsed)) {
1164
- throw new Error('Parsed value is not an array');
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
- return parsed;
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
- catch (error) {
1169
- throw new Error(`Invalid array JSON: "${value}"`);
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 new Promise((resolve, reject) => {
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;