@q1k-oss/behaviour-tree-workflows 0.0.6 → 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 +185 -188
  2. package/dist/index.js +185 -188
  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,21 +1194,161 @@ var Conditional = class extends CompositeNode {
1193
1194
  };
1194
1195
 
1195
1196
  // src/composites/for-each.ts
1196
- function resolveFromBlackboard(blackboard, path2) {
1197
- const parts = path2.split(".");
1198
- const firstPart = parts[0];
1199
- if (!firstPart) return void 0;
1200
- let value = blackboard.get(firstPart);
1201
- for (let i = 1; i < parts.length && value != null; i++) {
1202
- const part = parts[i];
1203
- if (part && typeof value === "object") {
1204
- value = value[part];
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 = "";
1205
1214
  } else {
1206
- return void 0;
1215
+ current += ch;
1207
1216
  }
1208
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
+ }
1209
1278
  return value;
1210
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
+ }
1211
1352
  var ForEach = class extends CompositeNode {
1212
1353
  collectionKey;
1213
1354
  itemKey;
@@ -1231,14 +1372,14 @@ var ForEach = class extends CompositeNode {
1231
1372
  "ForEach requires at least one child (body)"
1232
1373
  );
1233
1374
  }
1234
- const collection = this.collectionKey.includes(".") ? resolveFromBlackboard(context.blackboard, this.collectionKey) : context.blackboard.get(this.collectionKey);
1375
+ const collection = this.collectionKey.includes(".") || this.collectionKey.includes("[") ? resolveFromBlackboard(context.blackboard, this.collectionKey) : context.blackboard.get(this.collectionKey);
1235
1376
  if (!collection) {
1236
1377
  this.log(`Collection '${this.collectionKey}' not found in blackboard`);
1237
1378
  this._status = "FAILURE" /* FAILURE */;
1238
1379
  return "FAILURE" /* FAILURE */;
1239
1380
  }
1240
1381
  if (!Array.isArray(collection)) {
1241
- throw new Error(`Collection '${this.collectionKey}' is not an array`);
1382
+ throw new ConfigurationError(`Collection '${this.collectionKey}' is not an array`);
1242
1383
  }
1243
1384
  if (collection.length === 0) {
1244
1385
  this.log("Collection is empty - returning SUCCESS");
@@ -1256,7 +1397,7 @@ var ForEach = class extends CompositeNode {
1256
1397
  context.blackboard.set(this.indexKey, this.currentIndex);
1257
1398
  }
1258
1399
  this.log(
1259
- `Processing item ${this.currentIndex}: ${JSON.stringify(item)}`
1400
+ `Processing item ${this.currentIndex}: ${(0, import_safe_stable_stringify3.default)(item)}`
1260
1401
  );
1261
1402
  const bodyStatus = await body.tick(context);
1262
1403
  switch (bodyStatus) {
@@ -1276,7 +1417,7 @@ var ForEach = class extends CompositeNode {
1276
1417
  return "RUNNING" /* RUNNING */;
1277
1418
  // Will resume from this index next tick
1278
1419
  default:
1279
- throw new Error(`Unexpected status from body: ${bodyStatus}`);
1420
+ throw new ConfigurationError(`Unexpected status from body: ${bodyStatus}`);
1280
1421
  }
1281
1422
  }
1282
1423
  this.log("All items processed successfully");
@@ -1306,7 +1447,7 @@ var Sequence = class extends CompositeNode {
1306
1447
  checkSignal(context.signal);
1307
1448
  const child = this._children[this.currentChildIndex];
1308
1449
  if (!child) {
1309
- throw new Error(
1450
+ throw new ConfigurationError(
1310
1451
  `Child at index ${this.currentChildIndex} is undefined`
1311
1452
  );
1312
1453
  }
@@ -1327,7 +1468,7 @@ var Sequence = class extends CompositeNode {
1327
1468
  this._status = "RUNNING" /* RUNNING */;
1328
1469
  return "RUNNING" /* RUNNING */;
1329
1470
  default:
1330
- throw new Error(`Unexpected status from child: ${childStatus}`);
1471
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1331
1472
  }
1332
1473
  }
1333
1474
  this.log("All children succeeded");
@@ -1380,7 +1521,7 @@ var MemorySequence = class extends Sequence {
1380
1521
  this._status = "RUNNING" /* RUNNING */;
1381
1522
  return "RUNNING" /* RUNNING */;
1382
1523
  default:
1383
- throw new Error(`Unexpected status from child: ${childStatus}`);
1524
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1384
1525
  }
1385
1526
  }
1386
1527
  this.log("All children succeeded");
@@ -1511,7 +1652,7 @@ var ReactiveSequence = class extends Sequence {
1511
1652
  this._status = "RUNNING" /* RUNNING */;
1512
1653
  return "RUNNING" /* RUNNING */;
1513
1654
  default:
1514
- throw new Error(`Unexpected status from child: ${childStatus}`);
1655
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1515
1656
  }
1516
1657
  }
1517
1658
  this.log("All children succeeded");
@@ -1590,7 +1731,7 @@ var Selector = class extends CompositeNode {
1590
1731
  checkSignal(context.signal);
1591
1732
  const child = this._children[this.currentChildIndex];
1592
1733
  if (!child) {
1593
- throw new Error(
1734
+ throw new ConfigurationError(
1594
1735
  `Child at index ${this.currentChildIndex} is undefined`
1595
1736
  );
1596
1737
  }
@@ -1611,7 +1752,7 @@ var Selector = class extends CompositeNode {
1611
1752
  this._status = "RUNNING" /* RUNNING */;
1612
1753
  return "RUNNING" /* RUNNING */;
1613
1754
  default:
1614
- throw new Error(`Unexpected status from child: ${childStatus}`);
1755
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1615
1756
  }
1616
1757
  }
1617
1758
  this.log("All children failed");
@@ -1633,154 +1774,6 @@ var Fallback = class extends Selector {
1633
1774
  }
1634
1775
  };
1635
1776
 
1636
- // src/utilities/variable-resolver.ts
1637
- var VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/g;
1638
- var HAS_VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/;
1639
- var FULL_MATCH_PATTERN = /^\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}$|^\$\{([a-zA-Z0-9_.]+)\}$/;
1640
- var safeProcessEnv = () => {
1641
- try {
1642
- return typeof process !== "undefined" && process?.env ? process.env : {};
1643
- } catch {
1644
- return {};
1645
- }
1646
- };
1647
- function resolveString(str, ctx, opts = {}) {
1648
- const { preserveUndefined = true, envSource = safeProcessEnv() } = opts;
1649
- const fullMatch = str.match(FULL_MATCH_PATTERN);
1650
- if (fullMatch) {
1651
- const namespace = fullMatch[1];
1652
- const namespacedKey = fullMatch[2];
1653
- const simpleKey = fullMatch[3];
1654
- const key = namespacedKey || simpleKey;
1655
- const ns = namespace || "bb";
1656
- if (key) {
1657
- const value = resolveVariable(ns, key, ctx, envSource);
1658
- if (value !== void 0) {
1659
- return value;
1660
- }
1661
- return preserveUndefined ? str : void 0;
1662
- }
1663
- }
1664
- return str.replace(VARIABLE_PATTERN, (match, namespace, namespacedKey, simpleKey) => {
1665
- const key = namespacedKey || simpleKey;
1666
- const ns = namespace || "bb";
1667
- const value = resolveVariable(ns, key, ctx, envSource);
1668
- if (value === void 0) {
1669
- return preserveUndefined ? match : "";
1670
- }
1671
- if (value === null) {
1672
- return "null";
1673
- }
1674
- if (typeof value === "object") {
1675
- try {
1676
- return JSON.stringify(value);
1677
- } catch {
1678
- return String(value);
1679
- }
1680
- }
1681
- return String(value);
1682
- });
1683
- }
1684
- function resolveValue(value, ctx, opts = {}) {
1685
- if (typeof value === "string") {
1686
- return resolveString(value, ctx, opts);
1687
- }
1688
- if (Array.isArray(value)) {
1689
- return value.map((item) => resolveValue(item, ctx, opts));
1690
- }
1691
- if (value !== null && typeof value === "object") {
1692
- const resolved = {};
1693
- for (const [key, val] of Object.entries(value)) {
1694
- resolved[key] = resolveValue(val, ctx, opts);
1695
- }
1696
- return resolved;
1697
- }
1698
- return value;
1699
- }
1700
- function resolveVariable(namespace, key, ctx, envSource) {
1701
- switch (namespace) {
1702
- case "input":
1703
- return getNestedValue(ctx.input, key);
1704
- case "bb":
1705
- return getNestedBlackboardValue(ctx.blackboard, key);
1706
- case "env":
1707
- return envSource[key];
1708
- case "param":
1709
- if (ctx.testData) {
1710
- const parts = key.split(".");
1711
- const firstPart = parts[0];
1712
- if (firstPart) {
1713
- let value = ctx.testData.get(firstPart);
1714
- for (let i = 1; i < parts.length && value !== void 0; i++) {
1715
- const part = parts[i];
1716
- if (part && typeof value === "object" && value !== null) {
1717
- value = value[part];
1718
- } else {
1719
- return void 0;
1720
- }
1721
- }
1722
- return value;
1723
- }
1724
- }
1725
- return void 0;
1726
- default:
1727
- return getNestedBlackboardValue(ctx.blackboard, key);
1728
- }
1729
- }
1730
- function getNestedValue(obj, path2) {
1731
- if (obj === void 0 || obj === null) {
1732
- return void 0;
1733
- }
1734
- if (typeof obj !== "object") {
1735
- return void 0;
1736
- }
1737
- const parts = path2.split(".");
1738
- let value = obj;
1739
- for (const part of parts) {
1740
- if (value === void 0 || value === null) {
1741
- return void 0;
1742
- }
1743
- if (typeof value !== "object") {
1744
- return void 0;
1745
- }
1746
- value = value[part];
1747
- }
1748
- return value;
1749
- }
1750
- function getNestedBlackboardValue(blackboard, path2) {
1751
- const parts = path2.split(".");
1752
- const firstPart = parts[0];
1753
- if (!firstPart) {
1754
- return void 0;
1755
- }
1756
- let value = blackboard.get(firstPart);
1757
- for (let i = 1; i < parts.length && value !== void 0; i++) {
1758
- const part = parts[i];
1759
- if (part && typeof value === "object" && value !== null) {
1760
- value = value[part];
1761
- } else {
1762
- return void 0;
1763
- }
1764
- }
1765
- return value;
1766
- }
1767
- function hasVariables(str) {
1768
- return HAS_VARIABLE_PATTERN.test(str);
1769
- }
1770
- function extractVariables(str) {
1771
- const variables = [];
1772
- const pattern = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/g;
1773
- let match;
1774
- while ((match = pattern.exec(str)) !== null) {
1775
- const namespace = match[1] || "bb";
1776
- const key = match[2] || match[3];
1777
- if (key) {
1778
- variables.push({ namespace, key });
1779
- }
1780
- }
1781
- return variables;
1782
- }
1783
-
1784
1777
  // src/composites/sub-tree.ts
1785
1778
  var SubTree = class extends ActionNode {
1786
1779
  treeId;
@@ -1798,7 +1791,7 @@ var SubTree = class extends ActionNode {
1798
1791
  checkSignal(context.signal);
1799
1792
  if (!this.clonedTree) {
1800
1793
  if (!context.treeRegistry.hasTree(this.treeId)) {
1801
- throw new Error(
1794
+ throw new ConfigurationError(
1802
1795
  `SubTree tree '${this.treeId}' not found in registry. Available trees: ${context.treeRegistry.getAllTreeIds().join(", ") || "none"}`
1803
1796
  );
1804
1797
  }
@@ -1955,7 +1948,7 @@ var While = class extends CompositeNode {
1955
1948
  this._status = "RUNNING" /* RUNNING */;
1956
1949
  return "RUNNING" /* RUNNING */;
1957
1950
  default:
1958
- throw new Error(`Unexpected status from body: ${bodyStatus}`);
1951
+ throw new ConfigurationError(`Unexpected status from body: ${bodyStatus}`);
1959
1952
  }
1960
1953
  }
1961
1954
  this.log(`Max iterations (${this.maxIterations}) reached`);
@@ -3550,6 +3543,7 @@ var RunningNode = class extends ActionNode {
3550
3543
  };
3551
3544
 
3552
3545
  // src/utilities/log-message.ts
3546
+ var import_safe_stable_stringify4 = __toESM(require("safe-stable-stringify"), 1);
3553
3547
  var LogMessage = class extends ActionNode {
3554
3548
  message;
3555
3549
  level;
@@ -3612,11 +3606,7 @@ var LogMessage = class extends ActionNode {
3612
3606
  return "null";
3613
3607
  }
3614
3608
  if (typeof resolved === "object") {
3615
- try {
3616
- return JSON.stringify(resolved);
3617
- } catch {
3618
- return String(resolved);
3619
- }
3609
+ return (0, import_safe_stable_stringify4.default)(resolved) ?? String(resolved);
3620
3610
  }
3621
3611
  return String(resolved);
3622
3612
  }
@@ -3679,6 +3669,7 @@ var RegexExtract = class extends ActionNode {
3679
3669
  };
3680
3670
 
3681
3671
  // src/utilities/set-variable.ts
3672
+ var import_safe_stable_stringify5 = __toESM(require("safe-stable-stringify"), 1);
3682
3673
  var SetVariable = class extends ActionNode {
3683
3674
  key;
3684
3675
  value;
@@ -3697,7 +3688,7 @@ var SetVariable = class extends ActionNode {
3697
3688
  const resolvedKey = typeof this.key === "string" ? resolveValue(this.key, varCtx) : String(this.key);
3698
3689
  const resolvedValue = typeof this.value === "string" ? resolveValue(this.value, varCtx) : this.value;
3699
3690
  context.blackboard.set(resolvedKey, resolvedValue);
3700
- this.log(`Set ${resolvedKey} = ${JSON.stringify(resolvedValue)}`);
3691
+ this.log(`Set ${resolvedKey} = ${(0, import_safe_stable_stringify5.default)(resolvedValue)}`);
3701
3692
  return "SUCCESS" /* SUCCESS */;
3702
3693
  } catch (error) {
3703
3694
  this._lastError = error instanceof Error ? error.message : String(error);
@@ -3708,6 +3699,7 @@ var SetVariable = class extends ActionNode {
3708
3699
  };
3709
3700
 
3710
3701
  // src/utilities/math-op.ts
3702
+ var import_safe_stable_stringify6 = __toESM(require("safe-stable-stringify"), 1);
3711
3703
  function tokenize(expr) {
3712
3704
  const tokens = [];
3713
3705
  let i = 0;
@@ -3817,12 +3809,12 @@ function evaluate(tokens) {
3817
3809
  pos++;
3818
3810
  return val;
3819
3811
  }
3820
- throw new Error(`Unexpected token: ${JSON.stringify(tok)}`);
3812
+ throw new Error(`Unexpected token: ${(0, import_safe_stable_stringify6.default)(tok)}`);
3821
3813
  }
3822
3814
  const result = parseExpr();
3823
3815
  const remaining = current();
3824
3816
  if (remaining) {
3825
- 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)}`);
3826
3818
  }
3827
3819
  return result;
3828
3820
  }
@@ -3975,7 +3967,7 @@ var ArrayFilter = class extends ActionNode {
3975
3967
  };
3976
3968
  const inputResolved = typeof this.input === "string" ? resolveValue(this.input, varCtx) : this.input;
3977
3969
  if (!Array.isArray(inputResolved)) {
3978
- throw new Error(
3970
+ throw new ConfigurationError(
3979
3971
  `Input is not an array: got ${inputResolved === null ? "null" : typeof inputResolved}`
3980
3972
  );
3981
3973
  }
@@ -3994,6 +3986,7 @@ var ArrayFilter = class extends ActionNode {
3994
3986
  this.log(`Filtered ${inputResolved.length} \u2192 ${result.length} items`);
3995
3987
  return "SUCCESS" /* SUCCESS */;
3996
3988
  } catch (error) {
3989
+ if (error instanceof ConfigurationError) throw error;
3997
3990
  this._lastError = error instanceof Error ? error.message : String(error);
3998
3991
  this.log(`ArrayFilter failed: ${this._lastError}`);
3999
3992
  return "FAILURE" /* FAILURE */;
@@ -4002,6 +3995,7 @@ var ArrayFilter = class extends ActionNode {
4002
3995
  };
4003
3996
 
4004
3997
  // src/utilities/aggregate.ts
3998
+ var import_safe_stable_stringify7 = __toESM(require("safe-stable-stringify"), 1);
4005
3999
  function getFieldValue2(item, path2) {
4006
4000
  if (item === null || item === void 0) return void 0;
4007
4001
  const parts = path2.split(".");
@@ -4069,14 +4063,14 @@ var Aggregate = class extends ActionNode {
4069
4063
  };
4070
4064
  const inputResolved = typeof this.input === "string" ? resolveValue(this.input, varCtx) : this.input;
4071
4065
  if (!Array.isArray(inputResolved)) {
4072
- throw new Error(
4066
+ throw new ConfigurationError(
4073
4067
  `Input is not an array: got ${inputResolved === null ? "null" : typeof inputResolved}`
4074
4068
  );
4075
4069
  }
4076
4070
  if (!this.groupBy) {
4077
4071
  const result = computeAggregations(inputResolved, this.operations);
4078
4072
  context.blackboard.set(this.outputKey, result);
4079
- 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)}`);
4080
4074
  } else {
4081
4075
  const groups = {};
4082
4076
  for (const item of inputResolved) {
@@ -4096,6 +4090,7 @@ var Aggregate = class extends ActionNode {
4096
4090
  }
4097
4091
  return "SUCCESS" /* SUCCESS */;
4098
4092
  } catch (error) {
4093
+ if (error instanceof ConfigurationError) throw error;
4099
4094
  this._lastError = error instanceof Error ? error.message : String(error);
4100
4095
  this.log(`Aggregate failed: ${this._lastError}`);
4101
4096
  return "FAILURE" /* FAILURE */;
@@ -4104,6 +4099,7 @@ var Aggregate = class extends ActionNode {
4104
4099
  };
4105
4100
 
4106
4101
  // src/utilities/threshold-check.ts
4102
+ var import_safe_stable_stringify8 = __toESM(require("safe-stable-stringify"), 1);
4107
4103
  function evaluateThreshold(val, threshold, resolvedValue, resolvedRange) {
4108
4104
  switch (threshold.operator) {
4109
4105
  case "lte":
@@ -4148,7 +4144,7 @@ var ThresholdCheck = class extends ActionNode {
4148
4144
  const resolved = typeof this.valueRef === "string" ? resolveValue(this.valueRef, varCtx) : this.valueRef;
4149
4145
  const numValue = typeof resolved === "number" ? resolved : parseFloat(String(resolved));
4150
4146
  if (isNaN(numValue)) {
4151
- 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)}`);
4152
4148
  }
4153
4149
  let matchedLabel = "normal";
4154
4150
  for (const threshold of this.thresholds) {
@@ -5800,6 +5796,7 @@ function registerStandardNodes(registry) {
5800
5796
  }
5801
5797
 
5802
5798
  // src/data-store/memory-store.ts
5799
+ var import_safe_stable_stringify9 = __toESM(require("safe-stable-stringify"), 1);
5803
5800
  var MemoryDataStore = class {
5804
5801
  storage = /* @__PURE__ */ new Map();
5805
5802
  cleanupInterval = null;
@@ -5813,7 +5810,7 @@ var MemoryDataStore = class {
5813
5810
  }
5814
5811
  }
5815
5812
  async put(key, data, options) {
5816
- const serialized = JSON.stringify(data);
5813
+ const serialized = (0, import_safe_stable_stringify9.default)(data) ?? "{}";
5817
5814
  const sizeBytes = Buffer.byteLength(serialized, "utf8");
5818
5815
  const entry = {
5819
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,21 +1046,161 @@ var Conditional = class extends CompositeNode {
1045
1046
  };
1046
1047
 
1047
1048
  // src/composites/for-each.ts
1048
- function resolveFromBlackboard(blackboard, path2) {
1049
- const parts = path2.split(".");
1050
- const firstPart = parts[0];
1051
- if (!firstPart) return void 0;
1052
- let value = blackboard.get(firstPart);
1053
- for (let i = 1; i < parts.length && value != null; i++) {
1054
- const part = parts[i];
1055
- if (part && typeof value === "object") {
1056
- value = value[part];
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 = "";
1057
1066
  } else {
1058
- return void 0;
1067
+ current += ch;
1059
1068
  }
1060
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
+ }
1061
1130
  return value;
1062
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
+ }
1063
1204
  var ForEach = class extends CompositeNode {
1064
1205
  collectionKey;
1065
1206
  itemKey;
@@ -1083,14 +1224,14 @@ var ForEach = class extends CompositeNode {
1083
1224
  "ForEach requires at least one child (body)"
1084
1225
  );
1085
1226
  }
1086
- const collection = this.collectionKey.includes(".") ? resolveFromBlackboard(context.blackboard, this.collectionKey) : context.blackboard.get(this.collectionKey);
1227
+ const collection = this.collectionKey.includes(".") || this.collectionKey.includes("[") ? resolveFromBlackboard(context.blackboard, this.collectionKey) : context.blackboard.get(this.collectionKey);
1087
1228
  if (!collection) {
1088
1229
  this.log(`Collection '${this.collectionKey}' not found in blackboard`);
1089
1230
  this._status = "FAILURE" /* FAILURE */;
1090
1231
  return "FAILURE" /* FAILURE */;
1091
1232
  }
1092
1233
  if (!Array.isArray(collection)) {
1093
- throw new Error(`Collection '${this.collectionKey}' is not an array`);
1234
+ throw new ConfigurationError(`Collection '${this.collectionKey}' is not an array`);
1094
1235
  }
1095
1236
  if (collection.length === 0) {
1096
1237
  this.log("Collection is empty - returning SUCCESS");
@@ -1108,7 +1249,7 @@ var ForEach = class extends CompositeNode {
1108
1249
  context.blackboard.set(this.indexKey, this.currentIndex);
1109
1250
  }
1110
1251
  this.log(
1111
- `Processing item ${this.currentIndex}: ${JSON.stringify(item)}`
1252
+ `Processing item ${this.currentIndex}: ${stringify3(item)}`
1112
1253
  );
1113
1254
  const bodyStatus = await body.tick(context);
1114
1255
  switch (bodyStatus) {
@@ -1128,7 +1269,7 @@ var ForEach = class extends CompositeNode {
1128
1269
  return "RUNNING" /* RUNNING */;
1129
1270
  // Will resume from this index next tick
1130
1271
  default:
1131
- throw new Error(`Unexpected status from body: ${bodyStatus}`);
1272
+ throw new ConfigurationError(`Unexpected status from body: ${bodyStatus}`);
1132
1273
  }
1133
1274
  }
1134
1275
  this.log("All items processed successfully");
@@ -1158,7 +1299,7 @@ var Sequence = class extends CompositeNode {
1158
1299
  checkSignal(context.signal);
1159
1300
  const child = this._children[this.currentChildIndex];
1160
1301
  if (!child) {
1161
- throw new Error(
1302
+ throw new ConfigurationError(
1162
1303
  `Child at index ${this.currentChildIndex} is undefined`
1163
1304
  );
1164
1305
  }
@@ -1179,7 +1320,7 @@ var Sequence = class extends CompositeNode {
1179
1320
  this._status = "RUNNING" /* RUNNING */;
1180
1321
  return "RUNNING" /* RUNNING */;
1181
1322
  default:
1182
- throw new Error(`Unexpected status from child: ${childStatus}`);
1323
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1183
1324
  }
1184
1325
  }
1185
1326
  this.log("All children succeeded");
@@ -1232,7 +1373,7 @@ var MemorySequence = class extends Sequence {
1232
1373
  this._status = "RUNNING" /* RUNNING */;
1233
1374
  return "RUNNING" /* RUNNING */;
1234
1375
  default:
1235
- throw new Error(`Unexpected status from child: ${childStatus}`);
1376
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1236
1377
  }
1237
1378
  }
1238
1379
  this.log("All children succeeded");
@@ -1363,7 +1504,7 @@ var ReactiveSequence = class extends Sequence {
1363
1504
  this._status = "RUNNING" /* RUNNING */;
1364
1505
  return "RUNNING" /* RUNNING */;
1365
1506
  default:
1366
- throw new Error(`Unexpected status from child: ${childStatus}`);
1507
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1367
1508
  }
1368
1509
  }
1369
1510
  this.log("All children succeeded");
@@ -1442,7 +1583,7 @@ var Selector = class extends CompositeNode {
1442
1583
  checkSignal(context.signal);
1443
1584
  const child = this._children[this.currentChildIndex];
1444
1585
  if (!child) {
1445
- throw new Error(
1586
+ throw new ConfigurationError(
1446
1587
  `Child at index ${this.currentChildIndex} is undefined`
1447
1588
  );
1448
1589
  }
@@ -1463,7 +1604,7 @@ var Selector = class extends CompositeNode {
1463
1604
  this._status = "RUNNING" /* RUNNING */;
1464
1605
  return "RUNNING" /* RUNNING */;
1465
1606
  default:
1466
- throw new Error(`Unexpected status from child: ${childStatus}`);
1607
+ throw new ConfigurationError(`Unexpected status from child: ${childStatus}`);
1467
1608
  }
1468
1609
  }
1469
1610
  this.log("All children failed");
@@ -1485,154 +1626,6 @@ var Fallback = class extends Selector {
1485
1626
  }
1486
1627
  };
1487
1628
 
1488
- // src/utilities/variable-resolver.ts
1489
- var VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/g;
1490
- var HAS_VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/;
1491
- var FULL_MATCH_PATTERN = /^\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}$|^\$\{([a-zA-Z0-9_.]+)\}$/;
1492
- var safeProcessEnv = () => {
1493
- try {
1494
- return typeof process !== "undefined" && process?.env ? process.env : {};
1495
- } catch {
1496
- return {};
1497
- }
1498
- };
1499
- function resolveString(str, ctx, opts = {}) {
1500
- const { preserveUndefined = true, envSource = safeProcessEnv() } = opts;
1501
- const fullMatch = str.match(FULL_MATCH_PATTERN);
1502
- if (fullMatch) {
1503
- const namespace = fullMatch[1];
1504
- const namespacedKey = fullMatch[2];
1505
- const simpleKey = fullMatch[3];
1506
- const key = namespacedKey || simpleKey;
1507
- const ns = namespace || "bb";
1508
- if (key) {
1509
- const value = resolveVariable(ns, key, ctx, envSource);
1510
- if (value !== void 0) {
1511
- return value;
1512
- }
1513
- return preserveUndefined ? str : void 0;
1514
- }
1515
- }
1516
- return str.replace(VARIABLE_PATTERN, (match, namespace, namespacedKey, simpleKey) => {
1517
- const key = namespacedKey || simpleKey;
1518
- const ns = namespace || "bb";
1519
- const value = resolveVariable(ns, key, ctx, envSource);
1520
- if (value === void 0) {
1521
- return preserveUndefined ? match : "";
1522
- }
1523
- if (value === null) {
1524
- return "null";
1525
- }
1526
- if (typeof value === "object") {
1527
- try {
1528
- return JSON.stringify(value);
1529
- } catch {
1530
- return String(value);
1531
- }
1532
- }
1533
- return String(value);
1534
- });
1535
- }
1536
- function resolveValue(value, ctx, opts = {}) {
1537
- if (typeof value === "string") {
1538
- return resolveString(value, ctx, opts);
1539
- }
1540
- if (Array.isArray(value)) {
1541
- return value.map((item) => resolveValue(item, ctx, opts));
1542
- }
1543
- if (value !== null && typeof value === "object") {
1544
- const resolved = {};
1545
- for (const [key, val] of Object.entries(value)) {
1546
- resolved[key] = resolveValue(val, ctx, opts);
1547
- }
1548
- return resolved;
1549
- }
1550
- return value;
1551
- }
1552
- function resolveVariable(namespace, key, ctx, envSource) {
1553
- switch (namespace) {
1554
- case "input":
1555
- return getNestedValue(ctx.input, key);
1556
- case "bb":
1557
- return getNestedBlackboardValue(ctx.blackboard, key);
1558
- case "env":
1559
- return envSource[key];
1560
- case "param":
1561
- if (ctx.testData) {
1562
- const parts = key.split(".");
1563
- const firstPart = parts[0];
1564
- if (firstPart) {
1565
- let value = ctx.testData.get(firstPart);
1566
- for (let i = 1; i < parts.length && value !== void 0; i++) {
1567
- const part = parts[i];
1568
- if (part && typeof value === "object" && value !== null) {
1569
- value = value[part];
1570
- } else {
1571
- return void 0;
1572
- }
1573
- }
1574
- return value;
1575
- }
1576
- }
1577
- return void 0;
1578
- default:
1579
- return getNestedBlackboardValue(ctx.blackboard, key);
1580
- }
1581
- }
1582
- function getNestedValue(obj, path2) {
1583
- if (obj === void 0 || obj === null) {
1584
- return void 0;
1585
- }
1586
- if (typeof obj !== "object") {
1587
- return void 0;
1588
- }
1589
- const parts = path2.split(".");
1590
- let value = obj;
1591
- for (const part of parts) {
1592
- if (value === void 0 || value === null) {
1593
- return void 0;
1594
- }
1595
- if (typeof value !== "object") {
1596
- return void 0;
1597
- }
1598
- value = value[part];
1599
- }
1600
- return value;
1601
- }
1602
- function getNestedBlackboardValue(blackboard, path2) {
1603
- const parts = path2.split(".");
1604
- const firstPart = parts[0];
1605
- if (!firstPart) {
1606
- return void 0;
1607
- }
1608
- let value = blackboard.get(firstPart);
1609
- for (let i = 1; i < parts.length && value !== void 0; i++) {
1610
- const part = parts[i];
1611
- if (part && typeof value === "object" && value !== null) {
1612
- value = value[part];
1613
- } else {
1614
- return void 0;
1615
- }
1616
- }
1617
- return value;
1618
- }
1619
- function hasVariables(str) {
1620
- return HAS_VARIABLE_PATTERN.test(str);
1621
- }
1622
- function extractVariables(str) {
1623
- const variables = [];
1624
- const pattern = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/g;
1625
- let match;
1626
- while ((match = pattern.exec(str)) !== null) {
1627
- const namespace = match[1] || "bb";
1628
- const key = match[2] || match[3];
1629
- if (key) {
1630
- variables.push({ namespace, key });
1631
- }
1632
- }
1633
- return variables;
1634
- }
1635
-
1636
1629
  // src/composites/sub-tree.ts
1637
1630
  var SubTree = class extends ActionNode {
1638
1631
  treeId;
@@ -1650,7 +1643,7 @@ var SubTree = class extends ActionNode {
1650
1643
  checkSignal(context.signal);
1651
1644
  if (!this.clonedTree) {
1652
1645
  if (!context.treeRegistry.hasTree(this.treeId)) {
1653
- throw new Error(
1646
+ throw new ConfigurationError(
1654
1647
  `SubTree tree '${this.treeId}' not found in registry. Available trees: ${context.treeRegistry.getAllTreeIds().join(", ") || "none"}`
1655
1648
  );
1656
1649
  }
@@ -1807,7 +1800,7 @@ var While = class extends CompositeNode {
1807
1800
  this._status = "RUNNING" /* RUNNING */;
1808
1801
  return "RUNNING" /* RUNNING */;
1809
1802
  default:
1810
- throw new Error(`Unexpected status from body: ${bodyStatus}`);
1803
+ throw new ConfigurationError(`Unexpected status from body: ${bodyStatus}`);
1811
1804
  }
1812
1805
  }
1813
1806
  this.log(`Max iterations (${this.maxIterations}) reached`);
@@ -3402,6 +3395,7 @@ var RunningNode = class extends ActionNode {
3402
3395
  };
3403
3396
 
3404
3397
  // src/utilities/log-message.ts
3398
+ import stringify4 from "safe-stable-stringify";
3405
3399
  var LogMessage = class extends ActionNode {
3406
3400
  message;
3407
3401
  level;
@@ -3464,11 +3458,7 @@ var LogMessage = class extends ActionNode {
3464
3458
  return "null";
3465
3459
  }
3466
3460
  if (typeof resolved === "object") {
3467
- try {
3468
- return JSON.stringify(resolved);
3469
- } catch {
3470
- return String(resolved);
3471
- }
3461
+ return stringify4(resolved) ?? String(resolved);
3472
3462
  }
3473
3463
  return String(resolved);
3474
3464
  }
@@ -3531,6 +3521,7 @@ var RegexExtract = class extends ActionNode {
3531
3521
  };
3532
3522
 
3533
3523
  // src/utilities/set-variable.ts
3524
+ import stringify5 from "safe-stable-stringify";
3534
3525
  var SetVariable = class extends ActionNode {
3535
3526
  key;
3536
3527
  value;
@@ -3549,7 +3540,7 @@ var SetVariable = class extends ActionNode {
3549
3540
  const resolvedKey = typeof this.key === "string" ? resolveValue(this.key, varCtx) : String(this.key);
3550
3541
  const resolvedValue = typeof this.value === "string" ? resolveValue(this.value, varCtx) : this.value;
3551
3542
  context.blackboard.set(resolvedKey, resolvedValue);
3552
- this.log(`Set ${resolvedKey} = ${JSON.stringify(resolvedValue)}`);
3543
+ this.log(`Set ${resolvedKey} = ${stringify5(resolvedValue)}`);
3553
3544
  return "SUCCESS" /* SUCCESS */;
3554
3545
  } catch (error) {
3555
3546
  this._lastError = error instanceof Error ? error.message : String(error);
@@ -3560,6 +3551,7 @@ var SetVariable = class extends ActionNode {
3560
3551
  };
3561
3552
 
3562
3553
  // src/utilities/math-op.ts
3554
+ import stringify6 from "safe-stable-stringify";
3563
3555
  function tokenize(expr) {
3564
3556
  const tokens = [];
3565
3557
  let i = 0;
@@ -3669,12 +3661,12 @@ function evaluate(tokens) {
3669
3661
  pos++;
3670
3662
  return val;
3671
3663
  }
3672
- throw new Error(`Unexpected token: ${JSON.stringify(tok)}`);
3664
+ throw new Error(`Unexpected token: ${stringify6(tok)}`);
3673
3665
  }
3674
3666
  const result = parseExpr();
3675
3667
  const remaining = current();
3676
3668
  if (remaining) {
3677
- throw new Error(`Unexpected token after expression: ${JSON.stringify(remaining)}`);
3669
+ throw new Error(`Unexpected token after expression: ${stringify6(remaining)}`);
3678
3670
  }
3679
3671
  return result;
3680
3672
  }
@@ -3827,7 +3819,7 @@ var ArrayFilter = class extends ActionNode {
3827
3819
  };
3828
3820
  const inputResolved = typeof this.input === "string" ? resolveValue(this.input, varCtx) : this.input;
3829
3821
  if (!Array.isArray(inputResolved)) {
3830
- throw new Error(
3822
+ throw new ConfigurationError(
3831
3823
  `Input is not an array: got ${inputResolved === null ? "null" : typeof inputResolved}`
3832
3824
  );
3833
3825
  }
@@ -3846,6 +3838,7 @@ var ArrayFilter = class extends ActionNode {
3846
3838
  this.log(`Filtered ${inputResolved.length} \u2192 ${result.length} items`);
3847
3839
  return "SUCCESS" /* SUCCESS */;
3848
3840
  } catch (error) {
3841
+ if (error instanceof ConfigurationError) throw error;
3849
3842
  this._lastError = error instanceof Error ? error.message : String(error);
3850
3843
  this.log(`ArrayFilter failed: ${this._lastError}`);
3851
3844
  return "FAILURE" /* FAILURE */;
@@ -3854,6 +3847,7 @@ var ArrayFilter = class extends ActionNode {
3854
3847
  };
3855
3848
 
3856
3849
  // src/utilities/aggregate.ts
3850
+ import stringify7 from "safe-stable-stringify";
3857
3851
  function getFieldValue2(item, path2) {
3858
3852
  if (item === null || item === void 0) return void 0;
3859
3853
  const parts = path2.split(".");
@@ -3921,14 +3915,14 @@ var Aggregate = class extends ActionNode {
3921
3915
  };
3922
3916
  const inputResolved = typeof this.input === "string" ? resolveValue(this.input, varCtx) : this.input;
3923
3917
  if (!Array.isArray(inputResolved)) {
3924
- throw new Error(
3918
+ throw new ConfigurationError(
3925
3919
  `Input is not an array: got ${inputResolved === null ? "null" : typeof inputResolved}`
3926
3920
  );
3927
3921
  }
3928
3922
  if (!this.groupBy) {
3929
3923
  const result = computeAggregations(inputResolved, this.operations);
3930
3924
  context.blackboard.set(this.outputKey, result);
3931
- this.log(`Aggregated ${inputResolved.length} items \u2192 ${JSON.stringify(result)}`);
3925
+ this.log(`Aggregated ${inputResolved.length} items \u2192 ${stringify7(result)}`);
3932
3926
  } else {
3933
3927
  const groups = {};
3934
3928
  for (const item of inputResolved) {
@@ -3948,6 +3942,7 @@ var Aggregate = class extends ActionNode {
3948
3942
  }
3949
3943
  return "SUCCESS" /* SUCCESS */;
3950
3944
  } catch (error) {
3945
+ if (error instanceof ConfigurationError) throw error;
3951
3946
  this._lastError = error instanceof Error ? error.message : String(error);
3952
3947
  this.log(`Aggregate failed: ${this._lastError}`);
3953
3948
  return "FAILURE" /* FAILURE */;
@@ -3956,6 +3951,7 @@ var Aggregate = class extends ActionNode {
3956
3951
  };
3957
3952
 
3958
3953
  // src/utilities/threshold-check.ts
3954
+ import stringify8 from "safe-stable-stringify";
3959
3955
  function evaluateThreshold(val, threshold, resolvedValue, resolvedRange) {
3960
3956
  switch (threshold.operator) {
3961
3957
  case "lte":
@@ -4000,7 +3996,7 @@ var ThresholdCheck = class extends ActionNode {
4000
3996
  const resolved = typeof this.valueRef === "string" ? resolveValue(this.valueRef, varCtx) : this.valueRef;
4001
3997
  const numValue = typeof resolved === "number" ? resolved : parseFloat(String(resolved));
4002
3998
  if (isNaN(numValue)) {
4003
- throw new Error(`Value is not numeric: ${JSON.stringify(resolved)}`);
3999
+ throw new Error(`Value is not numeric: ${stringify8(resolved)}`);
4004
4000
  }
4005
4001
  let matchedLabel = "normal";
4006
4002
  for (const threshold of this.thresholds) {
@@ -5652,6 +5648,7 @@ function registerStandardNodes(registry) {
5652
5648
  }
5653
5649
 
5654
5650
  // src/data-store/memory-store.ts
5651
+ import stringify9 from "safe-stable-stringify";
5655
5652
  var MemoryDataStore = class {
5656
5653
  storage = /* @__PURE__ */ new Map();
5657
5654
  cleanupInterval = null;
@@ -5665,7 +5662,7 @@ var MemoryDataStore = class {
5665
5662
  }
5666
5663
  }
5667
5664
  async put(key, data, options) {
5668
- const serialized = JSON.stringify(data);
5665
+ const serialized = stringify9(data) ?? "{}";
5669
5666
  const sizeBytes = Buffer.byteLength(serialized, "utf8");
5670
5667
  const entry = {
5671
5668
  data,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@q1k-oss/behaviour-tree-workflows",
3
- "version": "0.0.6",
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",