@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/registry.ts
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Registry for behavior tree nodes
|
|
3
|
+
* Handles registration and creation of nodes by type
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { BehaviorTree } from "./behavior-tree.js";
|
|
7
|
+
import type {
|
|
8
|
+
NodeConfiguration,
|
|
9
|
+
NodeConstructor,
|
|
10
|
+
NodeMetadata,
|
|
11
|
+
TreeNode,
|
|
12
|
+
} from "./types.js";
|
|
13
|
+
import { z } from "zod";
|
|
14
|
+
import {
|
|
15
|
+
schemaRegistry,
|
|
16
|
+
validateConfiguration,
|
|
17
|
+
treeDefinitionSchema,
|
|
18
|
+
type TreeDefinition,
|
|
19
|
+
} from "./schemas/index.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Entry for a registered BehaviorTree with metadata
|
|
23
|
+
*/
|
|
24
|
+
interface TreeEntry {
|
|
25
|
+
tree: BehaviorTree;
|
|
26
|
+
sourceFile: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class Registry {
|
|
30
|
+
private nodeTypes: Map<string, NodeConstructor> = new Map();
|
|
31
|
+
private nodeMetadata: Map<string, NodeMetadata> = new Map();
|
|
32
|
+
private behaviorTrees: Map<string, TreeEntry> = new Map();
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
this.log("Registry created");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Register a node type with the registry
|
|
40
|
+
*/
|
|
41
|
+
register<T extends TreeNode>(
|
|
42
|
+
type: string,
|
|
43
|
+
ctor: NodeConstructor<T>,
|
|
44
|
+
metadata?: Partial<NodeMetadata>,
|
|
45
|
+
): void {
|
|
46
|
+
if (this.nodeTypes.has(type)) {
|
|
47
|
+
throw new Error(`Node type '${type}' is already registered`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
this.nodeTypes.set(type, ctor);
|
|
51
|
+
|
|
52
|
+
// Store metadata
|
|
53
|
+
const fullMetadata: NodeMetadata = {
|
|
54
|
+
type,
|
|
55
|
+
category: metadata?.category || "action",
|
|
56
|
+
description: metadata?.description,
|
|
57
|
+
ports: metadata?.ports || [],
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
this.nodeMetadata.set(type, fullMetadata);
|
|
61
|
+
this.log(`Registered node type: ${type} (${fullMetadata.category})`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Create a node instance by type
|
|
66
|
+
* Validates configuration against schema before creating node
|
|
67
|
+
*/
|
|
68
|
+
create(type: string, config: NodeConfiguration): TreeNode {
|
|
69
|
+
const Constructor = this.nodeTypes.get(type);
|
|
70
|
+
|
|
71
|
+
if (!Constructor) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`Unknown node type: '${type}'. Available types: ${this.getRegisteredTypes().join(", ")}`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Validate configuration using schema registry
|
|
78
|
+
const validatedConfig = validateConfiguration<NodeConfiguration>(
|
|
79
|
+
schemaRegistry.getSchema(type) as z.ZodSchema<NodeConfiguration>,
|
|
80
|
+
config,
|
|
81
|
+
type,
|
|
82
|
+
config.id,
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
this.log(`Creating node of type: ${type} with id: ${validatedConfig.id}`);
|
|
86
|
+
return new Constructor(validatedConfig);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Check if a node type is registered
|
|
91
|
+
*/
|
|
92
|
+
has(type: string): boolean {
|
|
93
|
+
return this.nodeTypes.has(type);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Get metadata for a node type
|
|
98
|
+
*/
|
|
99
|
+
getMetadata(type: string): NodeMetadata | undefined {
|
|
100
|
+
return this.nodeMetadata.get(type);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Get all registered node types
|
|
105
|
+
*/
|
|
106
|
+
getRegisteredTypes(): string[] {
|
|
107
|
+
return Array.from(this.nodeTypes.keys());
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Get registered types by category
|
|
112
|
+
*/
|
|
113
|
+
getTypesByCategory(category: NodeMetadata["category"]): string[] {
|
|
114
|
+
const types: string[] = [];
|
|
115
|
+
|
|
116
|
+
for (const [type, metadata] of this.nodeMetadata) {
|
|
117
|
+
if (metadata.category === category) {
|
|
118
|
+
types.push(type);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return types;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Clear all registrations (node types, metadata, and behavior trees)
|
|
127
|
+
*/
|
|
128
|
+
clear(): void {
|
|
129
|
+
this.nodeTypes.clear();
|
|
130
|
+
this.nodeMetadata.clear();
|
|
131
|
+
this.behaviorTrees.clear();
|
|
132
|
+
this.log("Registry cleared (nodes and trees)");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Register a behavior tree instance with source file metadata.
|
|
137
|
+
* Used for reusable trees like elements, step groups, and test cases.
|
|
138
|
+
*
|
|
139
|
+
* @param id Unique identifier for the tree
|
|
140
|
+
* @param tree BehaviorTree instance to register
|
|
141
|
+
* @param sourceFile Path to the source .sigma file
|
|
142
|
+
* @throws Error if a tree with the same ID is already registered
|
|
143
|
+
*/
|
|
144
|
+
registerTree(id: string, tree: BehaviorTree, sourceFile: string): void {
|
|
145
|
+
if (this.behaviorTrees.has(id)) {
|
|
146
|
+
throw new Error(`Behavior tree '${id}' is already registered`);
|
|
147
|
+
}
|
|
148
|
+
this.behaviorTrees.set(id, { tree, sourceFile });
|
|
149
|
+
this.log(`Registered behavior tree: ${id}`);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Unregister a behavior tree by ID.
|
|
154
|
+
* Useful for cleanup in long-running processes or tests.
|
|
155
|
+
*
|
|
156
|
+
* @param id Tree ID to unregister
|
|
157
|
+
* @returns true if tree was found and removed, false otherwise
|
|
158
|
+
*/
|
|
159
|
+
unregisterTree(id: string): boolean {
|
|
160
|
+
const deleted = this.behaviorTrees.delete(id);
|
|
161
|
+
if (deleted) {
|
|
162
|
+
this.log(`Unregistered behavior tree: ${id}`);
|
|
163
|
+
}
|
|
164
|
+
return deleted;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Replace an existing behavior tree registration.
|
|
169
|
+
* If the tree doesn't exist, it will be registered as new.
|
|
170
|
+
*
|
|
171
|
+
* @param id Tree ID to replace
|
|
172
|
+
* @param tree New BehaviorTree instance
|
|
173
|
+
* @param sourceFile Path to the source .sigma file
|
|
174
|
+
*/
|
|
175
|
+
replaceTree(id: string, tree: BehaviorTree, sourceFile: string): void {
|
|
176
|
+
const existed = this.behaviorTrees.has(id);
|
|
177
|
+
this.behaviorTrees.set(id, { tree, sourceFile });
|
|
178
|
+
this.log(`${existed ? "Replaced" : "Registered"} behavior tree: ${id}`);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Get a behavior tree instance by ID
|
|
183
|
+
*/
|
|
184
|
+
getTree(id: string): BehaviorTree | undefined {
|
|
185
|
+
return this.behaviorTrees.get(id)?.tree;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Get the source file path for a behavior tree
|
|
190
|
+
*/
|
|
191
|
+
getTreeSourceFile(id: string): string | undefined {
|
|
192
|
+
return this.behaviorTrees.get(id)?.sourceFile;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get all trees that belong to a specific source file
|
|
197
|
+
*/
|
|
198
|
+
getTreesForFile(filePath: string): Map<string, BehaviorTree> {
|
|
199
|
+
const result = new Map<string, BehaviorTree>();
|
|
200
|
+
for (const [id, entry] of this.behaviorTrees) {
|
|
201
|
+
if (entry.sourceFile === filePath) {
|
|
202
|
+
result.set(id, entry.tree);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return result;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Check if a behavior tree is registered
|
|
210
|
+
*/
|
|
211
|
+
hasTree(id: string): boolean {
|
|
212
|
+
return this.behaviorTrees.has(id);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Clone a registered behavior tree for instantiation
|
|
217
|
+
* Returns the cloned BehaviorTree (caller should use getRoot() for TreeNode)
|
|
218
|
+
*/
|
|
219
|
+
cloneTree(id: string): BehaviorTree {
|
|
220
|
+
const entry = this.behaviorTrees.get(id);
|
|
221
|
+
if (!entry) {
|
|
222
|
+
throw new Error(
|
|
223
|
+
`Behavior tree '${id}' not found. Available trees: ${this.getAllTreeIds().join(", ") || "none"}`,
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
this.log(`Cloning behavior tree: ${id}`);
|
|
227
|
+
return entry.tree.clone();
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get all registered behavior tree IDs
|
|
232
|
+
*/
|
|
233
|
+
getAllTreeIds(): string[] {
|
|
234
|
+
return Array.from(this.behaviorTrees.keys());
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Clear all registered behavior trees
|
|
239
|
+
*/
|
|
240
|
+
clearTrees(): void {
|
|
241
|
+
this.behaviorTrees.clear();
|
|
242
|
+
this.log("Cleared all behavior trees");
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Create a tree from a JSON definition
|
|
247
|
+
* Validates tree structure before creating nodes
|
|
248
|
+
*/
|
|
249
|
+
createTree(definition: unknown): TreeNode {
|
|
250
|
+
// Validate tree definition structure
|
|
251
|
+
const validatedDef = treeDefinitionSchema.parse(definition) as TreeDefinition;
|
|
252
|
+
|
|
253
|
+
if (!validatedDef.type) {
|
|
254
|
+
throw new Error("Node definition must have a type");
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Create the node configuration
|
|
258
|
+
const config: NodeConfiguration = {
|
|
259
|
+
id: validatedDef.id || `${validatedDef.type}_${Date.now()}`,
|
|
260
|
+
name: validatedDef.name || validatedDef.id,
|
|
261
|
+
...validatedDef.props,
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Create node with validated configuration (uses schema validation)
|
|
265
|
+
const node = this.create(validatedDef.type, config);
|
|
266
|
+
|
|
267
|
+
// Handle children for composite/decorator nodes
|
|
268
|
+
if (validatedDef.children && Array.isArray(validatedDef.children)) {
|
|
269
|
+
if ("setChild" in node && typeof node.setChild === "function") {
|
|
270
|
+
// Decorator node - single child
|
|
271
|
+
if (validatedDef.children.length !== 1) {
|
|
272
|
+
throw new Error(
|
|
273
|
+
`Decorator ${validatedDef.type} must have exactly one child`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
const child = this.createTree(validatedDef.children[0]);
|
|
277
|
+
(node as { setChild: (child: TreeNode) => void }).setChild(child);
|
|
278
|
+
} else if (
|
|
279
|
+
"addChildren" in node &&
|
|
280
|
+
typeof node.addChildren === "function"
|
|
281
|
+
) {
|
|
282
|
+
// Composite node - multiple children
|
|
283
|
+
const children = validatedDef.children.map((childDef) =>
|
|
284
|
+
this.createTree(childDef),
|
|
285
|
+
);
|
|
286
|
+
(node as { addChildren: (children: TreeNode[]) => void }).addChildren(
|
|
287
|
+
children,
|
|
288
|
+
);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return node;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* Safe tree creation that returns success/error result
|
|
297
|
+
* Useful for user-facing tools that need graceful error handling
|
|
298
|
+
*
|
|
299
|
+
* @param definition - Tree definition to create
|
|
300
|
+
* @returns Success result with tree or failure result with error
|
|
301
|
+
*
|
|
302
|
+
* @example
|
|
303
|
+
* ```typescript
|
|
304
|
+
* const result = registry.safeCreateTree({
|
|
305
|
+
* type: 'Sequence',
|
|
306
|
+
* id: 'root',
|
|
307
|
+
* children: [...]
|
|
308
|
+
* });
|
|
309
|
+
*
|
|
310
|
+
* if (result.success) {
|
|
311
|
+
* console.log('Tree created:', result.tree);
|
|
312
|
+
* } else {
|
|
313
|
+
* console.error('Failed:', result.error.message);
|
|
314
|
+
* }
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
317
|
+
safeCreateTree(
|
|
318
|
+
definition: unknown,
|
|
319
|
+
): { success: true; tree: TreeNode } | { success: false; error: Error } {
|
|
320
|
+
try {
|
|
321
|
+
const tree = this.createTree(definition);
|
|
322
|
+
return { success: true, tree };
|
|
323
|
+
} catch (error) {
|
|
324
|
+
return {
|
|
325
|
+
success: false,
|
|
326
|
+
error: error instanceof Error ? error : new Error(String(error)),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
private log(message: string): void {
|
|
332
|
+
console.log(`[Registry] ${message}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base Zod schemas for node configurations
|
|
3
|
+
* Foundation for all node-specific validation schemas
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Base schema for all node configurations
|
|
10
|
+
* Matches NodeConfiguration interface from types.ts
|
|
11
|
+
*/
|
|
12
|
+
export const nodeConfigurationSchema = z
|
|
13
|
+
.object({
|
|
14
|
+
id: z.string().min(1, "Node ID cannot be empty"),
|
|
15
|
+
name: z.string().optional(),
|
|
16
|
+
})
|
|
17
|
+
.passthrough(); // Allow additional properties for node-specific configs
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Branded type for validated configurations
|
|
21
|
+
* Ensures runtime type matches compile-time type
|
|
22
|
+
*/
|
|
23
|
+
export type ValidatedNodeConfiguration = z.infer<
|
|
24
|
+
typeof nodeConfigurationSchema
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Helper to create node-specific configuration schemas
|
|
29
|
+
* Extends base schema with node-specific fields
|
|
30
|
+
*
|
|
31
|
+
* @param nodeType - Name of the node type for error messages
|
|
32
|
+
* @param fields - Node-specific field definitions
|
|
33
|
+
* @returns Extended Zod schema
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* const timeoutSchema = createNodeSchema('Timeout', {
|
|
38
|
+
* timeoutMs: validations.positiveNumber('timeoutMs'),
|
|
39
|
+
* });
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function createNodeSchema<T extends z.ZodRawShape>(
|
|
43
|
+
nodeType: string,
|
|
44
|
+
fields: T,
|
|
45
|
+
) {
|
|
46
|
+
return nodeConfigurationSchema
|
|
47
|
+
.extend(fields)
|
|
48
|
+
.describe(`${nodeType} configuration`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Common validation helpers
|
|
53
|
+
* Reusable validators for standard patterns
|
|
54
|
+
*/
|
|
55
|
+
export const validations = {
|
|
56
|
+
/**
|
|
57
|
+
* Positive number validator (> 0)
|
|
58
|
+
*/
|
|
59
|
+
positiveNumber: (fieldName: string) =>
|
|
60
|
+
z.number().positive(`${fieldName} must be positive`),
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Non-negative number validator (>= 0)
|
|
64
|
+
*/
|
|
65
|
+
nonNegativeNumber: (fieldName: string) =>
|
|
66
|
+
z.number().nonnegative(`${fieldName} must be non-negative`),
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Positive integer validator (> 0, whole number)
|
|
70
|
+
*/
|
|
71
|
+
positiveInteger: (fieldName: string) =>
|
|
72
|
+
z.number().int().positive(`${fieldName} must be a positive integer`),
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Non-negative integer validator (>= 0, whole number)
|
|
76
|
+
*/
|
|
77
|
+
nonNegativeInteger: (fieldName: string) =>
|
|
78
|
+
z
|
|
79
|
+
.number()
|
|
80
|
+
.int()
|
|
81
|
+
.nonnegative(`${fieldName} must be a non-negative integer`),
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Blackboard key validator (non-empty string)
|
|
85
|
+
*/
|
|
86
|
+
blackboardKey: z.string().min(1, "Blackboard key cannot be empty"),
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Tree ID validator (non-empty string for SubTree references)
|
|
90
|
+
*/
|
|
91
|
+
treeId: z.string().min(1, "Tree ID cannot be empty"),
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Duration in milliseconds validator (non-negative)
|
|
95
|
+
*/
|
|
96
|
+
durationMs: (fieldName: string = "duration") =>
|
|
97
|
+
z.number().nonnegative(`${fieldName} must be non-negative milliseconds`),
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Infer TypeScript type from schema
|
|
102
|
+
* Helper for type-safe configuration interfaces
|
|
103
|
+
*/
|
|
104
|
+
export type InferSchema<T extends z.ZodSchema> = z.infer<T>;
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema registry and validation exports
|
|
3
|
+
* Central registry mapping node types to their validation schemas
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
import { nodeConfigurationSchema } from "./base.schema.js";
|
|
8
|
+
|
|
9
|
+
// Import decorator schemas
|
|
10
|
+
import { timeoutConfigurationSchema } from "../decorators/timeout.schema.js";
|
|
11
|
+
import { delayConfigurationSchema } from "../decorators/delay.schema.js";
|
|
12
|
+
import { repeatConfigurationSchema } from "../decorators/repeat.schema.js";
|
|
13
|
+
import { invertConfigurationSchema } from "../decorators/invert.schema.js";
|
|
14
|
+
import {
|
|
15
|
+
forceSuccessConfigurationSchema,
|
|
16
|
+
forceFailureConfigurationSchema,
|
|
17
|
+
} from "../decorators/force-result.schema.js";
|
|
18
|
+
import { preconditionConfigurationSchema } from "../decorators/precondition.schema.js";
|
|
19
|
+
import { runOnceConfigurationSchema } from "../decorators/run-once.schema.js";
|
|
20
|
+
import { keepRunningUntilFailureConfigurationSchema } from "../decorators/keep-running.schema.js";
|
|
21
|
+
import { softAssertConfigurationSchema } from "../decorators/soft-assert.schema.js";
|
|
22
|
+
|
|
23
|
+
// Import composite schemas
|
|
24
|
+
import { parallelConfigurationSchema } from "../composites/parallel.schema.js";
|
|
25
|
+
import { forEachConfigurationSchema } from "../composites/for-each.schema.js";
|
|
26
|
+
import { whileConfigurationSchema } from "../composites/while.schema.js";
|
|
27
|
+
import { subTreeConfigurationSchema } from "../composites/sub-tree.schema.js";
|
|
28
|
+
import { sequenceConfigurationSchema } from "../composites/sequence.schema.js";
|
|
29
|
+
import { selectorConfigurationSchema } from "../composites/selector.schema.js";
|
|
30
|
+
import { conditionalConfigurationSchema } from "../composites/conditional.schema.js";
|
|
31
|
+
import { reactiveSequenceConfigurationSchema } from "../composites/reactive-sequence.schema.js";
|
|
32
|
+
import { memorySequenceConfigurationSchema } from "../composites/memory-sequence.schema.js";
|
|
33
|
+
import { recoveryConfigurationSchema } from "../composites/recovery.schema.js";
|
|
34
|
+
|
|
35
|
+
// Action schemas - Script removed, use CodeExecution instead
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Central registry mapping node types to their validation schemas
|
|
39
|
+
*
|
|
40
|
+
* Responsibilities:
|
|
41
|
+
* - Register Zod schemas for each node type
|
|
42
|
+
* - Provide schema lookup by node type
|
|
43
|
+
* - Validate configurations against registered schemas
|
|
44
|
+
* - Fallback to base schema for unregistered types
|
|
45
|
+
*
|
|
46
|
+
* Usage:
|
|
47
|
+
* ```typescript
|
|
48
|
+
* // Register a schema
|
|
49
|
+
* schemaRegistry.register('Timeout', timeoutConfigurationSchema);
|
|
50
|
+
*
|
|
51
|
+
* // Get a schema
|
|
52
|
+
* const schema = schemaRegistry.getSchema('Timeout');
|
|
53
|
+
*
|
|
54
|
+
* // Validate configuration
|
|
55
|
+
* const config = schemaRegistry.validate('Timeout', { id: 'test', timeoutMs: 1000 });
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export class SchemaRegistry {
|
|
59
|
+
private schemas = new Map<string, z.ZodSchema>();
|
|
60
|
+
|
|
61
|
+
constructor() {
|
|
62
|
+
this.registerAllSchemas();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Register all node schemas
|
|
67
|
+
* Called automatically on construction
|
|
68
|
+
*/
|
|
69
|
+
private registerAllSchemas(): void {
|
|
70
|
+
// Register decorator schemas
|
|
71
|
+
this.register("Timeout", timeoutConfigurationSchema);
|
|
72
|
+
this.register("Delay", delayConfigurationSchema);
|
|
73
|
+
this.register("Repeat", repeatConfigurationSchema);
|
|
74
|
+
this.register("Invert", invertConfigurationSchema);
|
|
75
|
+
this.register("ForceSuccess", forceSuccessConfigurationSchema);
|
|
76
|
+
this.register("ForceFailure", forceFailureConfigurationSchema);
|
|
77
|
+
this.register("Precondition", preconditionConfigurationSchema);
|
|
78
|
+
this.register("RunOnce", runOnceConfigurationSchema);
|
|
79
|
+
this.register(
|
|
80
|
+
"KeepRunningUntilFailure",
|
|
81
|
+
keepRunningUntilFailureConfigurationSchema,
|
|
82
|
+
);
|
|
83
|
+
this.register("SoftAssert", softAssertConfigurationSchema);
|
|
84
|
+
|
|
85
|
+
// Register composite schemas
|
|
86
|
+
this.register("Parallel", parallelConfigurationSchema);
|
|
87
|
+
this.register("ForEach", forEachConfigurationSchema);
|
|
88
|
+
this.register("While", whileConfigurationSchema);
|
|
89
|
+
this.register("SubTree", subTreeConfigurationSchema);
|
|
90
|
+
this.register("Sequence", sequenceConfigurationSchema);
|
|
91
|
+
this.register("Selector", selectorConfigurationSchema);
|
|
92
|
+
this.register("Conditional", conditionalConfigurationSchema);
|
|
93
|
+
this.register("ReactiveSequence", reactiveSequenceConfigurationSchema);
|
|
94
|
+
this.register("MemorySequence", memorySequenceConfigurationSchema);
|
|
95
|
+
this.register("Recovery", recoveryConfigurationSchema);
|
|
96
|
+
|
|
97
|
+
// Action schemas - Script removed, use CodeExecution instead
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Register a validation schema for a node type
|
|
102
|
+
*
|
|
103
|
+
* @param nodeType - Name of the node type (e.g., 'Timeout', 'Sequence')
|
|
104
|
+
* @param schema - Zod schema for validating this node type's configuration
|
|
105
|
+
* @throws Error if node type is already registered
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* schemaRegistry.register('Timeout', timeoutConfigurationSchema);
|
|
110
|
+
* ```
|
|
111
|
+
*/
|
|
112
|
+
register(nodeType: string, schema: z.ZodSchema): void {
|
|
113
|
+
if (this.schemas.has(nodeType)) {
|
|
114
|
+
throw new Error(`Schema for node type '${nodeType}' already registered`);
|
|
115
|
+
}
|
|
116
|
+
this.schemas.set(nodeType, schema);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Get schema for a node type
|
|
121
|
+
* Returns base schema if no specific schema registered
|
|
122
|
+
*
|
|
123
|
+
* @param nodeType - Name of the node type
|
|
124
|
+
* @returns Zod schema for the node type (or base schema if not found)
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```typescript
|
|
128
|
+
* const schema = schemaRegistry.getSchema('Timeout');
|
|
129
|
+
* const validated = schema.parse({ id: 'test', timeoutMs: 1000 });
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
getSchema(nodeType: string): z.ZodSchema {
|
|
133
|
+
return this.schemas.get(nodeType) || nodeConfigurationSchema;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Check if a schema is registered for a node type
|
|
138
|
+
*
|
|
139
|
+
* @param nodeType - Name of the node type
|
|
140
|
+
* @returns True if schema is registered, false otherwise
|
|
141
|
+
*/
|
|
142
|
+
hasSchema(nodeType: string): boolean {
|
|
143
|
+
return this.schemas.has(nodeType);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get all registered node types
|
|
148
|
+
*
|
|
149
|
+
* @returns Array of registered node type names
|
|
150
|
+
*/
|
|
151
|
+
getRegisteredTypes(): string[] {
|
|
152
|
+
return Array.from(this.schemas.keys());
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Validate configuration for a specific node type
|
|
157
|
+
* Throws ZodError if validation fails
|
|
158
|
+
*
|
|
159
|
+
* @param nodeType - Name of the node type
|
|
160
|
+
* @param config - Configuration object to validate
|
|
161
|
+
* @returns Validated and parsed configuration
|
|
162
|
+
* @throws ZodError if validation fails
|
|
163
|
+
*
|
|
164
|
+
* @example
|
|
165
|
+
* ```typescript
|
|
166
|
+
* const config = schemaRegistry.validate('Timeout', {
|
|
167
|
+
* id: 'test',
|
|
168
|
+
* timeoutMs: 1000
|
|
169
|
+
* });
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
validate<T>(nodeType: string, config: unknown): T {
|
|
173
|
+
const schema = this.getSchema(nodeType);
|
|
174
|
+
return schema.parse(config) as T;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Safe validation that returns success/error result
|
|
179
|
+
* Does not throw errors
|
|
180
|
+
*
|
|
181
|
+
* @param nodeType - Name of the node type
|
|
182
|
+
* @param config - Configuration object to validate
|
|
183
|
+
* @returns Result with success/error
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* ```typescript
|
|
187
|
+
* const result = schemaRegistry.safeParse('Timeout', { id: 'test', timeoutMs: -100 });
|
|
188
|
+
* if (result.success) {
|
|
189
|
+
* console.log(result.data);
|
|
190
|
+
* } else {
|
|
191
|
+
* console.error(result.error);
|
|
192
|
+
* }
|
|
193
|
+
* ```
|
|
194
|
+
*/
|
|
195
|
+
safeParse(
|
|
196
|
+
nodeType: string,
|
|
197
|
+
config: unknown,
|
|
198
|
+
):
|
|
199
|
+
| { success: true; data: unknown }
|
|
200
|
+
| { success: false; error: z.ZodError<unknown> } {
|
|
201
|
+
const schema = this.getSchema(nodeType);
|
|
202
|
+
return schema.safeParse(config);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Clear all registered schemas
|
|
207
|
+
* Useful for testing
|
|
208
|
+
*/
|
|
209
|
+
clear(): void {
|
|
210
|
+
this.schemas.clear();
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Global singleton schema registry instance
|
|
216
|
+
* Used by Registry class for validation
|
|
217
|
+
*/
|
|
218
|
+
export const schemaRegistry = new SchemaRegistry();
|
|
219
|
+
|
|
220
|
+
// Re-export for convenience
|
|
221
|
+
export * from "./base.schema.js";
|
|
222
|
+
export * from "./validation.js";
|
|
223
|
+
export * from "./tree-definition.schema.js";
|