@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
package/src/base-node.ts
ADDED
|
@@ -0,0 +1,605 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base implementation for all behavior tree nodes
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ConfigurationError } from "./errors.js";
|
|
6
|
+
import { NodeEventType } from "./events.js";
|
|
7
|
+
import {
|
|
8
|
+
type TemporalContext,
|
|
9
|
+
type NodeConfiguration,
|
|
10
|
+
NodeStatus,
|
|
11
|
+
type PortDefinition,
|
|
12
|
+
type TreeNode,
|
|
13
|
+
} from "./types.js";
|
|
14
|
+
import { OperationCancelledError } from "./utils/signal-check.js";
|
|
15
|
+
import { handleNodeError } from "./utils/error-handler.js";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Abstract base class for all tree nodes
|
|
19
|
+
*/
|
|
20
|
+
export abstract class BaseNode implements TreeNode {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly type: string;
|
|
24
|
+
|
|
25
|
+
protected _status: NodeStatus = NodeStatus.IDLE;
|
|
26
|
+
protected _lastError?: string;
|
|
27
|
+
protected config: NodeConfiguration;
|
|
28
|
+
protected _eventEmitter?: import("./events.js").NodeEventEmitter;
|
|
29
|
+
|
|
30
|
+
parent?: TreeNode;
|
|
31
|
+
children?: TreeNode[];
|
|
32
|
+
|
|
33
|
+
constructor(config: NodeConfiguration) {
|
|
34
|
+
this.id = config.id;
|
|
35
|
+
this.name = config.name || config.id;
|
|
36
|
+
this.type = this.constructor.name;
|
|
37
|
+
this.config = config;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Main tick method - subclasses override to implement execution logic
|
|
42
|
+
* Returns Promise for async/RUNNING semantics
|
|
43
|
+
* All errors are caught and converted to NodeStatus.FAILURE
|
|
44
|
+
*/
|
|
45
|
+
abstract tick(context: TemporalContext): Promise<NodeStatus>;
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Clone this node (deep copy including children)
|
|
49
|
+
* Must be implemented by subclasses
|
|
50
|
+
*/
|
|
51
|
+
abstract clone(): TreeNode;
|
|
52
|
+
|
|
53
|
+
halt(): void {
|
|
54
|
+
console.log(`[${this.type}:${this.name}] Halting...`);
|
|
55
|
+
|
|
56
|
+
// Emit HALT event
|
|
57
|
+
this._eventEmitter?.emit({
|
|
58
|
+
type: NodeEventType.HALT,
|
|
59
|
+
nodeId: this.id,
|
|
60
|
+
nodeName: this.name,
|
|
61
|
+
nodeType: this.type,
|
|
62
|
+
timestamp: Date.now(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
if (this._status === NodeStatus.RUNNING) {
|
|
66
|
+
this.onHalt();
|
|
67
|
+
this._status = NodeStatus.IDLE;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
reset(): void {
|
|
72
|
+
console.log(`[${this.type}:${this.name}] Resetting...`);
|
|
73
|
+
|
|
74
|
+
// Emit RESET event
|
|
75
|
+
this._eventEmitter?.emit({
|
|
76
|
+
type: NodeEventType.RESET,
|
|
77
|
+
nodeId: this.id,
|
|
78
|
+
nodeName: this.name,
|
|
79
|
+
nodeType: this.type,
|
|
80
|
+
timestamp: Date.now(),
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
this._status = NodeStatus.IDLE;
|
|
84
|
+
this._lastError = undefined;
|
|
85
|
+
this.onReset();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
status(): NodeStatus {
|
|
89
|
+
return this._status;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get lastError(): string | undefined {
|
|
93
|
+
return this._lastError;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
providedPorts(): PortDefinition[] {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Hook for derived classes to implement custom halt logic
|
|
102
|
+
*/
|
|
103
|
+
protected onHalt(): void {
|
|
104
|
+
// Override in derived classes if needed
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Hook for derived classes to implement custom reset logic
|
|
109
|
+
*/
|
|
110
|
+
protected onReset(): void {
|
|
111
|
+
// Override in derived classes if needed
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Helper to get input value from blackboard
|
|
116
|
+
*/
|
|
117
|
+
protected getInput<T>(
|
|
118
|
+
context: TemporalContext,
|
|
119
|
+
key: string,
|
|
120
|
+
defaultValue?: T,
|
|
121
|
+
): T {
|
|
122
|
+
const fullKey = (this.config[key] as string) || key;
|
|
123
|
+
return context.blackboard.getPort<T>(fullKey, defaultValue);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Helper to set output value to blackboard
|
|
128
|
+
*/
|
|
129
|
+
protected setOutput<T>(
|
|
130
|
+
context: TemporalContext,
|
|
131
|
+
key: string,
|
|
132
|
+
value: T,
|
|
133
|
+
): void {
|
|
134
|
+
const fullKey = (this.config[key] as string) || key;
|
|
135
|
+
context.blackboard.setPort(fullKey, value);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Log helper for debugging
|
|
140
|
+
*/
|
|
141
|
+
protected log(message: string, ...args: unknown[]): void {
|
|
142
|
+
console.log(`[${this.type}:${this.name}] ${message}`, ...args);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Base class for action nodes
|
|
148
|
+
* Includes resumable execution support and Effect-based async/RUNNING semantics
|
|
149
|
+
*/
|
|
150
|
+
export abstract class ActionNode extends BaseNode {
|
|
151
|
+
/**
|
|
152
|
+
* Clone this action node
|
|
153
|
+
* Leaf nodes have no children to clone
|
|
154
|
+
*/
|
|
155
|
+
clone(): TreeNode {
|
|
156
|
+
const ClonedClass = this.constructor as new (
|
|
157
|
+
config: NodeConfiguration,
|
|
158
|
+
) => this;
|
|
159
|
+
return new ClonedClass({ ...this.config });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Tick with resumable execution support for leaf nodes
|
|
164
|
+
* Uses async/await for Promise-based async/RUNNING semantics
|
|
165
|
+
* All errors are caught and converted to NodeStatus.FAILURE
|
|
166
|
+
*/
|
|
167
|
+
async tick(context: TemporalContext): Promise<NodeStatus> {
|
|
168
|
+
try {
|
|
169
|
+
// Store eventEmitter reference for halt/reset
|
|
170
|
+
this._eventEmitter = context.eventEmitter;
|
|
171
|
+
|
|
172
|
+
// Emit TICK_START event
|
|
173
|
+
context.eventEmitter?.emit({
|
|
174
|
+
type: NodeEventType.TICK_START,
|
|
175
|
+
nodeId: this.id,
|
|
176
|
+
nodeName: this.name,
|
|
177
|
+
nodeType: this.type,
|
|
178
|
+
timestamp: Date.now(),
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
// Execute the actual node logic
|
|
182
|
+
const status = await this.executeTick(context);
|
|
183
|
+
this._status = status;
|
|
184
|
+
|
|
185
|
+
// Emit TICK_END event with status
|
|
186
|
+
context.eventEmitter?.emit({
|
|
187
|
+
type: NodeEventType.TICK_END,
|
|
188
|
+
nodeId: this.id,
|
|
189
|
+
nodeName: this.name,
|
|
190
|
+
nodeType: this.type,
|
|
191
|
+
timestamp: Date.now(),
|
|
192
|
+
data: { status },
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
return status;
|
|
196
|
+
} catch (error: unknown) {
|
|
197
|
+
const errorMessage =
|
|
198
|
+
error instanceof Error ? error.message : String(error);
|
|
199
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
200
|
+
|
|
201
|
+
// Store the error message
|
|
202
|
+
this._lastError = errorMessage;
|
|
203
|
+
this._status = NodeStatus.FAILURE;
|
|
204
|
+
|
|
205
|
+
// Emit ERROR event with proper format for ExecutionTracker
|
|
206
|
+
context.eventEmitter?.emit({
|
|
207
|
+
type: NodeEventType.ERROR,
|
|
208
|
+
nodeId: this.id,
|
|
209
|
+
nodeName: this.name,
|
|
210
|
+
nodeType: this.type,
|
|
211
|
+
timestamp: Date.now(),
|
|
212
|
+
data: {
|
|
213
|
+
error: {
|
|
214
|
+
message: errorMessage,
|
|
215
|
+
stack: errorStack,
|
|
216
|
+
},
|
|
217
|
+
blackboard: context.blackboard?.toJSON?.() ?? {},
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
// Emit TICK_END with FAILURE status
|
|
222
|
+
context.eventEmitter?.emit({
|
|
223
|
+
type: NodeEventType.TICK_END,
|
|
224
|
+
nodeId: this.id,
|
|
225
|
+
nodeName: this.name,
|
|
226
|
+
nodeType: this.type,
|
|
227
|
+
timestamp: Date.now(),
|
|
228
|
+
data: { status: NodeStatus.FAILURE },
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
// Use centralized error handler for consistent behavior
|
|
232
|
+
return handleNodeError(error);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Abstract method for subclasses to implement their execution logic
|
|
238
|
+
* Returns Promise for async operations
|
|
239
|
+
*/
|
|
240
|
+
protected abstract executeTick(
|
|
241
|
+
context: TemporalContext,
|
|
242
|
+
): Promise<NodeStatus>;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Base class for condition nodes
|
|
247
|
+
* Includes resumable execution support and async/RUNNING semantics
|
|
248
|
+
*/
|
|
249
|
+
export abstract class ConditionNode extends BaseNode {
|
|
250
|
+
/**
|
|
251
|
+
* Clone this condition node
|
|
252
|
+
* Leaf nodes have no children to clone
|
|
253
|
+
*/
|
|
254
|
+
clone(): TreeNode {
|
|
255
|
+
const ClonedClass = this.constructor as new (
|
|
256
|
+
config: NodeConfiguration,
|
|
257
|
+
) => this;
|
|
258
|
+
return new ClonedClass({ ...this.config });
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Tick with resumable execution support for leaf nodes
|
|
263
|
+
* Uses async/await for Promise-based async/RUNNING semantics
|
|
264
|
+
* All errors are caught and converted to NodeStatus.FAILURE
|
|
265
|
+
*/
|
|
266
|
+
async tick(context: TemporalContext): Promise<NodeStatus> {
|
|
267
|
+
try {
|
|
268
|
+
// Store eventEmitter reference for halt/reset
|
|
269
|
+
this._eventEmitter = context.eventEmitter;
|
|
270
|
+
|
|
271
|
+
// Emit TICK_START event
|
|
272
|
+
context.eventEmitter?.emit({
|
|
273
|
+
type: NodeEventType.TICK_START,
|
|
274
|
+
nodeId: this.id,
|
|
275
|
+
nodeName: this.name,
|
|
276
|
+
nodeType: this.type,
|
|
277
|
+
timestamp: Date.now(),
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
// Execute the actual node logic
|
|
281
|
+
const status = await this.executeTick(context);
|
|
282
|
+
this._status = status;
|
|
283
|
+
|
|
284
|
+
// Emit TICK_END event with status
|
|
285
|
+
context.eventEmitter?.emit({
|
|
286
|
+
type: NodeEventType.TICK_END,
|
|
287
|
+
nodeId: this.id,
|
|
288
|
+
nodeName: this.name,
|
|
289
|
+
nodeType: this.type,
|
|
290
|
+
timestamp: Date.now(),
|
|
291
|
+
data: { status },
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
return status;
|
|
295
|
+
} catch (error: unknown) {
|
|
296
|
+
const errorMessage =
|
|
297
|
+
error instanceof Error ? error.message : String(error);
|
|
298
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
299
|
+
|
|
300
|
+
// Store the error message
|
|
301
|
+
this._lastError = errorMessage;
|
|
302
|
+
this._status = NodeStatus.FAILURE;
|
|
303
|
+
|
|
304
|
+
// Emit ERROR event with proper format for ExecutionTracker
|
|
305
|
+
context.eventEmitter?.emit({
|
|
306
|
+
type: NodeEventType.ERROR,
|
|
307
|
+
nodeId: this.id,
|
|
308
|
+
nodeName: this.name,
|
|
309
|
+
nodeType: this.type,
|
|
310
|
+
timestamp: Date.now(),
|
|
311
|
+
data: {
|
|
312
|
+
error: {
|
|
313
|
+
message: errorMessage,
|
|
314
|
+
stack: errorStack,
|
|
315
|
+
},
|
|
316
|
+
blackboard: context.blackboard?.toJSON?.() ?? {},
|
|
317
|
+
},
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Emit TICK_END with FAILURE status
|
|
321
|
+
context.eventEmitter?.emit({
|
|
322
|
+
type: NodeEventType.TICK_END,
|
|
323
|
+
nodeId: this.id,
|
|
324
|
+
nodeName: this.name,
|
|
325
|
+
nodeType: this.type,
|
|
326
|
+
timestamp: Date.now(),
|
|
327
|
+
data: { status: NodeStatus.FAILURE },
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
// Use centralized error handler for consistent behavior
|
|
331
|
+
return handleNodeError(error);
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Abstract method for subclasses to implement their execution logic
|
|
337
|
+
* Returns Promise for async operations
|
|
338
|
+
*/
|
|
339
|
+
protected abstract executeTick(
|
|
340
|
+
context: TemporalContext,
|
|
341
|
+
): Promise<NodeStatus>;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Base class for decorator nodes (single child)
|
|
346
|
+
*/
|
|
347
|
+
export abstract class DecoratorNode extends BaseNode {
|
|
348
|
+
protected child?: TreeNode;
|
|
349
|
+
|
|
350
|
+
/**
|
|
351
|
+
* Clone this decorator node including its child
|
|
352
|
+
*/
|
|
353
|
+
clone(): TreeNode {
|
|
354
|
+
const ClonedClass = this.constructor as new (
|
|
355
|
+
config: NodeConfiguration,
|
|
356
|
+
) => this;
|
|
357
|
+
const cloned = new ClonedClass({ ...this.config });
|
|
358
|
+
if (this.child) {
|
|
359
|
+
cloned.setChild(this.child.clone());
|
|
360
|
+
}
|
|
361
|
+
return cloned;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
/**
|
|
365
|
+
* Tick with resumable execution support - decorators can be resume points
|
|
366
|
+
* Uses async/await for Promise-based async/RUNNING semantics
|
|
367
|
+
* All errors are caught and converted to NodeStatus.FAILURE
|
|
368
|
+
*/
|
|
369
|
+
async tick(context: TemporalContext): Promise<NodeStatus> {
|
|
370
|
+
try {
|
|
371
|
+
// Emit TICK_START event
|
|
372
|
+
context.eventEmitter?.emit({
|
|
373
|
+
type: NodeEventType.TICK_START,
|
|
374
|
+
nodeId: this.id,
|
|
375
|
+
nodeName: this.name,
|
|
376
|
+
nodeType: this.type,
|
|
377
|
+
timestamp: Date.now(),
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Execute decorator's tick logic
|
|
381
|
+
const status = await this.executeTick(context);
|
|
382
|
+
|
|
383
|
+
// Emit TICK_END event
|
|
384
|
+
context.eventEmitter?.emit({
|
|
385
|
+
type: NodeEventType.TICK_END,
|
|
386
|
+
nodeId: this.id,
|
|
387
|
+
nodeName: this.name,
|
|
388
|
+
nodeType: this.type,
|
|
389
|
+
timestamp: Date.now(),
|
|
390
|
+
data: { status },
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
return status;
|
|
394
|
+
} catch (error: unknown) {
|
|
395
|
+
const errorMessage =
|
|
396
|
+
error instanceof Error ? error.message : String(error);
|
|
397
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
398
|
+
|
|
399
|
+
// Store the error message
|
|
400
|
+
this._lastError = errorMessage;
|
|
401
|
+
this._status = NodeStatus.FAILURE;
|
|
402
|
+
|
|
403
|
+
// Emit ERROR event with proper format for ExecutionTracker
|
|
404
|
+
context.eventEmitter?.emit({
|
|
405
|
+
type: NodeEventType.ERROR,
|
|
406
|
+
nodeId: this.id,
|
|
407
|
+
nodeName: this.name,
|
|
408
|
+
nodeType: this.type,
|
|
409
|
+
timestamp: Date.now(),
|
|
410
|
+
data: {
|
|
411
|
+
error: {
|
|
412
|
+
message: errorMessage,
|
|
413
|
+
stack: errorStack,
|
|
414
|
+
},
|
|
415
|
+
blackboard: context.blackboard?.toJSON?.() ?? {},
|
|
416
|
+
},
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
// Emit TICK_END with FAILURE status
|
|
420
|
+
context.eventEmitter?.emit({
|
|
421
|
+
type: NodeEventType.TICK_END,
|
|
422
|
+
nodeId: this.id,
|
|
423
|
+
nodeName: this.name,
|
|
424
|
+
nodeType: this.type,
|
|
425
|
+
timestamp: Date.now(),
|
|
426
|
+
data: { status: NodeStatus.FAILURE },
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
// Use centralized error handler for consistent behavior
|
|
430
|
+
return handleNodeError(error);
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Decorator nodes must implement their wrapping logic
|
|
436
|
+
* Returns Promise for async operations
|
|
437
|
+
*/
|
|
438
|
+
protected abstract executeTick(
|
|
439
|
+
context: TemporalContext,
|
|
440
|
+
): Promise<NodeStatus>;
|
|
441
|
+
|
|
442
|
+
setChild(child: TreeNode): void {
|
|
443
|
+
if (!child) {
|
|
444
|
+
throw new Error("Cannot set undefined child on decorator node");
|
|
445
|
+
}
|
|
446
|
+
this.child = child;
|
|
447
|
+
this.children = [child];
|
|
448
|
+
child.parent = this;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
halt(): void {
|
|
452
|
+
super.halt();
|
|
453
|
+
if (this.child && this.child.status() === NodeStatus.RUNNING) {
|
|
454
|
+
this.child.halt();
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
reset(): void {
|
|
459
|
+
super.reset();
|
|
460
|
+
if (this.child) {
|
|
461
|
+
this.child.reset();
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Base class for composite nodes (multiple children)
|
|
468
|
+
*/
|
|
469
|
+
export abstract class CompositeNode extends BaseNode {
|
|
470
|
+
protected _children: TreeNode[] = [];
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Clone this composite node including all children
|
|
474
|
+
*/
|
|
475
|
+
clone(): TreeNode {
|
|
476
|
+
const ClonedClass = this.constructor as new (
|
|
477
|
+
config: NodeConfiguration,
|
|
478
|
+
) => this;
|
|
479
|
+
const cloned = new ClonedClass({ ...this.config });
|
|
480
|
+
// Clone all children
|
|
481
|
+
const clonedChildren = this._children.map((child) => child.clone());
|
|
482
|
+
cloned.addChildren(clonedChildren);
|
|
483
|
+
return cloned;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* Tick with resumable execution support - composites can be resume points
|
|
488
|
+
* Uses async/await for Promise-based async/RUNNING semantics
|
|
489
|
+
* All errors are caught and converted to NodeStatus.FAILURE
|
|
490
|
+
*/
|
|
491
|
+
async tick(context: TemporalContext): Promise<NodeStatus> {
|
|
492
|
+
try {
|
|
493
|
+
// Emit TICK_START event
|
|
494
|
+
context.eventEmitter?.emit({
|
|
495
|
+
type: NodeEventType.TICK_START,
|
|
496
|
+
nodeId: this.id,
|
|
497
|
+
nodeName: this.name,
|
|
498
|
+
nodeType: this.type,
|
|
499
|
+
timestamp: Date.now(),
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
// Execute composite's tick logic
|
|
503
|
+
const status = await this.executeTick(context);
|
|
504
|
+
|
|
505
|
+
// Emit TICK_END event
|
|
506
|
+
context.eventEmitter?.emit({
|
|
507
|
+
type: NodeEventType.TICK_END,
|
|
508
|
+
nodeId: this.id,
|
|
509
|
+
nodeName: this.name,
|
|
510
|
+
nodeType: this.type,
|
|
511
|
+
timestamp: Date.now(),
|
|
512
|
+
data: { status },
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
return status;
|
|
516
|
+
} catch (error: unknown) {
|
|
517
|
+
const errorMessage =
|
|
518
|
+
error instanceof Error ? error.message : String(error);
|
|
519
|
+
const errorStack = error instanceof Error ? error.stack : undefined;
|
|
520
|
+
|
|
521
|
+
// Store the error message
|
|
522
|
+
this._lastError = errorMessage;
|
|
523
|
+
this._status = NodeStatus.FAILURE;
|
|
524
|
+
|
|
525
|
+
// Emit ERROR event with proper format for ExecutionTracker
|
|
526
|
+
context.eventEmitter?.emit({
|
|
527
|
+
type: NodeEventType.ERROR,
|
|
528
|
+
nodeId: this.id,
|
|
529
|
+
nodeName: this.name,
|
|
530
|
+
nodeType: this.type,
|
|
531
|
+
timestamp: Date.now(),
|
|
532
|
+
data: {
|
|
533
|
+
error: {
|
|
534
|
+
message: errorMessage,
|
|
535
|
+
stack: errorStack,
|
|
536
|
+
},
|
|
537
|
+
blackboard: context.blackboard?.toJSON?.() ?? {},
|
|
538
|
+
},
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
// Emit TICK_END with FAILURE status
|
|
542
|
+
context.eventEmitter?.emit({
|
|
543
|
+
type: NodeEventType.TICK_END,
|
|
544
|
+
nodeId: this.id,
|
|
545
|
+
nodeName: this.name,
|
|
546
|
+
nodeType: this.type,
|
|
547
|
+
timestamp: Date.now(),
|
|
548
|
+
data: { status: NodeStatus.FAILURE },
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
// Use centralized error handler for consistent behavior
|
|
552
|
+
return handleNodeError(error);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* Composite nodes must implement their traversal logic
|
|
558
|
+
* Returns Promise for async operations
|
|
559
|
+
*/
|
|
560
|
+
protected abstract executeTick(
|
|
561
|
+
context: TemporalContext,
|
|
562
|
+
): Promise<NodeStatus>;
|
|
563
|
+
|
|
564
|
+
addChild(child: TreeNode): void {
|
|
565
|
+
if (!child) {
|
|
566
|
+
throw new Error("Cannot add undefined child to composite node");
|
|
567
|
+
}
|
|
568
|
+
this._children.push(child);
|
|
569
|
+
this.children = this._children;
|
|
570
|
+
child.parent = this;
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
addChildren(children: TreeNode[]): void {
|
|
574
|
+
children.forEach((child) => {
|
|
575
|
+
this.addChild(child);
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
halt(): void {
|
|
580
|
+
super.halt();
|
|
581
|
+
// Halt all running children
|
|
582
|
+
for (const child of this._children) {
|
|
583
|
+
if (child.status() === NodeStatus.RUNNING) {
|
|
584
|
+
child.halt();
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
reset(): void {
|
|
590
|
+
super.reset();
|
|
591
|
+
// Reset all children
|
|
592
|
+
for (const child of this._children) {
|
|
593
|
+
child.reset();
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
protected haltChildren(startIndex: number = 0): void {
|
|
598
|
+
for (let i = startIndex; i < this._children.length; i++) {
|
|
599
|
+
const child = this._children[i];
|
|
600
|
+
if (child && child.status() === NodeStatus.RUNNING) {
|
|
601
|
+
child.halt();
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
}
|