@swizzy_ai/kit 1.0.0
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 +717 -0
- package/dist/core/wizard/bungee/builder.d.ts +27 -0
- package/dist/core/wizard/bungee/builder.d.ts.map +1 -0
- package/dist/core/wizard/bungee/types.d.ts +12 -0
- package/dist/core/wizard/bungee/types.d.ts.map +1 -0
- package/dist/core/wizard/index.d.ts +7 -0
- package/dist/core/wizard/index.d.ts.map +1 -0
- package/dist/core/wizard/steps/base.d.ts +48 -0
- package/dist/core/wizard/steps/base.d.ts.map +1 -0
- package/dist/core/wizard/steps/compute.d.ts +16 -0
- package/dist/core/wizard/steps/compute.d.ts.map +1 -0
- package/dist/core/wizard/steps/text.d.ts +16 -0
- package/dist/core/wizard/steps/text.d.ts.map +1 -0
- package/dist/core/wizard/wizard.d.ts +110 -0
- package/dist/core/wizard/wizard.d.ts.map +1 -0
- package/dist/index.d.ts +309 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1451 -0
- package/dist/index.js.map +1 -0
- package/dist/services/client/index.d.ts +34 -0
- package/dist/services/client/index.d.ts.map +1 -0
- package/dist/services/client/models.d.ts +17 -0
- package/dist/services/client/models.d.ts.map +1 -0
- package/dist/services/client/providers.d.ts +29 -0
- package/dist/services/client/providers.d.ts.map +1 -0
- package/dist/services/client/registry.d.ts +11 -0
- package/dist/services/client/registry.d.ts.map +1 -0
- package/dist/ui/wizard-script.js +242 -0
- package/dist/ui/wizard-styles.css +96 -0
- package/dist/ui/wizard-visualizer.html +481 -0
- package/package.json +53 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1451 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
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
|
+
var openai = require('@ai-sdk/openai');
|
|
11
|
+
var anthropic = require('@ai-sdk/anthropic');
|
|
12
|
+
var google = require('@ai-sdk/google');
|
|
13
|
+
var xai = require('@ai-sdk/xai');
|
|
14
|
+
var ai = require('ai');
|
|
15
|
+
|
|
16
|
+
function _interopNamespaceDefault(e) {
|
|
17
|
+
var n = Object.create(null);
|
|
18
|
+
if (e) {
|
|
19
|
+
Object.keys(e).forEach(function (k) {
|
|
20
|
+
if (k !== 'default') {
|
|
21
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
22
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
23
|
+
enumerable: true,
|
|
24
|
+
get: function () { return e[k]; }
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
n.default = e;
|
|
30
|
+
return Object.freeze(n);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs);
|
|
34
|
+
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
35
|
+
var http__namespace = /*#__PURE__*/_interopNamespaceDefault(http);
|
|
36
|
+
var WebSocket__namespace = /*#__PURE__*/_interopNamespaceDefault(WebSocket);
|
|
37
|
+
|
|
38
|
+
class Step {
|
|
39
|
+
constructor(config) {
|
|
40
|
+
this.id = config.id;
|
|
41
|
+
this.instruction = config.instruction;
|
|
42
|
+
this.schema = config.schema;
|
|
43
|
+
this.update = config.update;
|
|
44
|
+
this.contextFunction = config.contextFunction;
|
|
45
|
+
this.contextType = config.contextType || 'xml'; // Default to xml
|
|
46
|
+
this.beforeRun = config.beforeRun;
|
|
47
|
+
this.afterRun = config.afterRun;
|
|
48
|
+
this.model = config.model;
|
|
49
|
+
}
|
|
50
|
+
validate(data) {
|
|
51
|
+
const result = this.schema.safeParse(data);
|
|
52
|
+
if (!result.success) {
|
|
53
|
+
throw new Error(`Step "${this.id}" validation failed: ${result.error.message}`);
|
|
54
|
+
}
|
|
55
|
+
return result.data;
|
|
56
|
+
}
|
|
57
|
+
getContext(workflowContext) {
|
|
58
|
+
return this.contextFunction ? this.contextFunction(workflowContext) : workflowContext;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
class TextStep extends Step {
|
|
63
|
+
constructor(config) {
|
|
64
|
+
// Create a dummy schema for string
|
|
65
|
+
const dummySchema = zod.z.string();
|
|
66
|
+
super({
|
|
67
|
+
...config,
|
|
68
|
+
schema: dummySchema,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
validate(data) {
|
|
72
|
+
// For text steps, just return the data as string
|
|
73
|
+
return typeof data === 'string' ? data : String(data);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
class ComputeStep extends Step {
|
|
78
|
+
constructor(config) {
|
|
79
|
+
// Use a permissive schema that accepts any data
|
|
80
|
+
const permissiveSchema = zod.z.any();
|
|
81
|
+
super({
|
|
82
|
+
...config,
|
|
83
|
+
schema: permissiveSchema,
|
|
84
|
+
model: '', // No model needed for compute steps
|
|
85
|
+
});
|
|
86
|
+
this.isComputeStep = true;
|
|
87
|
+
}
|
|
88
|
+
validate(data) {
|
|
89
|
+
// For compute steps, accept any data without validation
|
|
90
|
+
return data;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
exports.Models = void 0;
|
|
95
|
+
(function (Models) {
|
|
96
|
+
Models["GPT4"] = "gpt-4";
|
|
97
|
+
Models["GPT35_TURBO"] = "gpt-3.5-turbo";
|
|
98
|
+
Models["CLAUDE3_SONNET"] = "claude-3-sonnet-20240229";
|
|
99
|
+
Models["CLAUDE3_HAIKU"] = "claude-3-haiku-20240307";
|
|
100
|
+
Models["GEMINI_PRO"] = "gemini-pro";
|
|
101
|
+
Models["GEMINI_PRO_VISION"] = "gemini-pro-vision";
|
|
102
|
+
Models["GROK_BETA"] = "grok-beta";
|
|
103
|
+
Models["SWIZZY_DEFAULT"] = "swizzy-default";
|
|
104
|
+
})(exports.Models || (exports.Models = {}));
|
|
105
|
+
class Model {
|
|
106
|
+
constructor(modelEnum) {
|
|
107
|
+
this.model = modelEnum;
|
|
108
|
+
this.provider = this.getProviderFromModel(modelEnum);
|
|
109
|
+
}
|
|
110
|
+
getProviderFromModel(model) {
|
|
111
|
+
switch (model) {
|
|
112
|
+
case exports.Models.GPT4:
|
|
113
|
+
case exports.Models.GPT35_TURBO:
|
|
114
|
+
return 'openai';
|
|
115
|
+
case exports.Models.CLAUDE3_SONNET:
|
|
116
|
+
case exports.Models.CLAUDE3_HAIKU:
|
|
117
|
+
return 'anthropic';
|
|
118
|
+
case exports.Models.GEMINI_PRO:
|
|
119
|
+
case exports.Models.GEMINI_PRO_VISION:
|
|
120
|
+
return 'google';
|
|
121
|
+
case exports.Models.GROK_BETA:
|
|
122
|
+
return 'grok';
|
|
123
|
+
case exports.Models.SWIZZY_DEFAULT:
|
|
124
|
+
return 'swizzy';
|
|
125
|
+
default:
|
|
126
|
+
console.error("Unknown error defaulting to SWIZZY");
|
|
127
|
+
return 'swizzy';
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
class BaseProvider {
|
|
133
|
+
}
|
|
134
|
+
class SwizzyProvider extends BaseProvider {
|
|
135
|
+
constructor(config) {
|
|
136
|
+
super();
|
|
137
|
+
const baseURL = config.baseURL || process.env.SWIZZY_BASE_URL || 'https://swizzy-kit.hello-ad4.workers.dev';
|
|
138
|
+
const apiKey = config.apiKey || process.env.SWIZZY_API_KEY;
|
|
139
|
+
if (!apiKey) {
|
|
140
|
+
throw new Error('SWIZZY_API_KEY is required. Set it via environment variable SWIZZY_API_KEY or pass apiKey directly.');
|
|
141
|
+
}
|
|
142
|
+
this.config = { baseURL, apiKey };
|
|
143
|
+
}
|
|
144
|
+
async complete(options) {
|
|
145
|
+
const endpoint = options.stream ? '/completions/stream' : '/completions';
|
|
146
|
+
const response = await fetch(`${this.config.baseURL}${endpoint}`, {
|
|
147
|
+
method: 'POST',
|
|
148
|
+
headers: {
|
|
149
|
+
'Content-Type': 'application/json',
|
|
150
|
+
'x-api-key': this.config.apiKey,
|
|
151
|
+
},
|
|
152
|
+
body: JSON.stringify({
|
|
153
|
+
prompt: options.prompt,
|
|
154
|
+
max_tokens: options.maxTokens || 1000,
|
|
155
|
+
temperature: options.temperature || 0.7,
|
|
156
|
+
}),
|
|
157
|
+
});
|
|
158
|
+
if (!response.ok) {
|
|
159
|
+
let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
|
|
160
|
+
try {
|
|
161
|
+
const errorData = await response.json();
|
|
162
|
+
errorMessage = errorData.error || errorData.message || errorMessage;
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
const text = await response.text().catch(() => '');
|
|
166
|
+
if (text)
|
|
167
|
+
errorMessage = text;
|
|
168
|
+
}
|
|
169
|
+
throw new Error(`LLM API error: ${errorMessage}`);
|
|
170
|
+
}
|
|
171
|
+
if (options.stream) {
|
|
172
|
+
const reader = response.body?.getReader();
|
|
173
|
+
if (!reader)
|
|
174
|
+
throw new Error('No response body for streaming');
|
|
175
|
+
let fullText = '';
|
|
176
|
+
const decoder = new TextDecoder();
|
|
177
|
+
while (true) {
|
|
178
|
+
const { done, value } = await reader.read();
|
|
179
|
+
if (done)
|
|
180
|
+
break;
|
|
181
|
+
const chunk = decoder.decode(value);
|
|
182
|
+
fullText += chunk;
|
|
183
|
+
}
|
|
184
|
+
return { text: fullText };
|
|
185
|
+
}
|
|
186
|
+
else {
|
|
187
|
+
const data = await response.json();
|
|
188
|
+
let transformedUsage;
|
|
189
|
+
const usageData = data.fullResult?.usage || data.usage;
|
|
190
|
+
if (usageData) {
|
|
191
|
+
transformedUsage = {
|
|
192
|
+
promptTokens: usageData.prompt_tokens || 0,
|
|
193
|
+
completionTokens: usageData.completion_tokens || 0,
|
|
194
|
+
totalTokens: usageData.total_tokens || 0
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
if (options.onUsage && transformedUsage) {
|
|
198
|
+
options.onUsage(transformedUsage, this.getProviderName());
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
text: data.completion,
|
|
202
|
+
usage: transformedUsage,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
supportsModel(model) {
|
|
207
|
+
return model === exports.Models.SWIZZY_DEFAULT;
|
|
208
|
+
}
|
|
209
|
+
getProviderName() {
|
|
210
|
+
return 'swizzy';
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
class MultiProvider extends BaseProvider {
|
|
214
|
+
constructor() {
|
|
215
|
+
super();
|
|
216
|
+
this.openaiKey = process.env.OPENAI_API_KEY;
|
|
217
|
+
this.anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
218
|
+
this.googleKey = process.env.GOOGLE_GENERATIVE_AI_API_KEY;
|
|
219
|
+
this.xaiKey = process.env.XAI_API_KEY;
|
|
220
|
+
}
|
|
221
|
+
async complete(options) {
|
|
222
|
+
const model = options.model || exports.Models.GPT4;
|
|
223
|
+
let provider;
|
|
224
|
+
switch (model) {
|
|
225
|
+
case exports.Models.GPT4:
|
|
226
|
+
case exports.Models.GPT35_TURBO:
|
|
227
|
+
if (!this.openaiKey)
|
|
228
|
+
throw new Error('OpenAI API key not available');
|
|
229
|
+
provider = openai.openai(model);
|
|
230
|
+
break;
|
|
231
|
+
case exports.Models.CLAUDE3_SONNET:
|
|
232
|
+
case exports.Models.CLAUDE3_HAIKU:
|
|
233
|
+
if (!this.anthropicKey)
|
|
234
|
+
throw new Error('Anthropic API key not available');
|
|
235
|
+
provider = anthropic.anthropic(model);
|
|
236
|
+
break;
|
|
237
|
+
case exports.Models.GEMINI_PRO:
|
|
238
|
+
case exports.Models.GEMINI_PRO_VISION:
|
|
239
|
+
if (!this.googleKey)
|
|
240
|
+
throw new Error('Google API key not available');
|
|
241
|
+
provider = google.google(model);
|
|
242
|
+
break;
|
|
243
|
+
case exports.Models.GROK_BETA:
|
|
244
|
+
if (!this.xaiKey)
|
|
245
|
+
throw new Error('xAI API key not available');
|
|
246
|
+
provider = xai.xai(model);
|
|
247
|
+
break;
|
|
248
|
+
default:
|
|
249
|
+
throw new Error(`Unsupported model: ${model}`);
|
|
250
|
+
}
|
|
251
|
+
const result = await ai.generateText({
|
|
252
|
+
model: provider,
|
|
253
|
+
prompt: options.prompt,
|
|
254
|
+
maxTokens: options.maxTokens,
|
|
255
|
+
temperature: options.temperature,
|
|
256
|
+
});
|
|
257
|
+
const usage = {
|
|
258
|
+
promptTokens: result.usage.promptTokens,
|
|
259
|
+
completionTokens: result.usage.completionTokens,
|
|
260
|
+
totalTokens: result.usage.totalTokens,
|
|
261
|
+
};
|
|
262
|
+
if (options.onUsage) {
|
|
263
|
+
options.onUsage(usage, this.getProviderName());
|
|
264
|
+
}
|
|
265
|
+
return {
|
|
266
|
+
text: result.text,
|
|
267
|
+
usage,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
supportsModel(model) {
|
|
271
|
+
const m = model;
|
|
272
|
+
switch (m) {
|
|
273
|
+
case exports.Models.GPT4:
|
|
274
|
+
case exports.Models.GPT35_TURBO:
|
|
275
|
+
return !!this.openaiKey;
|
|
276
|
+
case exports.Models.CLAUDE3_SONNET:
|
|
277
|
+
case exports.Models.CLAUDE3_HAIKU:
|
|
278
|
+
return !!this.anthropicKey;
|
|
279
|
+
case exports.Models.GEMINI_PRO:
|
|
280
|
+
case exports.Models.GEMINI_PRO_VISION:
|
|
281
|
+
return !!this.googleKey;
|
|
282
|
+
case exports.Models.GROK_BETA:
|
|
283
|
+
return !!this.xaiKey;
|
|
284
|
+
default:
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
getProviderName() {
|
|
289
|
+
return 'multi';
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
class ProviderRegistry {
|
|
294
|
+
constructor() {
|
|
295
|
+
this.providers = new Map();
|
|
296
|
+
this.registerDefaultProviders();
|
|
297
|
+
}
|
|
298
|
+
registerDefaultProviders() {
|
|
299
|
+
const hasAnyMultiKey = !!(process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || process.env.GOOGLE_GENERATIVE_AI_API_KEY || process.env.XAI_API_KEY);
|
|
300
|
+
if (hasAnyMultiKey) {
|
|
301
|
+
this.providers.set('multi', new MultiProvider());
|
|
302
|
+
}
|
|
303
|
+
if (process.env.SWIZZY_API_KEY) {
|
|
304
|
+
this.providers.set('swizzy', new SwizzyProvider({ apiKey: process.env.SWIZZY_API_KEY }));
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
registerProvider(name, provider) {
|
|
308
|
+
this.providers.set(name, provider);
|
|
309
|
+
}
|
|
310
|
+
getProviderForModel(model) {
|
|
311
|
+
for (const provider of this.providers.values()) {
|
|
312
|
+
if (provider.supportsModel(model)) {
|
|
313
|
+
return provider;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
throw new Error(`No provider registered for model: ${model}`);
|
|
317
|
+
}
|
|
318
|
+
getAllProviders() {
|
|
319
|
+
return Array.from(this.providers.values());
|
|
320
|
+
}
|
|
321
|
+
hasProviderForModel(model) {
|
|
322
|
+
try {
|
|
323
|
+
this.getProviderForModel(model);
|
|
324
|
+
return true;
|
|
325
|
+
}
|
|
326
|
+
catch {
|
|
327
|
+
return false;
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
class LLMClient {
|
|
333
|
+
constructor(registry) {
|
|
334
|
+
this.registry = registry;
|
|
335
|
+
}
|
|
336
|
+
async complete(options) {
|
|
337
|
+
const model = options.model;
|
|
338
|
+
if (!model) {
|
|
339
|
+
throw new Error('Model must be specified in CompletionOptions');
|
|
340
|
+
}
|
|
341
|
+
const provider = this.registry.getProviderForModel(model);
|
|
342
|
+
return provider.complete(options);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
class BungeeBuilder {
|
|
347
|
+
constructor(currentStepId) {
|
|
348
|
+
/**
|
|
349
|
+
* Add a single step execution.
|
|
350
|
+
*/
|
|
351
|
+
this.add = (stepId) => {
|
|
352
|
+
this._plan.destinations.push({ type: 'step', targetId: stepId });
|
|
353
|
+
return this;
|
|
354
|
+
};
|
|
355
|
+
/**
|
|
356
|
+
* Add multiple executions based on count with config function.
|
|
357
|
+
*/
|
|
358
|
+
this.batch = (stepId, count, configFn) => {
|
|
359
|
+
for (let i = 0; i < count; i++) {
|
|
360
|
+
this._plan.destinations.push({ type: 'step', targetId: stepId });
|
|
361
|
+
}
|
|
362
|
+
this._plan.configFn = configFn;
|
|
363
|
+
return this;
|
|
364
|
+
};
|
|
365
|
+
/**
|
|
366
|
+
* Configure execution settings.
|
|
367
|
+
*/
|
|
368
|
+
this.config = (options) => {
|
|
369
|
+
if (options.concurrency !== undefined) {
|
|
370
|
+
this._plan.concurrency = options.concurrency;
|
|
371
|
+
}
|
|
372
|
+
return this;
|
|
373
|
+
};
|
|
374
|
+
/**
|
|
375
|
+
* Trigger the Jump.
|
|
376
|
+
*/
|
|
377
|
+
this.jump = () => {
|
|
378
|
+
return { type: 'BUNGEE_JUMP', plan: this._plan };
|
|
379
|
+
};
|
|
380
|
+
this._plan = {
|
|
381
|
+
id: `bungee_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
|
|
382
|
+
anchorId: currentStepId,
|
|
383
|
+
destinations: [],
|
|
384
|
+
concurrency: 5
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
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
|
+
};
|
|
428
|
+
}
|
|
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 });
|
|
433
|
+
}
|
|
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');
|
|
448
|
+
}
|
|
449
|
+
catch (error) {
|
|
450
|
+
console.log('Wizard log:', content.trim());
|
|
451
|
+
}
|
|
452
|
+
}
|
|
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';
|
|
499
|
+
}
|
|
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
|
+
}
|
|
517
|
+
}
|
|
518
|
+
}
|
|
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}`);
|
|
522
|
+
}
|
|
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);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
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)
|
|
570
|
+
}
|
|
571
|
+
};
|
|
572
|
+
}
|
|
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
|
+
};
|
|
590
|
+
}
|
|
591
|
+
createTelescopeContext(baseContext, telescope) {
|
|
592
|
+
return {
|
|
593
|
+
...baseContext,
|
|
594
|
+
...telescope,
|
|
595
|
+
_telescope: telescope,
|
|
596
|
+
_anchorId: null
|
|
597
|
+
};
|
|
598
|
+
}
|
|
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);
|
|
607
|
+
}
|
|
608
|
+
mergeWorkerResults(updates, telescope) {
|
|
609
|
+
Object.entries(updates).forEach(([key, value]) => {
|
|
610
|
+
this.workflowContext[key] = value;
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
async retriggerAnchor(anchorId) {
|
|
614
|
+
const anchorStep = this.findStep(anchorId);
|
|
615
|
+
if (anchorStep) {
|
|
616
|
+
await this.executeStep(anchorStep);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
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);
|
|
624
|
+
}
|
|
625
|
+
}
|
|
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;
|
|
632
|
+
}
|
|
633
|
+
else {
|
|
634
|
+
if (item.id === stepId)
|
|
635
|
+
return item;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
findStepIndex(stepId) {
|
|
641
|
+
return this.stepIndexMap.get(stepId) ?? -1;
|
|
642
|
+
}
|
|
643
|
+
async executeStep(step) {
|
|
644
|
+
console.log(`Starting step ${step.id}`);
|
|
645
|
+
this.log(`Starting step ${step.id}`);
|
|
646
|
+
const stepContext = step.getContext(this.workflowContext);
|
|
647
|
+
let processedInstruction = step.instruction;
|
|
648
|
+
if (step.contextType === 'template' || step.contextType === 'both') {
|
|
649
|
+
processedInstruction = this.applyTemplate(step.instruction, stepContext);
|
|
650
|
+
}
|
|
651
|
+
this.sendToClients({
|
|
652
|
+
type: 'step_update',
|
|
653
|
+
stepId: step.id,
|
|
654
|
+
status: 'current',
|
|
655
|
+
instruction: processedInstruction,
|
|
656
|
+
context: stepContext,
|
|
657
|
+
fields: this.extractSchemaFields(step.schema)
|
|
658
|
+
});
|
|
659
|
+
this.sendToClients({
|
|
660
|
+
type: 'context_update',
|
|
661
|
+
context: this.workflowContext
|
|
662
|
+
});
|
|
663
|
+
try {
|
|
664
|
+
if (step.beforeRun) {
|
|
665
|
+
await step.beforeRun();
|
|
666
|
+
}
|
|
667
|
+
this.log(() => `Context for step ${step.id}: ${JSON.stringify(stepContext)}`);
|
|
668
|
+
// Skip LLM data generation for compute steps
|
|
669
|
+
const stepData = step.isComputeStep ? null : await this.generateStepData(step, stepContext);
|
|
670
|
+
if (this.isPaused) {
|
|
671
|
+
console.log('βΈοΈ Paused before LLM call, waiting for user input...');
|
|
672
|
+
await this.waitForResume();
|
|
673
|
+
console.log('βΆοΈ Resumed, checking for user override...');
|
|
674
|
+
if (this.userOverrideData) {
|
|
675
|
+
console.log('π Using user override data');
|
|
676
|
+
try {
|
|
677
|
+
const validatedData = step.validate(this.userOverrideData);
|
|
678
|
+
this.sendToClients({
|
|
679
|
+
type: 'step_update',
|
|
680
|
+
stepId: step.id,
|
|
681
|
+
status: 'completed',
|
|
682
|
+
data: validatedData
|
|
683
|
+
});
|
|
684
|
+
this.userOverrideData = undefined;
|
|
685
|
+
return await this.processStepResult(step, validatedData);
|
|
686
|
+
}
|
|
687
|
+
catch (validationError) {
|
|
688
|
+
console.error('User override validation failed:', validationError.message);
|
|
689
|
+
this.userOverrideData = undefined;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
if (stepData && stepData.__validationFailed) {
|
|
694
|
+
console.log(`π Validation failed for step ${step.id}, retrying...`, stepData);
|
|
695
|
+
return 'RETRY';
|
|
696
|
+
}
|
|
697
|
+
const actions = this.createWizardActions(step.id);
|
|
698
|
+
const signal = await step.update(stepData, this.workflowContext, actions);
|
|
699
|
+
this.sendToClients({
|
|
700
|
+
type: 'step_update',
|
|
701
|
+
stepId: step.id,
|
|
702
|
+
status: 'completed',
|
|
703
|
+
data: stepData
|
|
704
|
+
});
|
|
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;
|
|
712
|
+
}
|
|
713
|
+
catch (error) {
|
|
714
|
+
console.log('Processing error', error);
|
|
715
|
+
this.updateContext({
|
|
716
|
+
[`${step.id}_error`]: error.message,
|
|
717
|
+
[`${step.id}_retryCount`]: (this.workflowContext[`${step.id}_retryCount`] || 0) + 1
|
|
718
|
+
});
|
|
719
|
+
return 'RETRY';
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
async processStepResult(step, stepData) {
|
|
723
|
+
const actions = this.createWizardActions(step.id);
|
|
724
|
+
const signal = await step.update(stepData, this.workflowContext, actions);
|
|
725
|
+
if (this.workflowContext[`${step.id}_error`]) {
|
|
726
|
+
this.clearStepError(step.id);
|
|
727
|
+
}
|
|
728
|
+
if (step.afterRun) {
|
|
729
|
+
await step.afterRun(stepData);
|
|
730
|
+
}
|
|
731
|
+
return signal;
|
|
732
|
+
}
|
|
733
|
+
setContext(context) {
|
|
734
|
+
this.workflowContext = { ...this.workflowContext, ...context };
|
|
735
|
+
return this;
|
|
736
|
+
}
|
|
737
|
+
getContext() {
|
|
738
|
+
return this.workflowContext;
|
|
739
|
+
}
|
|
740
|
+
updateContext(updates) {
|
|
741
|
+
this.workflowContext = { ...this.workflowContext, ...updates };
|
|
742
|
+
return this;
|
|
743
|
+
}
|
|
744
|
+
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 => {
|
|
753
|
+
if (Array.isArray(item)) {
|
|
754
|
+
return item.map(step => ({
|
|
755
|
+
id: step.id,
|
|
756
|
+
instruction: step.instruction,
|
|
757
|
+
fields: this.extractSchemaFields(step.schema),
|
|
758
|
+
status: 'pending'
|
|
759
|
+
}));
|
|
760
|
+
}
|
|
761
|
+
else {
|
|
762
|
+
return {
|
|
763
|
+
id: item.id,
|
|
764
|
+
instruction: item.instruction,
|
|
765
|
+
fields: this.extractSchemaFields(item.schema),
|
|
766
|
+
status: 'pending'
|
|
767
|
+
};
|
|
768
|
+
}
|
|
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;
|
|
775
|
+
while (this.currentStepIndex < this.steps.length && this.isRunning) {
|
|
776
|
+
const item = this.steps[this.currentStepIndex];
|
|
777
|
+
if (!item)
|
|
778
|
+
break;
|
|
779
|
+
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
|
+
}
|
|
812
|
+
}
|
|
813
|
+
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
|
+
}
|
|
842
|
+
}
|
|
843
|
+
if (this.isStepMode) {
|
|
844
|
+
this.isPaused = true;
|
|
845
|
+
await this.waitForResume();
|
|
846
|
+
}
|
|
847
|
+
await this.processReentries();
|
|
848
|
+
}
|
|
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
|
+
});
|
|
859
|
+
}
|
|
860
|
+
async generateStepData(step, stepContext) {
|
|
861
|
+
const systemContext = this.systemPrompt ? `${this.systemPrompt}\n\n` : '';
|
|
862
|
+
const errorContext = this.workflowContext[`${step.id}_error`] ?
|
|
863
|
+
`\n\nPREVIOUS ERROR (attempt ${this.workflowContext[`${step.id}_retryCount`] || 1}):\n${this.workflowContext[`${step.id}_error`]}\nPlease fix this.` : '';
|
|
864
|
+
let processedInstruction = step.instruction;
|
|
865
|
+
if (step.contextType === 'template' || step.contextType === 'both') {
|
|
866
|
+
processedInstruction = this.applyTemplate(step.instruction, stepContext);
|
|
867
|
+
}
|
|
868
|
+
this.log(() => `Processed instruction for step ${step.id}: ${processedInstruction}`);
|
|
869
|
+
let contextSection = '';
|
|
870
|
+
if (step.contextType === 'xml' || step.contextType === 'both' || !step.contextType) {
|
|
871
|
+
contextSection = `\n\nSTEP CONTEXT:\n${this.objectToXml(stepContext)}`;
|
|
872
|
+
}
|
|
873
|
+
this.log(() => `Context section for step ${step.id}: ${contextSection}`);
|
|
874
|
+
if (step instanceof TextStep) {
|
|
875
|
+
const prompt = `${systemContext}You are executing a wizard step. Generate text for this step.
|
|
876
|
+
|
|
877
|
+
STEP: ${step.id}
|
|
878
|
+
INSTRUCTION: ${processedInstruction}${errorContext}${contextSection}
|
|
879
|
+
|
|
880
|
+
Generate the text response now.`;
|
|
881
|
+
this.log(() => `Full prompt for step ${step.id}: ${prompt}`);
|
|
882
|
+
const llmResult = await this.llmClient.complete({
|
|
883
|
+
prompt,
|
|
884
|
+
model: step.model,
|
|
885
|
+
maxTokens: 1000,
|
|
886
|
+
temperature: 0.3,
|
|
887
|
+
});
|
|
888
|
+
this.log(() => `LLM response for step ${step.id}: ${llmResult.text}`);
|
|
889
|
+
console.log(`LLM response for step ${step.id}:`, llmResult.text);
|
|
890
|
+
return llmResult.text;
|
|
891
|
+
}
|
|
892
|
+
const schemaDescription = this.describeSchema(step.schema, step.id);
|
|
893
|
+
const prompt = `${systemContext}You are executing a wizard step. Generate data for this step.
|
|
894
|
+
|
|
895
|
+
STEP: ${step.id}
|
|
896
|
+
INSTRUCTION: ${processedInstruction}${errorContext}${contextSection}
|
|
897
|
+
|
|
898
|
+
SCHEMA REQUIREMENTS:
|
|
899
|
+
${schemaDescription}
|
|
900
|
+
|
|
901
|
+
REQUIRED OUTPUT FORMAT:
|
|
902
|
+
Return a plain XML response with a root <response> tag.
|
|
903
|
+
CRITICAL: Every field MUST include tag-category="wizard" attribute. This is MANDATORY.
|
|
904
|
+
Every field MUST also include a type attribute (e.g., type="string", type="number", type="boolean", type="array").
|
|
905
|
+
|
|
906
|
+
IMPORTANT PARSING RULES:
|
|
907
|
+
- Fields with tag-category="wizard" do NOT need closing tags
|
|
908
|
+
- Content ends when the next tag with tag-category="wizard" begins, OR when </response> is reached
|
|
909
|
+
- This means you can include ANY content (including code with <>, XML snippets, etc.) without worrying about breaking the parser
|
|
910
|
+
- Only fields marked with tag-category="wizard" will be parsed
|
|
911
|
+
|
|
912
|
+
Example:
|
|
913
|
+
<response>
|
|
914
|
+
<name tag-category="wizard" type="string">John Smith
|
|
915
|
+
<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
|
+
<tags tag-category="wizard" type="array">["a", "b", "c"]
|
|
922
|
+
</response>
|
|
923
|
+
|
|
924
|
+
Notice: No closing tags needed for wizard fields! Content naturally ends at the next wizard field or </response>.
|
|
925
|
+
|
|
926
|
+
Generate the XML response now.`;
|
|
927
|
+
this.log(() => `Full prompt for step ${step.id}: ${prompt}`);
|
|
928
|
+
const llmResult = await this.llmClient.complete({
|
|
929
|
+
prompt,
|
|
930
|
+
model: step.model,
|
|
931
|
+
maxTokens: 1000,
|
|
932
|
+
temperature: 0.3,
|
|
933
|
+
});
|
|
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)}`);
|
|
938
|
+
try {
|
|
939
|
+
return step.validate(jsonData);
|
|
940
|
+
}
|
|
941
|
+
catch (validationError) {
|
|
942
|
+
this.log(() => `Validation failed for step ${step.id}: ${validationError.message}`);
|
|
943
|
+
try {
|
|
944
|
+
const repairedData = await this.repairSchemaData(jsonData, step.schema, validationError.message, step.id);
|
|
945
|
+
this.log(() => `Repaired data for step ${step.id}: ${JSON.stringify(repairedData)}`);
|
|
946
|
+
return step.validate(repairedData);
|
|
947
|
+
}
|
|
948
|
+
catch (repairError) {
|
|
949
|
+
this.log(() => `Repair failed for step ${step.id}: ${repairError.message}`);
|
|
950
|
+
return { __validationFailed: true, error: validationError.message };
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
async repairSchemaData(invalidData, schema, validationError, stepId) {
|
|
955
|
+
const step = this.findStep(stepId);
|
|
956
|
+
if (!step)
|
|
957
|
+
throw new Error(`Step ${stepId} not found`);
|
|
958
|
+
const schemaDescription = this.describeSchema(schema, stepId);
|
|
959
|
+
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
|
+
|
|
961
|
+
INVALID DATA: ${JSON.stringify(invalidData, null, 2)}
|
|
962
|
+
VALIDATION ERROR: ${validationError}
|
|
963
|
+
|
|
964
|
+
SCHEMA REQUIREMENTS:
|
|
965
|
+
${schemaDescription}
|
|
966
|
+
|
|
967
|
+
REQUIRED OUTPUT FORMAT:
|
|
968
|
+
Return a plain XML response with a root <response> tag.
|
|
969
|
+
CRITICAL: Every field MUST include tag-category="wizard" attribute. This is MANDATORY.
|
|
970
|
+
Every field MUST also include a type attribute (e.g., type="string", type="number", type="boolean", type="array").
|
|
971
|
+
|
|
972
|
+
IMPORTANT: Fields with tag-category="wizard" do NOT need closing tags. Content ends at the next wizard field or </response>.
|
|
973
|
+
|
|
974
|
+
Example:
|
|
975
|
+
<response>
|
|
976
|
+
<name tag-category="wizard" type="string">John
|
|
977
|
+
<age tag-category="wizard" type="number">25
|
|
978
|
+
<tags tag-category="wizard" type="array">["a", "b"]
|
|
979
|
+
</response>
|
|
980
|
+
|
|
981
|
+
Fix the data to match the schema and generate the XML response now.`;
|
|
982
|
+
const llmResult = await this.llmClient.complete({
|
|
983
|
+
prompt,
|
|
984
|
+
model: step.model,
|
|
985
|
+
maxTokens: 10000,
|
|
986
|
+
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;
|
|
1017
|
+
}
|
|
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]}`;
|
|
1028
|
+
}
|
|
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(', ');
|
|
1058
|
+
}
|
|
1059
|
+
fields.push(field);
|
|
1060
|
+
}
|
|
1061
|
+
return fields;
|
|
1062
|
+
}
|
|
1063
|
+
parseXmlToJson(xml) {
|
|
1064
|
+
const responseMatch = xml.match(/<response\s*>([\s\S]*?)(?:<\/response\s*>|$)/i);
|
|
1065
|
+
if (!responseMatch) {
|
|
1066
|
+
throw new Error('Invalid XML response: missing <response> tag');
|
|
1067
|
+
}
|
|
1068
|
+
return this.parseXmlElementWithTagCategory(responseMatch[1]);
|
|
1069
|
+
}
|
|
1070
|
+
parseXmlElementWithTagCategory(xmlContent) {
|
|
1071
|
+
const result = {};
|
|
1072
|
+
const matches = [];
|
|
1073
|
+
let match;
|
|
1074
|
+
const pattern = new RegExp(Wizard.WIZARD_TAG_PATTERN);
|
|
1075
|
+
while ((match = pattern.exec(xmlContent)) !== null) {
|
|
1076
|
+
matches.push({
|
|
1077
|
+
tagName: match[1],
|
|
1078
|
+
attributes: match[2],
|
|
1079
|
+
index: match.index,
|
|
1080
|
+
fullMatch: match[0]
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
this.log(() => `Found ${matches.length} wizard-tagged fields`);
|
|
1084
|
+
for (let i = 0; i < matches.length; i++) {
|
|
1085
|
+
const current = matches[i];
|
|
1086
|
+
const next = matches[i + 1];
|
|
1087
|
+
const typeMatch = current.attributes.match(/type=["']([^"']+)["']/);
|
|
1088
|
+
const typeHint = typeMatch ? typeMatch[1].toLowerCase() : null;
|
|
1089
|
+
const contentStart = current.index + current.fullMatch.length;
|
|
1090
|
+
let contentEnd;
|
|
1091
|
+
if (next) {
|
|
1092
|
+
contentEnd = next.index;
|
|
1093
|
+
}
|
|
1094
|
+
else {
|
|
1095
|
+
const responseCloseIndex = xmlContent.indexOf('</response', contentStart);
|
|
1096
|
+
contentEnd = responseCloseIndex !== -1 ? responseCloseIndex : xmlContent.length;
|
|
1097
|
+
}
|
|
1098
|
+
let rawContent = xmlContent.slice(contentStart, contentEnd);
|
|
1099
|
+
// Optimize: avoid double trimEnd
|
|
1100
|
+
const trimmed = rawContent.trimEnd();
|
|
1101
|
+
const closingTag = `</${current.tagName}>`;
|
|
1102
|
+
if (trimmed.endsWith(closingTag)) {
|
|
1103
|
+
rawContent = trimmed.slice(0, -closingTag.length);
|
|
1104
|
+
}
|
|
1105
|
+
else {
|
|
1106
|
+
rawContent = trimmed;
|
|
1107
|
+
}
|
|
1108
|
+
this.log(() => `Parsing field "${current.tagName}" with type="${typeHint}"`);
|
|
1109
|
+
this.log(() => `Raw content (first 200 chars): ${rawContent.substring(0, 200)}`);
|
|
1110
|
+
let value;
|
|
1111
|
+
if (typeHint === 'string') {
|
|
1112
|
+
value = rawContent;
|
|
1113
|
+
}
|
|
1114
|
+
else if (typeHint === 'number') {
|
|
1115
|
+
value = this.parseNumber(rawContent.trim());
|
|
1116
|
+
}
|
|
1117
|
+
else if (typeHint === 'boolean') {
|
|
1118
|
+
value = this.parseBoolean(rawContent.trim());
|
|
1119
|
+
}
|
|
1120
|
+
else if (typeHint === 'array') {
|
|
1121
|
+
value = this.parseArray(rawContent.trim());
|
|
1122
|
+
}
|
|
1123
|
+
else if (typeHint === 'object') {
|
|
1124
|
+
value = this.parseXmlElementWithTagCategory(rawContent);
|
|
1125
|
+
}
|
|
1126
|
+
else if (typeHint === 'null') {
|
|
1127
|
+
value = null;
|
|
1128
|
+
}
|
|
1129
|
+
else {
|
|
1130
|
+
value = this.inferAndParseValue(rawContent.trim());
|
|
1131
|
+
}
|
|
1132
|
+
if (result[current.tagName] !== undefined) {
|
|
1133
|
+
if (!Array.isArray(result[current.tagName])) {
|
|
1134
|
+
result[current.tagName] = [result[current.tagName]];
|
|
1135
|
+
}
|
|
1136
|
+
result[current.tagName].push(value);
|
|
1137
|
+
}
|
|
1138
|
+
else {
|
|
1139
|
+
result[current.tagName] = value;
|
|
1140
|
+
}
|
|
1141
|
+
this.log(() => `Parsed "${current.tagName}" = ${JSON.stringify(value).substring(0, 200)}`);
|
|
1142
|
+
}
|
|
1143
|
+
return result;
|
|
1144
|
+
}
|
|
1145
|
+
parseNumber(value) {
|
|
1146
|
+
const num = Number(value);
|
|
1147
|
+
if (isNaN(num)) {
|
|
1148
|
+
throw new Error(`Invalid number value: "${value}"`);
|
|
1149
|
+
}
|
|
1150
|
+
return num;
|
|
1151
|
+
}
|
|
1152
|
+
parseBoolean(value) {
|
|
1153
|
+
const normalized = value.toLowerCase();
|
|
1154
|
+
if (normalized === 'true')
|
|
1155
|
+
return true;
|
|
1156
|
+
if (normalized === 'false')
|
|
1157
|
+
return false;
|
|
1158
|
+
throw new Error(`Invalid boolean value: "${value}" (expected "true" or "false")`);
|
|
1159
|
+
}
|
|
1160
|
+
parseArray(value) {
|
|
1161
|
+
try {
|
|
1162
|
+
const parsed = JSON.parse(value);
|
|
1163
|
+
if (!Array.isArray(parsed)) {
|
|
1164
|
+
throw new Error('Parsed value is not an array');
|
|
1165
|
+
}
|
|
1166
|
+
return parsed;
|
|
1167
|
+
}
|
|
1168
|
+
catch (error) {
|
|
1169
|
+
throw new Error(`Invalid array JSON: "${value}"`);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
inferAndParseValue(content) {
|
|
1173
|
+
const trimmed = content.trim();
|
|
1174
|
+
if (trimmed === '')
|
|
1175
|
+
return '';
|
|
1176
|
+
if (trimmed === 'null')
|
|
1177
|
+
return null;
|
|
1178
|
+
if (trimmed === 'true')
|
|
1179
|
+
return true;
|
|
1180
|
+
if (trimmed === 'false')
|
|
1181
|
+
return false;
|
|
1182
|
+
if (!isNaN(Number(trimmed)) && trimmed !== '') {
|
|
1183
|
+
return Number(trimmed);
|
|
1184
|
+
}
|
|
1185
|
+
if (/<\w+[^>]*tag-category=["']wizard["']/.test(trimmed)) {
|
|
1186
|
+
return this.parseXmlElementWithTagCategory(trimmed);
|
|
1187
|
+
}
|
|
1188
|
+
if ((trimmed.startsWith('[') && trimmed.endsWith(']')) ||
|
|
1189
|
+
(trimmed.startsWith('{') && trimmed.endsWith('}'))) {
|
|
1190
|
+
try {
|
|
1191
|
+
return JSON.parse(trimmed);
|
|
1192
|
+
}
|
|
1193
|
+
catch {
|
|
1194
|
+
return trimmed;
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
return trimmed;
|
|
1198
|
+
}
|
|
1199
|
+
objectToXml(obj, rootName = 'context') {
|
|
1200
|
+
const buildXml = (data, tagName) => {
|
|
1201
|
+
let type = typeof data;
|
|
1202
|
+
if (data === null)
|
|
1203
|
+
type = 'null';
|
|
1204
|
+
else if (Array.isArray(data))
|
|
1205
|
+
type = 'array';
|
|
1206
|
+
const attr = ` type="${type}"`;
|
|
1207
|
+
if (data === null || data === undefined)
|
|
1208
|
+
return `<${tagName}${attr}></${tagName}>`;
|
|
1209
|
+
if (type === 'string')
|
|
1210
|
+
return `<${tagName}${attr}>${this.escapeXml(data)}</${tagName}>`;
|
|
1211
|
+
if (type === 'number' || type === 'boolean')
|
|
1212
|
+
return `<${tagName}${attr}>${data}</${tagName}>`;
|
|
1213
|
+
if (type === 'array')
|
|
1214
|
+
return `<${tagName}${attr}>${JSON.stringify(data)}</${tagName}>`;
|
|
1215
|
+
if (type === 'object') {
|
|
1216
|
+
const children = Object.entries(data).map(([k, v]) => buildXml(v, k)).join('');
|
|
1217
|
+
return `<${tagName}${attr}>${children}</${tagName}>`;
|
|
1218
|
+
}
|
|
1219
|
+
return `<${tagName}${attr}>${String(data)}</${tagName}>`;
|
|
1220
|
+
};
|
|
1221
|
+
return buildXml(obj, rootName);
|
|
1222
|
+
}
|
|
1223
|
+
escapeXml(unsafe) {
|
|
1224
|
+
return unsafe
|
|
1225
|
+
.replace(/&/g, '&')
|
|
1226
|
+
.replace(/</g, '<')
|
|
1227
|
+
.replace(/>/g, '>')
|
|
1228
|
+
.replace(/"/g, '"')
|
|
1229
|
+
.replace(/'/g, ''');
|
|
1230
|
+
}
|
|
1231
|
+
applyTemplate(instruction, context) {
|
|
1232
|
+
return instruction.replace(Wizard.TEMPLATE_REGEX, (match, path) => {
|
|
1233
|
+
const keys = path.split('.');
|
|
1234
|
+
let value = context;
|
|
1235
|
+
for (const key of keys) {
|
|
1236
|
+
if (value && typeof value === 'object' && key in value) {
|
|
1237
|
+
value = value[key];
|
|
1238
|
+
}
|
|
1239
|
+
else {
|
|
1240
|
+
return match;
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return typeof value === 'string' ? value : JSON.stringify(value);
|
|
1244
|
+
});
|
|
1245
|
+
}
|
|
1246
|
+
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');
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
Wizard.TEMPLATE_REGEX = /\{\{(\w+(?:\.\w+)*)\}\}/g;
|
|
1437
|
+
Wizard.WIZARD_TAG_PATTERN = /<(\w+)\s+([^>]*tag-category=["']wizard["'][^>]*)>/gi;
|
|
1438
|
+
|
|
1439
|
+
exports.BaseProvider = BaseProvider;
|
|
1440
|
+
exports.BungeeBuilder = BungeeBuilder;
|
|
1441
|
+
exports.ComputeStep = ComputeStep;
|
|
1442
|
+
exports.LLMClient = LLMClient;
|
|
1443
|
+
exports.Model = Model;
|
|
1444
|
+
exports.MultiProvider = MultiProvider;
|
|
1445
|
+
exports.ProviderRegistry = ProviderRegistry;
|
|
1446
|
+
exports.Step = Step;
|
|
1447
|
+
exports.SwizzyProvider = SwizzyProvider;
|
|
1448
|
+
exports.TextStep = TextStep;
|
|
1449
|
+
exports.Wizard = Wizard;
|
|
1450
|
+
exports.default = Wizard;
|
|
1451
|
+
//# sourceMappingURL=index.js.map
|