jsfe 0.8.0 → 0.9.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 +272 -8
- package/dist/index.d.ts +23 -37
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +507 -224
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -302,6 +302,66 @@ export function getFlowPrompt(engine, flowName) {
|
|
|
302
302
|
// Fallback to default prompt or flow name
|
|
303
303
|
return flow.prompt || flow.name;
|
|
304
304
|
}
|
|
305
|
+
// Pure function-based transaction management (no methods, always serializable)
|
|
306
|
+
export class TransactionManager {
|
|
307
|
+
static create(flowName, initiator, userId) {
|
|
308
|
+
return {
|
|
309
|
+
id: crypto.randomUUID(),
|
|
310
|
+
flowName: flowName,
|
|
311
|
+
initiator: initiator,
|
|
312
|
+
userId: userId,
|
|
313
|
+
steps: [],
|
|
314
|
+
state: 'active',
|
|
315
|
+
createdAt: new Date(),
|
|
316
|
+
metadata: {}
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
static addStep(transaction, step, result, duration, status = 'success') {
|
|
320
|
+
const transactionStep = {
|
|
321
|
+
stepId: step.id || crypto.randomUUID(),
|
|
322
|
+
stepType: step.type || 'unknown',
|
|
323
|
+
timestamp: new Date(),
|
|
324
|
+
duration: duration,
|
|
325
|
+
status: status,
|
|
326
|
+
result: TransactionManager.sanitizeForLog(result)
|
|
327
|
+
};
|
|
328
|
+
if (status === 'error' && result instanceof Error) {
|
|
329
|
+
transactionStep.error = result.message;
|
|
330
|
+
}
|
|
331
|
+
transaction.steps.push(transactionStep);
|
|
332
|
+
}
|
|
333
|
+
static addError(transaction, step, error, duration) {
|
|
334
|
+
TransactionManager.addStep(transaction, step, error, duration, 'error');
|
|
335
|
+
}
|
|
336
|
+
static sanitizeForLog(data) {
|
|
337
|
+
if (typeof data === 'string') {
|
|
338
|
+
return data.length > 500 ? data.substring(0, 500) + '...' : data;
|
|
339
|
+
}
|
|
340
|
+
if (typeof data === 'object' && data !== null) {
|
|
341
|
+
try {
|
|
342
|
+
const jsonStr = JSON.stringify(data);
|
|
343
|
+
return jsonStr.length > 500 ? jsonStr.substring(0, 500) + '...' : data;
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return '[Circular or non-serializable object]';
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return data;
|
|
350
|
+
}
|
|
351
|
+
static rollback(transaction) {
|
|
352
|
+
transaction.state = 'rolled_back';
|
|
353
|
+
transaction.rolledBackAt = new Date();
|
|
354
|
+
}
|
|
355
|
+
static complete(transaction) {
|
|
356
|
+
transaction.state = 'completed';
|
|
357
|
+
transaction.completedAt = new Date();
|
|
358
|
+
}
|
|
359
|
+
static fail(transaction, reason) {
|
|
360
|
+
transaction.state = 'failed';
|
|
361
|
+
transaction.failedAt = new Date();
|
|
362
|
+
transaction.failureReason = reason;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
305
365
|
// Initialize JSON Schema validator
|
|
306
366
|
const ajv = new Ajv({ allErrors: true, strict: false });
|
|
307
367
|
addFormats(ajv);
|
|
@@ -318,6 +378,7 @@ function isPathTraversableObject(val) {
|
|
|
318
378
|
* @param data - Source data to transform (typically API response)
|
|
319
379
|
* @param mappingConfig - Mapping configuration specifying how to transform the data
|
|
320
380
|
* @param args - Arguments available for template variables and $args references
|
|
381
|
+
* @param engine - Engine instance for context and utilities
|
|
321
382
|
* @returns Transformed data according to mapping configuration
|
|
322
383
|
*
|
|
323
384
|
* @example
|
|
@@ -335,7 +396,7 @@ function isPathTraversableObject(val) {
|
|
|
335
396
|
* ) // "Name: Alice, Age: 25"
|
|
336
397
|
* ```
|
|
337
398
|
*/
|
|
338
|
-
function applyResponseMapping(data, mappingConfig, args = {}) {
|
|
399
|
+
function applyResponseMapping(data, mappingConfig, args = {}, engine) {
|
|
339
400
|
logger.debug(`applyResponseMapping called with: dataType=${typeof data}, data=${JSON.stringify(data)}, mappingConfig=${JSON.stringify(mappingConfig)}, args=${JSON.stringify(args)}`);
|
|
340
401
|
if (!mappingConfig) {
|
|
341
402
|
logger.debug(`No mapping config provided, returning original data`);
|
|
@@ -355,13 +416,13 @@ function applyResponseMapping(data, mappingConfig, args = {}) {
|
|
|
355
416
|
case 'jsonPath':
|
|
356
417
|
return applyJsonPathMapping(data, mappingConfig, args);
|
|
357
418
|
case 'object':
|
|
358
|
-
return applyObjectMapping(data, mappingConfig, args);
|
|
419
|
+
return applyObjectMapping(data, mappingConfig, args, engine);
|
|
359
420
|
case 'array':
|
|
360
|
-
return applyArrayMapping(data, mappingConfig, args);
|
|
421
|
+
return applyArrayMapping(data, mappingConfig, args, engine);
|
|
361
422
|
case 'template':
|
|
362
|
-
return applyTemplateMapping(data, mappingConfig, args);
|
|
423
|
+
return applyTemplateMapping(data, mappingConfig, args, engine);
|
|
363
424
|
case 'conditional':
|
|
364
|
-
return applyConditionalMapping(data, mappingConfig, args);
|
|
425
|
+
return applyConditionalMapping(data, mappingConfig, args, engine);
|
|
365
426
|
}
|
|
366
427
|
}
|
|
367
428
|
// Handle PathConfig (has path property but not type)
|
|
@@ -468,7 +529,7 @@ function applyJsonPathMapping(data, config, args) {
|
|
|
468
529
|
return result;
|
|
469
530
|
}
|
|
470
531
|
// Object structure mapping with nested transformations
|
|
471
|
-
function applyObjectMapping(data, config, args) {
|
|
532
|
+
function applyObjectMapping(data, config, args, engine) {
|
|
472
533
|
const result = {};
|
|
473
534
|
for (const [key, value] of Object.entries(config.mappings)) {
|
|
474
535
|
try {
|
|
@@ -478,7 +539,7 @@ function applyObjectMapping(data, config, args) {
|
|
|
478
539
|
}
|
|
479
540
|
else if (typeof value === 'object' && value !== null && 'type' in value) {
|
|
480
541
|
// Nested mapping with type
|
|
481
|
-
result[key] = applyResponseMapping(data, value, args);
|
|
542
|
+
result[key] = applyResponseMapping(data, value, args, engine);
|
|
482
543
|
}
|
|
483
544
|
else if (typeof value === 'object' && value !== null && 'path' in value) {
|
|
484
545
|
// Object with path and optional transform
|
|
@@ -496,7 +557,7 @@ function applyObjectMapping(data, config, args) {
|
|
|
496
557
|
}
|
|
497
558
|
else if (typeof value === 'object' && value !== null) {
|
|
498
559
|
// Static object with potential interpolation
|
|
499
|
-
result[key] = interpolateObject(value, data, args);
|
|
560
|
+
result[key] = interpolateObject(value, data, args, engine);
|
|
500
561
|
}
|
|
501
562
|
else {
|
|
502
563
|
// Literal value
|
|
@@ -517,7 +578,7 @@ function applyObjectMapping(data, config, args) {
|
|
|
517
578
|
return result;
|
|
518
579
|
}
|
|
519
580
|
// Array transformation and filtering
|
|
520
|
-
function applyArrayMapping(data, config, args) {
|
|
581
|
+
function applyArrayMapping(data, config, args, engine) {
|
|
521
582
|
const sourceArray = config.source ? extractByPath(data, config.source) : data;
|
|
522
583
|
if (!Array.isArray(sourceArray)) {
|
|
523
584
|
logger.warn(`Array mapping source is not an array:`, sourceArray);
|
|
@@ -541,7 +602,7 @@ function applyArrayMapping(data, config, args) {
|
|
|
541
602
|
if (config.itemMapping) {
|
|
542
603
|
result = result.map((item, index) => {
|
|
543
604
|
try {
|
|
544
|
-
return applyResponseMapping(item, config.itemMapping, { ...args, index });
|
|
605
|
+
return applyResponseMapping(item, config.itemMapping, { ...args, index }, engine);
|
|
545
606
|
}
|
|
546
607
|
catch (error) {
|
|
547
608
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
@@ -584,7 +645,7 @@ function applyArrayMapping(data, config, args) {
|
|
|
584
645
|
return result;
|
|
585
646
|
}
|
|
586
647
|
// Template-based string interpolation
|
|
587
|
-
function applyTemplateMapping(data, config, args) {
|
|
648
|
+
function applyTemplateMapping(data, config, args, engine) {
|
|
588
649
|
let template = config.template;
|
|
589
650
|
// Check if template contains complex Handlebars syntax
|
|
590
651
|
const hasHandlebarsIteration = /\{\{#each\s+[^}]+\}\}/.test(template);
|
|
@@ -606,7 +667,7 @@ function applyTemplateMapping(data, config, args) {
|
|
|
606
667
|
return template;
|
|
607
668
|
}
|
|
608
669
|
// Conditional mapping based on data content
|
|
609
|
-
function applyConditionalMapping(data, config, args) {
|
|
670
|
+
function applyConditionalMapping(data, config, args, engine) {
|
|
610
671
|
if (!config.conditions || !Array.isArray(config.conditions)) {
|
|
611
672
|
throw new Error('Conditional mapping requires a conditions array');
|
|
612
673
|
}
|
|
@@ -625,7 +686,7 @@ function applyConditionalMapping(data, config, args) {
|
|
|
625
686
|
conditionResult = evaluateCondition(data, condition.if);
|
|
626
687
|
}
|
|
627
688
|
if (conditionResult) {
|
|
628
|
-
return applyResponseMapping(data, condition.then, args);
|
|
689
|
+
return applyResponseMapping(data, condition.then, args, engine);
|
|
629
690
|
}
|
|
630
691
|
}
|
|
631
692
|
catch (error) {
|
|
@@ -636,7 +697,7 @@ function applyConditionalMapping(data, config, args) {
|
|
|
636
697
|
}
|
|
637
698
|
// Default case
|
|
638
699
|
if (config.else) {
|
|
639
|
-
return applyResponseMapping(data, config.else, args);
|
|
700
|
+
return applyResponseMapping(data, config.else, args, engine);
|
|
640
701
|
}
|
|
641
702
|
return data;
|
|
642
703
|
}
|
|
@@ -1212,15 +1273,49 @@ function evaluateCondition(data, condition) {
|
|
|
1212
1273
|
return false;
|
|
1213
1274
|
}
|
|
1214
1275
|
}
|
|
1215
|
-
function interpolateObject(obj, data, args = {}) {
|
|
1276
|
+
function interpolateObject(obj, data, args = {}, engine) {
|
|
1216
1277
|
if (typeof obj === 'string') {
|
|
1217
|
-
//
|
|
1278
|
+
// Check if the string is ONLY a single template expression (e.g., '{{cargo}}')
|
|
1279
|
+
const singleTemplateMatch = obj.match(/^\{\{([^}]+)\}\}$/);
|
|
1280
|
+
if (singleTemplateMatch) {
|
|
1281
|
+
// This is a single template expression - return the actual value, not a string conversion
|
|
1282
|
+
const path = singleTemplateMatch[1].trim();
|
|
1283
|
+
try {
|
|
1284
|
+
let value = undefined;
|
|
1285
|
+
if (isPathTraversableObject(data)) {
|
|
1286
|
+
value = extractByPath(data, path);
|
|
1287
|
+
}
|
|
1288
|
+
// If not found in data and engine is available, check engine session variables
|
|
1289
|
+
if ((value === undefined || value === null)) {
|
|
1290
|
+
try {
|
|
1291
|
+
value = resolveEngineSessionVariable(path, engine);
|
|
1292
|
+
}
|
|
1293
|
+
catch (error) {
|
|
1294
|
+
// Ignore engine session variable resolution errors
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
return value !== undefined && value !== null ? value : '';
|
|
1298
|
+
}
|
|
1299
|
+
catch (error) {
|
|
1300
|
+
return obj; // Keep original on error
|
|
1301
|
+
}
|
|
1302
|
+
}
|
|
1303
|
+
// Handle template strings with multiple expressions or mixed content
|
|
1218
1304
|
return obj.replace(/\{\{([^}]+)\}\}/g, (match, path) => {
|
|
1219
1305
|
try {
|
|
1220
1306
|
let value = undefined;
|
|
1221
1307
|
if (isPathTraversableObject(data)) {
|
|
1222
1308
|
value = extractByPath(data, path.trim());
|
|
1223
1309
|
}
|
|
1310
|
+
// If not found in data and engine is available, check engine session variables
|
|
1311
|
+
if ((value === undefined || value === null)) {
|
|
1312
|
+
try {
|
|
1313
|
+
value = resolveEngineSessionVariable(path.trim(), engine);
|
|
1314
|
+
}
|
|
1315
|
+
catch (error) {
|
|
1316
|
+
// Ignore engine session variable resolution errors
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1224
1319
|
return value !== null && value !== undefined ? String(value) : '';
|
|
1225
1320
|
}
|
|
1226
1321
|
catch (error) {
|
|
@@ -1237,24 +1332,34 @@ function interpolateObject(obj, data, args = {}) {
|
|
|
1237
1332
|
});
|
|
1238
1333
|
}
|
|
1239
1334
|
else if (Array.isArray(obj)) {
|
|
1240
|
-
return obj.map(item => interpolateObject(item, data, args));
|
|
1335
|
+
return obj.map(item => interpolateObject(item, data, args, engine));
|
|
1241
1336
|
}
|
|
1242
1337
|
else if (typeof obj === 'object' && obj !== null) {
|
|
1243
1338
|
const result = {};
|
|
1244
1339
|
for (const [key, value] of Object.entries(obj)) {
|
|
1245
|
-
result[key] = interpolateObject(value, data, args);
|
|
1340
|
+
result[key] = interpolateObject(value, data, args, engine);
|
|
1246
1341
|
}
|
|
1247
1342
|
return result;
|
|
1248
1343
|
}
|
|
1249
1344
|
return obj;
|
|
1250
1345
|
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1346
|
+
function makeLogger(level = process.env.LOG_LEVEL || "warn") {
|
|
1347
|
+
const ORDER = { debug: 10, info: 20, warn: 30, error: 40 };
|
|
1348
|
+
let current = ORDER[level] ?? ORDER.warn;
|
|
1349
|
+
const allow = (lvl) => ORDER[lvl] >= current;
|
|
1350
|
+
return {
|
|
1351
|
+
setLevel: (lvl) => { current = ORDER[lvl] ?? current; },
|
|
1352
|
+
debug: (...a) => { if (allow("debug"))
|
|
1353
|
+
console.debug(...a); },
|
|
1354
|
+
info: (...a) => { if (allow("info"))
|
|
1355
|
+
console.info(...a); },
|
|
1356
|
+
warn: (...a) => { if (allow("warn"))
|
|
1357
|
+
console.warn(...a); },
|
|
1358
|
+
error: (...a) => { if (allow("error"))
|
|
1359
|
+
console.error(...a); },
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
let logger = makeLogger();
|
|
1258
1363
|
// Fallback for any remaining console calls
|
|
1259
1364
|
if (!global.console) {
|
|
1260
1365
|
global.console = {
|
|
@@ -1264,73 +1369,6 @@ if (!global.console) {
|
|
|
1264
1369
|
info: (...args) => logger.info(args.join(' '))
|
|
1265
1370
|
};
|
|
1266
1371
|
}
|
|
1267
|
-
// === TRANSACTION MANAGEMENT CLASS ===
|
|
1268
|
-
class FlowTransaction {
|
|
1269
|
-
constructor(flowName, initiator, userId = 'anonymous') {
|
|
1270
|
-
this.steps = [];
|
|
1271
|
-
this.state = 'active';
|
|
1272
|
-
this.metadata = {};
|
|
1273
|
-
this.id = crypto.randomUUID();
|
|
1274
|
-
this.flowName = flowName;
|
|
1275
|
-
this.initiator = initiator;
|
|
1276
|
-
this.userId = userId;
|
|
1277
|
-
this.steps = [];
|
|
1278
|
-
this.state = 'active';
|
|
1279
|
-
this.createdAt = new Date();
|
|
1280
|
-
this.metadata = {};
|
|
1281
|
-
}
|
|
1282
|
-
addStep(step, result, duration, status = 'success') {
|
|
1283
|
-
this.steps.push({
|
|
1284
|
-
stepId: step.id || step.type,
|
|
1285
|
-
stepType: step.type,
|
|
1286
|
-
tool: step.tool,
|
|
1287
|
-
result: this.sanitizeForLog(result),
|
|
1288
|
-
duration,
|
|
1289
|
-
status,
|
|
1290
|
-
timestamp: new Date(),
|
|
1291
|
-
retryCount: step.retryCount || 0
|
|
1292
|
-
});
|
|
1293
|
-
}
|
|
1294
|
-
addError(step, error, duration) {
|
|
1295
|
-
this.steps.push({
|
|
1296
|
-
stepId: step.id || step.type,
|
|
1297
|
-
stepType: step.type,
|
|
1298
|
-
tool: step.tool,
|
|
1299
|
-
error: error.message,
|
|
1300
|
-
duration,
|
|
1301
|
-
status: 'error',
|
|
1302
|
-
timestamp: new Date()
|
|
1303
|
-
});
|
|
1304
|
-
}
|
|
1305
|
-
sanitizeForLog(data) {
|
|
1306
|
-
if (typeof data === 'object' && data !== null) {
|
|
1307
|
-
const sanitized = { ...data };
|
|
1308
|
-
// Remove sensitive fields
|
|
1309
|
-
delete sanitized.signature;
|
|
1310
|
-
delete sanitized.password;
|
|
1311
|
-
delete sanitized.token;
|
|
1312
|
-
return sanitized;
|
|
1313
|
-
}
|
|
1314
|
-
return data;
|
|
1315
|
-
}
|
|
1316
|
-
rollback() {
|
|
1317
|
-
// In a real implementation, this would execute compensating actions
|
|
1318
|
-
this.state = 'rolled_back';
|
|
1319
|
-
this.rolledBackAt = new Date();
|
|
1320
|
-
auditLogger.logTransactionRollback(this);
|
|
1321
|
-
}
|
|
1322
|
-
complete() {
|
|
1323
|
-
this.state = 'completed';
|
|
1324
|
-
this.completedAt = new Date();
|
|
1325
|
-
auditLogger.logTransactionComplete(this);
|
|
1326
|
-
}
|
|
1327
|
-
fail(reason) {
|
|
1328
|
-
this.state = 'failed';
|
|
1329
|
-
this.failedAt = new Date();
|
|
1330
|
-
this.failureReason = reason;
|
|
1331
|
-
auditLogger.logTransactionFailed(this);
|
|
1332
|
-
}
|
|
1333
|
-
}
|
|
1334
1372
|
// === COMPREHENSIVE AUDIT LOGGING ===
|
|
1335
1373
|
const auditLogger = {
|
|
1336
1374
|
logFlowStart(flowName, input, userId, transactionId) {
|
|
@@ -1616,7 +1654,8 @@ function exportConversationHistory(contextStack, format = 'simple') {
|
|
|
1616
1654
|
// Stored in engine.flowStacks for proper context isolation
|
|
1617
1655
|
function initializeFlowStacks(engine) {
|
|
1618
1656
|
try {
|
|
1619
|
-
engine
|
|
1657
|
+
// Use the engine method directly since Engine is now an alias for WorkflowEngine
|
|
1658
|
+
engine.initializeFlowStacks();
|
|
1620
1659
|
}
|
|
1621
1660
|
catch (error) {
|
|
1622
1661
|
logger.error("Failed to initialize flow stacks:", error.message);
|
|
@@ -1631,7 +1670,11 @@ function pushToCurrentStack(engine, flowFrame) {
|
|
|
1631
1670
|
getCurrentStack(engine).push(flowFrame);
|
|
1632
1671
|
}
|
|
1633
1672
|
function popFromCurrentStack(engine) {
|
|
1634
|
-
|
|
1673
|
+
const result = getCurrentStack(engine).pop();
|
|
1674
|
+
if (!result) {
|
|
1675
|
+
throw new Error('Cannot pop from empty stack');
|
|
1676
|
+
}
|
|
1677
|
+
return result;
|
|
1635
1678
|
}
|
|
1636
1679
|
function createNewStack(engine) {
|
|
1637
1680
|
// Create new stack and switch to it
|
|
@@ -1703,7 +1746,7 @@ async function isFlowActivated(input, engine, userId = 'anonymous') {
|
|
|
1703
1746
|
const flowsMenu = engine.flowsMenu;
|
|
1704
1747
|
const flow = await getFlowForInput(input, engine);
|
|
1705
1748
|
if (flow) {
|
|
1706
|
-
const transaction =
|
|
1749
|
+
const transaction = TransactionManager.create(flow.name, 'user-input', userId);
|
|
1707
1750
|
// Prepare tentative flow_init message that will be replaced by SAY-GET guidance if present
|
|
1708
1751
|
const tentativeFlowInit = getSystemMessage(engine, 'flow_init', {
|
|
1709
1752
|
flowName: flow.name,
|
|
@@ -1741,7 +1784,7 @@ async function playFlowFrame(engine) {
|
|
|
1741
1784
|
auditLogger.logFlowExit(currentFlowFrame.flowName, currentFlowFrame.userId, currentFlowFrame.transaction.id, 'max_recursion_depth');
|
|
1742
1785
|
// Do we need this?
|
|
1743
1786
|
//currentFlowFrame.contextStack.push(error.message);
|
|
1744
|
-
currentFlowFrame.transaction
|
|
1787
|
+
TransactionManager.fail(currentFlowFrame.transaction, "Max recursion depth exceeded");
|
|
1745
1788
|
}
|
|
1746
1789
|
throw error;
|
|
1747
1790
|
}
|
|
@@ -1778,7 +1821,7 @@ async function playFlowFrame(engine) {
|
|
|
1778
1821
|
if (currentFlowFrame.flowStepsStack.length === 0) {
|
|
1779
1822
|
logger.info(`Flow ${currentFlowFrame.flowName} completed, popping from stack (steps length: ${currentFlowFrame.flowStepsStack.length})`);
|
|
1780
1823
|
const completedFlow = popFromCurrentStack(engine);
|
|
1781
|
-
completedFlow.transaction
|
|
1824
|
+
TransactionManager.complete(completedFlow.transaction);
|
|
1782
1825
|
// When a flow completes, it doesn't "return" a value in the traditional sense.
|
|
1783
1826
|
// It communicates results by setting variables in the shared `variables` object,
|
|
1784
1827
|
// which are then accessible to the parent flow.
|
|
@@ -1832,6 +1875,9 @@ async function playFlowFrame(engine) {
|
|
|
1832
1875
|
}
|
|
1833
1876
|
continue;
|
|
1834
1877
|
}
|
|
1878
|
+
else {
|
|
1879
|
+
logger.error(`Failed to resume flow - no flow frames available after switching stacks.`);
|
|
1880
|
+
}
|
|
1835
1881
|
}
|
|
1836
1882
|
else {
|
|
1837
1883
|
logger.info(`No more flow frames to process, all flows completed.`);
|
|
@@ -1846,7 +1892,7 @@ async function playFlowFrame(engine) {
|
|
|
1846
1892
|
try {
|
|
1847
1893
|
const result = await playStep(currentFlowFrame, engine);
|
|
1848
1894
|
const duration = Date.now() - startTime;
|
|
1849
|
-
currentFlowFrame.transaction
|
|
1895
|
+
TransactionManager.addStep(currentFlowFrame.transaction, step, result, duration, 'success');
|
|
1850
1896
|
logger.info(`Step ${step.type} executed successfully, result: ${typeof result === 'object' ? '[object]' : result}`);
|
|
1851
1897
|
// If this was a SAY-GET step, return and wait for user input
|
|
1852
1898
|
if (step.type === 'SAY-GET') {
|
|
@@ -1854,7 +1900,7 @@ async function playFlowFrame(engine) {
|
|
|
1854
1900
|
if (currentFlowFrame.flowStepsStack.length === 0) {
|
|
1855
1901
|
logger.info(`SAY-GET step was final step, flow ${currentFlowFrame.flowName} completed`);
|
|
1856
1902
|
const completedFlow = popFromCurrentStack(engine);
|
|
1857
|
-
completedFlow.transaction
|
|
1903
|
+
TransactionManager.complete(completedFlow.transaction);
|
|
1858
1904
|
return result;
|
|
1859
1905
|
}
|
|
1860
1906
|
return result;
|
|
@@ -1864,7 +1910,7 @@ async function playFlowFrame(engine) {
|
|
|
1864
1910
|
}
|
|
1865
1911
|
catch (error) {
|
|
1866
1912
|
const duration = Date.now() - startTime;
|
|
1867
|
-
currentFlowFrame.transaction
|
|
1913
|
+
TransactionManager.addError(currentFlowFrame.transaction, step, error, duration);
|
|
1868
1914
|
logger.error(`Step ${step.type} failed: ${error.message}`);
|
|
1869
1915
|
logger.info(`Stack trace: ${error.stack}`);
|
|
1870
1916
|
throw error;
|
|
@@ -2494,18 +2540,29 @@ async function handleToolStep(currentFlowFrame, engine) {
|
|
|
2494
2540
|
const onFailStep = Array.isArray(effectiveOnFail) ? effectiveOnFail[0] : effectiveOnFail;
|
|
2495
2541
|
const callType = onFailStep.callType || "replace"; // Default to current behavior
|
|
2496
2542
|
if (callType === "reboot") {
|
|
2497
|
-
// Clear
|
|
2498
|
-
logger.info(`
|
|
2499
|
-
// Clean up
|
|
2500
|
-
|
|
2501
|
-
|
|
2502
|
-
|
|
2543
|
+
// Clear ALL flows across ALL stacks and start completely fresh with the onFail flow
|
|
2544
|
+
logger.info(`Rebooted due to 'reboot' type of onFail step: ${onFailStep.name} in flow ${currentFlowFrame.flowName} for tool ${step.tool}`);
|
|
2545
|
+
// Clean up ALL existing flows across ALL stacks (using proven user exit pattern)
|
|
2546
|
+
const exitedFlows = [];
|
|
2547
|
+
while (engine.flowStacks.length > 0) {
|
|
2548
|
+
const poppedStack = engine.flowStacks.pop();
|
|
2549
|
+
if (!poppedStack || poppedStack.length === 0) {
|
|
2550
|
+
continue;
|
|
2551
|
+
}
|
|
2552
|
+
// Process all flows in this stack
|
|
2553
|
+
for (const flow of poppedStack) {
|
|
2554
|
+
TransactionManager.fail(flow.transaction, `Rebooted due to 'reboot' type of onFail step: ${onFailStep.name} in flow ${flow?.flowName} for tool ${step.tool}`);
|
|
2555
|
+
exitedFlows.push(flow.flowName);
|
|
2556
|
+
auditLogger.logFlowExit(flow.flowName, currentFlowFrame.userId, flow.transaction.id, 'onFail_reboot');
|
|
2557
|
+
}
|
|
2503
2558
|
}
|
|
2559
|
+
// Initialize completely fresh stack system (using proven pattern)
|
|
2560
|
+
initializeFlowStacks(engine);
|
|
2504
2561
|
// Start the onFail flow as a new root flow
|
|
2505
2562
|
if (onFailStep.type === "FLOW") {
|
|
2506
2563
|
const rebootFlow = flowsMenu?.find(f => f.name === onFailStep.name);
|
|
2507
2564
|
if (rebootFlow) {
|
|
2508
|
-
const transaction =
|
|
2565
|
+
const transaction = TransactionManager.create(rebootFlow.name, 'reboot-recovery', currentFlowFrame.userId);
|
|
2509
2566
|
pushToCurrentStack(engine, {
|
|
2510
2567
|
flowName: rebootFlow.name,
|
|
2511
2568
|
flowId: rebootFlow.id,
|
|
@@ -2525,7 +2582,8 @@ async function handleToolStep(currentFlowFrame, engine) {
|
|
|
2525
2582
|
// Handle non-FLOW onFail steps in reboot mode
|
|
2526
2583
|
currentFlowFrame.flowStepsStack = [onFailStep];
|
|
2527
2584
|
}
|
|
2528
|
-
|
|
2585
|
+
logger.info(`System completely rebooted due to onFail step for tool ${step.tool}. Exited flows: ${exitedFlows.join(', ')}`);
|
|
2586
|
+
return `System rebooted due to onFail step for tool ${step.tool}. Starting recovery flow ${onFailStep.name}`;
|
|
2529
2587
|
}
|
|
2530
2588
|
else if (callType === "replace") {
|
|
2531
2589
|
// Current behavior - replace remaining steps in current flow
|
|
@@ -2543,7 +2601,7 @@ async function handleToolStep(currentFlowFrame, engine) {
|
|
|
2543
2601
|
// Push onFail flow as sub-flow
|
|
2544
2602
|
const onFailFlow = flowsMenu?.find(f => f.name === onFailStep.name);
|
|
2545
2603
|
if (onFailFlow) {
|
|
2546
|
-
const transaction =
|
|
2604
|
+
const transaction = TransactionManager.create(onFailFlow.name, 'onFail-recovery', currentFlowFrame.userId);
|
|
2547
2605
|
pushToCurrentStack(engine, {
|
|
2548
2606
|
flowName: onFailFlow.name,
|
|
2549
2607
|
flowId: onFailFlow.id,
|
|
@@ -2785,12 +2843,12 @@ async function handleSwitchStep(currentFlowFrame, engine) {
|
|
|
2785
2843
|
// Find the matching branch (exact value matching only)
|
|
2786
2844
|
let selectedStep = null;
|
|
2787
2845
|
let selectedBranch = null;
|
|
2788
|
-
// SWITCH now
|
|
2846
|
+
// SWITCH now supports exact value matching for strings, booleans, and numbers
|
|
2789
2847
|
// For conditional logic, use the CASE step instead
|
|
2790
|
-
if (switchValue !== undefined &&
|
|
2791
|
-
selectedStep = step.branches[switchValue];
|
|
2848
|
+
if (switchValue !== undefined && step.branches[String(switchValue)]) {
|
|
2849
|
+
selectedStep = step.branches[String(switchValue)];
|
|
2792
2850
|
selectedBranch = String(switchValue);
|
|
2793
|
-
logger.info(`SWITCH: selected exact match branch '${switchValue}'`);
|
|
2851
|
+
logger.info(`SWITCH: selected exact match branch '${switchValue}' (converted to string key '${String(switchValue)}')`);
|
|
2794
2852
|
}
|
|
2795
2853
|
// If no exact match found, use default
|
|
2796
2854
|
if (!selectedStep && step.branches.default) {
|
|
@@ -2887,19 +2945,26 @@ async function handleSubFlowStep(currentFlowFrame, engine) {
|
|
|
2887
2945
|
const callType = step.callType || "call"; // Default to normal sub-flow call
|
|
2888
2946
|
logger.info(`Starting sub-flow ${subFlow.name} with callType: ${callType}, input: ${input}`);
|
|
2889
2947
|
if (callType === "reboot") {
|
|
2890
|
-
// Clear
|
|
2891
|
-
logger.info(`Rebooting with flow: ${subFlow.name}`);
|
|
2892
|
-
// Clean up
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2948
|
+
// Clear ALL flows across ALL stacks and start completely fresh
|
|
2949
|
+
logger.info(`Rebooting system with flow: ${subFlow.name}`);
|
|
2950
|
+
// Clean up ALL existing flows across ALL stacks (using proven user exit pattern)
|
|
2951
|
+
const exitedFlows = [];
|
|
2952
|
+
while (engine.flowStacks.length > 0) {
|
|
2953
|
+
const poppedStack = engine.flowStacks.pop();
|
|
2954
|
+
if (!poppedStack || poppedStack.length === 0) {
|
|
2955
|
+
continue;
|
|
2956
|
+
}
|
|
2957
|
+
// Process all flows in this stack
|
|
2958
|
+
for (const flow of poppedStack) {
|
|
2959
|
+
TransactionManager.fail(flow.transaction, `System rebooted to flow ${subFlow.name}`);
|
|
2960
|
+
exitedFlows.push(flow.flowName);
|
|
2961
|
+
auditLogger.logFlowExit(flow.flowName, currentFlowFrame.userId, flow.transaction.id, 'system_reboot');
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
// Initialize completely fresh stack system (using proven pattern)
|
|
2900
2965
|
initializeFlowStacks(engine);
|
|
2901
|
-
// Start the
|
|
2902
|
-
const transaction =
|
|
2966
|
+
// Start the reboot flow as the only flow in the system
|
|
2967
|
+
const transaction = TransactionManager.create(subFlow.name, 'reboot', currentFlowFrame.userId);
|
|
2903
2968
|
pushToCurrentStack(engine, {
|
|
2904
2969
|
flowName: subFlow.name,
|
|
2905
2970
|
flowId: subFlow.id,
|
|
@@ -2913,6 +2978,7 @@ async function handleSubFlowStep(currentFlowFrame, engine) {
|
|
|
2913
2978
|
startTime: Date.now()
|
|
2914
2979
|
});
|
|
2915
2980
|
auditLogger.logFlowStart(subFlow.name, input, currentFlowFrame.userId, transaction.id);
|
|
2981
|
+
logger.info(`System completely rebooted to flow ${subFlow.name}. Exited flows: ${exitedFlows.join(', ')}`);
|
|
2916
2982
|
return `System rebooted to flow ${subFlow.name}`;
|
|
2917
2983
|
}
|
|
2918
2984
|
else if (callType === "replace") {
|
|
@@ -2927,15 +2993,15 @@ async function handleSubFlowStep(currentFlowFrame, engine) {
|
|
|
2927
2993
|
addToContextStack(currentFlowFrame.contextStack, 'user', input);
|
|
2928
2994
|
currentFlowFrame.inputStack.push(input);
|
|
2929
2995
|
// Update transaction
|
|
2930
|
-
currentFlowFrame.transaction
|
|
2931
|
-
currentFlowFrame.transaction =
|
|
2996
|
+
TransactionManager.fail(currentFlowFrame.transaction, `Replaced by flow ${subFlow.name}`);
|
|
2997
|
+
currentFlowFrame.transaction = TransactionManager.create(subFlow.name, 'replacement', currentFlowFrame.userId);
|
|
2932
2998
|
auditLogger.logFlowStart(subFlow.name, input, currentFlowFrame.userId, currentFlowFrame.transaction.id);
|
|
2933
2999
|
logger.debug(`About to return from handleSubFlowStep replacement, flowStepsStack length: ${currentFlowFrame.flowStepsStack.length}`);
|
|
2934
3000
|
return `Flow replaced with ${subFlow.name}`;
|
|
2935
3001
|
}
|
|
2936
3002
|
else { // callType === "call" (default)
|
|
2937
3003
|
// Normal sub-flow call - create new transaction for sub-flow
|
|
2938
|
-
const subTransaction =
|
|
3004
|
+
const subTransaction = TransactionManager.create(subFlow.name, 'sub-flow', currentFlowFrame.userId);
|
|
2939
3005
|
// Push sub-flow onto stack - INHERIT parent's variables for unified scope
|
|
2940
3006
|
pushToCurrentStack(engine, {
|
|
2941
3007
|
flowName: subFlow.name,
|
|
@@ -2963,7 +3029,7 @@ async function handleSubFlowStep(currentFlowFrame, engine) {
|
|
|
2963
3029
|
}
|
|
2964
3030
|
}
|
|
2965
3031
|
// === TOOL CALLING AND ARGUMENT GENERATION SYSTEM ===
|
|
2966
|
-
async function generateToolCallAndResponse(engine, toolName, input, contextStack = [], userId = 'anonymous', transactionId = null, flowFrame
|
|
3032
|
+
async function generateToolCallAndResponse(engine, toolName, input, contextStack = [], userId = 'anonymous', transactionId = null, flowFrame, explicitArgs) {
|
|
2967
3033
|
try {
|
|
2968
3034
|
const toolsRegistry = engine.toolsRegistry;
|
|
2969
3035
|
if (!toolsRegistry) {
|
|
@@ -2991,7 +3057,7 @@ async function generateToolCallAndResponse(engine, toolName, input, contextStack
|
|
|
2991
3057
|
const contextStack = flowFrame.contextStack || [];
|
|
2992
3058
|
logger.debug(`Interpolating args templates:`, rawArgs);
|
|
2993
3059
|
logger.debug(`Available variables:`, variables);
|
|
2994
|
-
rawArgs = interpolateObject(rawArgs, variables,
|
|
3060
|
+
rawArgs = interpolateObject(rawArgs, variables, {}, engine);
|
|
2995
3061
|
logger.debug(`Interpolated args:`, rawArgs);
|
|
2996
3062
|
}
|
|
2997
3063
|
catch (error) {
|
|
@@ -3007,7 +3073,7 @@ async function generateToolCallAndResponse(engine, toolName, input, contextStack
|
|
|
3007
3073
|
throw error;
|
|
3008
3074
|
}
|
|
3009
3075
|
}
|
|
3010
|
-
async function generateToolArgs(schema, input, contextStack = [], flowFrame
|
|
3076
|
+
async function generateToolArgs(schema, input, contextStack = [], flowFrame, engine) {
|
|
3011
3077
|
try {
|
|
3012
3078
|
if (!schema || typeof schema !== 'object') {
|
|
3013
3079
|
logger.warn(`Invalid schema provided for argument generation: ${schema}`);
|
|
@@ -3047,7 +3113,7 @@ async function generateToolArgs(schema, input, contextStack = [], flowFrame = nu
|
|
|
3047
3113
|
throw new Error(`Failed to generate tool arguments: ${error.message}`);
|
|
3048
3114
|
}
|
|
3049
3115
|
}
|
|
3050
|
-
async function generateArgsWithAI(schema, input, contextStack, flowFrame
|
|
3116
|
+
async function generateArgsWithAI(schema, input, contextStack, flowFrame, engine) {
|
|
3051
3117
|
const properties = schema.properties || {};
|
|
3052
3118
|
const required = schema.required || [];
|
|
3053
3119
|
const schemaDescription = Object.entries(properties)
|
|
@@ -3134,7 +3200,7 @@ Context Extraction Examples:
|
|
|
3134
3200
|
}
|
|
3135
3201
|
}
|
|
3136
3202
|
// Enhanced pattern-based fallback that uses variables and input parsing
|
|
3137
|
-
function generateEnhancedFallbackArgs(schema, input, flowFrame
|
|
3203
|
+
function generateEnhancedFallbackArgs(schema, input, flowFrame) {
|
|
3138
3204
|
const properties = schema.properties || {};
|
|
3139
3205
|
const required = schema.required || [];
|
|
3140
3206
|
const args = {};
|
|
@@ -3255,12 +3321,96 @@ async function callTool(engine, tool, args, userId = 'anonymous', transactionId
|
|
|
3255
3321
|
}
|
|
3256
3322
|
// Apply timeout if configured
|
|
3257
3323
|
const timeout = tool.implementation.timeout || 5000;
|
|
3258
|
-
|
|
3324
|
+
let timeoutId;
|
|
3325
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
3326
|
+
timeoutId = setTimeout(() => reject(new Error(`Tool execution timeout after ${timeout}ms - ${tool.implementation.function}`)), timeout);
|
|
3327
|
+
});
|
|
3259
3328
|
try {
|
|
3260
|
-
|
|
3329
|
+
// Determine how to call the function based on its signature and tool schema
|
|
3330
|
+
let result;
|
|
3331
|
+
// Check if the tool schema indicates individual parameters vs object parameter
|
|
3332
|
+
const toolSchema = tool.schema || tool.parameters || tool;
|
|
3333
|
+
// Handle different schema formats:
|
|
3334
|
+
// Format 1: JSONSchema with type="object" -> single object parameter
|
|
3335
|
+
// Format 2: Direct parameters object -> individual parameters
|
|
3336
|
+
let schemaProperties = {};
|
|
3337
|
+
let useObjectParameter = false;
|
|
3338
|
+
if (tool.parameters?.type === "object" && tool.parameters?.properties) {
|
|
3339
|
+
// Format 1: JSONSchema format { type: "object", properties: {...} }
|
|
3340
|
+
// Function expects single object parameter
|
|
3341
|
+
schemaProperties = tool.parameters.properties;
|
|
3342
|
+
useObjectParameter = true;
|
|
3343
|
+
logger.debug(`Detected JSONSchema format (type: object) - will use object parameter`);
|
|
3344
|
+
}
|
|
3345
|
+
else if (tool.parameters && typeof tool.parameters === 'object' && !tool.parameters.type) {
|
|
3346
|
+
// Format 2: Direct parameters { param1: {...}, param2: {...} }
|
|
3347
|
+
// Function expects individual parameters
|
|
3348
|
+
schemaProperties = tool.parameters;
|
|
3349
|
+
useObjectParameter = false;
|
|
3350
|
+
logger.debug(`Detected direct parameters format - will use individual parameters`);
|
|
3351
|
+
}
|
|
3352
|
+
else if (toolSchema?.properties) {
|
|
3353
|
+
// Fallback: Standard JSONSchema format
|
|
3354
|
+
schemaProperties = toolSchema.properties;
|
|
3355
|
+
useObjectParameter = toolSchema.type === "object";
|
|
3356
|
+
logger.debug(`Detected schema.properties format - object parameter: ${useObjectParameter}`);
|
|
3357
|
+
}
|
|
3358
|
+
const propertyNames = Object.keys(schemaProperties);
|
|
3359
|
+
// Get function parameter count
|
|
3360
|
+
const fnParamCount = fn.length;
|
|
3361
|
+
logger.debug(`Function ${tool.implementation.function} parameter count: ${fnParamCount}`);
|
|
3362
|
+
logger.debug(`Schema properties count: ${propertyNames.length}`);
|
|
3363
|
+
logger.debug(`Schema properties: ${propertyNames.join(', ')}`);
|
|
3364
|
+
logger.debug(`Use object parameter: ${useObjectParameter}`);
|
|
3365
|
+
// Strategy 1: If schema explicitly indicates object parameter OR function expects 1 param
|
|
3366
|
+
if (useObjectParameter || fnParamCount === 1) {
|
|
3367
|
+
logger.debug(`Calling function with single object parameter`);
|
|
3368
|
+
result = await Promise.race([fn(args), timeoutPromise]);
|
|
3369
|
+
}
|
|
3370
|
+
// Strategy 2: If function expects multiple parameters and we have multiple schema properties
|
|
3371
|
+
else if (fnParamCount > 1 && propertyNames.length > 1) {
|
|
3372
|
+
logger.debug(`Calling function with individual parameters (${fnParamCount} params expected)`);
|
|
3373
|
+
// For tools with 'parameters' property, use the required array or property order
|
|
3374
|
+
let orderedParamNames;
|
|
3375
|
+
if (tool.required && Array.isArray(tool.required)) {
|
|
3376
|
+
// Use required array order first, then add any optional parameters
|
|
3377
|
+
const optionalParams = propertyNames.filter(name => !tool.required.includes(name));
|
|
3378
|
+
orderedParamNames = [...tool.required, ...optionalParams];
|
|
3379
|
+
}
|
|
3380
|
+
else {
|
|
3381
|
+
// Fall back to property definition order (note: this may not be reliable in all JS engines)
|
|
3382
|
+
orderedParamNames = propertyNames;
|
|
3383
|
+
}
|
|
3384
|
+
logger.debug(`Parameter order: ${orderedParamNames.join(', ')}`);
|
|
3385
|
+
const orderedArgs = orderedParamNames.map(propName => {
|
|
3386
|
+
const value = args[propName];
|
|
3387
|
+
logger.debug(`Parameter ${propName}: ${JSON.stringify(value)}`);
|
|
3388
|
+
return value;
|
|
3389
|
+
});
|
|
3390
|
+
logger.debug(`Calling ${tool.implementation.function}(${orderedArgs.map(arg => typeof arg === 'object' ? '[object]' : arg).join(', ')})`);
|
|
3391
|
+
result = await Promise.race([fn(...orderedArgs), timeoutPromise]);
|
|
3392
|
+
}
|
|
3393
|
+
// Strategy 3: Function expects 0 parameters, call without arguments
|
|
3394
|
+
else if (fnParamCount === 0) {
|
|
3395
|
+
logger.debug(`Calling function with no parameters`);
|
|
3396
|
+
result = await Promise.race([fn(), timeoutPromise]);
|
|
3397
|
+
}
|
|
3398
|
+
// Fallback: Use object approach
|
|
3399
|
+
else {
|
|
3400
|
+
logger.debug(`Calling function with object parameter (fallback)`);
|
|
3401
|
+
result = await Promise.race([fn(args), timeoutPromise]);
|
|
3402
|
+
}
|
|
3403
|
+
// Clear the timeout since function completed successfully
|
|
3404
|
+
if (timeoutId) {
|
|
3405
|
+
clearTimeout(timeoutId);
|
|
3406
|
+
}
|
|
3261
3407
|
return result;
|
|
3262
3408
|
}
|
|
3263
3409
|
catch (error) {
|
|
3410
|
+
// Clear the timeout on error as well
|
|
3411
|
+
if (timeoutId) {
|
|
3412
|
+
clearTimeout(timeoutId);
|
|
3413
|
+
}
|
|
3264
3414
|
// Unconditional Retry logic for local functions
|
|
3265
3415
|
const retries = tool.implementation.retries || 0;
|
|
3266
3416
|
if (retries > 0) {
|
|
@@ -3297,7 +3447,7 @@ async function callHttpTool(tool, args, userId = 'anonymous', transactionId = nu
|
|
|
3297
3447
|
// Apply response mapping if configured
|
|
3298
3448
|
if (implementation.responseMapping) {
|
|
3299
3449
|
try {
|
|
3300
|
-
const mappedResult = applyResponseMapping(mockData, implementation.responseMapping, args);
|
|
3450
|
+
const mappedResult = applyResponseMapping(mockData, implementation.responseMapping, args, engine);
|
|
3301
3451
|
logger.info(`[MOCK] Response mapping applied for ${tool.name}`);
|
|
3302
3452
|
return mappedResult;
|
|
3303
3453
|
}
|
|
@@ -3571,7 +3721,7 @@ async function callHttpTool(tool, args, userId = 'anonymous', transactionId = nu
|
|
|
3571
3721
|
// Apply declarative response mapping if configured (preferred)
|
|
3572
3722
|
if (implementation.responseMapping) {
|
|
3573
3723
|
try {
|
|
3574
|
-
return applyResponseMapping(data, implementation.responseMapping, mappingArgs);
|
|
3724
|
+
return applyResponseMapping(data, implementation.responseMapping, mappingArgs, engine);
|
|
3575
3725
|
}
|
|
3576
3726
|
catch (error) {
|
|
3577
3727
|
logger.error(`Response mapping failed:`, error.message);
|
|
@@ -3792,6 +3942,11 @@ function evaluateExpression(expression, variables = {}, contextStack = [], optio
|
|
|
3792
3942
|
const result = evaluateSafeMathematicalExpression(processedExpression, variables, contextStack, engine);
|
|
3793
3943
|
return convertReturnType(result, opts.returnType);
|
|
3794
3944
|
}
|
|
3945
|
+
// Handle typeof operator (e.g., "typeof variable", "typeof container")
|
|
3946
|
+
if (processedExpression.trim().startsWith('typeof ')) {
|
|
3947
|
+
const result = evaluateTypeofExpression(processedExpression, variables, contextStack, engine);
|
|
3948
|
+
return convertReturnType(result, opts.returnType);
|
|
3949
|
+
}
|
|
3795
3950
|
// Handle simple variable paths (e.g., "user.name", "data.items.length")
|
|
3796
3951
|
if (isSimpleVariablePath(processedExpression)) {
|
|
3797
3952
|
const result = resolveSimpleVariable(processedExpression, variables, contextStack, engine);
|
|
@@ -4525,6 +4680,37 @@ function evaluateSafeMathematicalExpression(expression, variables, contextStack,
|
|
|
4525
4680
|
return `[math-error: ${expression}]`;
|
|
4526
4681
|
}
|
|
4527
4682
|
}
|
|
4683
|
+
// Handle typeof operator expressions (e.g., "typeof variable", "typeof container.prop")
|
|
4684
|
+
function evaluateTypeofExpression(expression, variables, contextStack, engine) {
|
|
4685
|
+
try {
|
|
4686
|
+
// Extract the variable name after "typeof "
|
|
4687
|
+
const typeofMatch = expression.trim().match(/^typeof\s+(.+)$/);
|
|
4688
|
+
if (!typeofMatch) {
|
|
4689
|
+
logger.warn(`Invalid typeof expression: ${expression}`);
|
|
4690
|
+
return 'undefined';
|
|
4691
|
+
}
|
|
4692
|
+
const variablePath = typeofMatch[1].trim();
|
|
4693
|
+
logger.debug(`Evaluating typeof for variable path: ${variablePath}`);
|
|
4694
|
+
// Resolve the variable value
|
|
4695
|
+
let value;
|
|
4696
|
+
// Try to resolve from variables first
|
|
4697
|
+
if (variables && Object.keys(variables).length > 0) {
|
|
4698
|
+
value = variablePath.split('.').reduce((obj, part) => obj?.[part], variables);
|
|
4699
|
+
}
|
|
4700
|
+
// If not found in variables, check engine session variables
|
|
4701
|
+
if (value === undefined && engine) {
|
|
4702
|
+
value = resolveEngineSessionVariable(variablePath, engine);
|
|
4703
|
+
}
|
|
4704
|
+
// Return the JavaScript typeof result
|
|
4705
|
+
const typeResult = typeof value;
|
|
4706
|
+
logger.debug(`typeof ${variablePath} = ${typeResult} (value: ${JSON.stringify(value)})`);
|
|
4707
|
+
return typeResult;
|
|
4708
|
+
}
|
|
4709
|
+
catch (error) {
|
|
4710
|
+
logger.warn(`Typeof expression evaluation error: ${error.message}`);
|
|
4711
|
+
return 'undefined';
|
|
4712
|
+
}
|
|
4713
|
+
}
|
|
4528
4714
|
// === ENHANCED FLOW CONTROL COMMANDS ===
|
|
4529
4715
|
// Universal commands that work during any flow
|
|
4530
4716
|
async function handleFlowControlCommands(input, engine, userId) {
|
|
@@ -4727,7 +4913,7 @@ async function handlePendingInterruptionSwitch(engine, userId) {
|
|
|
4727
4913
|
return getSystemMessage(engine, 'flow_switch_error', { targetFlow: 'unknown' });
|
|
4728
4914
|
}
|
|
4729
4915
|
// Clean up current flow
|
|
4730
|
-
currentFlowFrame.transaction
|
|
4916
|
+
TransactionManager.fail(currentFlowFrame.transaction, "User confirmed flow switch");
|
|
4731
4917
|
popFromCurrentStack(engine);
|
|
4732
4918
|
logger.info(`🔄 User confirmed switch from "${currentFlowName}" to "${targetFlow}"`);
|
|
4733
4919
|
// Find and activate the target flow
|
|
@@ -4735,7 +4921,7 @@ async function handlePendingInterruptionSwitch(engine, userId) {
|
|
|
4735
4921
|
if (!targetFlowDefinition) {
|
|
4736
4922
|
return getSystemMessage(engine, 'flow_switch_error', { targetFlow });
|
|
4737
4923
|
}
|
|
4738
|
-
const newTransaction =
|
|
4924
|
+
const newTransaction = TransactionManager.create(targetFlow, 'confirmed-switch', userId);
|
|
4739
4925
|
const newFlowFrame = {
|
|
4740
4926
|
flowName: targetFlow,
|
|
4741
4927
|
flowId: targetFlowDefinition.id,
|
|
@@ -4781,7 +4967,7 @@ async function handleRegularFlowInterruption(intentAnalysis, engine, userId) {
|
|
|
4781
4967
|
const flowsMenu = engine.flowsMenu; // Access the global flows menu
|
|
4782
4968
|
logger.info(`🔄 Processing flow interruption: "${userInput}" -> ${targetFlow}`);
|
|
4783
4969
|
// For non-financial flows, allow graceful switching with option to resume
|
|
4784
|
-
currentFlowFrame.transaction
|
|
4970
|
+
TransactionManager.complete(currentFlowFrame.transaction);
|
|
4785
4971
|
// IMPORTANT: Clear pendingVariable from interrupted flow to avoid stale state when resuming
|
|
4786
4972
|
if (currentFlowFrame.pendingVariable) {
|
|
4787
4973
|
logger.info(`🧹 Clearing stale pendingVariable '${currentFlowFrame.pendingVariable}' from interrupted flow`);
|
|
@@ -4800,7 +4986,7 @@ async function handleRegularFlowInterruption(intentAnalysis, engine, userId) {
|
|
|
4800
4986
|
// Activate the new flow
|
|
4801
4987
|
const targetFlowDefinition = flowsMenu?.find(f => f.name === targetFlow);
|
|
4802
4988
|
if (targetFlowDefinition) {
|
|
4803
|
-
const newTransaction =
|
|
4989
|
+
const newTransaction = TransactionManager.create(targetFlow, 'intent-switch', userId);
|
|
4804
4990
|
const newFlowFrame = {
|
|
4805
4991
|
flowName: targetFlow,
|
|
4806
4992
|
flowId: targetFlowDefinition.id,
|
|
@@ -4848,9 +5034,14 @@ async function handleFlowExit(engine, userId, input) {
|
|
|
4848
5034
|
// Clean up all flows with proper transaction logging
|
|
4849
5035
|
const exitedFlows = [];
|
|
4850
5036
|
let flow;
|
|
4851
|
-
while (
|
|
5037
|
+
while (engine.flowStacks.length > 0) {
|
|
5038
|
+
const poppedStack = engine.flowStacks.pop();
|
|
5039
|
+
if (!poppedStack || poppedStack.length === 0) {
|
|
5040
|
+
break;
|
|
5041
|
+
}
|
|
5042
|
+
flow = poppedStack[0];
|
|
4852
5043
|
logger.info(`Exiting flow: ${flow.flowName} due to user request`);
|
|
4853
|
-
flow.transaction
|
|
5044
|
+
TransactionManager.fail(flow.transaction, `User requested exit: ${input}`);
|
|
4854
5045
|
exitedFlows.push(flow.flowName);
|
|
4855
5046
|
auditLogger.logFlowExit(flow.flowName, userId, flow.transaction.id, 'user_requested');
|
|
4856
5047
|
}
|
|
@@ -4905,7 +5096,7 @@ async function processActivity(input, userId, engine) {
|
|
|
4905
5096
|
// Clean up failed flow
|
|
4906
5097
|
if (getCurrentStackLength(engine) > 0) {
|
|
4907
5098
|
const failedFrame = popFromCurrentStack(engine);
|
|
4908
|
-
failedFrame.transaction
|
|
5099
|
+
TransactionManager.fail(failedFrame.transaction, error.message);
|
|
4909
5100
|
}
|
|
4910
5101
|
return `I encountered an error: ${error.message}. Please try again or contact support if the issue persists.`;
|
|
4911
5102
|
}
|
|
@@ -4917,7 +5108,12 @@ async function processActivity(input, userId, engine) {
|
|
|
4917
5108
|
if (activatedFlow) {
|
|
4918
5109
|
logger.info(`Flow activated: ${activatedFlow.name}`);
|
|
4919
5110
|
// Clear lastChatTurn since we now have flow context
|
|
4920
|
-
|
|
5111
|
+
const lastChatTurn = engine.lastChatTurn;
|
|
5112
|
+
if (lastChatTurn) {
|
|
5113
|
+
// Clear the existing properties
|
|
5114
|
+
delete lastChatTurn.user;
|
|
5115
|
+
delete lastChatTurn.assistant;
|
|
5116
|
+
}
|
|
4921
5117
|
logger.debug(`Cleared lastChatTurn - now using flow context for AI operations`);
|
|
4922
5118
|
const response = await playFlowFrame(engine);
|
|
4923
5119
|
logger.info(`Initial flow response: ${response}`);
|
|
@@ -4940,6 +5136,16 @@ async function processActivity(input, userId, engine) {
|
|
|
4940
5136
|
}
|
|
4941
5137
|
// === WORKFLOW ENGINE CLASS ===
|
|
4942
5138
|
export class WorkflowEngine {
|
|
5139
|
+
// Session-specific properties as getters - work directly with session data
|
|
5140
|
+
get flowStacks() {
|
|
5141
|
+
return this.sessionContext?.flowStacks || [[]];
|
|
5142
|
+
}
|
|
5143
|
+
get globalAccumulatedMessages() {
|
|
5144
|
+
return this.sessionContext?.globalAccumulatedMessages || [];
|
|
5145
|
+
}
|
|
5146
|
+
get lastChatTurn() {
|
|
5147
|
+
return this.sessionContext?.lastChatTurn || {};
|
|
5148
|
+
}
|
|
4943
5149
|
/**
|
|
4944
5150
|
* Initialize a new EngineSessionContext for a user session.
|
|
4945
5151
|
* If hostLogger is null, uses the global default logger.
|
|
@@ -4956,7 +5162,8 @@ export class WorkflowEngine {
|
|
|
4956
5162
|
*/
|
|
4957
5163
|
constructor(hostLogger, aiCallback, flowsMenu, toolsRegistry, APPROVED_FUNCTIONS, globalVariables, // Optional global variables shared across all new flows
|
|
4958
5164
|
validateOnInit, language, messageRegistry, guidanceConfig) {
|
|
4959
|
-
|
|
5165
|
+
// Private session context - engine works directly with session data (no copying!)
|
|
5166
|
+
this.sessionContext = null;
|
|
4960
5167
|
hostLogger = hostLogger || logger; // Fallback to global fake logger if none provided
|
|
4961
5168
|
logger = hostLogger; // Assign to global logger
|
|
4962
5169
|
this.aiCallback = aiCallback;
|
|
@@ -4974,9 +5181,7 @@ export class WorkflowEngine {
|
|
|
4974
5181
|
separator: '\n\n',
|
|
4975
5182
|
contextSelector: 'auto'
|
|
4976
5183
|
};
|
|
4977
|
-
//
|
|
4978
|
-
this.flowStacks = [[]]; // Stack of flowFrames stacks for proper flow interruption/resumption
|
|
4979
|
-
this.globalAccumulatedMessages = []; // Global SAY message accumulation across all stacks
|
|
5184
|
+
// No longer initialize session-specific data in constructor - it's now in sessionContext
|
|
4980
5185
|
this.sessionId = crypto.randomUUID();
|
|
4981
5186
|
this.createdAt = new Date();
|
|
4982
5187
|
this.lastActivity = new Date();
|
|
@@ -4999,117 +5204,190 @@ export class WorkflowEngine {
|
|
|
4999
5204
|
* const session = engine.initSession(yourLogger, 'user-123', 'session-456');
|
|
5000
5205
|
*/
|
|
5001
5206
|
initSession(hostLogger, userId, sessionId) {
|
|
5002
|
-
hostLogger = hostLogger || logger; // Fallback to global fake logger if none provided
|
|
5003
5207
|
// Validate logger compatibility
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
5007
|
-
|
|
5208
|
+
if (hostLogger) {
|
|
5209
|
+
const requiredMethods = ['info', 'warn', 'error', 'debug'];
|
|
5210
|
+
for (const method of requiredMethods) {
|
|
5211
|
+
if (typeof hostLogger[method] !== 'function') {
|
|
5212
|
+
throw new Error(`Logger is missing required method: ${method}`);
|
|
5213
|
+
}
|
|
5008
5214
|
}
|
|
5009
5215
|
}
|
|
5010
5216
|
// Assign the session logger to the global logger
|
|
5011
5217
|
logger = hostLogger || logger;
|
|
5012
5218
|
const engineSessionContext = {
|
|
5013
|
-
hostLogger: hostLogger,
|
|
5219
|
+
hostLogger: hostLogger || logger,
|
|
5014
5220
|
sessionId: sessionId || crypto.randomUUID(),
|
|
5015
5221
|
userId: userId,
|
|
5016
5222
|
createdAt: new Date(),
|
|
5017
5223
|
lastActivity: new Date(),
|
|
5018
|
-
flowStacks: [[]],
|
|
5224
|
+
flowStacks: [[]], // Initialize as array with one empty stack (pure data)
|
|
5019
5225
|
globalAccumulatedMessages: [],
|
|
5020
5226
|
lastChatTurn: {},
|
|
5021
5227
|
globalVariables: this.globalVariables ? { ...this.globalVariables } : {},
|
|
5022
|
-
cargo: {}
|
|
5228
|
+
cargo: {},
|
|
5229
|
+
response: null, // Initialize with no response
|
|
5230
|
+
completedTransactions: [] // Initialize with no completed transactions
|
|
5023
5231
|
};
|
|
5024
5232
|
logger.info(`Engine session initialized: ${engineSessionContext.sessionId} for user: ${userId}`);
|
|
5025
5233
|
return engineSessionContext;
|
|
5026
5234
|
}
|
|
5027
5235
|
async updateActivity(contextEntry, engineSessionContext) {
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5236
|
+
try {
|
|
5237
|
+
// Load session context if provided
|
|
5238
|
+
let hostLogger = engineSessionContext.hostLogger || logger; // Fallback to global logger if not provided
|
|
5239
|
+
if (engineSessionContext.hostLogger) {
|
|
5240
|
+
const requiredMethods = ['info', 'warn', 'error', 'debug'];
|
|
5241
|
+
for (const method of requiredMethods) {
|
|
5242
|
+
if (typeof engineSessionContext.hostLogger[method] !== 'function') {
|
|
5243
|
+
hostLogger = logger; // Fallback to global logger if hostLogger is missing methods
|
|
5244
|
+
break; // No need to throw error, just use global logger
|
|
5245
|
+
//throw new Error(`Logger is missing required method: ${method}`);
|
|
5246
|
+
}
|
|
5247
|
+
}
|
|
5248
|
+
}
|
|
5249
|
+
logger = hostLogger; // Assign to global logger
|
|
5031
5250
|
this.cargo = engineSessionContext.cargo;
|
|
5251
|
+
logger.info(`Received Cargo: ${JSON.stringify(this.cargo)}`);
|
|
5032
5252
|
this.sessionId = engineSessionContext.sessionId;
|
|
5033
5253
|
this.createdAt = engineSessionContext.createdAt;
|
|
5034
|
-
|
|
5035
|
-
this.
|
|
5036
|
-
|
|
5037
|
-
this.
|
|
5038
|
-
|
|
5039
|
-
|
|
5040
|
-
|
|
5041
|
-
|
|
5042
|
-
|
|
5043
|
-
|
|
5044
|
-
|
|
5045
|
-
|
|
5046
|
-
|
|
5047
|
-
|
|
5048
|
-
|
|
5049
|
-
|
|
5050
|
-
//
|
|
5051
|
-
if (
|
|
5052
|
-
|
|
5053
|
-
|
|
5054
|
-
|
|
5254
|
+
// Store reference to session context - no copying needed! Engine works directly with session data
|
|
5255
|
+
this.sessionContext = engineSessionContext;
|
|
5256
|
+
// Ensure session context has proper initialization
|
|
5257
|
+
if (!this.sessionContext.flowStacks || !Array.isArray(this.sessionContext.flowStacks)) {
|
|
5258
|
+
this.sessionContext.flowStacks = [[]];
|
|
5259
|
+
logger.warn('engineSessionContext.flowStacks was invalid, initialized fresh flowStacks');
|
|
5260
|
+
}
|
|
5261
|
+
if (!this.sessionContext.globalAccumulatedMessages) {
|
|
5262
|
+
this.sessionContext.globalAccumulatedMessages = [];
|
|
5263
|
+
}
|
|
5264
|
+
if (!this.sessionContext.lastChatTurn) {
|
|
5265
|
+
this.sessionContext.lastChatTurn = {};
|
|
5266
|
+
}
|
|
5267
|
+
if (!this.sessionContext.globalVariables) {
|
|
5268
|
+
this.sessionContext.globalVariables = {};
|
|
5269
|
+
}
|
|
5270
|
+
// Safety check: ensure flowStacks is always properly initialized
|
|
5271
|
+
if (this.sessionContext.flowStacks.length === 0) {
|
|
5272
|
+
logger.warn('flowStacks was empty, adding initial stack...');
|
|
5273
|
+
this.sessionContext.flowStacks.push([]); // Add empty stack
|
|
5274
|
+
}
|
|
5275
|
+
// Get userId from session context
|
|
5276
|
+
const userId = engineSessionContext?.userId || 'anonymous';
|
|
5277
|
+
this.lastActivity = new Date();
|
|
5278
|
+
// Update session context with latest activity time
|
|
5055
5279
|
if (engineSessionContext) {
|
|
5056
|
-
engineSessionContext.
|
|
5057
|
-
|
|
5058
|
-
|
|
5059
|
-
|
|
5280
|
+
engineSessionContext.lastActivity = this.lastActivity;
|
|
5281
|
+
}
|
|
5282
|
+
// Role-based processing logic
|
|
5283
|
+
if (contextEntry.role === 'user') {
|
|
5284
|
+
// Detect intent to activate a flow or switch flows
|
|
5285
|
+
const responseOrNull = await processActivity(String(contextEntry.content), userId, this);
|
|
5286
|
+
// Store user turn in lastChatTurn if not in a flow
|
|
5287
|
+
if (responseOrNull === null) {
|
|
5288
|
+
this.sessionContext.lastChatTurn.user = contextEntry;
|
|
5289
|
+
}
|
|
5290
|
+
// No copying needed! Session context already contains the latest data
|
|
5291
|
+
if (engineSessionContext) {
|
|
5292
|
+
engineSessionContext.response = responseOrNull; // Store the response from flow processing
|
|
5293
|
+
// Extract and store completed transactions for host access
|
|
5294
|
+
const newCompletedTransactions = [];
|
|
5295
|
+
for (const stack of this.flowStacks) {
|
|
5296
|
+
for (const frame of stack) {
|
|
5297
|
+
if (frame.transaction && (frame.transaction.state === 'completed' || frame.transaction.state === 'failed')) {
|
|
5298
|
+
newCompletedTransactions.push(frame.transaction);
|
|
5299
|
+
}
|
|
5300
|
+
}
|
|
5301
|
+
}
|
|
5302
|
+
if (!engineSessionContext.completedTransactions) {
|
|
5303
|
+
engineSessionContext.completedTransactions = [];
|
|
5304
|
+
}
|
|
5305
|
+
// Add any new completed transactions
|
|
5306
|
+
for (const transaction of newCompletedTransactions) {
|
|
5307
|
+
const existingTransaction = engineSessionContext.completedTransactions.find(t => t.id === transaction.id);
|
|
5308
|
+
if (!existingTransaction) {
|
|
5309
|
+
engineSessionContext.completedTransactions.push(transaction);
|
|
5310
|
+
}
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5313
|
+
return engineSessionContext;
|
|
5060
5314
|
}
|
|
5061
|
-
|
|
5062
|
-
|
|
5063
|
-
|
|
5064
|
-
|
|
5065
|
-
|
|
5066
|
-
|
|
5067
|
-
|
|
5315
|
+
else if (contextEntry.role === 'assistant') {
|
|
5316
|
+
// Check if we're in a flow or not
|
|
5317
|
+
if (getCurrentStackLength(this) === 0) {
|
|
5318
|
+
// Not in a flow - store in lastChatTurn for context
|
|
5319
|
+
this.sessionContext.lastChatTurn.assistant = contextEntry;
|
|
5320
|
+
}
|
|
5321
|
+
else {
|
|
5322
|
+
// In a flow - add to current flow's context stack
|
|
5323
|
+
const currentFlowFrame = getCurrentFlowFrame(this);
|
|
5324
|
+
addToContextStack(currentFlowFrame.contextStack, 'assistant', contextEntry.content, contextEntry.stepId, contextEntry.toolName, contextEntry.metadata);
|
|
5325
|
+
}
|
|
5326
|
+
// No copying needed! Session context already contains the latest data
|
|
5327
|
+
if (engineSessionContext) {
|
|
5328
|
+
engineSessionContext.response = null; // No response for assistant turns
|
|
5329
|
+
// Extract and store completed transactions for host access
|
|
5330
|
+
const newCompletedTransactions = [];
|
|
5331
|
+
for (const stack of this.flowStacks) {
|
|
5332
|
+
for (const frame of stack) {
|
|
5333
|
+
if (frame.transaction && (frame.transaction.state === 'completed' || frame.transaction.state === 'failed')) {
|
|
5334
|
+
newCompletedTransactions.push(frame.transaction);
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
}
|
|
5338
|
+
if (!engineSessionContext.completedTransactions) {
|
|
5339
|
+
engineSessionContext.completedTransactions = [];
|
|
5340
|
+
}
|
|
5341
|
+
// Add any new completed transactions
|
|
5342
|
+
for (const transaction of newCompletedTransactions) {
|
|
5343
|
+
const existingTransaction = engineSessionContext.completedTransactions.find(t => t.id === transaction.id);
|
|
5344
|
+
if (!existingTransaction) {
|
|
5345
|
+
engineSessionContext.completedTransactions.push(transaction);
|
|
5346
|
+
}
|
|
5347
|
+
}
|
|
5348
|
+
}
|
|
5349
|
+
return engineSessionContext;
|
|
5068
5350
|
}
|
|
5069
5351
|
else {
|
|
5070
|
-
//
|
|
5071
|
-
|
|
5072
|
-
|
|
5352
|
+
// Throw error for unsupported roles
|
|
5353
|
+
throw new Error(`Unsupported role '${contextEntry.role}' in updateActivity. Only 'user' and 'assistant' roles are supported.`);
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5356
|
+
catch (error) {
|
|
5357
|
+
if (logger && logger.error) {
|
|
5358
|
+
logger.error(`Error in updateActivity: ${error.message}`);
|
|
5073
5359
|
}
|
|
5074
|
-
//
|
|
5360
|
+
// Return session context with error information
|
|
5075
5361
|
if (engineSessionContext) {
|
|
5076
|
-
engineSessionContext.
|
|
5077
|
-
engineSessionContext
|
|
5078
|
-
engineSessionContext.lastChatTurn = this.lastChatTurn;
|
|
5079
|
-
engineSessionContext.globalVariables = this.globalVariables || {};
|
|
5362
|
+
engineSessionContext.response = `Error: ${error.message}`;
|
|
5363
|
+
return engineSessionContext;
|
|
5080
5364
|
}
|
|
5081
|
-
//
|
|
5082
|
-
|
|
5083
|
-
}
|
|
5084
|
-
else {
|
|
5085
|
-
// Throw error for unsupported roles
|
|
5086
|
-
throw new Error(`Unsupported role '${contextEntry.role}' in updateActivity. Only 'user' and 'assistant' roles are supported.`);
|
|
5365
|
+
// If no session context provided, we can't return it - this should not happen
|
|
5366
|
+
throw error;
|
|
5087
5367
|
}
|
|
5088
5368
|
}
|
|
5089
5369
|
// Add a SAY message to global accumulation
|
|
5090
|
-
addAccumulatedMessage(message
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5370
|
+
addAccumulatedMessage(message) {
|
|
5371
|
+
if (!this.sessionContext) {
|
|
5372
|
+
logger.warn('No session context available for addAccumulatedMessage');
|
|
5373
|
+
return;
|
|
5094
5374
|
}
|
|
5095
|
-
this.globalAccumulatedMessages
|
|
5096
|
-
|
|
5097
|
-
if (engineSessionContext) {
|
|
5098
|
-
engineSessionContext.globalAccumulatedMessages = this.globalAccumulatedMessages;
|
|
5375
|
+
if (!this.sessionContext.globalAccumulatedMessages) {
|
|
5376
|
+
this.sessionContext.globalAccumulatedMessages = [];
|
|
5099
5377
|
}
|
|
5378
|
+
this.sessionContext.globalAccumulatedMessages.push(message);
|
|
5100
5379
|
}
|
|
5101
5380
|
// Get and clear all accumulated messages
|
|
5102
|
-
getAndClearAccumulatedMessages(
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5381
|
+
getAndClearAccumulatedMessages() {
|
|
5382
|
+
if (!this.sessionContext) {
|
|
5383
|
+
logger.warn('No session context available for getAndClearAccumulatedMessages');
|
|
5384
|
+
return [];
|
|
5106
5385
|
}
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
// Update session context with cleared messages
|
|
5110
|
-
if (engineSessionContext) {
|
|
5111
|
-
engineSessionContext.globalAccumulatedMessages = this.globalAccumulatedMessages;
|
|
5386
|
+
if (!this.sessionContext.globalAccumulatedMessages) {
|
|
5387
|
+
this.sessionContext.globalAccumulatedMessages = [];
|
|
5112
5388
|
}
|
|
5389
|
+
const messages = [...this.sessionContext.globalAccumulatedMessages];
|
|
5390
|
+
this.sessionContext.globalAccumulatedMessages = [];
|
|
5113
5391
|
return messages;
|
|
5114
5392
|
}
|
|
5115
5393
|
// Check if there are accumulated messages
|
|
@@ -5117,7 +5395,12 @@ export class WorkflowEngine {
|
|
|
5117
5395
|
return this.globalAccumulatedMessages.length > 0;
|
|
5118
5396
|
}
|
|
5119
5397
|
initializeFlowStacks() {
|
|
5120
|
-
|
|
5398
|
+
if (this.sessionContext) {
|
|
5399
|
+
this.sessionContext.flowStacks = [[]];
|
|
5400
|
+
}
|
|
5401
|
+
else {
|
|
5402
|
+
logger.warn('No session context available for initializeFlowStacks');
|
|
5403
|
+
}
|
|
5121
5404
|
}
|
|
5122
5405
|
getCurrentStack() {
|
|
5123
5406
|
return getCurrentStack(this);
|