@q1k-oss/btree-workflows 0.0.1

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 (203) hide show
  1. package/.claude/settings.local.json +31 -0
  2. package/CLAUDE.md +181 -0
  3. package/LICENSE +21 -0
  4. package/README.md +920 -0
  5. package/behaviour-tree-workflows-landing/index.html +16 -0
  6. package/behaviour-tree-workflows-landing/package-lock.json +2074 -0
  7. package/behaviour-tree-workflows-landing/package.json +31 -0
  8. package/behaviour-tree-workflows-landing/public/favicon.svg +17 -0
  9. package/behaviour-tree-workflows-landing/src/App.css +103 -0
  10. package/behaviour-tree-workflows-landing/src/App.tsx +176 -0
  11. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.css +89 -0
  12. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.tsx +64 -0
  13. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.css +64 -0
  14. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.tsx +34 -0
  15. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.css +107 -0
  16. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.tsx +85 -0
  17. package/behaviour-tree-workflows-landing/src/components/Header.css +50 -0
  18. package/behaviour-tree-workflows-landing/src/components/Header.tsx +26 -0
  19. package/behaviour-tree-workflows-landing/src/components/StatusBadge.css +45 -0
  20. package/behaviour-tree-workflows-landing/src/components/StatusBadge.tsx +15 -0
  21. package/behaviour-tree-workflows-landing/src/components/Toolbar.css +74 -0
  22. package/behaviour-tree-workflows-landing/src/components/Toolbar.tsx +53 -0
  23. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.css +67 -0
  24. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.tsx +192 -0
  25. package/behaviour-tree-workflows-landing/src/components/YamlEditor.css +18 -0
  26. package/behaviour-tree-workflows-landing/src/components/YamlEditor.tsx +96 -0
  27. package/behaviour-tree-workflows-landing/src/lib/count-nodes.ts +11 -0
  28. package/behaviour-tree-workflows-landing/src/lib/execution-engine.ts +96 -0
  29. package/behaviour-tree-workflows-landing/src/lib/tree-layout.ts +136 -0
  30. package/behaviour-tree-workflows-landing/src/lib/yaml-examples.ts +549 -0
  31. package/behaviour-tree-workflows-landing/src/main.tsx +9 -0
  32. package/behaviour-tree-workflows-landing/src/stubs/activepieces.ts +18 -0
  33. package/behaviour-tree-workflows-landing/src/stubs/fs.ts +24 -0
  34. package/behaviour-tree-workflows-landing/src/stubs/path.ts +16 -0
  35. package/behaviour-tree-workflows-landing/src/stubs/temporal-activity.ts +6 -0
  36. package/behaviour-tree-workflows-landing/src/stubs/temporal-workflow.ts +22 -0
  37. package/behaviour-tree-workflows-landing/tsconfig.json +25 -0
  38. package/behaviour-tree-workflows-landing/vite.config.ts +40 -0
  39. package/demo-google-sheets.ts +181 -0
  40. package/demo-runtime-variables.ts +174 -0
  41. package/demo-template.ts +208 -0
  42. package/docs/ARCHITECTURE_SUMMARY.md +613 -0
  43. package/docs/NODE_REFERENCE.md +504 -0
  44. package/docs/README.md +53 -0
  45. package/docs/custom-nodes-architecture.md +826 -0
  46. package/docs/observability.md +175 -0
  47. package/docs/yaml-specification.md +990 -0
  48. package/examples/temporal/README.md +117 -0
  49. package/examples/temporal/activities.ts +373 -0
  50. package/examples/temporal/client.ts +115 -0
  51. package/examples/temporal/python-worker/activities.py +339 -0
  52. package/examples/temporal/python-worker/requirements.txt +12 -0
  53. package/examples/temporal/python-worker/worker.py +106 -0
  54. package/examples/temporal/worker.ts +66 -0
  55. package/examples/temporal/workflows.ts +6 -0
  56. package/examples/temporal/yaml-workflow-loader.ts +105 -0
  57. package/examples/yaml-test.ts +97 -0
  58. package/examples/yaml-workflows/01-simple-sequence.yaml +25 -0
  59. package/examples/yaml-workflows/02-parallel-timeout.yaml +45 -0
  60. package/examples/yaml-workflows/03-ecommerce-checkout.yaml +94 -0
  61. package/examples/yaml-workflows/04-ai-agent-workflow.yaml +346 -0
  62. package/examples/yaml-workflows/05-order-processing.yaml +146 -0
  63. package/examples/yaml-workflows/06-activity-test.yaml +71 -0
  64. package/examples/yaml-workflows/07-activity-simple-test.yaml +43 -0
  65. package/examples/yaml-workflows/08-file-processing.yaml +141 -0
  66. package/examples/yaml-workflows/09-http-request.yaml +137 -0
  67. package/examples/yaml-workflows/README.md +211 -0
  68. package/package.json +38 -0
  69. package/src/actions/code-execution.schema.ts +27 -0
  70. package/src/actions/code-execution.ts +218 -0
  71. package/src/actions/generate-file.test.ts +516 -0
  72. package/src/actions/generate-file.ts +166 -0
  73. package/src/actions/http-request.test.ts +784 -0
  74. package/src/actions/http-request.ts +228 -0
  75. package/src/actions/index.ts +20 -0
  76. package/src/actions/parse-file.test.ts +448 -0
  77. package/src/actions/parse-file.ts +139 -0
  78. package/src/actions/python-script.test.ts +439 -0
  79. package/src/actions/python-script.ts +154 -0
  80. package/src/base-node.test.ts +511 -0
  81. package/src/base-node.ts +605 -0
  82. package/src/behavior-tree.test.ts +431 -0
  83. package/src/behavior-tree.ts +283 -0
  84. package/src/blackboard.test.ts +222 -0
  85. package/src/blackboard.ts +192 -0
  86. package/src/composites/conditional.schema.ts +19 -0
  87. package/src/composites/conditional.test.ts +309 -0
  88. package/src/composites/conditional.ts +129 -0
  89. package/src/composites/for-each.schema.ts +23 -0
  90. package/src/composites/for-each.test.ts +254 -0
  91. package/src/composites/for-each.ts +132 -0
  92. package/src/composites/index.ts +15 -0
  93. package/src/composites/memory-sequence.schema.ts +19 -0
  94. package/src/composites/memory-sequence.test.ts +223 -0
  95. package/src/composites/memory-sequence.ts +98 -0
  96. package/src/composites/parallel.schema.ts +28 -0
  97. package/src/composites/parallel.test.ts +502 -0
  98. package/src/composites/parallel.ts +157 -0
  99. package/src/composites/reactive-sequence.schema.ts +19 -0
  100. package/src/composites/reactive-sequence.test.ts +170 -0
  101. package/src/composites/reactive-sequence.ts +85 -0
  102. package/src/composites/recovery.schema.ts +19 -0
  103. package/src/composites/recovery.test.ts +366 -0
  104. package/src/composites/recovery.ts +90 -0
  105. package/src/composites/selector.schema.ts +19 -0
  106. package/src/composites/selector.test.ts +387 -0
  107. package/src/composites/selector.ts +85 -0
  108. package/src/composites/sequence.schema.ts +19 -0
  109. package/src/composites/sequence.test.ts +337 -0
  110. package/src/composites/sequence.ts +72 -0
  111. package/src/composites/sub-tree.schema.ts +21 -0
  112. package/src/composites/sub-tree.test.ts +893 -0
  113. package/src/composites/sub-tree.ts +177 -0
  114. package/src/composites/while.schema.ts +24 -0
  115. package/src/composites/while.test.ts +381 -0
  116. package/src/composites/while.ts +149 -0
  117. package/src/data-store/index.ts +10 -0
  118. package/src/data-store/memory-store.ts +161 -0
  119. package/src/data-store/types.ts +94 -0
  120. package/src/debug/breakpoint.test.ts +47 -0
  121. package/src/debug/breakpoint.ts +30 -0
  122. package/src/debug/index.ts +17 -0
  123. package/src/debug/resume-point.test.ts +49 -0
  124. package/src/debug/resume-point.ts +29 -0
  125. package/src/decorators/delay.schema.ts +21 -0
  126. package/src/decorators/delay.test.ts +261 -0
  127. package/src/decorators/delay.ts +140 -0
  128. package/src/decorators/force-result.schema.ts +32 -0
  129. package/src/decorators/force-result.test.ts +133 -0
  130. package/src/decorators/force-result.ts +63 -0
  131. package/src/decorators/index.ts +13 -0
  132. package/src/decorators/invert.schema.ts +19 -0
  133. package/src/decorators/invert.test.ts +135 -0
  134. package/src/decorators/invert.ts +42 -0
  135. package/src/decorators/keep-running.schema.ts +20 -0
  136. package/src/decorators/keep-running.test.ts +105 -0
  137. package/src/decorators/keep-running.ts +49 -0
  138. package/src/decorators/precondition.schema.ts +19 -0
  139. package/src/decorators/precondition.test.ts +351 -0
  140. package/src/decorators/precondition.ts +139 -0
  141. package/src/decorators/repeat.schema.ts +21 -0
  142. package/src/decorators/repeat.test.ts +187 -0
  143. package/src/decorators/repeat.ts +94 -0
  144. package/src/decorators/run-once.schema.ts +19 -0
  145. package/src/decorators/run-once.test.ts +140 -0
  146. package/src/decorators/run-once.ts +61 -0
  147. package/src/decorators/soft-assert.schema.ts +19 -0
  148. package/src/decorators/soft-assert.test.ts +107 -0
  149. package/src/decorators/soft-assert.ts +68 -0
  150. package/src/decorators/timeout.schema.ts +21 -0
  151. package/src/decorators/timeout.test.ts +274 -0
  152. package/src/decorators/timeout.ts +159 -0
  153. package/src/errors.test.ts +63 -0
  154. package/src/errors.ts +34 -0
  155. package/src/events.test.ts +347 -0
  156. package/src/events.ts +183 -0
  157. package/src/index.ts +80 -0
  158. package/src/integrations/index.ts +30 -0
  159. package/src/integrations/integration-action.test.ts +571 -0
  160. package/src/integrations/integration-action.ts +233 -0
  161. package/src/integrations/piece-executor.ts +320 -0
  162. package/src/observability/execution-tracker.ts +320 -0
  163. package/src/observability/index.ts +23 -0
  164. package/src/observability/sinks.ts +138 -0
  165. package/src/observability/types.ts +130 -0
  166. package/src/registry-utils.ts +147 -0
  167. package/src/registry.test.ts +466 -0
  168. package/src/registry.ts +334 -0
  169. package/src/schemas/base.schema.ts +104 -0
  170. package/src/schemas/index.ts +223 -0
  171. package/src/schemas/integration.test.ts +238 -0
  172. package/src/schemas/tree-definition.schema.ts +170 -0
  173. package/src/schemas/validation.test.ts +146 -0
  174. package/src/schemas/validation.ts +122 -0
  175. package/src/scripting/index.ts +22 -0
  176. package/src/templates/template-loader.test.ts +281 -0
  177. package/src/templates/template-loader.ts +152 -0
  178. package/src/temporal-integration.test.ts +213 -0
  179. package/src/test-nodes.ts +259 -0
  180. package/src/types.ts +503 -0
  181. package/src/utilities/index.ts +17 -0
  182. package/src/utilities/log-message.test.ts +275 -0
  183. package/src/utilities/log-message.ts +134 -0
  184. package/src/utilities/regex-extract.test.ts +138 -0
  185. package/src/utilities/regex-extract.ts +108 -0
  186. package/src/utilities/variable-resolver.test.ts +416 -0
  187. package/src/utilities/variable-resolver.ts +318 -0
  188. package/src/utils/error-handler.test.ts +117 -0
  189. package/src/utils/error-handler.ts +48 -0
  190. package/src/utils/signal-check.test.ts +234 -0
  191. package/src/utils/signal-check.ts +140 -0
  192. package/src/yaml/errors.ts +143 -0
  193. package/src/yaml/index.ts +30 -0
  194. package/src/yaml/loader.ts +39 -0
  195. package/src/yaml/parser.ts +286 -0
  196. package/src/yaml/validation/semantic-validator.ts +196 -0
  197. package/templates/google-sheets/insert-row.yaml +76 -0
  198. package/templates/notification-sender.yaml +33 -0
  199. package/templates/order-validation.yaml +44 -0
  200. package/tsconfig.json +24 -0
  201. package/vitest.config.ts +25 -0
  202. package/workflows/order-processor.yaml +59 -0
  203. package/workflows/process-order-workflow.yaml +142 -0
@@ -0,0 +1,347 @@
1
+ /**
2
+ * Tests for event system
3
+ */
4
+
5
+ import { beforeEach, describe, expect, it } from "vitest";
6
+ import { ScopedBlackboard } from "./blackboard.js";
7
+ import { Sequence } from "./composites/sequence.js";
8
+ import { type NodeEvent, NodeEventEmitter, NodeEventType } from "./events.js";
9
+ import { RunningNode, SuccessNode } from "./test-nodes.js";
10
+ import { type TemporalContext, NodeStatus } from "./types.js";
11
+
12
+ describe("NodeEventEmitter", () => {
13
+ let emitter: NodeEventEmitter;
14
+
15
+ beforeEach(() => {
16
+ emitter = new NodeEventEmitter();
17
+ });
18
+
19
+ describe("Basic Functionality", () => {
20
+ it("should emit events to registered listeners", () => {
21
+ const events: NodeEvent[] = [];
22
+
23
+ emitter.on(NodeEventType.TICK_START, (event) => {
24
+ events.push(event);
25
+ });
26
+
27
+ emitter.emit({
28
+ type: NodeEventType.TICK_START,
29
+ nodeId: "test",
30
+ nodeName: "Test",
31
+ nodeType: "TestNode",
32
+ timestamp: Date.now(),
33
+ });
34
+
35
+ expect(events.length).toBe(1);
36
+ expect(events[0]?.type).toBe(NodeEventType.TICK_START);
37
+ });
38
+
39
+ it("should support multiple listeners for same event type", () => {
40
+ let count1 = 0;
41
+ let count2 = 0;
42
+
43
+ emitter.on(NodeEventType.TICK_START, () => count1++);
44
+ emitter.on(NodeEventType.TICK_START, () => count2++);
45
+
46
+ emitter.emit({
47
+ type: NodeEventType.TICK_START,
48
+ nodeId: "test",
49
+ nodeName: "Test",
50
+ nodeType: "TestNode",
51
+ timestamp: Date.now(),
52
+ });
53
+
54
+ expect(count1).toBe(1);
55
+ expect(count2).toBe(1);
56
+ });
57
+
58
+ it("should support onAll for listening to all events", () => {
59
+ const events: NodeEvent[] = [];
60
+
61
+ emitter.onAll((event) => events.push(event));
62
+
63
+ emitter.emit({
64
+ type: NodeEventType.TICK_START,
65
+ nodeId: "test",
66
+ nodeName: "Test",
67
+ nodeType: "TestNode",
68
+ timestamp: Date.now(),
69
+ });
70
+
71
+ emitter.emit({
72
+ type: NodeEventType.TICK_END,
73
+ nodeId: "test",
74
+ nodeName: "Test",
75
+ nodeType: "TestNode",
76
+ timestamp: Date.now(),
77
+ data: { status: NodeStatus.SUCCESS },
78
+ });
79
+
80
+ expect(events.length).toBe(2);
81
+ expect(events[0]?.type).toBe(NodeEventType.TICK_START);
82
+ expect(events[1]?.type).toBe(NodeEventType.TICK_END);
83
+ });
84
+
85
+ it("should remove listeners with off()", () => {
86
+ let count = 0;
87
+ const callback = () => count++;
88
+
89
+ emitter.on(NodeEventType.TICK_START, callback);
90
+
91
+ emitter.emit({
92
+ type: NodeEventType.TICK_START,
93
+ nodeId: "test",
94
+ nodeName: "Test",
95
+ nodeType: "TestNode",
96
+ timestamp: Date.now(),
97
+ });
98
+
99
+ expect(count).toBe(1);
100
+
101
+ emitter.off(NodeEventType.TICK_START, callback);
102
+
103
+ emitter.emit({
104
+ type: NodeEventType.TICK_START,
105
+ nodeId: "test",
106
+ nodeName: "Test",
107
+ nodeType: "TestNode",
108
+ timestamp: Date.now(),
109
+ });
110
+
111
+ expect(count).toBe(1); // Should not increment
112
+ });
113
+
114
+ it("should clear all listeners", () => {
115
+ let count = 0;
116
+
117
+ emitter.on(NodeEventType.TICK_START, () => count++);
118
+ emitter.onAll(() => count++);
119
+
120
+ emitter.clear();
121
+
122
+ emitter.emit({
123
+ type: NodeEventType.TICK_START,
124
+ nodeId: "test",
125
+ nodeName: "Test",
126
+ nodeType: "TestNode",
127
+ timestamp: Date.now(),
128
+ });
129
+
130
+ expect(count).toBe(0);
131
+ });
132
+
133
+ it("should handle errors in callbacks gracefully", () => {
134
+ const events: NodeEvent[] = [];
135
+
136
+ // This callback throws an error
137
+ emitter.on(NodeEventType.TICK_START, () => {
138
+ throw new Error("Test error");
139
+ });
140
+
141
+ // This callback should still execute
142
+ emitter.on(NodeEventType.TICK_START, (event) => {
143
+ events.push(event);
144
+ });
145
+
146
+ // Should not throw
147
+ expect(() => {
148
+ emitter.emit({
149
+ type: NodeEventType.TICK_START,
150
+ nodeId: "test",
151
+ nodeName: "Test",
152
+ nodeType: "TestNode",
153
+ timestamp: Date.now(),
154
+ });
155
+ }).not.toThrow();
156
+
157
+ // Second callback should have executed
158
+ expect(events.length).toBe(1);
159
+ });
160
+ });
161
+
162
+ describe("Listener Counting", () => {
163
+ it("should report correct listener counts", () => {
164
+ expect(emitter.listenerCount(NodeEventType.TICK_START)).toBe(0);
165
+ expect(emitter.allListenerCount()).toBe(0);
166
+ expect(emitter.hasListeners()).toBe(false);
167
+
168
+ emitter.on(NodeEventType.TICK_START, () => {});
169
+ emitter.on(NodeEventType.TICK_START, () => {});
170
+ emitter.onAll(() => {});
171
+
172
+ expect(emitter.listenerCount(NodeEventType.TICK_START)).toBe(2);
173
+ expect(emitter.allListenerCount()).toBe(1);
174
+ expect(emitter.hasListeners()).toBe(true);
175
+ });
176
+ });
177
+ });
178
+
179
+ describe("Event Integration with Nodes", () => {
180
+ let blackboard: ScopedBlackboard;
181
+ let emitter: NodeEventEmitter;
182
+
183
+ beforeEach(() => {
184
+ blackboard = new ScopedBlackboard("root");
185
+ emitter = new NodeEventEmitter();
186
+ });
187
+
188
+ describe("TICK Events", () => {
189
+ it("should emit TICK_START and TICK_END for successful execution", async () => {
190
+ const events: NodeEvent[] = [];
191
+ emitter.onAll((event) => events.push(event));
192
+
193
+ const node = new SuccessNode({ id: "success" });
194
+ const context: TemporalContext = {
195
+ blackboard,
196
+ timestamp: Date.now(),
197
+ deltaTime: 0,
198
+ eventEmitter: emitter,
199
+ };
200
+ await node.tick(context);
201
+
202
+ expect(events.length).toBe(2);
203
+ expect(events[0]?.type).toBe(NodeEventType.TICK_START);
204
+ expect(events[0]?.nodeId).toBe("success");
205
+ expect(events[1]?.type).toBe(NodeEventType.TICK_END);
206
+ expect(events[1]?.data?.status).toBe(NodeStatus.SUCCESS);
207
+ });
208
+
209
+ it("should emit events for all nodes in a tree", async () => {
210
+ const events: NodeEvent[] = [];
211
+ emitter.onAll((event) => events.push(event));
212
+
213
+ const sequence = new Sequence({ id: "seq" });
214
+ sequence.addChildren([
215
+ new SuccessNode({ id: "child1" }),
216
+ new SuccessNode({ id: "child2" }),
217
+ ]);
218
+
219
+ const context: TemporalContext = {
220
+ blackboard,
221
+ timestamp: Date.now(),
222
+ deltaTime: 0,
223
+ eventEmitter: emitter,
224
+ };
225
+ await sequence.tick(context);
226
+
227
+ // Sequence starts, child1 starts/ends, child2 starts/ends, sequence ends
228
+ const nodeIds = events.map((e) => e.nodeId);
229
+ expect(nodeIds).toContain("seq");
230
+ expect(nodeIds).toContain("child1");
231
+ expect(nodeIds).toContain("child2");
232
+ });
233
+ });
234
+
235
+ describe("ERROR Events", () => {
236
+ it("should emit ERROR event when node throws", async () => {
237
+ const events: NodeEvent[] = [];
238
+ emitter.on(NodeEventType.ERROR, (event) => events.push(event));
239
+
240
+ class ThrowingNode extends SuccessNode {
241
+ async executeTick(_context: TemporalContext) {
242
+ throw new Error("Test error");
243
+ }
244
+ }
245
+
246
+ const node = new ThrowingNode({ id: "throwing" });
247
+ const context: TemporalContext = {
248
+ blackboard,
249
+ timestamp: Date.now(),
250
+ deltaTime: 0,
251
+ eventEmitter: emitter,
252
+ };
253
+
254
+ // Errors are converted to FAILURE status
255
+ const status = await node.tick(context);
256
+ expect(status).toBe(NodeStatus.FAILURE);
257
+ expect(node.lastError).toBe("Test error");
258
+
259
+ // ERROR events are emitted for thrown errors
260
+ expect(events.length).toBe(1);
261
+ expect(events[0].type).toBe(NodeEventType.ERROR);
262
+ expect(events[0].nodeId).toBe("throwing");
263
+ expect(events[0].data).toEqual({ error: "Test error" });
264
+ });
265
+ });
266
+
267
+ describe("HALT Events", () => {
268
+ it("should emit HALT event when node is halted", () => {
269
+ const events: NodeEvent[] = [];
270
+ emitter.on(NodeEventType.HALT, (event) => events.push(event));
271
+
272
+ const node = new RunningNode({ id: "running" });
273
+ // Store emitter reference
274
+ node._eventEmitter = emitter;
275
+ node._status = NodeStatus.RUNNING;
276
+
277
+ node.halt();
278
+
279
+ expect(events.length).toBe(1);
280
+ expect(events[0]?.type).toBe(NodeEventType.HALT);
281
+ expect(events[0]?.nodeId).toBe("running");
282
+ });
283
+ });
284
+
285
+ describe("RESET Events", () => {
286
+ it("should emit RESET event when node is reset", () => {
287
+ const events: NodeEvent[] = [];
288
+ emitter.on(NodeEventType.RESET, (event) => events.push(event));
289
+
290
+ const node = new SuccessNode({ id: "node" });
291
+ // Store emitter reference
292
+ node._eventEmitter = emitter;
293
+
294
+ node.reset();
295
+
296
+ expect(events.length).toBe(1);
297
+ expect(events[0]?.type).toBe(NodeEventType.RESET);
298
+ expect(events[0]?.nodeId).toBe("node");
299
+ });
300
+ });
301
+ });
302
+
303
+ describe("Event Data Validation", () => {
304
+ it("should include all required fields in events", async () => {
305
+ const events: NodeEvent[] = [];
306
+ const emitter = new NodeEventEmitter();
307
+ emitter.onAll((event) => events.push(event));
308
+
309
+ const node = new SuccessNode({ id: "test", name: "TestNode" });
310
+ const context: TemporalContext = {
311
+ blackboard: new ScopedBlackboard("root"),
312
+ timestamp: Date.now(),
313
+ deltaTime: 0,
314
+ eventEmitter: emitter,
315
+ };
316
+ await node.tick(context);
317
+
318
+ for (const event of events) {
319
+ expect(event.type).toBeDefined();
320
+ expect(event.nodeId).toBeDefined();
321
+ expect(event.nodeName).toBeDefined();
322
+ expect(event.nodeType).toBeDefined();
323
+ expect(event.timestamp).toBeGreaterThan(0);
324
+ }
325
+ });
326
+
327
+ it("should include status in TICK_END data", async () => {
328
+ const emitter = new NodeEventEmitter();
329
+ let tickEndEvent: NodeEvent | undefined;
330
+
331
+ emitter.on(NodeEventType.TICK_END, (event) => {
332
+ tickEndEvent = event;
333
+ });
334
+
335
+ const node = new SuccessNode({ id: "test" });
336
+ const context: TemporalContext = {
337
+ blackboard: new ScopedBlackboard("root"),
338
+ timestamp: Date.now(),
339
+ deltaTime: 0,
340
+ eventEmitter: emitter,
341
+ };
342
+ await node.tick(context);
343
+
344
+ expect(tickEndEvent).toBeDefined();
345
+ expect(tickEndEvent?.data?.status).toBe(NodeStatus.SUCCESS);
346
+ });
347
+ });
package/src/events.ts ADDED
@@ -0,0 +1,183 @@
1
+ /**
2
+ * Event system for observing behavior tree execution
3
+ * Enables external systems (debuggers, monitors, agents) to track execution in real-time
4
+ */
5
+
6
+ // Effect-TS removed in Phase 1 - toStream() method commented out
7
+
8
+ /**
9
+ * Types of events emitted by nodes during execution
10
+ */
11
+ export enum NodeEventType {
12
+ TICK_START = "tick_start",
13
+ TICK_END = "tick_end",
14
+ STATUS_CHANGE = "status_change",
15
+ ERROR = "error",
16
+ HALT = "halt",
17
+ RESET = "reset",
18
+ LOG = "log",
19
+ }
20
+
21
+ /**
22
+ * Data for LOG events emitted by LogMessage nodes
23
+ */
24
+ export interface LogEventData {
25
+ level: "info" | "warn" | "error" | "debug";
26
+ message: string;
27
+ }
28
+
29
+ /**
30
+ * Event emitted by a node during execution
31
+ */
32
+ export interface NodeEvent<TData> {
33
+ type: NodeEventType;
34
+ nodeId: string;
35
+ nodeName: string;
36
+ nodeType: string;
37
+ timestamp: number;
38
+ /** Tree path (e.g., "/0/1/2") - set by tree execution */
39
+ nodePath?: string;
40
+ data?: TData;
41
+ }
42
+
43
+ /**
44
+ * Callback function for node events
45
+ */
46
+ export type NodeEventCallback<TData> = (event: NodeEvent<TData>) => void;
47
+
48
+ /**
49
+ * Event emitter for behavior tree nodes
50
+ * Supports subscribing to specific event types and emitting events
51
+ */
52
+ export class NodeEventEmitter {
53
+ private listeners: Map<NodeEventType, Set<NodeEventCallback<unknown>>> =
54
+ new Map();
55
+ private allListeners: Set<NodeEventCallback<unknown>> = new Set();
56
+
57
+ /**
58
+ * Subscribe to a specific event type
59
+ * @param type - The event type to listen for
60
+ * @param callback - Function to call when event occurs
61
+ */
62
+ on<TData>(type: NodeEventType, callback: NodeEventCallback<TData>): void {
63
+ if (!this.listeners.has(type)) {
64
+ this.listeners.set(type, new Set());
65
+ }
66
+ this.listeners.get(type)?.add(callback as NodeEventCallback<unknown>);
67
+ }
68
+
69
+ /**
70
+ * Subscribe to all event types
71
+ * @param callback - Function to call for any event
72
+ */
73
+ onAll<TData>(callback: NodeEventCallback<TData>): void {
74
+ this.allListeners.add(callback as NodeEventCallback<unknown>);
75
+ }
76
+
77
+ /**
78
+ * Unsubscribe from a specific event type
79
+ * @param type - The event type to stop listening for
80
+ * @param callback - The callback to remove
81
+ */
82
+ off<TData>(type: NodeEventType, callback: NodeEventCallback<TData>): void {
83
+ this.listeners.get(type)?.delete(callback as NodeEventCallback<unknown>);
84
+ }
85
+
86
+ /**
87
+ * Unsubscribe from all events
88
+ * @param callback - The callback to remove
89
+ */
90
+ offAll<TData>(callback: NodeEventCallback<TData>): void {
91
+ this.allListeners.delete(callback as NodeEventCallback<unknown>);
92
+ }
93
+
94
+ /**
95
+ * Emit an event to all registered listeners
96
+ * Errors in callbacks are caught and logged to prevent breaking execution
97
+ * @param event - The event to emit
98
+ */
99
+ emit<TData>(event: NodeEvent<TData>): void {
100
+ // Emit to type-specific listeners
101
+ const typeListeners = this.listeners.get(event.type);
102
+ if (typeListeners) {
103
+ for (const callback of typeListeners) {
104
+ try {
105
+ callback(event as NodeEvent<unknown>);
106
+ } catch (error) {
107
+ console.error(`Error in event callback for ${event.type}:`, error);
108
+ }
109
+ }
110
+ }
111
+
112
+ // Emit to "all events" listeners
113
+ for (const callback of this.allListeners) {
114
+ try {
115
+ callback(event);
116
+ } catch (error) {
117
+ console.error("Error in event callback (onAll):", error);
118
+ }
119
+ }
120
+ }
121
+
122
+ /**
123
+ * Remove all listeners
124
+ */
125
+ clear(): void {
126
+ this.listeners.clear();
127
+ this.allListeners.clear();
128
+ }
129
+
130
+ /**
131
+ * Get count of listeners for a specific type
132
+ * @param type - The event type to check
133
+ * @returns Number of listeners
134
+ */
135
+ listenerCount(type: NodeEventType): number {
136
+ return this.listeners.get(type)?.size || 0;
137
+ }
138
+
139
+ /**
140
+ * Get count of "all events" listeners
141
+ * @returns Number of listeners
142
+ */
143
+ allListenerCount(): number {
144
+ return this.allListeners.size;
145
+ }
146
+
147
+ /**
148
+ * Check if there are any listeners
149
+ * @returns True if any listeners are registered
150
+ */
151
+ hasListeners(): boolean {
152
+ return this.listeners.size > 0 || this.allListeners.size > 0;
153
+ }
154
+
155
+ /**
156
+ * Returns an Effect Stream of all events
157
+ * REMOVED: Effect-TS integration removed in Phase 1
158
+ * For streaming events in Temporal workflows, use native event emitter pattern
159
+ */
160
+ // toStream(): Stream.Stream<NodeEvent<unknown>> {
161
+ // return Stream.asyncPush<NodeEvent<unknown>>((emit) =>
162
+ // Effect.acquireRelease(
163
+ // Effect.sync(() => {
164
+ // const callback = (event: NodeEvent<unknown>) => {
165
+ // emit.single(event);
166
+ // };
167
+ // this.onAll(callback);
168
+ // return callback;
169
+ // }),
170
+ // (callback) => Effect.sync(() => this.offAll(callback)),
171
+ // ),
172
+ // );
173
+ // }
174
+
175
+ /**
176
+ * Returns an AsyncIterable of all events
177
+ * REMOVED: Effect-TS dependency removed in Phase 1
178
+ * Implement using native async generators if needed
179
+ */
180
+ // toAsyncIterable(): AsyncIterable<NodeEvent<unknown>> {
181
+ // return Stream.toAsyncIterable(this.toStream());
182
+ // }
183
+ }
package/src/index.ts ADDED
@@ -0,0 +1,80 @@
1
+ /**
2
+ * btree - Core behavior tree implementation
3
+ * Inspired by BehaviorTree.CPP
4
+ */
5
+
6
+ // Base nodes
7
+ export {
8
+ ActionNode,
9
+ BaseNode,
10
+ CompositeNode,
11
+ ConditionNode,
12
+ DecoratorNode,
13
+ } from "./base-node.js";
14
+ // Behavior tree wrapper
15
+ export { BehaviorTree, type ParsedPath } from "./behavior-tree.js";
16
+ // Blackboard
17
+ export { ScopedBlackboard } from "./blackboard.js";
18
+ // Composite nodes
19
+ export * from "./composites/index.js";
20
+
21
+ // Decorator nodes
22
+ export * from "./decorators/index.js";
23
+ // Event system
24
+ export * from "./events.js";
25
+ // Registry
26
+ export { Registry } from "./registry.js";
27
+ export { registerStandardNodes } from "./registry-utils.js";
28
+ // Scripting (deprecated) - use CodeExecution instead
29
+ // export * from "./scripting/index.js";
30
+ // Test nodes (for examples and testing)
31
+ export * from "./test-nodes.js";
32
+ export type {
33
+ IScopedBlackboard,
34
+ ITreeRegistry,
35
+ TemporalContext,
36
+ WorkflowArgs,
37
+ WorkflowResult,
38
+ // Activity types
39
+ BtreeActivities,
40
+ PieceActivityRequest,
41
+ PieceAuth,
42
+ PythonScriptRequest,
43
+ PythonScriptResult,
44
+ ParseFileRequest,
45
+ ParseFileResult,
46
+ GenerateFileRequest,
47
+ GenerateFileResult,
48
+ HttpRequestActivity,
49
+ HttpResponseActivity,
50
+ // Code execution types
51
+ CodeExecutionRequest,
52
+ CodeExecutionResult,
53
+ DataRef,
54
+ // Token provider
55
+ TokenProvider,
56
+ } from "./types.js";
57
+ // DataStore module
58
+ export * from "./data-store/index.js";
59
+ // Core types
60
+ export * from "./types.js";
61
+ // Re-export commonly used types for convenience
62
+ export { NodeStatus } from "./types.js";
63
+ // Utility nodes
64
+ export * from "./utilities/index.js";
65
+ // Debug nodes
66
+ export * from "./debug/index.js";
67
+ // Error types
68
+ export { ConfigurationError } from "./errors.js";
69
+ // YAML parsing and validation
70
+ export * from "./yaml/index.js";
71
+ // Schemas (for advanced usage)
72
+ export * from "./schemas/index.js";
73
+ // Template loading
74
+ export * from "./templates/template-loader.js";
75
+ // Integrations (Active Pieces)
76
+ export * from "./integrations/index.js";
77
+ // Activity-based action nodes
78
+ export * from "./actions/index.js";
79
+ // Observability (execution tracking, sinks)
80
+ export * from "./observability/index.js";
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Integrations Module
3
+ * Third-party service integrations via Active Pieces
4
+ */
5
+
6
+ // Main integration action node
7
+ export {
8
+ IntegrationAction,
9
+ type IntegrationActionConfig,
10
+ type IntegrationContext,
11
+ type TokenProvider,
12
+ envTokenProvider,
13
+ } from "./integration-action.js";
14
+
15
+ // Piece executor
16
+ export {
17
+ executePieceAction,
18
+ listPieceActions,
19
+ isPieceInstalled,
20
+ clearPieceCache,
21
+ } from "./piece-executor.js";
22
+
23
+ // Re-export types from types.ts for convenience
24
+ export type {
25
+ PieceAuth,
26
+ PieceActivityRequest,
27
+ } from "../types.js";
28
+
29
+ // Backward compatibility alias
30
+ export type { PieceActivityRequest as PieceActionRequest } from "../types.js";