@q1k-oss/btree-workflows 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +31 -0
- package/CLAUDE.md +181 -0
- package/LICENSE +21 -0
- package/README.md +920 -0
- package/behaviour-tree-workflows-landing/index.html +16 -0
- package/behaviour-tree-workflows-landing/package-lock.json +2074 -0
- package/behaviour-tree-workflows-landing/package.json +31 -0
- package/behaviour-tree-workflows-landing/public/favicon.svg +17 -0
- package/behaviour-tree-workflows-landing/src/App.css +103 -0
- package/behaviour-tree-workflows-landing/src/App.tsx +176 -0
- package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.css +89 -0
- package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.tsx +64 -0
- package/behaviour-tree-workflows-landing/src/components/ExampleSelector.css +64 -0
- package/behaviour-tree-workflows-landing/src/components/ExampleSelector.tsx +34 -0
- package/behaviour-tree-workflows-landing/src/components/ExecutionLog.css +107 -0
- package/behaviour-tree-workflows-landing/src/components/ExecutionLog.tsx +85 -0
- package/behaviour-tree-workflows-landing/src/components/Header.css +50 -0
- package/behaviour-tree-workflows-landing/src/components/Header.tsx +26 -0
- package/behaviour-tree-workflows-landing/src/components/StatusBadge.css +45 -0
- package/behaviour-tree-workflows-landing/src/components/StatusBadge.tsx +15 -0
- package/behaviour-tree-workflows-landing/src/components/Toolbar.css +74 -0
- package/behaviour-tree-workflows-landing/src/components/Toolbar.tsx +53 -0
- package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.css +67 -0
- package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.tsx +192 -0
- package/behaviour-tree-workflows-landing/src/components/YamlEditor.css +18 -0
- package/behaviour-tree-workflows-landing/src/components/YamlEditor.tsx +96 -0
- package/behaviour-tree-workflows-landing/src/lib/count-nodes.ts +11 -0
- package/behaviour-tree-workflows-landing/src/lib/execution-engine.ts +96 -0
- package/behaviour-tree-workflows-landing/src/lib/tree-layout.ts +136 -0
- package/behaviour-tree-workflows-landing/src/lib/yaml-examples.ts +549 -0
- package/behaviour-tree-workflows-landing/src/main.tsx +9 -0
- package/behaviour-tree-workflows-landing/src/stubs/activepieces.ts +18 -0
- package/behaviour-tree-workflows-landing/src/stubs/fs.ts +24 -0
- package/behaviour-tree-workflows-landing/src/stubs/path.ts +16 -0
- package/behaviour-tree-workflows-landing/src/stubs/temporal-activity.ts +6 -0
- package/behaviour-tree-workflows-landing/src/stubs/temporal-workflow.ts +22 -0
- package/behaviour-tree-workflows-landing/tsconfig.json +25 -0
- package/behaviour-tree-workflows-landing/vite.config.ts +40 -0
- package/demo-google-sheets.ts +181 -0
- package/demo-runtime-variables.ts +174 -0
- package/demo-template.ts +208 -0
- package/docs/ARCHITECTURE_SUMMARY.md +613 -0
- package/docs/NODE_REFERENCE.md +504 -0
- package/docs/README.md +53 -0
- package/docs/custom-nodes-architecture.md +826 -0
- package/docs/observability.md +175 -0
- package/docs/yaml-specification.md +990 -0
- package/examples/temporal/README.md +117 -0
- package/examples/temporal/activities.ts +373 -0
- package/examples/temporal/client.ts +115 -0
- package/examples/temporal/python-worker/activities.py +339 -0
- package/examples/temporal/python-worker/requirements.txt +12 -0
- package/examples/temporal/python-worker/worker.py +106 -0
- package/examples/temporal/worker.ts +66 -0
- package/examples/temporal/workflows.ts +6 -0
- package/examples/temporal/yaml-workflow-loader.ts +105 -0
- package/examples/yaml-test.ts +97 -0
- package/examples/yaml-workflows/01-simple-sequence.yaml +25 -0
- package/examples/yaml-workflows/02-parallel-timeout.yaml +45 -0
- package/examples/yaml-workflows/03-ecommerce-checkout.yaml +94 -0
- package/examples/yaml-workflows/04-ai-agent-workflow.yaml +346 -0
- package/examples/yaml-workflows/05-order-processing.yaml +146 -0
- package/examples/yaml-workflows/06-activity-test.yaml +71 -0
- package/examples/yaml-workflows/07-activity-simple-test.yaml +43 -0
- package/examples/yaml-workflows/08-file-processing.yaml +141 -0
- package/examples/yaml-workflows/09-http-request.yaml +137 -0
- package/examples/yaml-workflows/README.md +211 -0
- package/package.json +38 -0
- package/src/actions/code-execution.schema.ts +27 -0
- package/src/actions/code-execution.ts +218 -0
- package/src/actions/generate-file.test.ts +516 -0
- package/src/actions/generate-file.ts +166 -0
- package/src/actions/http-request.test.ts +784 -0
- package/src/actions/http-request.ts +228 -0
- package/src/actions/index.ts +20 -0
- package/src/actions/parse-file.test.ts +448 -0
- package/src/actions/parse-file.ts +139 -0
- package/src/actions/python-script.test.ts +439 -0
- package/src/actions/python-script.ts +154 -0
- package/src/base-node.test.ts +511 -0
- package/src/base-node.ts +605 -0
- package/src/behavior-tree.test.ts +431 -0
- package/src/behavior-tree.ts +283 -0
- package/src/blackboard.test.ts +222 -0
- package/src/blackboard.ts +192 -0
- package/src/composites/conditional.schema.ts +19 -0
- package/src/composites/conditional.test.ts +309 -0
- package/src/composites/conditional.ts +129 -0
- package/src/composites/for-each.schema.ts +23 -0
- package/src/composites/for-each.test.ts +254 -0
- package/src/composites/for-each.ts +132 -0
- package/src/composites/index.ts +15 -0
- package/src/composites/memory-sequence.schema.ts +19 -0
- package/src/composites/memory-sequence.test.ts +223 -0
- package/src/composites/memory-sequence.ts +98 -0
- package/src/composites/parallel.schema.ts +28 -0
- package/src/composites/parallel.test.ts +502 -0
- package/src/composites/parallel.ts +157 -0
- package/src/composites/reactive-sequence.schema.ts +19 -0
- package/src/composites/reactive-sequence.test.ts +170 -0
- package/src/composites/reactive-sequence.ts +85 -0
- package/src/composites/recovery.schema.ts +19 -0
- package/src/composites/recovery.test.ts +366 -0
- package/src/composites/recovery.ts +90 -0
- package/src/composites/selector.schema.ts +19 -0
- package/src/composites/selector.test.ts +387 -0
- package/src/composites/selector.ts +85 -0
- package/src/composites/sequence.schema.ts +19 -0
- package/src/composites/sequence.test.ts +337 -0
- package/src/composites/sequence.ts +72 -0
- package/src/composites/sub-tree.schema.ts +21 -0
- package/src/composites/sub-tree.test.ts +893 -0
- package/src/composites/sub-tree.ts +177 -0
- package/src/composites/while.schema.ts +24 -0
- package/src/composites/while.test.ts +381 -0
- package/src/composites/while.ts +149 -0
- package/src/data-store/index.ts +10 -0
- package/src/data-store/memory-store.ts +161 -0
- package/src/data-store/types.ts +94 -0
- package/src/debug/breakpoint.test.ts +47 -0
- package/src/debug/breakpoint.ts +30 -0
- package/src/debug/index.ts +17 -0
- package/src/debug/resume-point.test.ts +49 -0
- package/src/debug/resume-point.ts +29 -0
- package/src/decorators/delay.schema.ts +21 -0
- package/src/decorators/delay.test.ts +261 -0
- package/src/decorators/delay.ts +140 -0
- package/src/decorators/force-result.schema.ts +32 -0
- package/src/decorators/force-result.test.ts +133 -0
- package/src/decorators/force-result.ts +63 -0
- package/src/decorators/index.ts +13 -0
- package/src/decorators/invert.schema.ts +19 -0
- package/src/decorators/invert.test.ts +135 -0
- package/src/decorators/invert.ts +42 -0
- package/src/decorators/keep-running.schema.ts +20 -0
- package/src/decorators/keep-running.test.ts +105 -0
- package/src/decorators/keep-running.ts +49 -0
- package/src/decorators/precondition.schema.ts +19 -0
- package/src/decorators/precondition.test.ts +351 -0
- package/src/decorators/precondition.ts +139 -0
- package/src/decorators/repeat.schema.ts +21 -0
- package/src/decorators/repeat.test.ts +187 -0
- package/src/decorators/repeat.ts +94 -0
- package/src/decorators/run-once.schema.ts +19 -0
- package/src/decorators/run-once.test.ts +140 -0
- package/src/decorators/run-once.ts +61 -0
- package/src/decorators/soft-assert.schema.ts +19 -0
- package/src/decorators/soft-assert.test.ts +107 -0
- package/src/decorators/soft-assert.ts +68 -0
- package/src/decorators/timeout.schema.ts +21 -0
- package/src/decorators/timeout.test.ts +274 -0
- package/src/decorators/timeout.ts +159 -0
- package/src/errors.test.ts +63 -0
- package/src/errors.ts +34 -0
- package/src/events.test.ts +347 -0
- package/src/events.ts +183 -0
- package/src/index.ts +80 -0
- package/src/integrations/index.ts +30 -0
- package/src/integrations/integration-action.test.ts +571 -0
- package/src/integrations/integration-action.ts +233 -0
- package/src/integrations/piece-executor.ts +320 -0
- package/src/observability/execution-tracker.ts +320 -0
- package/src/observability/index.ts +23 -0
- package/src/observability/sinks.ts +138 -0
- package/src/observability/types.ts +130 -0
- package/src/registry-utils.ts +147 -0
- package/src/registry.test.ts +466 -0
- package/src/registry.ts +334 -0
- package/src/schemas/base.schema.ts +104 -0
- package/src/schemas/index.ts +223 -0
- package/src/schemas/integration.test.ts +238 -0
- package/src/schemas/tree-definition.schema.ts +170 -0
- package/src/schemas/validation.test.ts +146 -0
- package/src/schemas/validation.ts +122 -0
- package/src/scripting/index.ts +22 -0
- package/src/templates/template-loader.test.ts +281 -0
- package/src/templates/template-loader.ts +152 -0
- package/src/temporal-integration.test.ts +213 -0
- package/src/test-nodes.ts +259 -0
- package/src/types.ts +503 -0
- package/src/utilities/index.ts +17 -0
- package/src/utilities/log-message.test.ts +275 -0
- package/src/utilities/log-message.ts +134 -0
- package/src/utilities/regex-extract.test.ts +138 -0
- package/src/utilities/regex-extract.ts +108 -0
- package/src/utilities/variable-resolver.test.ts +416 -0
- package/src/utilities/variable-resolver.ts +318 -0
- package/src/utils/error-handler.test.ts +117 -0
- package/src/utils/error-handler.ts +48 -0
- package/src/utils/signal-check.test.ts +234 -0
- package/src/utils/signal-check.ts +140 -0
- package/src/yaml/errors.ts +143 -0
- package/src/yaml/index.ts +30 -0
- package/src/yaml/loader.ts +39 -0
- package/src/yaml/parser.ts +286 -0
- package/src/yaml/validation/semantic-validator.ts +196 -0
- package/templates/google-sheets/insert-row.yaml +76 -0
- package/templates/notification-sender.yaml +33 -0
- package/templates/order-validation.yaml +44 -0
- package/tsconfig.json +24 -0
- package/vitest.config.ts +25 -0
- package/workflows/order-processor.yaml +59 -0
- package/workflows/process-order-workflow.yaml +142 -0
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* YAML parser for behavior tree definitions
|
|
3
|
+
* Implements 4-stage validation pipeline
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as yaml from "js-yaml";
|
|
7
|
+
import type { Registry } from "../registry.js";
|
|
8
|
+
import type { TreeNode } from "../types.js";
|
|
9
|
+
import type { TreeDefinition } from "../schemas/tree-definition.schema.js";
|
|
10
|
+
import {
|
|
11
|
+
YamlSyntaxError,
|
|
12
|
+
StructureValidationError,
|
|
13
|
+
ValidationErrors,
|
|
14
|
+
ValidationError,
|
|
15
|
+
} from "./errors.js";
|
|
16
|
+
import { treeDefinitionSchema } from "../schemas/tree-definition.schema.js";
|
|
17
|
+
import { semanticValidator } from "./validation/semantic-validator.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Options for YAML loading and validation
|
|
21
|
+
*/
|
|
22
|
+
export interface LoadOptions {
|
|
23
|
+
/**
|
|
24
|
+
* Enable validation (default: true)
|
|
25
|
+
*/
|
|
26
|
+
validate?: boolean;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Fail on first error or collect all errors (default: true - fail fast)
|
|
30
|
+
*/
|
|
31
|
+
failFast?: boolean;
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Auto-generate IDs for nodes without IDs (default: true)
|
|
35
|
+
*/
|
|
36
|
+
autoGenerateIds?: boolean;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Validation options for validateYaml()
|
|
41
|
+
*/
|
|
42
|
+
export interface ValidationOptions {
|
|
43
|
+
/**
|
|
44
|
+
* Collect all errors instead of failing fast (default: false)
|
|
45
|
+
*/
|
|
46
|
+
collectAllErrors?: boolean;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check semantic rules like references and circular deps (default: true)
|
|
50
|
+
*/
|
|
51
|
+
checkReferences?: boolean;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Validation result
|
|
56
|
+
*/
|
|
57
|
+
export interface ValidationResult {
|
|
58
|
+
valid: boolean;
|
|
59
|
+
errors: ValidationError[];
|
|
60
|
+
warnings?: string[];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Parse YAML string to TreeDefinition
|
|
65
|
+
* Stage 1: YAML Syntax Validation
|
|
66
|
+
*
|
|
67
|
+
* @param yamlString - YAML content to parse
|
|
68
|
+
* @returns Parsed tree definition object
|
|
69
|
+
* @throws YamlSyntaxError if YAML is malformed
|
|
70
|
+
*/
|
|
71
|
+
export function parseYaml(yamlString: string): TreeDefinition {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = yaml.load(yamlString);
|
|
74
|
+
|
|
75
|
+
if (!parsed || typeof parsed !== "object") {
|
|
76
|
+
throw new YamlSyntaxError(
|
|
77
|
+
"Invalid YAML: expected an object",
|
|
78
|
+
undefined,
|
|
79
|
+
undefined,
|
|
80
|
+
"Ensure your YAML defines a tree structure with 'type' field",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return parsed as TreeDefinition;
|
|
85
|
+
} catch (error) {
|
|
86
|
+
if (error instanceof YamlSyntaxError) {
|
|
87
|
+
throw error;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Convert js-yaml errors to our error format
|
|
91
|
+
if (error instanceof yaml.YAMLException) {
|
|
92
|
+
throw new YamlSyntaxError(
|
|
93
|
+
error.message,
|
|
94
|
+
error.mark?.line,
|
|
95
|
+
error.mark?.column,
|
|
96
|
+
"Check YAML syntax (indentation, colons, quotes)",
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
throw new YamlSyntaxError(
|
|
101
|
+
`Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`,
|
|
102
|
+
undefined,
|
|
103
|
+
undefined,
|
|
104
|
+
"Ensure your YAML is well-formed",
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Validate tree definition structure
|
|
111
|
+
* Stage 2: Structure Validation (using Zod schema)
|
|
112
|
+
*
|
|
113
|
+
* @param definition - Parsed tree definition
|
|
114
|
+
* @throws StructureValidationError if structure is invalid
|
|
115
|
+
*/
|
|
116
|
+
function validateStructure(definition: unknown): TreeDefinition {
|
|
117
|
+
try {
|
|
118
|
+
return treeDefinitionSchema.parse(definition) as TreeDefinition;
|
|
119
|
+
} catch (error) {
|
|
120
|
+
// Convert Zod errors to StructureValidationError
|
|
121
|
+
throw new StructureValidationError(
|
|
122
|
+
`Invalid tree structure: ${error instanceof Error ? error.message : String(error)}`,
|
|
123
|
+
undefined,
|
|
124
|
+
"Ensure all nodes have 'type' field and children are arrays",
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Load and create tree from YAML string
|
|
131
|
+
*
|
|
132
|
+
* @param yamlString - YAML content defining the tree
|
|
133
|
+
* @param registry - Registry with registered node types
|
|
134
|
+
* @param options - Loading options
|
|
135
|
+
* @returns Created tree node
|
|
136
|
+
* @throws ValidationError if validation fails
|
|
137
|
+
*
|
|
138
|
+
* @example
|
|
139
|
+
* ```typescript
|
|
140
|
+
* const yaml = `
|
|
141
|
+
* type: Sequence
|
|
142
|
+
* id: my-seq
|
|
143
|
+
* children:
|
|
144
|
+
* - type: PrintAction
|
|
145
|
+
* id: action1
|
|
146
|
+
* `;
|
|
147
|
+
*
|
|
148
|
+
* const tree = loadTreeFromYaml(yaml, registry);
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
export function loadTreeFromYaml(
|
|
152
|
+
yamlString: string,
|
|
153
|
+
registry: Registry,
|
|
154
|
+
options: LoadOptions = {},
|
|
155
|
+
): TreeNode {
|
|
156
|
+
const { validate = true, failFast = true, autoGenerateIds = true } = options;
|
|
157
|
+
|
|
158
|
+
// Stage 1: Parse YAML syntax
|
|
159
|
+
const parsed = parseYaml(yamlString);
|
|
160
|
+
|
|
161
|
+
if (validate) {
|
|
162
|
+
// Stage 2: Validate structure
|
|
163
|
+
const definition = validateStructure(parsed);
|
|
164
|
+
|
|
165
|
+
// Stage 3 & 4: Node config and semantic validation happen in Registry.createTree()
|
|
166
|
+
// which calls schema validation for each node
|
|
167
|
+
if (failFast) {
|
|
168
|
+
// Semantic validation
|
|
169
|
+
const semanticErrors = semanticValidator.validate(definition, registry);
|
|
170
|
+
if (semanticErrors.length > 0) {
|
|
171
|
+
if (failFast) {
|
|
172
|
+
throw semanticErrors[0];
|
|
173
|
+
} else {
|
|
174
|
+
throw new ValidationErrors(semanticErrors);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Create tree (this validates node configs via schemas)
|
|
180
|
+
return registry.createTree(definition);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// No validation - directly create tree
|
|
184
|
+
return registry.createTree(parsed);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Validate YAML without creating tree
|
|
189
|
+
* Useful for editors and validators
|
|
190
|
+
*
|
|
191
|
+
* @param yamlString - YAML content to validate
|
|
192
|
+
* @param registry - Registry with registered node types
|
|
193
|
+
* @param options - Validation options
|
|
194
|
+
* @returns Validation result with errors
|
|
195
|
+
*
|
|
196
|
+
* @example
|
|
197
|
+
* ```typescript
|
|
198
|
+
* const result = validateYaml(yaml, registry);
|
|
199
|
+
* if (!result.valid) {
|
|
200
|
+
* console.error('Validation errors:', result.errors);
|
|
201
|
+
* }
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
export function validateYaml(
|
|
205
|
+
yamlString: string,
|
|
206
|
+
registry: Registry,
|
|
207
|
+
options: ValidationOptions = {},
|
|
208
|
+
): ValidationResult {
|
|
209
|
+
const { collectAllErrors = false, checkReferences = true } = options;
|
|
210
|
+
const errors: ValidationError[] = [];
|
|
211
|
+
|
|
212
|
+
try {
|
|
213
|
+
// Stage 1: Parse YAML
|
|
214
|
+
const parsed = parseYaml(yamlString);
|
|
215
|
+
|
|
216
|
+
// Stage 2: Validate structure
|
|
217
|
+
const definition = validateStructure(parsed);
|
|
218
|
+
|
|
219
|
+
// Stage 4: Semantic validation
|
|
220
|
+
if (checkReferences) {
|
|
221
|
+
const semanticErrors = semanticValidator.validate(definition, registry);
|
|
222
|
+
errors.push(...semanticErrors);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Stage 3: Node config validation (via safe create)
|
|
226
|
+
if (errors.length === 0 || collectAllErrors) {
|
|
227
|
+
const result = registry.safeCreateTree(definition);
|
|
228
|
+
if (!result.success) {
|
|
229
|
+
errors.push(
|
|
230
|
+
new StructureValidationError(
|
|
231
|
+
result.error.message,
|
|
232
|
+
undefined,
|
|
233
|
+
"Check node configurations match their schema requirements",
|
|
234
|
+
),
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
return {
|
|
240
|
+
valid: errors.length === 0,
|
|
241
|
+
errors,
|
|
242
|
+
};
|
|
243
|
+
} catch (error) {
|
|
244
|
+
if (error instanceof ValidationErrors) {
|
|
245
|
+
return {
|
|
246
|
+
valid: false,
|
|
247
|
+
errors: error.errors,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (error instanceof ValidationError) {
|
|
252
|
+
errors.push(error);
|
|
253
|
+
} else {
|
|
254
|
+
errors.push(
|
|
255
|
+
new YamlSyntaxError(
|
|
256
|
+
error instanceof Error ? error.message : String(error),
|
|
257
|
+
),
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
valid: false,
|
|
263
|
+
errors,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* Convert TreeDefinition to YAML string
|
|
270
|
+
*
|
|
271
|
+
* @param definition - Tree definition to convert
|
|
272
|
+
* @returns YAML string representation
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```typescript
|
|
276
|
+
* const definition = { type: 'Sequence', id: 'root', children: [] };
|
|
277
|
+
* const yamlString = toYaml(definition);
|
|
278
|
+
* ```
|
|
279
|
+
*/
|
|
280
|
+
export function toYaml(definition: TreeDefinition): string {
|
|
281
|
+
return yaml.dump(definition, {
|
|
282
|
+
indent: 2,
|
|
283
|
+
lineWidth: 80,
|
|
284
|
+
noRefs: true,
|
|
285
|
+
});
|
|
286
|
+
}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic validation (Stage 4)
|
|
3
|
+
* Validates semantic rules like ID uniqueness, circular references, etc.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Registry } from "../../registry.js";
|
|
7
|
+
import type { TreeDefinition } from "../../schemas/tree-definition.schema.js";
|
|
8
|
+
import { SemanticValidationError } from "../errors.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Semantic validator for tree definitions
|
|
12
|
+
* Validates cross-node rules that can't be expressed in schemas
|
|
13
|
+
*/
|
|
14
|
+
class SemanticValidator {
|
|
15
|
+
/**
|
|
16
|
+
* Validate semantic rules for a tree definition
|
|
17
|
+
*
|
|
18
|
+
* @param definition - Tree definition to validate
|
|
19
|
+
* @param registry - Registry to check node types and tree references
|
|
20
|
+
* @returns Array of validation errors (empty if valid)
|
|
21
|
+
*/
|
|
22
|
+
validate(definition: TreeDefinition, registry: Registry): SemanticValidationError[] {
|
|
23
|
+
const errors: SemanticValidationError[] = [];
|
|
24
|
+
|
|
25
|
+
// Track IDs for uniqueness checking
|
|
26
|
+
const seenIds = new Set<string>();
|
|
27
|
+
|
|
28
|
+
// Track SubTree references for circular dependency detection
|
|
29
|
+
const subTreePath: string[] = [];
|
|
30
|
+
|
|
31
|
+
// Recursive validation
|
|
32
|
+
this.validateNode(definition, registry, seenIds, subTreePath, "", errors);
|
|
33
|
+
|
|
34
|
+
return errors;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Recursively validate a node and its children
|
|
39
|
+
*/
|
|
40
|
+
private validateNode(
|
|
41
|
+
node: TreeDefinition,
|
|
42
|
+
registry: Registry,
|
|
43
|
+
seenIds: Set<string>,
|
|
44
|
+
subTreePath: string[],
|
|
45
|
+
path: string,
|
|
46
|
+
errors: SemanticValidationError[],
|
|
47
|
+
): void {
|
|
48
|
+
const nodePath = path ? `${path}.${node.id || node.type}` : node.id || node.type;
|
|
49
|
+
|
|
50
|
+
// Validate node type exists in registry
|
|
51
|
+
if (!registry.has(node.type)) {
|
|
52
|
+
errors.push(
|
|
53
|
+
new SemanticValidationError(
|
|
54
|
+
`Unknown node type '${node.type}'`,
|
|
55
|
+
nodePath,
|
|
56
|
+
`Available types: ${registry.getRegisteredTypes().join(", ")}`,
|
|
57
|
+
),
|
|
58
|
+
);
|
|
59
|
+
return; // Can't continue validation without valid type
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Validate ID uniqueness
|
|
63
|
+
if (node.id) {
|
|
64
|
+
if (seenIds.has(node.id)) {
|
|
65
|
+
errors.push(
|
|
66
|
+
new SemanticValidationError(
|
|
67
|
+
`Duplicate ID '${node.id}'`,
|
|
68
|
+
nodePath,
|
|
69
|
+
"Use unique IDs for each node in the tree",
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
} else {
|
|
73
|
+
seenIds.add(node.id);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Validate SubTree references for circular dependencies
|
|
78
|
+
if (node.type === "SubTree") {
|
|
79
|
+
const treeId = (node.props?.treeId as string) || "";
|
|
80
|
+
|
|
81
|
+
if (!treeId) {
|
|
82
|
+
errors.push(
|
|
83
|
+
new SemanticValidationError(
|
|
84
|
+
"SubTree node missing 'treeId' property",
|
|
85
|
+
nodePath,
|
|
86
|
+
"Specify which tree to reference with 'treeId' in props",
|
|
87
|
+
),
|
|
88
|
+
);
|
|
89
|
+
} else {
|
|
90
|
+
// Check for circular reference
|
|
91
|
+
if (subTreePath.includes(treeId)) {
|
|
92
|
+
errors.push(
|
|
93
|
+
new SemanticValidationError(
|
|
94
|
+
`Circular SubTree reference detected: ${subTreePath.join(" -> ")} -> ${treeId}`,
|
|
95
|
+
nodePath,
|
|
96
|
+
"Remove circular tree references",
|
|
97
|
+
),
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Check if referenced tree exists (if registry has tree lookup)
|
|
102
|
+
if (registry.hasTree && !registry.hasTree(treeId)) {
|
|
103
|
+
errors.push(
|
|
104
|
+
new SemanticValidationError(
|
|
105
|
+
`SubTree references unknown tree '${treeId}'`,
|
|
106
|
+
nodePath,
|
|
107
|
+
`Register the tree '${treeId}' before referencing it`,
|
|
108
|
+
),
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Validate children based on node category
|
|
115
|
+
const metadata = registry.getMetadata(node.type);
|
|
116
|
+
if (metadata) {
|
|
117
|
+
if (metadata.category === "decorator") {
|
|
118
|
+
// Decorators must have exactly 1 child
|
|
119
|
+
const childCount = node.children?.length || 0;
|
|
120
|
+
if (childCount !== 1) {
|
|
121
|
+
errors.push(
|
|
122
|
+
new SemanticValidationError(
|
|
123
|
+
`Decorator '${node.type}' must have exactly 1 child (has ${childCount})`,
|
|
124
|
+
nodePath,
|
|
125
|
+
"Decorators wrap a single child node",
|
|
126
|
+
),
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Some composites have specific child requirements
|
|
132
|
+
if (node.type === "While") {
|
|
133
|
+
const childCount = node.children?.length || 0;
|
|
134
|
+
if (childCount !== 2) {
|
|
135
|
+
errors.push(
|
|
136
|
+
new SemanticValidationError(
|
|
137
|
+
`While node requires exactly 2 children: condition and body (has ${childCount})`,
|
|
138
|
+
nodePath,
|
|
139
|
+
"First child is the condition, second is the body to execute",
|
|
140
|
+
),
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (node.type === "Conditional") {
|
|
146
|
+
const childCount = node.children?.length || 0;
|
|
147
|
+
if (childCount < 2 || childCount > 3) {
|
|
148
|
+
errors.push(
|
|
149
|
+
new SemanticValidationError(
|
|
150
|
+
`Conditional node requires 2-3 children: condition, then, optional else (has ${childCount})`,
|
|
151
|
+
nodePath,
|
|
152
|
+
"First child is condition, second is 'then' branch, third is optional 'else' branch",
|
|
153
|
+
),
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (node.type === "ForEach") {
|
|
159
|
+
const childCount = node.children?.length || 0;
|
|
160
|
+
if (childCount === 0) {
|
|
161
|
+
errors.push(
|
|
162
|
+
new SemanticValidationError(
|
|
163
|
+
"ForEach node requires at least 1 child (the body to execute for each item)",
|
|
164
|
+
nodePath,
|
|
165
|
+
"Add a child node to execute for each item in the collection",
|
|
166
|
+
),
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Recursively validate children
|
|
173
|
+
if (node.children && Array.isArray(node.children)) {
|
|
174
|
+
const newSubTreePath =
|
|
175
|
+
node.type === "SubTree" && node.props?.treeId
|
|
176
|
+
? [...subTreePath, node.props.treeId as string]
|
|
177
|
+
: subTreePath;
|
|
178
|
+
|
|
179
|
+
node.children.forEach((child, index) => {
|
|
180
|
+
this.validateNode(
|
|
181
|
+
child,
|
|
182
|
+
registry,
|
|
183
|
+
seenIds,
|
|
184
|
+
newSubTreePath,
|
|
185
|
+
`${nodePath}.children[${index}]`,
|
|
186
|
+
errors,
|
|
187
|
+
);
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Singleton semantic validator instance
|
|
195
|
+
*/
|
|
196
|
+
export const semanticValidator = new SemanticValidator();
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Template: GoogleSheets.InsertRow
|
|
2
|
+
#
|
|
3
|
+
# Friendly interface for inserting rows into Google Sheets.
|
|
4
|
+
# Handles data transformation to Active Pieces format.
|
|
5
|
+
#
|
|
6
|
+
# Expected inputs (set on blackboard before calling):
|
|
7
|
+
# tpl_spreadsheet: string - Spreadsheet ID
|
|
8
|
+
# tpl_sheet: number - Sheet index (default: 0)
|
|
9
|
+
# tpl_values: array|object - Values as array or { "Column": "value" }
|
|
10
|
+
#
|
|
11
|
+
# Output:
|
|
12
|
+
# tpl_result: object - Result from insert operation
|
|
13
|
+
|
|
14
|
+
type: Sequence
|
|
15
|
+
id: google-sheets-insert-row
|
|
16
|
+
children:
|
|
17
|
+
# Step 1: Validate and prepare inputs
|
|
18
|
+
- type: CodeExecution
|
|
19
|
+
id: _prepare-inputs
|
|
20
|
+
props:
|
|
21
|
+
language: javascript
|
|
22
|
+
code: |
|
|
23
|
+
const spreadsheet = getBB('tpl_spreadsheet');
|
|
24
|
+
if (!spreadsheet) {
|
|
25
|
+
throw new Error('Missing required input: tpl_spreadsheet');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const sheet = getBB('tpl_sheet');
|
|
29
|
+
const sheetId = (sheet === undefined || sheet === null) ? 0 : Number(sheet);
|
|
30
|
+
|
|
31
|
+
const values = getBB('tpl_values');
|
|
32
|
+
if (!values) {
|
|
33
|
+
throw new Error('Missing required input: tpl_values');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Transform values to Active Pieces format
|
|
37
|
+
let transformed;
|
|
38
|
+
if (Array.isArray(values)) {
|
|
39
|
+
transformed = { values: values };
|
|
40
|
+
} else if (typeof values === 'object') {
|
|
41
|
+
transformed = { values: Object.values(values) };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Store prepared values
|
|
45
|
+
setBB('tpl_spreadsheetId', spreadsheet);
|
|
46
|
+
setBB('tpl_sheetId', sheetId);
|
|
47
|
+
setBB('tpl_transformedValues', transformed);
|
|
48
|
+
|
|
49
|
+
# Step 2: Log what we're doing
|
|
50
|
+
- type: LogMessage
|
|
51
|
+
id: _log-insert
|
|
52
|
+
props:
|
|
53
|
+
message: "Inserting row into sheet ${tpl_spreadsheetId}"
|
|
54
|
+
level: debug
|
|
55
|
+
|
|
56
|
+
# Step 3: Execute the insert
|
|
57
|
+
- type: IntegrationAction
|
|
58
|
+
id: _do-insert
|
|
59
|
+
props:
|
|
60
|
+
provider: google
|
|
61
|
+
action: insert_row
|
|
62
|
+
inputs:
|
|
63
|
+
spreadsheetId: "${bb.tpl_spreadsheetId}"
|
|
64
|
+
sheetId: "${bb.tpl_sheetId}"
|
|
65
|
+
first_row_headers: false
|
|
66
|
+
as_string: true
|
|
67
|
+
values: "${bb.tpl_transformedValues}"
|
|
68
|
+
|
|
69
|
+
# Step 4: Store result for caller
|
|
70
|
+
- type: CodeExecution
|
|
71
|
+
id: _store-result
|
|
72
|
+
props:
|
|
73
|
+
language: javascript
|
|
74
|
+
code: |
|
|
75
|
+
const result = getBB('_do-insert.result');
|
|
76
|
+
setBB('tpl_result', result);
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Notification Sender Template
|
|
2
|
+
# Sends notifications through configured channels
|
|
3
|
+
#
|
|
4
|
+
# Inputs (expected on blackboard):
|
|
5
|
+
# - notificationMessage: string
|
|
6
|
+
# - notificationType: string (email, slack, sms)
|
|
7
|
+
# - recipient: string
|
|
8
|
+
#
|
|
9
|
+
# Outputs (set on blackboard):
|
|
10
|
+
# - notificationSent: boolean
|
|
11
|
+
# - notificationId: string (optional)
|
|
12
|
+
|
|
13
|
+
type: Sequence
|
|
14
|
+
id: notification-sender
|
|
15
|
+
name: Notification Sender
|
|
16
|
+
|
|
17
|
+
children:
|
|
18
|
+
- type: LogMessage
|
|
19
|
+
id: log-start
|
|
20
|
+
props:
|
|
21
|
+
message: "Preparing to send notification: ${notificationMessage}"
|
|
22
|
+
level: info
|
|
23
|
+
|
|
24
|
+
- type: LogMessage
|
|
25
|
+
id: log-type
|
|
26
|
+
props:
|
|
27
|
+
message: "Notification type: ${notificationType}, Recipient: ${recipient}"
|
|
28
|
+
|
|
29
|
+
- type: LogMessage
|
|
30
|
+
id: log-complete
|
|
31
|
+
props:
|
|
32
|
+
message: "Notification sent successfully"
|
|
33
|
+
level: info
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# Order Validation Template
|
|
2
|
+
# Validates order data before processing
|
|
3
|
+
#
|
|
4
|
+
# Inputs (expected on blackboard):
|
|
5
|
+
# - orderId: string
|
|
6
|
+
# - items: array of order items
|
|
7
|
+
# - customerId: string (optional)
|
|
8
|
+
#
|
|
9
|
+
# Outputs (set on blackboard):
|
|
10
|
+
# - validationPassed: boolean
|
|
11
|
+
# - validationErrors: array of error messages
|
|
12
|
+
|
|
13
|
+
type: Sequence
|
|
14
|
+
id: order-validation
|
|
15
|
+
name: Order Validation
|
|
16
|
+
|
|
17
|
+
children:
|
|
18
|
+
- type: LogMessage
|
|
19
|
+
id: start-validation
|
|
20
|
+
props:
|
|
21
|
+
message: "Starting order validation for order: ${orderId}"
|
|
22
|
+
level: info
|
|
23
|
+
|
|
24
|
+
- type: Selector
|
|
25
|
+
id: validate-all
|
|
26
|
+
children:
|
|
27
|
+
- type: Sequence
|
|
28
|
+
id: all-checks
|
|
29
|
+
children:
|
|
30
|
+
- type: LogMessage
|
|
31
|
+
id: check-order-id
|
|
32
|
+
props:
|
|
33
|
+
message: "Validating order ID..."
|
|
34
|
+
|
|
35
|
+
- type: LogMessage
|
|
36
|
+
id: check-items
|
|
37
|
+
props:
|
|
38
|
+
message: "Validating order items..."
|
|
39
|
+
|
|
40
|
+
- type: LogMessage
|
|
41
|
+
id: validation-complete
|
|
42
|
+
props:
|
|
43
|
+
message: "Order validation completed successfully"
|
|
44
|
+
level: info
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"incremental": false,
|
|
4
|
+
"isolatedModules": true,
|
|
5
|
+
"moduleDetection": "force",
|
|
6
|
+
"noUncheckedIndexedAccess": true,
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"outDir": "./dist",
|
|
9
|
+
"rootDir": "./",
|
|
10
|
+
"declaration": true,
|
|
11
|
+
"declarationMap": true,
|
|
12
|
+
"lib": ["ES2022"],
|
|
13
|
+
"target": "ES2022",
|
|
14
|
+
"module": "NodeNext",
|
|
15
|
+
"moduleResolution": "NodeNext",
|
|
16
|
+
"strict": true,
|
|
17
|
+
"esModuleInterop": true,
|
|
18
|
+
"skipLibCheck": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
"types": ["node"]
|
|
21
|
+
},
|
|
22
|
+
"include": ["src/**/*", "examples/**/*"],
|
|
23
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts"]
|
|
24
|
+
}
|
package/vitest.config.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
watch: false,
|
|
6
|
+
globals: true,
|
|
7
|
+
environment: "node",
|
|
8
|
+
include: ["src/**/*.{test,spec}.{js,ts}"],
|
|
9
|
+
coverage: {
|
|
10
|
+
provider: "v8",
|
|
11
|
+
reporter: ["text", "json", "html"],
|
|
12
|
+
include: ["src/**/*.ts"],
|
|
13
|
+
exclude: [
|
|
14
|
+
"src/index.ts",
|
|
15
|
+
"src/**/*.test.ts",
|
|
16
|
+
"src/**/*.spec.ts",
|
|
17
|
+
"src/test-nodes.ts", // Test utilities
|
|
18
|
+
"src/scripting/generated/**/*", // Generated files
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
resolve: {
|
|
23
|
+
extensions: [".ts", ".js"],
|
|
24
|
+
},
|
|
25
|
+
});
|