@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.
- package/.claude/settings.local.json +31 -0
- package/CLAUDE.md +181 -0
- package/LICENSE +21 -0
- package/README.md +920 -0
- package/behaviour-tree-workflows-landing/index.html +16 -0
- package/behaviour-tree-workflows-landing/package-lock.json +2074 -0
- package/behaviour-tree-workflows-landing/package.json +31 -0
- package/behaviour-tree-workflows-landing/public/favicon.svg +17 -0
- package/behaviour-tree-workflows-landing/src/App.css +103 -0
- package/behaviour-tree-workflows-landing/src/App.tsx +176 -0
- package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.css +89 -0
- package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.tsx +64 -0
- package/behaviour-tree-workflows-landing/src/components/ExampleSelector.css +64 -0
- package/behaviour-tree-workflows-landing/src/components/ExampleSelector.tsx +34 -0
- package/behaviour-tree-workflows-landing/src/components/ExecutionLog.css +107 -0
- package/behaviour-tree-workflows-landing/src/components/ExecutionLog.tsx +85 -0
- package/behaviour-tree-workflows-landing/src/components/Header.css +50 -0
- package/behaviour-tree-workflows-landing/src/components/Header.tsx +26 -0
- package/behaviour-tree-workflows-landing/src/components/StatusBadge.css +45 -0
- package/behaviour-tree-workflows-landing/src/components/StatusBadge.tsx +15 -0
- package/behaviour-tree-workflows-landing/src/components/Toolbar.css +74 -0
- package/behaviour-tree-workflows-landing/src/components/Toolbar.tsx +53 -0
- package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.css +67 -0
- package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.tsx +192 -0
- package/behaviour-tree-workflows-landing/src/components/YamlEditor.css +18 -0
- package/behaviour-tree-workflows-landing/src/components/YamlEditor.tsx +96 -0
- package/behaviour-tree-workflows-landing/src/lib/count-nodes.ts +11 -0
- package/behaviour-tree-workflows-landing/src/lib/execution-engine.ts +96 -0
- package/behaviour-tree-workflows-landing/src/lib/tree-layout.ts +136 -0
- package/behaviour-tree-workflows-landing/src/lib/yaml-examples.ts +549 -0
- package/behaviour-tree-workflows-landing/src/main.tsx +9 -0
- package/behaviour-tree-workflows-landing/src/stubs/activepieces.ts +18 -0
- package/behaviour-tree-workflows-landing/src/stubs/fs.ts +24 -0
- package/behaviour-tree-workflows-landing/src/stubs/path.ts +16 -0
- package/behaviour-tree-workflows-landing/src/stubs/temporal-activity.ts +6 -0
- package/behaviour-tree-workflows-landing/src/stubs/temporal-workflow.ts +22 -0
- package/behaviour-tree-workflows-landing/tsconfig.json +25 -0
- package/behaviour-tree-workflows-landing/vite.config.ts +40 -0
- package/demo-google-sheets.ts +181 -0
- package/demo-runtime-variables.ts +174 -0
- package/demo-template.ts +208 -0
- package/docs/ARCHITECTURE_SUMMARY.md +613 -0
- package/docs/NODE_REFERENCE.md +504 -0
- package/docs/README.md +53 -0
- package/docs/custom-nodes-architecture.md +826 -0
- package/docs/observability.md +175 -0
- package/docs/yaml-specification.md +990 -0
- package/examples/temporal/README.md +117 -0
- package/examples/temporal/activities.ts +373 -0
- package/examples/temporal/client.ts +115 -0
- package/examples/temporal/python-worker/activities.py +339 -0
- package/examples/temporal/python-worker/requirements.txt +12 -0
- package/examples/temporal/python-worker/worker.py +106 -0
- package/examples/temporal/worker.ts +66 -0
- package/examples/temporal/workflows.ts +6 -0
- package/examples/temporal/yaml-workflow-loader.ts +105 -0
- package/examples/yaml-test.ts +97 -0
- package/examples/yaml-workflows/01-simple-sequence.yaml +25 -0
- package/examples/yaml-workflows/02-parallel-timeout.yaml +45 -0
- package/examples/yaml-workflows/03-ecommerce-checkout.yaml +94 -0
- package/examples/yaml-workflows/04-ai-agent-workflow.yaml +346 -0
- package/examples/yaml-workflows/05-order-processing.yaml +146 -0
- package/examples/yaml-workflows/06-activity-test.yaml +71 -0
- package/examples/yaml-workflows/07-activity-simple-test.yaml +43 -0
- package/examples/yaml-workflows/08-file-processing.yaml +141 -0
- package/examples/yaml-workflows/09-http-request.yaml +137 -0
- package/examples/yaml-workflows/README.md +211 -0
- package/package.json +38 -0
- package/src/actions/code-execution.schema.ts +27 -0
- package/src/actions/code-execution.ts +218 -0
- package/src/actions/generate-file.test.ts +516 -0
- package/src/actions/generate-file.ts +166 -0
- package/src/actions/http-request.test.ts +784 -0
- package/src/actions/http-request.ts +228 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/parse-file.test.ts +448 -0
- package/src/actions/parse-file.ts +139 -0
- package/src/actions/python-script.test.ts +439 -0
- package/src/actions/python-script.ts +154 -0
- package/src/base-node.test.ts +511 -0
- package/src/base-node.ts +605 -0
- package/src/behavior-tree.test.ts +431 -0
- package/src/behavior-tree.ts +283 -0
- package/src/blackboard.test.ts +222 -0
- package/src/blackboard.ts +192 -0
- package/src/composites/conditional.schema.ts +19 -0
- package/src/composites/conditional.test.ts +309 -0
- package/src/composites/conditional.ts +129 -0
- package/src/composites/for-each.schema.ts +23 -0
- package/src/composites/for-each.test.ts +254 -0
- package/src/composites/for-each.ts +132 -0
- package/src/composites/index.ts +15 -0
- package/src/composites/memory-sequence.schema.ts +19 -0
- package/src/composites/memory-sequence.test.ts +223 -0
- package/src/composites/memory-sequence.ts +98 -0
- package/src/composites/parallel.schema.ts +28 -0
- package/src/composites/parallel.test.ts +502 -0
- package/src/composites/parallel.ts +157 -0
- package/src/composites/reactive-sequence.schema.ts +19 -0
- package/src/composites/reactive-sequence.test.ts +170 -0
- package/src/composites/reactive-sequence.ts +85 -0
- package/src/composites/recovery.schema.ts +19 -0
- package/src/composites/recovery.test.ts +366 -0
- package/src/composites/recovery.ts +90 -0
- package/src/composites/selector.schema.ts +19 -0
- package/src/composites/selector.test.ts +387 -0
- package/src/composites/selector.ts +85 -0
- package/src/composites/sequence.schema.ts +19 -0
- package/src/composites/sequence.test.ts +337 -0
- package/src/composites/sequence.ts +72 -0
- package/src/composites/sub-tree.schema.ts +21 -0
- package/src/composites/sub-tree.test.ts +893 -0
- package/src/composites/sub-tree.ts +177 -0
- package/src/composites/while.schema.ts +24 -0
- package/src/composites/while.test.ts +381 -0
- package/src/composites/while.ts +149 -0
- package/src/data-store/index.ts +10 -0
- package/src/data-store/memory-store.ts +161 -0
- package/src/data-store/types.ts +94 -0
- package/src/debug/breakpoint.test.ts +47 -0
- package/src/debug/breakpoint.ts +30 -0
- package/src/debug/index.ts +17 -0
- package/src/debug/resume-point.test.ts +49 -0
- package/src/debug/resume-point.ts +29 -0
- package/src/decorators/delay.schema.ts +21 -0
- package/src/decorators/delay.test.ts +261 -0
- package/src/decorators/delay.ts +140 -0
- package/src/decorators/force-result.schema.ts +32 -0
- package/src/decorators/force-result.test.ts +133 -0
- package/src/decorators/force-result.ts +63 -0
- package/src/decorators/index.ts +13 -0
- package/src/decorators/invert.schema.ts +19 -0
- package/src/decorators/invert.test.ts +135 -0
- package/src/decorators/invert.ts +42 -0
- package/src/decorators/keep-running.schema.ts +20 -0
- package/src/decorators/keep-running.test.ts +105 -0
- package/src/decorators/keep-running.ts +49 -0
- package/src/decorators/precondition.schema.ts +19 -0
- package/src/decorators/precondition.test.ts +351 -0
- package/src/decorators/precondition.ts +139 -0
- package/src/decorators/repeat.schema.ts +21 -0
- package/src/decorators/repeat.test.ts +187 -0
- package/src/decorators/repeat.ts +94 -0
- package/src/decorators/run-once.schema.ts +19 -0
- package/src/decorators/run-once.test.ts +140 -0
- package/src/decorators/run-once.ts +61 -0
- package/src/decorators/soft-assert.schema.ts +19 -0
- package/src/decorators/soft-assert.test.ts +107 -0
- package/src/decorators/soft-assert.ts +68 -0
- package/src/decorators/timeout.schema.ts +21 -0
- package/src/decorators/timeout.test.ts +274 -0
- package/src/decorators/timeout.ts +159 -0
- package/src/errors.test.ts +63 -0
- package/src/errors.ts +34 -0
- package/src/events.test.ts +347 -0
- package/src/events.ts +183 -0
- package/src/index.ts +80 -0
- package/src/integrations/index.ts +30 -0
- package/src/integrations/integration-action.test.ts +571 -0
- package/src/integrations/integration-action.ts +233 -0
- package/src/integrations/piece-executor.ts +320 -0
- package/src/observability/execution-tracker.ts +320 -0
- package/src/observability/index.ts +23 -0
- package/src/observability/sinks.ts +138 -0
- package/src/observability/types.ts +130 -0
- package/src/registry-utils.ts +147 -0
- package/src/registry.test.ts +466 -0
- package/src/registry.ts +334 -0
- package/src/schemas/base.schema.ts +104 -0
- package/src/schemas/index.ts +223 -0
- package/src/schemas/integration.test.ts +238 -0
- package/src/schemas/tree-definition.schema.ts +170 -0
- package/src/schemas/validation.test.ts +146 -0
- package/src/schemas/validation.ts +122 -0
- package/src/scripting/index.ts +22 -0
- package/src/templates/template-loader.test.ts +281 -0
- package/src/templates/template-loader.ts +152 -0
- package/src/temporal-integration.test.ts +213 -0
- package/src/test-nodes.ts +259 -0
- package/src/types.ts +503 -0
- package/src/utilities/index.ts +17 -0
- package/src/utilities/log-message.test.ts +275 -0
- package/src/utilities/log-message.ts +134 -0
- package/src/utilities/regex-extract.test.ts +138 -0
- package/src/utilities/regex-extract.ts +108 -0
- package/src/utilities/variable-resolver.test.ts +416 -0
- package/src/utilities/variable-resolver.ts +318 -0
- package/src/utils/error-handler.test.ts +117 -0
- package/src/utils/error-handler.ts +48 -0
- package/src/utils/signal-check.test.ts +234 -0
- package/src/utils/signal-check.ts +140 -0
- package/src/yaml/errors.ts +143 -0
- package/src/yaml/index.ts +30 -0
- package/src/yaml/loader.ts +39 -0
- package/src/yaml/parser.ts +286 -0
- package/src/yaml/validation/semantic-validator.ts +196 -0
- package/templates/google-sheets/insert-row.yaml +76 -0
- package/templates/notification-sender.yaml +33 -0
- package/templates/order-validation.yaml +44 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +25 -0
- package/workflows/order-processor.yaml +59 -0
- 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";
|