@q1k-oss/behaviour-tree-workflows 0.0.5 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/index.cjs +190 -178
  2. package/dist/index.js +190 -178
  3. package/package.json +4 -1
package/dist/index.cjs CHANGED
@@ -758,6 +758,7 @@ var CompositeNode = class extends BaseNode {
758
758
  };
759
759
 
760
760
  // src/blackboard.ts
761
+ var import_safe_stable_stringify = __toESM(require("safe-stable-stringify"), 1);
761
762
  var ScopedBlackboard = class _ScopedBlackboard {
762
763
  data = {};
763
764
  parent = null;
@@ -879,7 +880,7 @@ var ScopedBlackboard = class _ScopedBlackboard {
879
880
  const prefix = " ".repeat(indent);
880
881
  console.log(`${prefix}Scope: ${this.scopeName}`);
881
882
  for (const [key, value] of Object.entries(this.data)) {
882
- console.log(`${prefix} ${key}: ${JSON.stringify(value)}`);
883
+ console.log(`${prefix} ${key}: ${(0, import_safe_stable_stringify.default)(value)}`);
883
884
  }
884
885
  for (const [_name, childScope] of this.childScopes) {
885
886
  childScope.debug(indent + 1);
@@ -1128,10 +1129,10 @@ var Conditional = class extends CompositeNode {
1128
1129
  async executeTick(context) {
1129
1130
  checkSignal(context.signal);
1130
1131
  if (!this.condition) {
1131
- throw new Error("Conditional requires at least a condition child");
1132
+ throw new ConfigurationError("Conditional requires at least a condition child");
1132
1133
  }
1133
1134
  if (!this.thenBranch) {
1134
- throw new Error(
1135
+ throw new ConfigurationError(
1135
1136
  "Conditional requires at least condition and then branch"
1136
1137
  );
1137
1138
  }
@@ -1160,7 +1161,7 @@ var Conditional = class extends CompositeNode {
1160
1161
  this._status = "RUNNING" /* RUNNING */;
1161
1162
  return "RUNNING" /* RUNNING */;
1162
1163
  default:
1163
- throw new Error(
1164
+ throw new ConfigurationError(
1164
1165
  `Unexpected status from condition: ${conditionStatus}`
1165
1166
  );
1166
1167
  }
@@ -1168,7 +1169,7 @@ var Conditional = class extends CompositeNode {
1168
1169
  this.log("Condition already evaluated - continuing branch execution");
1169
1170
  }
1170
1171
  if (!this.selectedBranch) {
1171
- throw new Error("No branch selected for execution");
1172
+ throw new ConfigurationError("No branch selected for execution");
1172
1173
  }
1173
1174
  const branchStatus = await this.selectedBranch.tick(context);
1174
1175
  this._status = branchStatus;
@@ -1193,6 +1194,161 @@ var Conditional = class extends CompositeNode {
1193
1194
  };
1194
1195
 
1195
1196
  // src/composites/for-each.ts
1197
+ var import_dlv2 = __toESM(require("dlv"), 1);
1198
+ var import_safe_stable_stringify3 = __toESM(require("safe-stable-stringify"), 1);
1199
+
1200
+ // src/utilities/variable-resolver.ts
1201
+ var import_dlv = __toESM(require("dlv"), 1);
1202
+ var import_safe_stable_stringify2 = __toESM(require("safe-stable-stringify"), 1);
1203
+ function parsePath(path2) {
1204
+ const segments = [];
1205
+ let current = "";
1206
+ for (let i = 0; i < path2.length; i++) {
1207
+ const ch = path2[i];
1208
+ if (ch === "." || ch === "[") {
1209
+ if (current) segments.push(current);
1210
+ current = "";
1211
+ } else if (ch === "]") {
1212
+ if (current) segments.push(current);
1213
+ current = "";
1214
+ } else {
1215
+ current += ch;
1216
+ }
1217
+ }
1218
+ if (current) segments.push(current);
1219
+ return segments;
1220
+ }
1221
+ var VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.\[\]]+)\}|\$\{([a-zA-Z0-9_.\[\]]+)\}/g;
1222
+ var HAS_VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.\[\]]+)\}|\$\{([a-zA-Z0-9_.\[\]]+)\}/;
1223
+ var FULL_MATCH_PATTERN = /^\$\{(input|bb|env|param)\.([a-zA-Z0-9_.\[\]]+)\}$|^\$\{([a-zA-Z0-9_.\[\]]+)\}$/;
1224
+ var safeProcessEnv = () => {
1225
+ try {
1226
+ return typeof process !== "undefined" && process?.env ? process.env : {};
1227
+ } catch {
1228
+ return {};
1229
+ }
1230
+ };
1231
+ function resolveString(str, ctx, opts = {}) {
1232
+ const { preserveUndefined = true, envSource = safeProcessEnv() } = opts;
1233
+ const fullMatch = str.match(FULL_MATCH_PATTERN);
1234
+ if (fullMatch) {
1235
+ const namespace = fullMatch[1];
1236
+ const namespacedKey = fullMatch[2];
1237
+ const simpleKey = fullMatch[3];
1238
+ const key = namespacedKey || simpleKey;
1239
+ const ns = namespace || "bb";
1240
+ if (key) {
1241
+ const value = resolveVariable(ns, key, ctx, envSource);
1242
+ if (value !== void 0) {
1243
+ return value;
1244
+ }
1245
+ return preserveUndefined ? str : void 0;
1246
+ }
1247
+ }
1248
+ return str.replace(VARIABLE_PATTERN, (match, namespace, namespacedKey, simpleKey) => {
1249
+ const key = namespacedKey || simpleKey;
1250
+ const ns = namespace || "bb";
1251
+ const value = resolveVariable(ns, key, ctx, envSource);
1252
+ if (value === void 0) {
1253
+ return preserveUndefined ? match : "";
1254
+ }
1255
+ if (value === null) {
1256
+ return "null";
1257
+ }
1258
+ if (typeof value === "object") {
1259
+ return (0, import_safe_stable_stringify2.default)(value) ?? String(value);
1260
+ }
1261
+ return String(value);
1262
+ });
1263
+ }
1264
+ function resolveValue(value, ctx, opts = {}) {
1265
+ if (typeof value === "string") {
1266
+ return resolveString(value, ctx, opts);
1267
+ }
1268
+ if (Array.isArray(value)) {
1269
+ return value.map((item) => resolveValue(item, ctx, opts));
1270
+ }
1271
+ if (value !== null && typeof value === "object") {
1272
+ const resolved = {};
1273
+ for (const [key, val] of Object.entries(value)) {
1274
+ resolved[key] = resolveValue(val, ctx, opts);
1275
+ }
1276
+ return resolved;
1277
+ }
1278
+ return value;
1279
+ }
1280
+ function resolveVariable(namespace, key, ctx, envSource) {
1281
+ switch (namespace) {
1282
+ case "input":
1283
+ return getNestedValue(ctx.input, key);
1284
+ case "bb":
1285
+ return getNestedBlackboardValue(ctx.blackboard, key);
1286
+ case "env":
1287
+ return envSource[key];
1288
+ case "param":
1289
+ if (ctx.testData) {
1290
+ const parts = parsePath(key);
1291
+ const firstPart = parts[0];
1292
+ if (firstPart) {
1293
+ const value = ctx.testData.get(firstPart);
1294
+ if (parts.length === 1) return value;
1295
+ if (value === void 0 || value === null || typeof value !== "object") return void 0;
1296
+ return (0, import_dlv.default)(value, parts.slice(1));
1297
+ }
1298
+ }
1299
+ return void 0;
1300
+ default:
1301
+ return getNestedBlackboardValue(ctx.blackboard, key);
1302
+ }
1303
+ }
1304
+ function getNestedValue(obj, path2) {
1305
+ if (obj === void 0 || obj === null || typeof obj !== "object") {
1306
+ return void 0;
1307
+ }
1308
+ return (0, import_dlv.default)(obj, parsePath(path2));
1309
+ }
1310
+ function getNestedBlackboardValue(blackboard, path2) {
1311
+ const parts = parsePath(path2);
1312
+ const firstPart = parts[0];
1313
+ if (!firstPart) {
1314
+ return void 0;
1315
+ }
1316
+ const value = blackboard.get(firstPart);
1317
+ if (parts.length === 1) {
1318
+ return value;
1319
+ }
1320
+ if (value === void 0 || value === null || typeof value !== "object") {
1321
+ return void 0;
1322
+ }
1323
+ return (0, import_dlv.default)(value, parts.slice(1));
1324
+ }
1325
+ function hasVariables(str) {
1326
+ return HAS_VARIABLE_PATTERN.test(str);
1327
+ }
1328
+ function extractVariables(str) {
1329
+ const variables = [];
1330
+ const pattern = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.\[\]]+)\}|\$\{([a-zA-Z0-9_.\[\]]+)\}/g;
1331
+ let match;
1332
+ while ((match = pattern.exec(str)) !== null) {
1333
+ const namespace = match[1] || "bb";
1334
+ const key = match[2] || match[3];
1335
+ if (key) {
1336
+ variables.push({ namespace, key });
1337
+ }
1338
+ }
1339
+ return variables;
1340
+ }
1341
+
1342
+ // src/composites/for-each.ts
1343
+ function resolveFromBlackboard(blackboard, path2) {
1344
+ const parts = parsePath(path2);
1345
+ const firstPart = parts[0];
1346
+ if (!firstPart) return void 0;
1347
+ const value = blackboard.get(firstPart);
1348
+ if (parts.length === 1) return value;
1349
+ if (value === void 0 || value === null || typeof value !== "object") return void 0;
1350
+ return (0, import_dlv2.default)(value, parts.slice(1));
1351
+ }
1196
1352
  var ForEach = class extends CompositeNode {
1197
1353
  collectionKey;
1198
1354
  itemKey;
@@ -1216,14 +1372,14 @@ var ForEach = class extends CompositeNode {
1216
1372
  "ForEach requires at least one child (body)"
1217
1373
  );
1218
1374
  }
1219
- const collection = context.blackboard.get(this.collectionKey);
1375
+ const collection = this.collectionKey.includes(".") || this.collectionKey.includes("[") ? resolveFromBlackboard(context.blackboard, this.collectionKey) : context.blackboard.get(this.collectionKey);
1220
1376
  if (!collection) {
1221
1377
  this.log(`Collection '${this.collectionKey}' not found in blackboard`);
1222
1378
  this._status = "FAILURE" /* FAILURE */;
1223
1379
  return "FAILURE" /* FAILURE */;
1224
1380
  }
1225
1381
  if (!Array.isArray(collection)) {
1226
- throw new Error(`Collection '${this.collectionKey}' is not an array`);
1382
+ throw new ConfigurationError(`Collection '${this.collectionKey}' is not an array`);
1227
1383
  }
1228
1384
  if (collection.length === 0) {
1229
1385
  this.log("Collection is empty - returning SUCCESS");
@@ -1241,7 +1397,7 @@ var ForEach = class extends CompositeNode {
1241
1397
  context.blackboard.set(this.indexKey, this.currentIndex);
1242
1398
  }
1243
1399
  this.log(
1244
- `Processing item ${this.currentIndex}: ${JSON.stringify(item)}`
1400
+ `Processing item ${this.currentIndex}: ${(0, import_safe_stable_stringify3.default)(item)}`
1245
1401
  );
1246
1402
  const bodyStatus = await body.tick(context);
1247
1403
  switch (bodyStatus) {
@@ -1261,7 +1417,7 @@ var ForEach = class extends CompositeNode {
1261
1417
  return "RUNNING" /* RUNNING */;
1262
1418
  // Will resume from this index next tick
1263
1419
  default:
1264
- throw new Error(`Unexpected status from body: ${bodyStatus}`);
1420
+ throw new ConfigurationError(`Unexpected status from body: ${bodyStatus}`);
1265
1421
  }
1266
1422
  }
1267
1423
  this.log("All items processed successfully");
@@ -1291,7 +1447,7 @@ var Sequence = class extends CompositeNode {
1291
1447
  checkSignal(context.signal);
1292
1448
  const child = this._children[this.currentChildIndex];
1293
1449
  if (!child) {
1294
- throw new Error(
1450
+ throw new ConfigurationError(
1295
1451
  `Child at index ${this.currentChildIndex} is undefined`
1296
1452
  );
1297
1453
  }
@@ -1312,7 +1468,7 @@ var Sequence = class extends CompositeNode {
1312
1468
  this._status = "RUNNING" /* RUNNING */;
1313
1469
  return "RUNNING" /* RUNNING */;
1314
1470
  default:
1315
- throw new Error(`Unexpected status from child: ${childStatus}`);
1471
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1316
1472
  }
1317
1473
  }
1318
1474
  this.log("All children succeeded");
@@ -1365,7 +1521,7 @@ var MemorySequence = class extends Sequence {
1365
1521
  this._status = "RUNNING" /* RUNNING */;
1366
1522
  return "RUNNING" /* RUNNING */;
1367
1523
  default:
1368
- throw new Error(`Unexpected status from child: ${childStatus}`);
1524
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1369
1525
  }
1370
1526
  }
1371
1527
  this.log("All children succeeded");
@@ -1496,7 +1652,7 @@ var ReactiveSequence = class extends Sequence {
1496
1652
  this._status = "RUNNING" /* RUNNING */;
1497
1653
  return "RUNNING" /* RUNNING */;
1498
1654
  default:
1499
- throw new Error(`Unexpected status from child: ${childStatus}`);
1655
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1500
1656
  }
1501
1657
  }
1502
1658
  this.log("All children succeeded");
@@ -1575,7 +1731,7 @@ var Selector = class extends CompositeNode {
1575
1731
  checkSignal(context.signal);
1576
1732
  const child = this._children[this.currentChildIndex];
1577
1733
  if (!child) {
1578
- throw new Error(
1734
+ throw new ConfigurationError(
1579
1735
  `Child at index ${this.currentChildIndex} is undefined`
1580
1736
  );
1581
1737
  }
@@ -1596,7 +1752,7 @@ var Selector = class extends CompositeNode {
1596
1752
  this._status = "RUNNING" /* RUNNING */;
1597
1753
  return "RUNNING" /* RUNNING */;
1598
1754
  default:
1599
- throw new Error(`Unexpected status from child: ${childStatus}`);
1755
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1600
1756
  }
1601
1757
  }
1602
1758
  this.log("All children failed");
@@ -1618,154 +1774,6 @@ var Fallback = class extends Selector {
1618
1774
  }
1619
1775
  };
1620
1776
 
1621
- // src/utilities/variable-resolver.ts
1622
- var VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/g;
1623
- var HAS_VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/;
1624
- var FULL_MATCH_PATTERN = /^\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}$|^\$\{([a-zA-Z0-9_.]+)\}$/;
1625
- var safeProcessEnv = () => {
1626
- try {
1627
- return typeof process !== "undefined" && process?.env ? process.env : {};
1628
- } catch {
1629
- return {};
1630
- }
1631
- };
1632
- function resolveString(str, ctx, opts = {}) {
1633
- const { preserveUndefined = true, envSource = safeProcessEnv() } = opts;
1634
- const fullMatch = str.match(FULL_MATCH_PATTERN);
1635
- if (fullMatch) {
1636
- const namespace = fullMatch[1];
1637
- const namespacedKey = fullMatch[2];
1638
- const simpleKey = fullMatch[3];
1639
- const key = namespacedKey || simpleKey;
1640
- const ns = namespace || "bb";
1641
- if (key) {
1642
- const value = resolveVariable(ns, key, ctx, envSource);
1643
- if (value !== void 0) {
1644
- return value;
1645
- }
1646
- return preserveUndefined ? str : void 0;
1647
- }
1648
- }
1649
- return str.replace(VARIABLE_PATTERN, (match, namespace, namespacedKey, simpleKey) => {
1650
- const key = namespacedKey || simpleKey;
1651
- const ns = namespace || "bb";
1652
- const value = resolveVariable(ns, key, ctx, envSource);
1653
- if (value === void 0) {
1654
- return preserveUndefined ? match : "";
1655
- }
1656
- if (value === null) {
1657
- return "null";
1658
- }
1659
- if (typeof value === "object") {
1660
- try {
1661
- return JSON.stringify(value);
1662
- } catch {
1663
- return String(value);
1664
- }
1665
- }
1666
- return String(value);
1667
- });
1668
- }
1669
- function resolveValue(value, ctx, opts = {}) {
1670
- if (typeof value === "string") {
1671
- return resolveString(value, ctx, opts);
1672
- }
1673
- if (Array.isArray(value)) {
1674
- return value.map((item) => resolveValue(item, ctx, opts));
1675
- }
1676
- if (value !== null && typeof value === "object") {
1677
- const resolved = {};
1678
- for (const [key, val] of Object.entries(value)) {
1679
- resolved[key] = resolveValue(val, ctx, opts);
1680
- }
1681
- return resolved;
1682
- }
1683
- return value;
1684
- }
1685
- function resolveVariable(namespace, key, ctx, envSource) {
1686
- switch (namespace) {
1687
- case "input":
1688
- return getNestedValue(ctx.input, key);
1689
- case "bb":
1690
- return getNestedBlackboardValue(ctx.blackboard, key);
1691
- case "env":
1692
- return envSource[key];
1693
- case "param":
1694
- if (ctx.testData) {
1695
- const parts = key.split(".");
1696
- const firstPart = parts[0];
1697
- if (firstPart) {
1698
- let value = ctx.testData.get(firstPart);
1699
- for (let i = 1; i < parts.length && value !== void 0; i++) {
1700
- const part = parts[i];
1701
- if (part && typeof value === "object" && value !== null) {
1702
- value = value[part];
1703
- } else {
1704
- return void 0;
1705
- }
1706
- }
1707
- return value;
1708
- }
1709
- }
1710
- return void 0;
1711
- default:
1712
- return getNestedBlackboardValue(ctx.blackboard, key);
1713
- }
1714
- }
1715
- function getNestedValue(obj, path2) {
1716
- if (obj === void 0 || obj === null) {
1717
- return void 0;
1718
- }
1719
- if (typeof obj !== "object") {
1720
- return void 0;
1721
- }
1722
- const parts = path2.split(".");
1723
- let value = obj;
1724
- for (const part of parts) {
1725
- if (value === void 0 || value === null) {
1726
- return void 0;
1727
- }
1728
- if (typeof value !== "object") {
1729
- return void 0;
1730
- }
1731
- value = value[part];
1732
- }
1733
- return value;
1734
- }
1735
- function getNestedBlackboardValue(blackboard, path2) {
1736
- const parts = path2.split(".");
1737
- const firstPart = parts[0];
1738
- if (!firstPart) {
1739
- return void 0;
1740
- }
1741
- let value = blackboard.get(firstPart);
1742
- for (let i = 1; i < parts.length && value !== void 0; i++) {
1743
- const part = parts[i];
1744
- if (part && typeof value === "object" && value !== null) {
1745
- value = value[part];
1746
- } else {
1747
- return void 0;
1748
- }
1749
- }
1750
- return value;
1751
- }
1752
- function hasVariables(str) {
1753
- return HAS_VARIABLE_PATTERN.test(str);
1754
- }
1755
- function extractVariables(str) {
1756
- const variables = [];
1757
- const pattern = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/g;
1758
- let match;
1759
- while ((match = pattern.exec(str)) !== null) {
1760
- const namespace = match[1] || "bb";
1761
- const key = match[2] || match[3];
1762
- if (key) {
1763
- variables.push({ namespace, key });
1764
- }
1765
- }
1766
- return variables;
1767
- }
1768
-
1769
1777
  // src/composites/sub-tree.ts
1770
1778
  var SubTree = class extends ActionNode {
1771
1779
  treeId;
@@ -1783,7 +1791,7 @@ var SubTree = class extends ActionNode {
1783
1791
  checkSignal(context.signal);
1784
1792
  if (!this.clonedTree) {
1785
1793
  if (!context.treeRegistry.hasTree(this.treeId)) {
1786
- throw new Error(
1794
+ throw new ConfigurationError(
1787
1795
  `SubTree tree '${this.treeId}' not found in registry. Available trees: ${context.treeRegistry.getAllTreeIds().join(", ") || "none"}`
1788
1796
  );
1789
1797
  }
@@ -1940,7 +1948,7 @@ var While = class extends CompositeNode {
1940
1948
  this._status = "RUNNING" /* RUNNING */;
1941
1949
  return "RUNNING" /* RUNNING */;
1942
1950
  default:
1943
- throw new Error(`Unexpected status from body: ${bodyStatus}`);
1951
+ throw new ConfigurationError(`Unexpected status from body: ${bodyStatus}`);
1944
1952
  }
1945
1953
  }
1946
1954
  this.log(`Max iterations (${this.maxIterations}) reached`);
@@ -3535,6 +3543,7 @@ var RunningNode = class extends ActionNode {
3535
3543
  };
3536
3544
 
3537
3545
  // src/utilities/log-message.ts
3546
+ var import_safe_stable_stringify4 = __toESM(require("safe-stable-stringify"), 1);
3538
3547
  var LogMessage = class extends ActionNode {
3539
3548
  message;
3540
3549
  level;
@@ -3597,11 +3606,7 @@ var LogMessage = class extends ActionNode {
3597
3606
  return "null";
3598
3607
  }
3599
3608
  if (typeof resolved === "object") {
3600
- try {
3601
- return JSON.stringify(resolved);
3602
- } catch {
3603
- return String(resolved);
3604
- }
3609
+ return (0, import_safe_stable_stringify4.default)(resolved) ?? String(resolved);
3605
3610
  }
3606
3611
  return String(resolved);
3607
3612
  }
@@ -3664,6 +3669,7 @@ var RegexExtract = class extends ActionNode {
3664
3669
  };
3665
3670
 
3666
3671
  // src/utilities/set-variable.ts
3672
+ var import_safe_stable_stringify5 = __toESM(require("safe-stable-stringify"), 1);
3667
3673
  var SetVariable = class extends ActionNode {
3668
3674
  key;
3669
3675
  value;
@@ -3682,7 +3688,7 @@ var SetVariable = class extends ActionNode {
3682
3688
  const resolvedKey = typeof this.key === "string" ? resolveValue(this.key, varCtx) : String(this.key);
3683
3689
  const resolvedValue = typeof this.value === "string" ? resolveValue(this.value, varCtx) : this.value;
3684
3690
  context.blackboard.set(resolvedKey, resolvedValue);
3685
- this.log(`Set ${resolvedKey} = ${JSON.stringify(resolvedValue)}`);
3691
+ this.log(`Set ${resolvedKey} = ${(0, import_safe_stable_stringify5.default)(resolvedValue)}`);
3686
3692
  return "SUCCESS" /* SUCCESS */;
3687
3693
  } catch (error) {
3688
3694
  this._lastError = error instanceof Error ? error.message : String(error);
@@ -3693,6 +3699,7 @@ var SetVariable = class extends ActionNode {
3693
3699
  };
3694
3700
 
3695
3701
  // src/utilities/math-op.ts
3702
+ var import_safe_stable_stringify6 = __toESM(require("safe-stable-stringify"), 1);
3696
3703
  function tokenize(expr) {
3697
3704
  const tokens = [];
3698
3705
  let i = 0;
@@ -3802,12 +3809,12 @@ function evaluate(tokens) {
3802
3809
  pos++;
3803
3810
  return val;
3804
3811
  }
3805
- throw new Error(`Unexpected token: ${JSON.stringify(tok)}`);
3812
+ throw new Error(`Unexpected token: ${(0, import_safe_stable_stringify6.default)(tok)}`);
3806
3813
  }
3807
3814
  const result = parseExpr();
3808
3815
  const remaining = current();
3809
3816
  if (remaining) {
3810
- throw new Error(`Unexpected token after expression: ${JSON.stringify(remaining)}`);
3817
+ throw new Error(`Unexpected token after expression: ${(0, import_safe_stable_stringify6.default)(remaining)}`);
3811
3818
  }
3812
3819
  return result;
3813
3820
  }
@@ -3960,7 +3967,7 @@ var ArrayFilter = class extends ActionNode {
3960
3967
  };
3961
3968
  const inputResolved = typeof this.input === "string" ? resolveValue(this.input, varCtx) : this.input;
3962
3969
  if (!Array.isArray(inputResolved)) {
3963
- throw new Error(
3970
+ throw new ConfigurationError(
3964
3971
  `Input is not an array: got ${inputResolved === null ? "null" : typeof inputResolved}`
3965
3972
  );
3966
3973
  }
@@ -3979,6 +3986,7 @@ var ArrayFilter = class extends ActionNode {
3979
3986
  this.log(`Filtered ${inputResolved.length} \u2192 ${result.length} items`);
3980
3987
  return "SUCCESS" /* SUCCESS */;
3981
3988
  } catch (error) {
3989
+ if (error instanceof ConfigurationError) throw error;
3982
3990
  this._lastError = error instanceof Error ? error.message : String(error);
3983
3991
  this.log(`ArrayFilter failed: ${this._lastError}`);
3984
3992
  return "FAILURE" /* FAILURE */;
@@ -3987,6 +3995,7 @@ var ArrayFilter = class extends ActionNode {
3987
3995
  };
3988
3996
 
3989
3997
  // src/utilities/aggregate.ts
3998
+ var import_safe_stable_stringify7 = __toESM(require("safe-stable-stringify"), 1);
3990
3999
  function getFieldValue2(item, path2) {
3991
4000
  if (item === null || item === void 0) return void 0;
3992
4001
  const parts = path2.split(".");
@@ -4054,14 +4063,14 @@ var Aggregate = class extends ActionNode {
4054
4063
  };
4055
4064
  const inputResolved = typeof this.input === "string" ? resolveValue(this.input, varCtx) : this.input;
4056
4065
  if (!Array.isArray(inputResolved)) {
4057
- throw new Error(
4066
+ throw new ConfigurationError(
4058
4067
  `Input is not an array: got ${inputResolved === null ? "null" : typeof inputResolved}`
4059
4068
  );
4060
4069
  }
4061
4070
  if (!this.groupBy) {
4062
4071
  const result = computeAggregations(inputResolved, this.operations);
4063
4072
  context.blackboard.set(this.outputKey, result);
4064
- this.log(`Aggregated ${inputResolved.length} items \u2192 ${JSON.stringify(result)}`);
4073
+ this.log(`Aggregated ${inputResolved.length} items \u2192 ${(0, import_safe_stable_stringify7.default)(result)}`);
4065
4074
  } else {
4066
4075
  const groups = {};
4067
4076
  for (const item of inputResolved) {
@@ -4081,6 +4090,7 @@ var Aggregate = class extends ActionNode {
4081
4090
  }
4082
4091
  return "SUCCESS" /* SUCCESS */;
4083
4092
  } catch (error) {
4093
+ if (error instanceof ConfigurationError) throw error;
4084
4094
  this._lastError = error instanceof Error ? error.message : String(error);
4085
4095
  this.log(`Aggregate failed: ${this._lastError}`);
4086
4096
  return "FAILURE" /* FAILURE */;
@@ -4089,6 +4099,7 @@ var Aggregate = class extends ActionNode {
4089
4099
  };
4090
4100
 
4091
4101
  // src/utilities/threshold-check.ts
4102
+ var import_safe_stable_stringify8 = __toESM(require("safe-stable-stringify"), 1);
4092
4103
  function evaluateThreshold(val, threshold, resolvedValue, resolvedRange) {
4093
4104
  switch (threshold.operator) {
4094
4105
  case "lte":
@@ -4133,7 +4144,7 @@ var ThresholdCheck = class extends ActionNode {
4133
4144
  const resolved = typeof this.valueRef === "string" ? resolveValue(this.valueRef, varCtx) : this.valueRef;
4134
4145
  const numValue = typeof resolved === "number" ? resolved : parseFloat(String(resolved));
4135
4146
  if (isNaN(numValue)) {
4136
- throw new Error(`Value is not numeric: ${JSON.stringify(resolved)}`);
4147
+ throw new Error(`Value is not numeric: ${(0, import_safe_stable_stringify8.default)(resolved)}`);
4137
4148
  }
4138
4149
  let matchedLabel = "normal";
4139
4150
  for (const threshold of this.thresholds) {
@@ -5785,6 +5796,7 @@ function registerStandardNodes(registry) {
5785
5796
  }
5786
5797
 
5787
5798
  // src/data-store/memory-store.ts
5799
+ var import_safe_stable_stringify9 = __toESM(require("safe-stable-stringify"), 1);
5788
5800
  var MemoryDataStore = class {
5789
5801
  storage = /* @__PURE__ */ new Map();
5790
5802
  cleanupInterval = null;
@@ -5798,7 +5810,7 @@ var MemoryDataStore = class {
5798
5810
  }
5799
5811
  }
5800
5812
  async put(key, data, options) {
5801
- const serialized = JSON.stringify(data);
5813
+ const serialized = (0, import_safe_stable_stringify9.default)(data) ?? "{}";
5802
5814
  const sizeBytes = Buffer.byteLength(serialized, "utf8");
5803
5815
  const entry = {
5804
5816
  data,
package/dist/index.js CHANGED
@@ -610,6 +610,7 @@ var CompositeNode = class extends BaseNode {
610
610
  };
611
611
 
612
612
  // src/blackboard.ts
613
+ import stringify from "safe-stable-stringify";
613
614
  var ScopedBlackboard = class _ScopedBlackboard {
614
615
  data = {};
615
616
  parent = null;
@@ -731,7 +732,7 @@ var ScopedBlackboard = class _ScopedBlackboard {
731
732
  const prefix = " ".repeat(indent);
732
733
  console.log(`${prefix}Scope: ${this.scopeName}`);
733
734
  for (const [key, value] of Object.entries(this.data)) {
734
- console.log(`${prefix} ${key}: ${JSON.stringify(value)}`);
735
+ console.log(`${prefix} ${key}: ${stringify(value)}`);
735
736
  }
736
737
  for (const [_name, childScope] of this.childScopes) {
737
738
  childScope.debug(indent + 1);
@@ -980,10 +981,10 @@ var Conditional = class extends CompositeNode {
980
981
  async executeTick(context) {
981
982
  checkSignal(context.signal);
982
983
  if (!this.condition) {
983
- throw new Error("Conditional requires at least a condition child");
984
+ throw new ConfigurationError("Conditional requires at least a condition child");
984
985
  }
985
986
  if (!this.thenBranch) {
986
- throw new Error(
987
+ throw new ConfigurationError(
987
988
  "Conditional requires at least condition and then branch"
988
989
  );
989
990
  }
@@ -1012,7 +1013,7 @@ var Conditional = class extends CompositeNode {
1012
1013
  this._status = "RUNNING" /* RUNNING */;
1013
1014
  return "RUNNING" /* RUNNING */;
1014
1015
  default:
1015
- throw new Error(
1016
+ throw new ConfigurationError(
1016
1017
  `Unexpected status from condition: ${conditionStatus}`
1017
1018
  );
1018
1019
  }
@@ -1020,7 +1021,7 @@ var Conditional = class extends CompositeNode {
1020
1021
  this.log("Condition already evaluated - continuing branch execution");
1021
1022
  }
1022
1023
  if (!this.selectedBranch) {
1023
- throw new Error("No branch selected for execution");
1024
+ throw new ConfigurationError("No branch selected for execution");
1024
1025
  }
1025
1026
  const branchStatus = await this.selectedBranch.tick(context);
1026
1027
  this._status = branchStatus;
@@ -1045,6 +1046,161 @@ var Conditional = class extends CompositeNode {
1045
1046
  };
1046
1047
 
1047
1048
  // src/composites/for-each.ts
1049
+ import dlv2 from "dlv";
1050
+ import stringify3 from "safe-stable-stringify";
1051
+
1052
+ // src/utilities/variable-resolver.ts
1053
+ import dlv from "dlv";
1054
+ import stringify2 from "safe-stable-stringify";
1055
+ function parsePath(path2) {
1056
+ const segments = [];
1057
+ let current = "";
1058
+ for (let i = 0; i < path2.length; i++) {
1059
+ const ch = path2[i];
1060
+ if (ch === "." || ch === "[") {
1061
+ if (current) segments.push(current);
1062
+ current = "";
1063
+ } else if (ch === "]") {
1064
+ if (current) segments.push(current);
1065
+ current = "";
1066
+ } else {
1067
+ current += ch;
1068
+ }
1069
+ }
1070
+ if (current) segments.push(current);
1071
+ return segments;
1072
+ }
1073
+ var VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.\[\]]+)\}|\$\{([a-zA-Z0-9_.\[\]]+)\}/g;
1074
+ var HAS_VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.\[\]]+)\}|\$\{([a-zA-Z0-9_.\[\]]+)\}/;
1075
+ var FULL_MATCH_PATTERN = /^\$\{(input|bb|env|param)\.([a-zA-Z0-9_.\[\]]+)\}$|^\$\{([a-zA-Z0-9_.\[\]]+)\}$/;
1076
+ var safeProcessEnv = () => {
1077
+ try {
1078
+ return typeof process !== "undefined" && process?.env ? process.env : {};
1079
+ } catch {
1080
+ return {};
1081
+ }
1082
+ };
1083
+ function resolveString(str, ctx, opts = {}) {
1084
+ const { preserveUndefined = true, envSource = safeProcessEnv() } = opts;
1085
+ const fullMatch = str.match(FULL_MATCH_PATTERN);
1086
+ if (fullMatch) {
1087
+ const namespace = fullMatch[1];
1088
+ const namespacedKey = fullMatch[2];
1089
+ const simpleKey = fullMatch[3];
1090
+ const key = namespacedKey || simpleKey;
1091
+ const ns = namespace || "bb";
1092
+ if (key) {
1093
+ const value = resolveVariable(ns, key, ctx, envSource);
1094
+ if (value !== void 0) {
1095
+ return value;
1096
+ }
1097
+ return preserveUndefined ? str : void 0;
1098
+ }
1099
+ }
1100
+ return str.replace(VARIABLE_PATTERN, (match, namespace, namespacedKey, simpleKey) => {
1101
+ const key = namespacedKey || simpleKey;
1102
+ const ns = namespace || "bb";
1103
+ const value = resolveVariable(ns, key, ctx, envSource);
1104
+ if (value === void 0) {
1105
+ return preserveUndefined ? match : "";
1106
+ }
1107
+ if (value === null) {
1108
+ return "null";
1109
+ }
1110
+ if (typeof value === "object") {
1111
+ return stringify2(value) ?? String(value);
1112
+ }
1113
+ return String(value);
1114
+ });
1115
+ }
1116
+ function resolveValue(value, ctx, opts = {}) {
1117
+ if (typeof value === "string") {
1118
+ return resolveString(value, ctx, opts);
1119
+ }
1120
+ if (Array.isArray(value)) {
1121
+ return value.map((item) => resolveValue(item, ctx, opts));
1122
+ }
1123
+ if (value !== null && typeof value === "object") {
1124
+ const resolved = {};
1125
+ for (const [key, val] of Object.entries(value)) {
1126
+ resolved[key] = resolveValue(val, ctx, opts);
1127
+ }
1128
+ return resolved;
1129
+ }
1130
+ return value;
1131
+ }
1132
+ function resolveVariable(namespace, key, ctx, envSource) {
1133
+ switch (namespace) {
1134
+ case "input":
1135
+ return getNestedValue(ctx.input, key);
1136
+ case "bb":
1137
+ return getNestedBlackboardValue(ctx.blackboard, key);
1138
+ case "env":
1139
+ return envSource[key];
1140
+ case "param":
1141
+ if (ctx.testData) {
1142
+ const parts = parsePath(key);
1143
+ const firstPart = parts[0];
1144
+ if (firstPart) {
1145
+ const value = ctx.testData.get(firstPart);
1146
+ if (parts.length === 1) return value;
1147
+ if (value === void 0 || value === null || typeof value !== "object") return void 0;
1148
+ return dlv(value, parts.slice(1));
1149
+ }
1150
+ }
1151
+ return void 0;
1152
+ default:
1153
+ return getNestedBlackboardValue(ctx.blackboard, key);
1154
+ }
1155
+ }
1156
+ function getNestedValue(obj, path2) {
1157
+ if (obj === void 0 || obj === null || typeof obj !== "object") {
1158
+ return void 0;
1159
+ }
1160
+ return dlv(obj, parsePath(path2));
1161
+ }
1162
+ function getNestedBlackboardValue(blackboard, path2) {
1163
+ const parts = parsePath(path2);
1164
+ const firstPart = parts[0];
1165
+ if (!firstPart) {
1166
+ return void 0;
1167
+ }
1168
+ const value = blackboard.get(firstPart);
1169
+ if (parts.length === 1) {
1170
+ return value;
1171
+ }
1172
+ if (value === void 0 || value === null || typeof value !== "object") {
1173
+ return void 0;
1174
+ }
1175
+ return dlv(value, parts.slice(1));
1176
+ }
1177
+ function hasVariables(str) {
1178
+ return HAS_VARIABLE_PATTERN.test(str);
1179
+ }
1180
+ function extractVariables(str) {
1181
+ const variables = [];
1182
+ const pattern = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.\[\]]+)\}|\$\{([a-zA-Z0-9_.\[\]]+)\}/g;
1183
+ let match;
1184
+ while ((match = pattern.exec(str)) !== null) {
1185
+ const namespace = match[1] || "bb";
1186
+ const key = match[2] || match[3];
1187
+ if (key) {
1188
+ variables.push({ namespace, key });
1189
+ }
1190
+ }
1191
+ return variables;
1192
+ }
1193
+
1194
+ // src/composites/for-each.ts
1195
+ function resolveFromBlackboard(blackboard, path2) {
1196
+ const parts = parsePath(path2);
1197
+ const firstPart = parts[0];
1198
+ if (!firstPart) return void 0;
1199
+ const value = blackboard.get(firstPart);
1200
+ if (parts.length === 1) return value;
1201
+ if (value === void 0 || value === null || typeof value !== "object") return void 0;
1202
+ return dlv2(value, parts.slice(1));
1203
+ }
1048
1204
  var ForEach = class extends CompositeNode {
1049
1205
  collectionKey;
1050
1206
  itemKey;
@@ -1068,14 +1224,14 @@ var ForEach = class extends CompositeNode {
1068
1224
  "ForEach requires at least one child (body)"
1069
1225
  );
1070
1226
  }
1071
- const collection = context.blackboard.get(this.collectionKey);
1227
+ const collection = this.collectionKey.includes(".") || this.collectionKey.includes("[") ? resolveFromBlackboard(context.blackboard, this.collectionKey) : context.blackboard.get(this.collectionKey);
1072
1228
  if (!collection) {
1073
1229
  this.log(`Collection '${this.collectionKey}' not found in blackboard`);
1074
1230
  this._status = "FAILURE" /* FAILURE */;
1075
1231
  return "FAILURE" /* FAILURE */;
1076
1232
  }
1077
1233
  if (!Array.isArray(collection)) {
1078
- throw new Error(`Collection '${this.collectionKey}' is not an array`);
1234
+ throw new ConfigurationError(`Collection '${this.collectionKey}' is not an array`);
1079
1235
  }
1080
1236
  if (collection.length === 0) {
1081
1237
  this.log("Collection is empty - returning SUCCESS");
@@ -1093,7 +1249,7 @@ var ForEach = class extends CompositeNode {
1093
1249
  context.blackboard.set(this.indexKey, this.currentIndex);
1094
1250
  }
1095
1251
  this.log(
1096
- `Processing item ${this.currentIndex}: ${JSON.stringify(item)}`
1252
+ `Processing item ${this.currentIndex}: ${stringify3(item)}`
1097
1253
  );
1098
1254
  const bodyStatus = await body.tick(context);
1099
1255
  switch (bodyStatus) {
@@ -1113,7 +1269,7 @@ var ForEach = class extends CompositeNode {
1113
1269
  return "RUNNING" /* RUNNING */;
1114
1270
  // Will resume from this index next tick
1115
1271
  default:
1116
- throw new Error(`Unexpected status from body: ${bodyStatus}`);
1272
+ throw new ConfigurationError(`Unexpected status from body: ${bodyStatus}`);
1117
1273
  }
1118
1274
  }
1119
1275
  this.log("All items processed successfully");
@@ -1143,7 +1299,7 @@ var Sequence = class extends CompositeNode {
1143
1299
  checkSignal(context.signal);
1144
1300
  const child = this._children[this.currentChildIndex];
1145
1301
  if (!child) {
1146
- throw new Error(
1302
+ throw new ConfigurationError(
1147
1303
  `Child at index ${this.currentChildIndex} is undefined`
1148
1304
  );
1149
1305
  }
@@ -1164,7 +1320,7 @@ var Sequence = class extends CompositeNode {
1164
1320
  this._status = "RUNNING" /* RUNNING */;
1165
1321
  return "RUNNING" /* RUNNING */;
1166
1322
  default:
1167
- throw new Error(`Unexpected status from child: ${childStatus}`);
1323
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1168
1324
  }
1169
1325
  }
1170
1326
  this.log("All children succeeded");
@@ -1217,7 +1373,7 @@ var MemorySequence = class extends Sequence {
1217
1373
  this._status = "RUNNING" /* RUNNING */;
1218
1374
  return "RUNNING" /* RUNNING */;
1219
1375
  default:
1220
- throw new Error(`Unexpected status from child: ${childStatus}`);
1376
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1221
1377
  }
1222
1378
  }
1223
1379
  this.log("All children succeeded");
@@ -1348,7 +1504,7 @@ var ReactiveSequence = class extends Sequence {
1348
1504
  this._status = "RUNNING" /* RUNNING */;
1349
1505
  return "RUNNING" /* RUNNING */;
1350
1506
  default:
1351
- throw new Error(`Unexpected status from child: ${childStatus}`);
1507
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1352
1508
  }
1353
1509
  }
1354
1510
  this.log("All children succeeded");
@@ -1427,7 +1583,7 @@ var Selector = class extends CompositeNode {
1427
1583
  checkSignal(context.signal);
1428
1584
  const child = this._children[this.currentChildIndex];
1429
1585
  if (!child) {
1430
- throw new Error(
1586
+ throw new ConfigurationError(
1431
1587
  `Child at index ${this.currentChildIndex} is undefined`
1432
1588
  );
1433
1589
  }
@@ -1448,7 +1604,7 @@ var Selector = class extends CompositeNode {
1448
1604
  this._status = "RUNNING" /* RUNNING */;
1449
1605
  return "RUNNING" /* RUNNING */;
1450
1606
  default:
1451
- throw new Error(`Unexpected status from child: ${childStatus}`);
1607
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1452
1608
  }
1453
1609
  }
1454
1610
  this.log("All children failed");
@@ -1470,154 +1626,6 @@ var Fallback = class extends Selector {
1470
1626
  }
1471
1627
  };
1472
1628
 
1473
- // src/utilities/variable-resolver.ts
1474
- var VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/g;
1475
- var HAS_VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/;
1476
- var FULL_MATCH_PATTERN = /^\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}$|^\$\{([a-zA-Z0-9_.]+)\}$/;
1477
- var safeProcessEnv = () => {
1478
- try {
1479
- return typeof process !== "undefined" && process?.env ? process.env : {};
1480
- } catch {
1481
- return {};
1482
- }
1483
- };
1484
- function resolveString(str, ctx, opts = {}) {
1485
- const { preserveUndefined = true, envSource = safeProcessEnv() } = opts;
1486
- const fullMatch = str.match(FULL_MATCH_PATTERN);
1487
- if (fullMatch) {
1488
- const namespace = fullMatch[1];
1489
- const namespacedKey = fullMatch[2];
1490
- const simpleKey = fullMatch[3];
1491
- const key = namespacedKey || simpleKey;
1492
- const ns = namespace || "bb";
1493
- if (key) {
1494
- const value = resolveVariable(ns, key, ctx, envSource);
1495
- if (value !== void 0) {
1496
- return value;
1497
- }
1498
- return preserveUndefined ? str : void 0;
1499
- }
1500
- }
1501
- return str.replace(VARIABLE_PATTERN, (match, namespace, namespacedKey, simpleKey) => {
1502
- const key = namespacedKey || simpleKey;
1503
- const ns = namespace || "bb";
1504
- const value = resolveVariable(ns, key, ctx, envSource);
1505
- if (value === void 0) {
1506
- return preserveUndefined ? match : "";
1507
- }
1508
- if (value === null) {
1509
- return "null";
1510
- }
1511
- if (typeof value === "object") {
1512
- try {
1513
- return JSON.stringify(value);
1514
- } catch {
1515
- return String(value);
1516
- }
1517
- }
1518
- return String(value);
1519
- });
1520
- }
1521
- function resolveValue(value, ctx, opts = {}) {
1522
- if (typeof value === "string") {
1523
- return resolveString(value, ctx, opts);
1524
- }
1525
- if (Array.isArray(value)) {
1526
- return value.map((item) => resolveValue(item, ctx, opts));
1527
- }
1528
- if (value !== null && typeof value === "object") {
1529
- const resolved = {};
1530
- for (const [key, val] of Object.entries(value)) {
1531
- resolved[key] = resolveValue(val, ctx, opts);
1532
- }
1533
- return resolved;
1534
- }
1535
- return value;
1536
- }
1537
- function resolveVariable(namespace, key, ctx, envSource) {
1538
- switch (namespace) {
1539
- case "input":
1540
- return getNestedValue(ctx.input, key);
1541
- case "bb":
1542
- return getNestedBlackboardValue(ctx.blackboard, key);
1543
- case "env":
1544
- return envSource[key];
1545
- case "param":
1546
- if (ctx.testData) {
1547
- const parts = key.split(".");
1548
- const firstPart = parts[0];
1549
- if (firstPart) {
1550
- let value = ctx.testData.get(firstPart);
1551
- for (let i = 1; i < parts.length && value !== void 0; i++) {
1552
- const part = parts[i];
1553
- if (part && typeof value === "object" && value !== null) {
1554
- value = value[part];
1555
- } else {
1556
- return void 0;
1557
- }
1558
- }
1559
- return value;
1560
- }
1561
- }
1562
- return void 0;
1563
- default:
1564
- return getNestedBlackboardValue(ctx.blackboard, key);
1565
- }
1566
- }
1567
- function getNestedValue(obj, path2) {
1568
- if (obj === void 0 || obj === null) {
1569
- return void 0;
1570
- }
1571
- if (typeof obj !== "object") {
1572
- return void 0;
1573
- }
1574
- const parts = path2.split(".");
1575
- let value = obj;
1576
- for (const part of parts) {
1577
- if (value === void 0 || value === null) {
1578
- return void 0;
1579
- }
1580
- if (typeof value !== "object") {
1581
- return void 0;
1582
- }
1583
- value = value[part];
1584
- }
1585
- return value;
1586
- }
1587
- function getNestedBlackboardValue(blackboard, path2) {
1588
- const parts = path2.split(".");
1589
- const firstPart = parts[0];
1590
- if (!firstPart) {
1591
- return void 0;
1592
- }
1593
- let value = blackboard.get(firstPart);
1594
- for (let i = 1; i < parts.length && value !== void 0; i++) {
1595
- const part = parts[i];
1596
- if (part && typeof value === "object" && value !== null) {
1597
- value = value[part];
1598
- } else {
1599
- return void 0;
1600
- }
1601
- }
1602
- return value;
1603
- }
1604
- function hasVariables(str) {
1605
- return HAS_VARIABLE_PATTERN.test(str);
1606
- }
1607
- function extractVariables(str) {
1608
- const variables = [];
1609
- const pattern = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/g;
1610
- let match;
1611
- while ((match = pattern.exec(str)) !== null) {
1612
- const namespace = match[1] || "bb";
1613
- const key = match[2] || match[3];
1614
- if (key) {
1615
- variables.push({ namespace, key });
1616
- }
1617
- }
1618
- return variables;
1619
- }
1620
-
1621
1629
  // src/composites/sub-tree.ts
1622
1630
  var SubTree = class extends ActionNode {
1623
1631
  treeId;
@@ -1635,7 +1643,7 @@ var SubTree = class extends ActionNode {
1635
1643
  checkSignal(context.signal);
1636
1644
  if (!this.clonedTree) {
1637
1645
  if (!context.treeRegistry.hasTree(this.treeId)) {
1638
- throw new Error(
1646
+ throw new ConfigurationError(
1639
1647
  `SubTree tree '${this.treeId}' not found in registry. Available trees: ${context.treeRegistry.getAllTreeIds().join(", ") || "none"}`
1640
1648
  );
1641
1649
  }
@@ -1792,7 +1800,7 @@ var While = class extends CompositeNode {
1792
1800
  this._status = "RUNNING" /* RUNNING */;
1793
1801
  return "RUNNING" /* RUNNING */;
1794
1802
  default:
1795
- throw new Error(`Unexpected status from body: ${bodyStatus}`);
1803
+ throw new ConfigurationError(`Unexpected status from body: ${bodyStatus}`);
1796
1804
  }
1797
1805
  }
1798
1806
  this.log(`Max iterations (${this.maxIterations}) reached`);
@@ -3387,6 +3395,7 @@ var RunningNode = class extends ActionNode {
3387
3395
  };
3388
3396
 
3389
3397
  // src/utilities/log-message.ts
3398
+ import stringify4 from "safe-stable-stringify";
3390
3399
  var LogMessage = class extends ActionNode {
3391
3400
  message;
3392
3401
  level;
@@ -3449,11 +3458,7 @@ var LogMessage = class extends ActionNode {
3449
3458
  return "null";
3450
3459
  }
3451
3460
  if (typeof resolved === "object") {
3452
- try {
3453
- return JSON.stringify(resolved);
3454
- } catch {
3455
- return String(resolved);
3456
- }
3461
+ return stringify4(resolved) ?? String(resolved);
3457
3462
  }
3458
3463
  return String(resolved);
3459
3464
  }
@@ -3516,6 +3521,7 @@ var RegexExtract = class extends ActionNode {
3516
3521
  };
3517
3522
 
3518
3523
  // src/utilities/set-variable.ts
3524
+ import stringify5 from "safe-stable-stringify";
3519
3525
  var SetVariable = class extends ActionNode {
3520
3526
  key;
3521
3527
  value;
@@ -3534,7 +3540,7 @@ var SetVariable = class extends ActionNode {
3534
3540
  const resolvedKey = typeof this.key === "string" ? resolveValue(this.key, varCtx) : String(this.key);
3535
3541
  const resolvedValue = typeof this.value === "string" ? resolveValue(this.value, varCtx) : this.value;
3536
3542
  context.blackboard.set(resolvedKey, resolvedValue);
3537
- this.log(`Set ${resolvedKey} = ${JSON.stringify(resolvedValue)}`);
3543
+ this.log(`Set ${resolvedKey} = ${stringify5(resolvedValue)}`);
3538
3544
  return "SUCCESS" /* SUCCESS */;
3539
3545
  } catch (error) {
3540
3546
  this._lastError = error instanceof Error ? error.message : String(error);
@@ -3545,6 +3551,7 @@ var SetVariable = class extends ActionNode {
3545
3551
  };
3546
3552
 
3547
3553
  // src/utilities/math-op.ts
3554
+ import stringify6 from "safe-stable-stringify";
3548
3555
  function tokenize(expr) {
3549
3556
  const tokens = [];
3550
3557
  let i = 0;
@@ -3654,12 +3661,12 @@ function evaluate(tokens) {
3654
3661
  pos++;
3655
3662
  return val;
3656
3663
  }
3657
- throw new Error(`Unexpected token: ${JSON.stringify(tok)}`);
3664
+ throw new Error(`Unexpected token: ${stringify6(tok)}`);
3658
3665
  }
3659
3666
  const result = parseExpr();
3660
3667
  const remaining = current();
3661
3668
  if (remaining) {
3662
- throw new Error(`Unexpected token after expression: ${JSON.stringify(remaining)}`);
3669
+ throw new Error(`Unexpected token after expression: ${stringify6(remaining)}`);
3663
3670
  }
3664
3671
  return result;
3665
3672
  }
@@ -3812,7 +3819,7 @@ var ArrayFilter = class extends ActionNode {
3812
3819
  };
3813
3820
  const inputResolved = typeof this.input === "string" ? resolveValue(this.input, varCtx) : this.input;
3814
3821
  if (!Array.isArray(inputResolved)) {
3815
- throw new Error(
3822
+ throw new ConfigurationError(
3816
3823
  `Input is not an array: got ${inputResolved === null ? "null" : typeof inputResolved}`
3817
3824
  );
3818
3825
  }
@@ -3831,6 +3838,7 @@ var ArrayFilter = class extends ActionNode {
3831
3838
  this.log(`Filtered ${inputResolved.length} \u2192 ${result.length} items`);
3832
3839
  return "SUCCESS" /* SUCCESS */;
3833
3840
  } catch (error) {
3841
+ if (error instanceof ConfigurationError) throw error;
3834
3842
  this._lastError = error instanceof Error ? error.message : String(error);
3835
3843
  this.log(`ArrayFilter failed: ${this._lastError}`);
3836
3844
  return "FAILURE" /* FAILURE */;
@@ -3839,6 +3847,7 @@ var ArrayFilter = class extends ActionNode {
3839
3847
  };
3840
3848
 
3841
3849
  // src/utilities/aggregate.ts
3850
+ import stringify7 from "safe-stable-stringify";
3842
3851
  function getFieldValue2(item, path2) {
3843
3852
  if (item === null || item === void 0) return void 0;
3844
3853
  const parts = path2.split(".");
@@ -3906,14 +3915,14 @@ var Aggregate = class extends ActionNode {
3906
3915
  };
3907
3916
  const inputResolved = typeof this.input === "string" ? resolveValue(this.input, varCtx) : this.input;
3908
3917
  if (!Array.isArray(inputResolved)) {
3909
- throw new Error(
3918
+ throw new ConfigurationError(
3910
3919
  `Input is not an array: got ${inputResolved === null ? "null" : typeof inputResolved}`
3911
3920
  );
3912
3921
  }
3913
3922
  if (!this.groupBy) {
3914
3923
  const result = computeAggregations(inputResolved, this.operations);
3915
3924
  context.blackboard.set(this.outputKey, result);
3916
- this.log(`Aggregated ${inputResolved.length} items \u2192 ${JSON.stringify(result)}`);
3925
+ this.log(`Aggregated ${inputResolved.length} items \u2192 ${stringify7(result)}`);
3917
3926
  } else {
3918
3927
  const groups = {};
3919
3928
  for (const item of inputResolved) {
@@ -3933,6 +3942,7 @@ var Aggregate = class extends ActionNode {
3933
3942
  }
3934
3943
  return "SUCCESS" /* SUCCESS */;
3935
3944
  } catch (error) {
3945
+ if (error instanceof ConfigurationError) throw error;
3936
3946
  this._lastError = error instanceof Error ? error.message : String(error);
3937
3947
  this.log(`Aggregate failed: ${this._lastError}`);
3938
3948
  return "FAILURE" /* FAILURE */;
@@ -3941,6 +3951,7 @@ var Aggregate = class extends ActionNode {
3941
3951
  };
3942
3952
 
3943
3953
  // src/utilities/threshold-check.ts
3954
+ import stringify8 from "safe-stable-stringify";
3944
3955
  function evaluateThreshold(val, threshold, resolvedValue, resolvedRange) {
3945
3956
  switch (threshold.operator) {
3946
3957
  case "lte":
@@ -3985,7 +3996,7 @@ var ThresholdCheck = class extends ActionNode {
3985
3996
  const resolved = typeof this.valueRef === "string" ? resolveValue(this.valueRef, varCtx) : this.valueRef;
3986
3997
  const numValue = typeof resolved === "number" ? resolved : parseFloat(String(resolved));
3987
3998
  if (isNaN(numValue)) {
3988
- throw new Error(`Value is not numeric: ${JSON.stringify(resolved)}`);
3999
+ throw new Error(`Value is not numeric: ${stringify8(resolved)}`);
3989
4000
  }
3990
4001
  let matchedLabel = "normal";
3991
4002
  for (const threshold of this.thresholds) {
@@ -5637,6 +5648,7 @@ function registerStandardNodes(registry) {
5637
5648
  }
5638
5649
 
5639
5650
  // src/data-store/memory-store.ts
5651
+ import stringify9 from "safe-stable-stringify";
5640
5652
  var MemoryDataStore = class {
5641
5653
  storage = /* @__PURE__ */ new Map();
5642
5654
  cleanupInterval = null;
@@ -5650,7 +5662,7 @@ var MemoryDataStore = class {
5650
5662
  }
5651
5663
  }
5652
5664
  async put(key, data, options) {
5653
- const serialized = JSON.stringify(data);
5665
+ const serialized = stringify9(data) ?? "{}";
5654
5666
  const sizeBytes = Buffer.byteLength(serialized, "utf8");
5655
5667
  const entry = {
5656
5668
  data,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@q1k-oss/behaviour-tree-workflows",
3
- "version": "0.0.5",
3
+ "version": "0.0.7",
4
4
  "description": "Behavior tree library for TypeScript — 30+ production-ready nodes, YAML workflows, Temporal integration, and built-in observability",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
@@ -67,7 +67,9 @@
67
67
  "@activepieces/pieces-framework": "^0.23.0",
68
68
  "@temporalio/activity": "^1.10.0",
69
69
  "@temporalio/workflow": "^1.10.0",
70
+ "dlv": "^1.1.3",
70
71
  "js-yaml": "^4.1.1",
72
+ "safe-stable-stringify": "^2.5.0",
71
73
  "zod": "^4.2.1"
72
74
  },
73
75
  "peerDependencies": {
@@ -100,6 +102,7 @@
100
102
  "@temporalio/client": "^1.10.0",
101
103
  "@temporalio/testing": "^1.10.0",
102
104
  "@temporalio/worker": "^1.10.0",
105
+ "@types/dlv": "^1.1.5",
103
106
  "@types/js-yaml": "^4.0.9",
104
107
  "@types/node": "^24.2.1",
105
108
  "@vitest/coverage-v8": "^3.2.4",