@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,211 @@
|
|
|
1
|
+
# YAML Workflow Examples
|
|
2
|
+
|
|
3
|
+
This directory contains curated YAML workflow examples demonstrating the power and flexibility of declarative behavior tree definitions.
|
|
4
|
+
|
|
5
|
+
## ✅ Working Examples
|
|
6
|
+
|
|
7
|
+
These workflows are actively tested with Temporal and demonstrate production-ready patterns.
|
|
8
|
+
|
|
9
|
+
### 01-simple-sequence.yaml
|
|
10
|
+
**Complexity**: Low
|
|
11
|
+
**Pattern**: Sequential execution
|
|
12
|
+
**Nodes**: 4 nodes, 1 level deep
|
|
13
|
+
|
|
14
|
+
Basic sequence of print actions. Perfect for getting started and testing the YAML workflow system.
|
|
15
|
+
|
|
16
|
+
**Use cases:**
|
|
17
|
+
- Learning YAML workflow format
|
|
18
|
+
- Testing Temporal integration
|
|
19
|
+
- Simple linear processes
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
### 02-parallel-timeout.yaml
|
|
24
|
+
**Complexity**: Low-Medium
|
|
25
|
+
**Pattern**: Parallel execution with timeout protection
|
|
26
|
+
**Nodes**: 6 nodes, 3 levels deep
|
|
27
|
+
|
|
28
|
+
Demonstrates concurrent task execution with timeout wrapper. All tasks must complete within 5 seconds.
|
|
29
|
+
|
|
30
|
+
**Patterns demonstrated:**
|
|
31
|
+
- Parallel execution (strict strategy)
|
|
32
|
+
- Timeout decorator
|
|
33
|
+
- Sequential flow with parallel section
|
|
34
|
+
|
|
35
|
+
**Use cases:**
|
|
36
|
+
- Concurrent API calls
|
|
37
|
+
- Parallel data fetching
|
|
38
|
+
- Time-critical operations
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
### 05-order-processing.yaml
|
|
43
|
+
**Complexity**: High
|
|
44
|
+
**Pattern**: Multi-phase workflow with parallel operations
|
|
45
|
+
**Nodes**: 20+ nodes, 5 levels deep
|
|
46
|
+
|
|
47
|
+
**Real-world e-commerce order processing workflow with 3 phases:**
|
|
48
|
+
|
|
49
|
+
**Phase 1: Validation**
|
|
50
|
+
- Parallel validation checks (inventory, payment, shipping)
|
|
51
|
+
- 5-second timeout protection
|
|
52
|
+
|
|
53
|
+
**Phase 2: Payment Processing**
|
|
54
|
+
- Sequential payment flow
|
|
55
|
+
- 1-second simulated delay
|
|
56
|
+
- 10-second timeout
|
|
57
|
+
|
|
58
|
+
**Phase 3: Order Fulfillment**
|
|
59
|
+
- Parallel fulfillment tasks (inventory update, shipping label, email)
|
|
60
|
+
- 500ms delays for each task
|
|
61
|
+
|
|
62
|
+
**Patterns demonstrated:**
|
|
63
|
+
- Nested sequences
|
|
64
|
+
- Parallel execution at multiple levels
|
|
65
|
+
- Multiple timeout wrappers
|
|
66
|
+
- Delay decorators
|
|
67
|
+
- Multi-phase workflow structure
|
|
68
|
+
|
|
69
|
+
**Use cases:**
|
|
70
|
+
- E-commerce checkout flows
|
|
71
|
+
- Multi-step transaction processing
|
|
72
|
+
- Complex orchestration workflows
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## 📝 Reference Examples (Not Tested)
|
|
77
|
+
|
|
78
|
+
These workflows showcase advanced patterns but contain node types that may require additional setup.
|
|
79
|
+
|
|
80
|
+
### 03-ecommerce-checkout.yaml
|
|
81
|
+
**Complexity**: Medium
|
|
82
|
+
**Patterns**: Conditional logic, CheckCondition nodes, complex validation
|
|
83
|
+
**Note**: Requires CheckCondition and additional node types to be registered
|
|
84
|
+
|
|
85
|
+
Multi-phase checkout with validation, payment processing, and parallel post-checkout tasks.
|
|
86
|
+
|
|
87
|
+
---
|
|
88
|
+
|
|
89
|
+
### 04-ai-agent-workflow.yaml
|
|
90
|
+
**Complexity**: Very High
|
|
91
|
+
**Patterns**: Decision trees, iterative processing, complex routing
|
|
92
|
+
**Note**: AI-generated workflow showcasing LLM's strength at complex decision logic
|
|
93
|
+
|
|
94
|
+
Multi-step AI agent with decision routing, iterative research, quality checks, and fallback handling.
|
|
95
|
+
|
|
96
|
+
**Key Insight**: This demonstrates where LLMs excel - generating complex decision logic that would be tedious to build manually.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Usage
|
|
101
|
+
|
|
102
|
+
### Running with Temporal
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
# Terminal 1: Start Temporal server
|
|
106
|
+
temporal server start-dev
|
|
107
|
+
|
|
108
|
+
# Terminal 2: Build and start worker
|
|
109
|
+
npm run build
|
|
110
|
+
cd examples/temporal
|
|
111
|
+
npx tsx worker.ts
|
|
112
|
+
|
|
113
|
+
# Terminal 3: Execute workflows
|
|
114
|
+
cd examples/temporal
|
|
115
|
+
npx tsx client.ts
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Loading in Your Code
|
|
119
|
+
|
|
120
|
+
```typescript
|
|
121
|
+
import { readFileSync } from 'fs';
|
|
122
|
+
import { loadTreeFromYaml, Registry, registerStandardNodes } from '@wayfarer-ai/btree';
|
|
123
|
+
|
|
124
|
+
// Setup registry with all built-in nodes
|
|
125
|
+
const registry = new Registry();
|
|
126
|
+
registerStandardNodes(registry);
|
|
127
|
+
|
|
128
|
+
// Optionally register your custom nodes
|
|
129
|
+
// registry.register('MyCustomAction', MyCustomAction, { category: 'action' });
|
|
130
|
+
|
|
131
|
+
// Load YAML
|
|
132
|
+
const yamlContent = readFileSync('./01-simple-sequence.yaml', 'utf-8');
|
|
133
|
+
const tree = loadTreeFromYaml(yamlContent, registry);
|
|
134
|
+
|
|
135
|
+
// Execute
|
|
136
|
+
await tree.execute();
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### With Temporal
|
|
140
|
+
|
|
141
|
+
```typescript
|
|
142
|
+
import { yamlWorkflow } from './yaml-workflow-loader.js';
|
|
143
|
+
|
|
144
|
+
// In client
|
|
145
|
+
const yamlContent = readFileSync('./workflow.yaml', 'utf-8');
|
|
146
|
+
|
|
147
|
+
const result = await client.workflow.execute("yamlWorkflow", {
|
|
148
|
+
taskQueue: "btree-workflows",
|
|
149
|
+
workflowId: `my-workflow-${Date.now()}`,
|
|
150
|
+
args: [{
|
|
151
|
+
input: {},
|
|
152
|
+
treeRegistry: new Registry(),
|
|
153
|
+
yamlContent
|
|
154
|
+
}]
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Node Types Reference
|
|
159
|
+
|
|
160
|
+
### All Standard Nodes (via `registerStandardNodes()`)
|
|
161
|
+
|
|
162
|
+
The `registerStandardNodes()` utility automatically registers all built-in nodes:
|
|
163
|
+
|
|
164
|
+
**Composites (10):**
|
|
165
|
+
- Sequence, Selector, Parallel
|
|
166
|
+
- Conditional, ForEach, While
|
|
167
|
+
- Recovery, ReactiveSequence, MemorySequence
|
|
168
|
+
- SubTree
|
|
169
|
+
|
|
170
|
+
**Decorators (10):**
|
|
171
|
+
- Timeout, Delay, Repeat
|
|
172
|
+
- Invert, ForceSuccess, ForceFailure
|
|
173
|
+
- RunOnce, KeepRunningUntilFailure
|
|
174
|
+
- Precondition, SoftAssert
|
|
175
|
+
|
|
176
|
+
**Actions/Conditions (9):**
|
|
177
|
+
- PrintAction, MockAction, CounterAction
|
|
178
|
+
- CheckCondition, AlwaysCondition, WaitAction
|
|
179
|
+
- Script, LogMessage, RegexExtract
|
|
180
|
+
|
|
181
|
+
**Test Nodes (3):**
|
|
182
|
+
- SuccessNode, FailureNode, RunningNode
|
|
183
|
+
|
|
184
|
+
### Adding Custom Nodes
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
import { Registry, registerStandardNodes } from "@wayfarer-ai/btree";
|
|
188
|
+
import { MyCustomNode } from "./my-nodes.js";
|
|
189
|
+
|
|
190
|
+
const registry = new Registry();
|
|
191
|
+
registerStandardNodes(registry); // Register all built-in nodes
|
|
192
|
+
|
|
193
|
+
// Add your custom nodes
|
|
194
|
+
registry.register("MyCustomNode", MyCustomNode, { category: "action" });
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
## Benefits of YAML Workflows
|
|
198
|
+
|
|
199
|
+
✅ **Declarative** - Define what, not how
|
|
200
|
+
✅ **Version Controlled** - Track workflow changes in git
|
|
201
|
+
✅ **AI-Friendly** - Easy for LLMs to generate and modify
|
|
202
|
+
✅ **Validated** - 4-stage validation catches errors early
|
|
203
|
+
✅ **Type-Safe** - Runtime validation via Zod schemas
|
|
204
|
+
✅ **Portable** - Same YAML works standalone or in Temporal
|
|
205
|
+
✅ **Readable** - Non-developers can understand workflow logic
|
|
206
|
+
|
|
207
|
+
## Complete Documentation
|
|
208
|
+
|
|
209
|
+
- [YAML Specification](../../docs/yaml-specification.md) - Complete format reference
|
|
210
|
+
- [Temporal Integration](../temporal/README.md) - Running YAML workflows in Temporal
|
|
211
|
+
- [Node Reference](../../docs/workflow-engine/02-node-reference.md) - All available node types
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@q1k-oss/btree-workflows",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"module": "./dist/index.mjs",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"scripts": {
|
|
9
|
+
"clean": "rm -rf dist",
|
|
10
|
+
"dev": "tsup-node src/index.ts --watch --format cjs,esm --dts",
|
|
11
|
+
"build": "npm run clean && tsup-node src/index.ts --format cjs,esm --dts",
|
|
12
|
+
"typecheck": "tsc --noEmit",
|
|
13
|
+
"test": "CI=true vitest --coverage",
|
|
14
|
+
"test:watch": "vitest --watch",
|
|
15
|
+
"test:ui": "vitest --ui"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@activepieces/piece-google-sheets": "^0.14.0",
|
|
19
|
+
"@activepieces/pieces-framework": "^0.23.0",
|
|
20
|
+
"@temporalio/activity": "^1.10.0",
|
|
21
|
+
"@temporalio/workflow": "^1.10.0",
|
|
22
|
+
"js-yaml": "^4.1.1",
|
|
23
|
+
"zod": "^4.2.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"@temporalio/client": "^1.10.0",
|
|
27
|
+
"@temporalio/testing": "^1.10.0",
|
|
28
|
+
"@temporalio/worker": "^1.10.0",
|
|
29
|
+
"@types/js-yaml": "^4.0.9",
|
|
30
|
+
"@types/node": "^24.2.1",
|
|
31
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
32
|
+
"@vitest/ui": "^3.2.4",
|
|
33
|
+
"tsup": "^8.5.0",
|
|
34
|
+
"tsx": "^4.19.2",
|
|
35
|
+
"typescript": "latest",
|
|
36
|
+
"vitest": "^3.2.4"
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zod schema for CodeExecution node validation
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createNodeSchema } from "../schemas/base.schema.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Schema for CodeExecution node props
|
|
10
|
+
*/
|
|
11
|
+
export const codeExecutionSchema = createNodeSchema("CodeExecution", {
|
|
12
|
+
/** Code to execute */
|
|
13
|
+
code: z.string().min(1, "Code is required"),
|
|
14
|
+
|
|
15
|
+
/** Programming language */
|
|
16
|
+
language: z.enum(["javascript", "python"], {
|
|
17
|
+
message: "Language must be 'javascript' or 'python'",
|
|
18
|
+
}),
|
|
19
|
+
|
|
20
|
+
/** Execution timeout in milliseconds */
|
|
21
|
+
timeout: z.number().int().positive().optional().default(30000),
|
|
22
|
+
|
|
23
|
+
/** Python packages to install before execution */
|
|
24
|
+
packages: z.array(z.string()).optional().default([]),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
export type CodeExecutionSchemaType = z.infer<typeof codeExecutionSchema>;
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CodeExecution Node
|
|
3
|
+
*
|
|
4
|
+
* Executes JavaScript or Python code in a secure sandboxed environment
|
|
5
|
+
* via Microsandbox (libkrun microVM). This node replaces the inline Script
|
|
6
|
+
* node for production use where untrusted/AI-generated code needs isolation.
|
|
7
|
+
*
|
|
8
|
+
* Features:
|
|
9
|
+
* - Support for JavaScript and Python
|
|
10
|
+
* - Full VM isolation (no network, no filesystem, no cloud credentials)
|
|
11
|
+
* - DataStore integration for large payloads
|
|
12
|
+
* - getBB/setBB helpers for blackboard access
|
|
13
|
+
* - getInput helper for workflow input access
|
|
14
|
+
* - Configurable timeout
|
|
15
|
+
* - Python package installation support
|
|
16
|
+
*
|
|
17
|
+
* Security:
|
|
18
|
+
* - Code runs in Microsandbox microVM
|
|
19
|
+
* - No cloud credentials exposed to sandbox
|
|
20
|
+
* - Activity fetches data from DataStore, not the sandbox
|
|
21
|
+
* - Only JSON data crosses the sandbox boundary
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { ActionNode } from "../base-node.js";
|
|
25
|
+
import { ConfigurationError } from "../errors.js";
|
|
26
|
+
import {
|
|
27
|
+
type TemporalContext,
|
|
28
|
+
type NodeConfiguration,
|
|
29
|
+
type CodeExecutionRequest,
|
|
30
|
+
type DataRef,
|
|
31
|
+
NodeStatus,
|
|
32
|
+
} from "../types.js";
|
|
33
|
+
import { isDataRef } from "../data-store/types.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Configuration for CodeExecution node
|
|
37
|
+
*/
|
|
38
|
+
export interface CodeExecutionConfig extends NodeConfiguration {
|
|
39
|
+
/** Code to execute */
|
|
40
|
+
code: string;
|
|
41
|
+
/** Programming language: 'javascript' or 'python' */
|
|
42
|
+
language: "javascript" | "python";
|
|
43
|
+
/** Execution timeout in milliseconds (default: 30000) */
|
|
44
|
+
timeout?: number;
|
|
45
|
+
/** Python packages to install before execution */
|
|
46
|
+
packages?: string[];
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* CodeExecution Node
|
|
51
|
+
*
|
|
52
|
+
* Executes code in a sandboxed environment via Temporal activity.
|
|
53
|
+
* Requires the `executeCode` activity to be configured.
|
|
54
|
+
*
|
|
55
|
+
* @example YAML (JavaScript)
|
|
56
|
+
* ```yaml
|
|
57
|
+
* type: CodeExecution
|
|
58
|
+
* id: transform-data
|
|
59
|
+
* props:
|
|
60
|
+
* language: javascript
|
|
61
|
+
* code: |
|
|
62
|
+
* const users = getBB('apiUsers');
|
|
63
|
+
* const processed = users.map(u => ({
|
|
64
|
+
* id: u.id,
|
|
65
|
+
* name: u.name.toUpperCase()
|
|
66
|
+
* }));
|
|
67
|
+
* setBB('processedUsers', processed);
|
|
68
|
+
* timeout: 30000
|
|
69
|
+
* ```
|
|
70
|
+
*
|
|
71
|
+
* @example YAML (Python)
|
|
72
|
+
* ```yaml
|
|
73
|
+
* type: CodeExecution
|
|
74
|
+
* id: analyze-data
|
|
75
|
+
* props:
|
|
76
|
+
* language: python
|
|
77
|
+
* packages:
|
|
78
|
+
* - pandas
|
|
79
|
+
* code: |
|
|
80
|
+
* import pandas as pd
|
|
81
|
+
*
|
|
82
|
+
* users = getBB('apiUsers')
|
|
83
|
+
* df = pd.DataFrame(users)
|
|
84
|
+
*
|
|
85
|
+
* setBB('userCount', len(df))
|
|
86
|
+
* setBB('domains', df['email'].str.split('@').str[1].unique().tolist())
|
|
87
|
+
* timeout: 60000
|
|
88
|
+
* ```
|
|
89
|
+
*
|
|
90
|
+
* Available functions in sandbox:
|
|
91
|
+
* - getBB(key): Get value from blackboard
|
|
92
|
+
* - setBB(key, value): Set value on blackboard
|
|
93
|
+
* - getInput(key): Get value from workflow input (read-only)
|
|
94
|
+
*/
|
|
95
|
+
export class CodeExecution extends ActionNode {
|
|
96
|
+
private code: string;
|
|
97
|
+
private language: "javascript" | "python";
|
|
98
|
+
private timeout: number;
|
|
99
|
+
private packages: string[];
|
|
100
|
+
|
|
101
|
+
constructor(config: CodeExecutionConfig) {
|
|
102
|
+
super(config);
|
|
103
|
+
|
|
104
|
+
if (!config.code) {
|
|
105
|
+
throw new ConfigurationError("CodeExecution requires code");
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (!config.language) {
|
|
109
|
+
throw new ConfigurationError("CodeExecution requires language");
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!["javascript", "python"].includes(config.language)) {
|
|
113
|
+
throw new ConfigurationError(
|
|
114
|
+
`CodeExecution language must be 'javascript' or 'python', got: ${config.language}`
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
this.code = config.code;
|
|
119
|
+
this.language = config.language;
|
|
120
|
+
this.timeout = config.timeout ?? 30000;
|
|
121
|
+
this.packages = config.packages ?? [];
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
protected async executeTick(context: TemporalContext): Promise<NodeStatus> {
|
|
125
|
+
// Validate activity is available
|
|
126
|
+
if (!context.activities?.executeCode) {
|
|
127
|
+
this._lastError =
|
|
128
|
+
"CodeExecution requires activities.executeCode to be configured. " +
|
|
129
|
+
"This activity handles sandboxed code execution via Microsandbox.";
|
|
130
|
+
this.log(`Error: ${this._lastError}`);
|
|
131
|
+
return NodeStatus.FAILURE;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
// Separate DataRefs from inline values in blackboard
|
|
136
|
+
const dataRefs: Record<string, DataRef> = {};
|
|
137
|
+
const inlineContext: Record<string, unknown> = {};
|
|
138
|
+
|
|
139
|
+
for (const [key, value] of Object.entries(context.blackboard.toJSON())) {
|
|
140
|
+
if (isDataRef(value)) {
|
|
141
|
+
dataRefs[key] = value;
|
|
142
|
+
} else {
|
|
143
|
+
inlineContext[key] = value;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// Build the request
|
|
148
|
+
const request: CodeExecutionRequest = {
|
|
149
|
+
code: this.code,
|
|
150
|
+
language: this.language,
|
|
151
|
+
dataRefs,
|
|
152
|
+
context: inlineContext,
|
|
153
|
+
input: context.input ? { ...context.input } : undefined,
|
|
154
|
+
timeout: this.timeout,
|
|
155
|
+
packages: this.packages.length > 0 ? this.packages : undefined,
|
|
156
|
+
workflowId: context.workflowInfo?.workflowId,
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
this.log(
|
|
160
|
+
`Executing ${this.language} code (timeout: ${this.timeout}ms, packages: ${this.packages.length})`
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Execute in sandbox via activity
|
|
164
|
+
const result = await context.activities.executeCode(request);
|
|
165
|
+
|
|
166
|
+
// Log any output from the sandbox
|
|
167
|
+
if (result.logs.length > 0) {
|
|
168
|
+
for (const log of result.logs) {
|
|
169
|
+
this.log(`[sandbox] ${log}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Merge results back to blackboard
|
|
174
|
+
for (const [key, value] of Object.entries(result.values)) {
|
|
175
|
+
context.blackboard.set(key, value);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Store DataRefs for large results
|
|
179
|
+
for (const [key, ref] of Object.entries(result.dataRefs)) {
|
|
180
|
+
context.blackboard.set(key, ref);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this.log(
|
|
184
|
+
`Code execution completed in ${result.executionTimeMs}ms ` +
|
|
185
|
+
`(${Object.keys(result.values).length} values, ${Object.keys(result.dataRefs).length} refs)`
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
return NodeStatus.SUCCESS;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
// Extract the actual error message from Temporal's ActivityFailure chain
|
|
191
|
+
// ActivityFailure -> ApplicationFailure -> cause -> actual error
|
|
192
|
+
let actualMessage = error instanceof Error ? error.message : String(error);
|
|
193
|
+
|
|
194
|
+
// Try to extract the actual cause from Temporal's error chain
|
|
195
|
+
if (error && typeof error === "object") {
|
|
196
|
+
const err = error as {
|
|
197
|
+
cause?: {
|
|
198
|
+
message?: string;
|
|
199
|
+
cause?: { message?: string };
|
|
200
|
+
};
|
|
201
|
+
};
|
|
202
|
+
// ActivityFailure.cause is ApplicationFailure
|
|
203
|
+
// ApplicationFailure.cause.message is the actual error
|
|
204
|
+
if (err.cause?.cause?.message) {
|
|
205
|
+
actualMessage = err.cause.cause.message;
|
|
206
|
+
} else if (err.cause?.message) {
|
|
207
|
+
actualMessage = err.cause.message;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
this.log(`Code execution failed: ${actualMessage}`);
|
|
212
|
+
|
|
213
|
+
// Re-throw with the extracted message so ActionNode's error handler
|
|
214
|
+
// can emit ERROR event with the actual error context
|
|
215
|
+
throw new Error(`Code execution failed: ${actualMessage}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|