@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,337 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { ScopedBlackboard } from "../blackboard.js";
3
+ import { MockAction } from "../test-nodes.js";
4
+ import { type TemporalContext, NodeStatus } from "../types.js";
5
+ import { checkSignal } from "../utils/signal-check.js";
6
+ import { Sequence } from "./sequence.js";
7
+
8
+ describe("Sequence", () => {
9
+ let context: TemporalContext;
10
+ let sequence: Sequence;
11
+
12
+ beforeEach(() => {
13
+ context = {
14
+ blackboard: new ScopedBlackboard(),
15
+ timestamp: Date.now(),
16
+ deltaTime: 0,
17
+ };
18
+ sequence = new Sequence({ id: "test-sequence" });
19
+ });
20
+
21
+ it("should return SUCCESS when empty", async () => {
22
+ const status = await sequence.tick(context);
23
+ expect(status).toBe(NodeStatus.SUCCESS);
24
+ });
25
+
26
+ it("should execute all children in order when all succeed", async () => {
27
+ const executionOrder: string[] = [];
28
+
29
+ const child1 = new MockAction({
30
+ id: "child1",
31
+ returnStatus: NodeStatus.SUCCESS,
32
+ });
33
+ const child2 = new MockAction({
34
+ id: "child2",
35
+ returnStatus: NodeStatus.SUCCESS,
36
+ });
37
+ const child3 = new MockAction({
38
+ id: "child3",
39
+ returnStatus: NodeStatus.SUCCESS,
40
+ });
41
+
42
+ // Spy on tick to track execution order
43
+ const originalTick1 = child1.tick.bind(child1);
44
+ const originalTick2 = child2.tick.bind(child2);
45
+ const originalTick3 = child3.tick.bind(child3);
46
+
47
+ child1.tick = (ctx) => {
48
+ executionOrder.push("child1");
49
+ return originalTick1(ctx);
50
+ };
51
+ child2.tick = (ctx) => {
52
+ executionOrder.push("child2");
53
+ return originalTick2(ctx);
54
+ };
55
+ child3.tick = (ctx) => {
56
+ executionOrder.push("child3");
57
+ return originalTick3(ctx);
58
+ };
59
+
60
+ sequence.addChildren([child1, child2, child3]);
61
+
62
+ const status = await sequence.tick(context);
63
+
64
+ expect(status).toBe(NodeStatus.SUCCESS);
65
+ expect(executionOrder).toEqual(["child1", "child2", "child3"]);
66
+ });
67
+
68
+ it("should stop and return FAILURE when a child fails", async () => {
69
+ const executionOrder: string[] = [];
70
+
71
+ const child1 = new MockAction({
72
+ id: "child1",
73
+ returnStatus: NodeStatus.SUCCESS,
74
+ });
75
+ const child2 = new MockAction({
76
+ id: "child2",
77
+ returnStatus: NodeStatus.FAILURE,
78
+ });
79
+ const child3 = new MockAction({
80
+ id: "child3",
81
+ returnStatus: NodeStatus.SUCCESS,
82
+ });
83
+
84
+ // Track execution
85
+ const originalTick1 = child1.tick.bind(child1);
86
+ const originalTick2 = child2.tick.bind(child2);
87
+ const originalTick3 = child3.tick.bind(child3);
88
+
89
+ child1.tick = (ctx) => {
90
+ executionOrder.push("child1");
91
+ return originalTick1(ctx);
92
+ };
93
+ child2.tick = (ctx) => {
94
+ executionOrder.push("child2");
95
+ return originalTick2(ctx);
96
+ };
97
+ child3.tick = (ctx) => {
98
+ executionOrder.push("child3");
99
+ return originalTick3(ctx);
100
+ };
101
+
102
+ sequence.addChildren([child1, child2, child3]);
103
+
104
+ const status = await sequence.tick(context);
105
+
106
+ expect(status).toBe(NodeStatus.FAILURE);
107
+ expect(executionOrder).toEqual(["child1", "child2"]); // child3 should not execute
108
+ expect(sequence.status()).toBe(NodeStatus.FAILURE);
109
+ });
110
+
111
+ it("should handle RUNNING status correctly", async () => {
112
+ const child1 = new MockAction({
113
+ id: "child1",
114
+ returnStatus: NodeStatus.SUCCESS,
115
+ });
116
+ let child2 = new MockAction({
117
+ id: "child2",
118
+ returnStatus: NodeStatus.RUNNING,
119
+ ticksBeforeComplete: 3,
120
+ });
121
+ const child3 = new MockAction({
122
+ id: "child3",
123
+ returnStatus: NodeStatus.SUCCESS,
124
+ });
125
+
126
+ sequence.addChildren([child1, child2, child3]);
127
+
128
+ // First tick - child2 returns RUNNING
129
+ let status = await sequence.tick(context);
130
+ expect(status).toBe(NodeStatus.RUNNING);
131
+ expect(child1.status()).toBe(NodeStatus.SUCCESS);
132
+ expect(child2.status()).toBe(NodeStatus.RUNNING);
133
+
134
+ // Second tick - child2 still RUNNING
135
+ status = await sequence.tick(context);
136
+ expect(status).toBe(NodeStatus.RUNNING);
137
+
138
+ // Third tick - child2 completes
139
+ child2 = new MockAction({
140
+ id: "child2",
141
+ returnStatus: NodeStatus.SUCCESS,
142
+ });
143
+ sequence._children[1] = child2; // Replace the child
144
+
145
+ status = await sequence.tick(context);
146
+ expect(status).toBe(NodeStatus.SUCCESS);
147
+ expect(child3.status()).toBe(NodeStatus.SUCCESS);
148
+ });
149
+
150
+ it("should reset child index on completion", async () => {
151
+ const child1 = new MockAction({
152
+ id: "child1",
153
+ returnStatus: NodeStatus.SUCCESS,
154
+ });
155
+ const child2 = new MockAction({
156
+ id: "child2",
157
+ returnStatus: NodeStatus.SUCCESS,
158
+ });
159
+
160
+ sequence.addChildren([child1, child2]);
161
+
162
+ // First execution
163
+ await sequence.tick(context);
164
+ expect((sequence as unknown).currentChildIndex).toBe(0);
165
+
166
+ // Second execution should start from beginning
167
+ await sequence.tick(context);
168
+ expect(child1.status()).toBe(NodeStatus.SUCCESS);
169
+ expect(child2.status()).toBe(NodeStatus.SUCCESS);
170
+ });
171
+
172
+ it("should halt running children when halted", async () => {
173
+ const child1 = new MockAction({
174
+ id: "child1",
175
+ returnStatus: NodeStatus.SUCCESS,
176
+ });
177
+ const child2 = new MockAction({
178
+ id: "child2",
179
+ returnStatus: NodeStatus.RUNNING,
180
+ });
181
+ const child3 = new MockAction({
182
+ id: "child3",
183
+ returnStatus: NodeStatus.SUCCESS,
184
+ });
185
+
186
+ sequence.addChildren([child1, child2, child3]);
187
+
188
+ // Start execution
189
+ await sequence.tick(context);
190
+ expect(sequence.status()).toBe(NodeStatus.RUNNING);
191
+
192
+ // Halt the sequence
193
+ sequence.halt();
194
+
195
+ expect(sequence.status()).toBe(NodeStatus.IDLE);
196
+ expect((sequence as unknown).currentChildIndex).toBe(0);
197
+ });
198
+
199
+ it("should throw error if child is undefined", () => {
200
+ expect(() => sequence.addChild(undefined as unknown)).toThrow(
201
+ "Cannot add undefined child to composite node",
202
+ );
203
+ });
204
+
205
+ it("should handle mixed sync and async children", async () => {
206
+ const syncChild = {
207
+ id: "sync",
208
+ name: "sync",
209
+ type: "SyncNode",
210
+ status: () => NodeStatus.SUCCESS,
211
+ tick: async (_ctx: TemporalContext) => NodeStatus.SUCCESS, // Sync tick
212
+ halt: () => {},
213
+ reset: () => {},
214
+ parent: undefined,
215
+ };
216
+
217
+ const asyncChild = new MockAction({
218
+ id: "async",
219
+ returnStatus: NodeStatus.SUCCESS,
220
+ });
221
+
222
+ sequence.addChildren([syncChild as unknown, asyncChild]);
223
+
224
+ const status = await sequence.tick(context);
225
+ expect(status).toBe(NodeStatus.SUCCESS);
226
+ });
227
+
228
+ describe("Signal-based cancellation", () => {
229
+ it("should stop executing children when signal is aborted", async () => {
230
+ const executionOrder: string[] = [];
231
+ const controller = new AbortController();
232
+
233
+ // Add signal to context
234
+ context.signal = controller.signal;
235
+
236
+ // Create children
237
+ const child1 = new MockAction({
238
+ id: "child1",
239
+ returnStatus: NodeStatus.SUCCESS,
240
+ });
241
+ const child2 = new MockAction({
242
+ id: "child2",
243
+ returnStatus: NodeStatus.SUCCESS,
244
+ });
245
+ const child3 = new MockAction({
246
+ id: "child3",
247
+ returnStatus: NodeStatus.SUCCESS,
248
+ });
249
+
250
+ // Track execution order
251
+ const originalTick1 = child1.tick.bind(child1);
252
+ const originalTick2 = child2.tick.bind(child2);
253
+ const originalTick3 = child3.tick.bind(child3);
254
+
255
+ child1.tick = (ctx: TemporalContext) => {
256
+ executionOrder.push("child1");
257
+ return originalTick1(ctx);
258
+ };
259
+
260
+ child2.tick = (ctx: TemporalContext) => {
261
+ executionOrder.push("child2");
262
+ return originalTick2(ctx);
263
+ };
264
+
265
+ child3.tick = (ctx: TemporalContext) => {
266
+ executionOrder.push("child3");
267
+ return originalTick3(ctx);
268
+ };
269
+
270
+ sequence.addChildren([child1, child2, child3]);
271
+
272
+ // Abort signal BEFORE ticking - Sequence should check signal immediately
273
+ controller.abort();
274
+
275
+ // Tick should fail with OperationCancelledError before any children execute
276
+ try {
277
+ await sequence.tick(context);
278
+ expect.fail("Should have thrown an error");
279
+ } catch (error) {
280
+ expect(error).toBeInstanceOf(Error);
281
+ expect((error as Error).name).toBe("OperationCancelledError");
282
+ }
283
+
284
+ // No children should execute because signal was already aborted
285
+ expect(executionOrder.length).toBe(0);
286
+ });
287
+
288
+ it("should respect abort signal in child iteration loop", async () => {
289
+ const controller = new AbortController();
290
+ const childrenExecuted: string[] = [];
291
+
292
+ // Add signal to context
293
+ context.signal = controller.signal;
294
+
295
+ // Create 5 children
296
+ const children = Array.from({ length: 5 }, (_, i) => {
297
+ const child = new MockAction({
298
+ id: `child${i}`,
299
+ returnStatus: NodeStatus.SUCCESS,
300
+ });
301
+ child.tick = async (ctx: TemporalContext) => {
302
+ await checkSignal(ctx.signal);
303
+ childrenExecuted.push(`child${i}`);
304
+ return NodeStatus.SUCCESS;
305
+ };
306
+ return child;
307
+ });
308
+
309
+ sequence.addChildren(children);
310
+
311
+ // Abort after 2 children have executed
312
+ let execCount = 0;
313
+ const _originalTick = children[0].tick;
314
+ children.forEach((child, _idx) => {
315
+ const orig = child.tick;
316
+ child.tick = async (ctx: TemporalContext) => {
317
+ execCount++;
318
+ if (execCount === 2) {
319
+ controller.abort();
320
+ }
321
+ return await orig(ctx);
322
+ };
323
+ });
324
+
325
+ try {
326
+ await sequence.tick(context);
327
+ expect.fail("Should have thrown an error");
328
+ } catch (error) {
329
+ expect(error).toBeInstanceOf(Error);
330
+ expect((error as Error).name).toBe("OperationCancelledError");
331
+ }
332
+
333
+ // Should stop after detecting abort
334
+ expect(childrenExecuted.length).toBeLessThanOrEqual(2);
335
+ });
336
+ });
337
+ });
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Sequence composite node
3
+ * Executes children in order until one fails or all succeed
4
+ */
5
+
6
+ import { CompositeNode } from "../base-node.js";
7
+ import { type TemporalContext, NodeStatus } from "../types.js";
8
+ import { checkSignal } from "../utils/signal-check.js";
9
+
10
+ export class Sequence extends CompositeNode {
11
+ private currentChildIndex: number = 0;
12
+
13
+ async executeTick(context: TemporalContext): Promise<NodeStatus> {
14
+ this.log("Ticking with", this._children.length, "children");
15
+
16
+ if (this._children.length === 0) {
17
+ return NodeStatus.SUCCESS;
18
+ }
19
+
20
+ // Continue from where we left off if RUNNING
21
+ while (this.currentChildIndex < this._children.length) {
22
+ // Check for cancellation before ticking each child
23
+ checkSignal(context.signal);
24
+
25
+ const child = this._children[this.currentChildIndex];
26
+ if (!child) {
27
+ throw new Error(
28
+ `Child at index ${this.currentChildIndex} is undefined`,
29
+ );
30
+ }
31
+
32
+ this.log(`Ticking child ${this.currentChildIndex}: ${child.name}`);
33
+ const childStatus = await child.tick(context);
34
+
35
+ switch (childStatus) {
36
+ case NodeStatus.SUCCESS:
37
+ this.log(`Child ${child.name} succeeded`);
38
+ this.currentChildIndex++;
39
+ break;
40
+
41
+ case NodeStatus.FAILURE:
42
+ this.log(`Child ${child.name} failed - sequence fails`);
43
+ this._status = NodeStatus.FAILURE;
44
+ this.currentChildIndex = 0;
45
+ return NodeStatus.FAILURE;
46
+
47
+ case NodeStatus.RUNNING:
48
+ this.log(`Child ${child.name} is running`);
49
+ this._status = NodeStatus.RUNNING;
50
+ return NodeStatus.RUNNING;
51
+
52
+ default:
53
+ throw new Error(`Unexpected status from child: ${childStatus}`);
54
+ }
55
+ }
56
+
57
+ // All children succeeded
58
+ this.log("All children succeeded");
59
+ this._status = NodeStatus.SUCCESS;
60
+ this.currentChildIndex = 0;
61
+ return NodeStatus.SUCCESS;
62
+ }
63
+
64
+ protected onHalt(): void {
65
+ this.haltChildren(this.currentChildIndex);
66
+ this.currentChildIndex = 0;
67
+ }
68
+
69
+ protected onReset(): void {
70
+ this.currentChildIndex = 0;
71
+ }
72
+ }
@@ -0,0 +1,21 @@
1
+ /**
2
+ * SubTree composite configuration schema
3
+ */
4
+
5
+ import { z } from "zod";
6
+ import { createNodeSchema, validations } from "../schemas/base.schema.js";
7
+
8
+ /**
9
+ * Schema for SubTree composite configuration
10
+ * Validates treeId reference
11
+ */
12
+ export const subTreeConfigurationSchema = createNodeSchema("SubTree", {
13
+ treeId: validations.treeId,
14
+ });
15
+
16
+ /**
17
+ * Validated SubTree configuration type
18
+ */
19
+ export type ValidatedSubTreeConfiguration = z.infer<
20
+ typeof subTreeConfigurationSchema
21
+ >;