@q1k-oss/btree-workflows 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (203) hide show
  1. package/.claude/settings.local.json +31 -0
  2. package/CLAUDE.md +181 -0
  3. package/LICENSE +21 -0
  4. package/README.md +920 -0
  5. package/behaviour-tree-workflows-landing/index.html +16 -0
  6. package/behaviour-tree-workflows-landing/package-lock.json +2074 -0
  7. package/behaviour-tree-workflows-landing/package.json +31 -0
  8. package/behaviour-tree-workflows-landing/public/favicon.svg +17 -0
  9. package/behaviour-tree-workflows-landing/src/App.css +103 -0
  10. package/behaviour-tree-workflows-landing/src/App.tsx +176 -0
  11. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.css +89 -0
  12. package/behaviour-tree-workflows-landing/src/components/BlackboardInspector.tsx +64 -0
  13. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.css +64 -0
  14. package/behaviour-tree-workflows-landing/src/components/ExampleSelector.tsx +34 -0
  15. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.css +107 -0
  16. package/behaviour-tree-workflows-landing/src/components/ExecutionLog.tsx +85 -0
  17. package/behaviour-tree-workflows-landing/src/components/Header.css +50 -0
  18. package/behaviour-tree-workflows-landing/src/components/Header.tsx +26 -0
  19. package/behaviour-tree-workflows-landing/src/components/StatusBadge.css +45 -0
  20. package/behaviour-tree-workflows-landing/src/components/StatusBadge.tsx +15 -0
  21. package/behaviour-tree-workflows-landing/src/components/Toolbar.css +74 -0
  22. package/behaviour-tree-workflows-landing/src/components/Toolbar.tsx +53 -0
  23. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.css +67 -0
  24. package/behaviour-tree-workflows-landing/src/components/TreeVisualizer.tsx +192 -0
  25. package/behaviour-tree-workflows-landing/src/components/YamlEditor.css +18 -0
  26. package/behaviour-tree-workflows-landing/src/components/YamlEditor.tsx +96 -0
  27. package/behaviour-tree-workflows-landing/src/lib/count-nodes.ts +11 -0
  28. package/behaviour-tree-workflows-landing/src/lib/execution-engine.ts +96 -0
  29. package/behaviour-tree-workflows-landing/src/lib/tree-layout.ts +136 -0
  30. package/behaviour-tree-workflows-landing/src/lib/yaml-examples.ts +549 -0
  31. package/behaviour-tree-workflows-landing/src/main.tsx +9 -0
  32. package/behaviour-tree-workflows-landing/src/stubs/activepieces.ts +18 -0
  33. package/behaviour-tree-workflows-landing/src/stubs/fs.ts +24 -0
  34. package/behaviour-tree-workflows-landing/src/stubs/path.ts +16 -0
  35. package/behaviour-tree-workflows-landing/src/stubs/temporal-activity.ts +6 -0
  36. package/behaviour-tree-workflows-landing/src/stubs/temporal-workflow.ts +22 -0
  37. package/behaviour-tree-workflows-landing/tsconfig.json +25 -0
  38. package/behaviour-tree-workflows-landing/vite.config.ts +40 -0
  39. package/demo-google-sheets.ts +181 -0
  40. package/demo-runtime-variables.ts +174 -0
  41. package/demo-template.ts +208 -0
  42. package/docs/ARCHITECTURE_SUMMARY.md +613 -0
  43. package/docs/NODE_REFERENCE.md +504 -0
  44. package/docs/README.md +53 -0
  45. package/docs/custom-nodes-architecture.md +826 -0
  46. package/docs/observability.md +175 -0
  47. package/docs/yaml-specification.md +990 -0
  48. package/examples/temporal/README.md +117 -0
  49. package/examples/temporal/activities.ts +373 -0
  50. package/examples/temporal/client.ts +115 -0
  51. package/examples/temporal/python-worker/activities.py +339 -0
  52. package/examples/temporal/python-worker/requirements.txt +12 -0
  53. package/examples/temporal/python-worker/worker.py +106 -0
  54. package/examples/temporal/worker.ts +66 -0
  55. package/examples/temporal/workflows.ts +6 -0
  56. package/examples/temporal/yaml-workflow-loader.ts +105 -0
  57. package/examples/yaml-test.ts +97 -0
  58. package/examples/yaml-workflows/01-simple-sequence.yaml +25 -0
  59. package/examples/yaml-workflows/02-parallel-timeout.yaml +45 -0
  60. package/examples/yaml-workflows/03-ecommerce-checkout.yaml +94 -0
  61. package/examples/yaml-workflows/04-ai-agent-workflow.yaml +346 -0
  62. package/examples/yaml-workflows/05-order-processing.yaml +146 -0
  63. package/examples/yaml-workflows/06-activity-test.yaml +71 -0
  64. package/examples/yaml-workflows/07-activity-simple-test.yaml +43 -0
  65. package/examples/yaml-workflows/08-file-processing.yaml +141 -0
  66. package/examples/yaml-workflows/09-http-request.yaml +137 -0
  67. package/examples/yaml-workflows/README.md +211 -0
  68. package/package.json +38 -0
  69. package/src/actions/code-execution.schema.ts +27 -0
  70. package/src/actions/code-execution.ts +218 -0
  71. package/src/actions/generate-file.test.ts +516 -0
  72. package/src/actions/generate-file.ts +166 -0
  73. package/src/actions/http-request.test.ts +784 -0
  74. package/src/actions/http-request.ts +228 -0
  75. package/src/actions/index.ts +20 -0
  76. package/src/actions/parse-file.test.ts +448 -0
  77. package/src/actions/parse-file.ts +139 -0
  78. package/src/actions/python-script.test.ts +439 -0
  79. package/src/actions/python-script.ts +154 -0
  80. package/src/base-node.test.ts +511 -0
  81. package/src/base-node.ts +605 -0
  82. package/src/behavior-tree.test.ts +431 -0
  83. package/src/behavior-tree.ts +283 -0
  84. package/src/blackboard.test.ts +222 -0
  85. package/src/blackboard.ts +192 -0
  86. package/src/composites/conditional.schema.ts +19 -0
  87. package/src/composites/conditional.test.ts +309 -0
  88. package/src/composites/conditional.ts +129 -0
  89. package/src/composites/for-each.schema.ts +23 -0
  90. package/src/composites/for-each.test.ts +254 -0
  91. package/src/composites/for-each.ts +132 -0
  92. package/src/composites/index.ts +15 -0
  93. package/src/composites/memory-sequence.schema.ts +19 -0
  94. package/src/composites/memory-sequence.test.ts +223 -0
  95. package/src/composites/memory-sequence.ts +98 -0
  96. package/src/composites/parallel.schema.ts +28 -0
  97. package/src/composites/parallel.test.ts +502 -0
  98. package/src/composites/parallel.ts +157 -0
  99. package/src/composites/reactive-sequence.schema.ts +19 -0
  100. package/src/composites/reactive-sequence.test.ts +170 -0
  101. package/src/composites/reactive-sequence.ts +85 -0
  102. package/src/composites/recovery.schema.ts +19 -0
  103. package/src/composites/recovery.test.ts +366 -0
  104. package/src/composites/recovery.ts +90 -0
  105. package/src/composites/selector.schema.ts +19 -0
  106. package/src/composites/selector.test.ts +387 -0
  107. package/src/composites/selector.ts +85 -0
  108. package/src/composites/sequence.schema.ts +19 -0
  109. package/src/composites/sequence.test.ts +337 -0
  110. package/src/composites/sequence.ts +72 -0
  111. package/src/composites/sub-tree.schema.ts +21 -0
  112. package/src/composites/sub-tree.test.ts +893 -0
  113. package/src/composites/sub-tree.ts +177 -0
  114. package/src/composites/while.schema.ts +24 -0
  115. package/src/composites/while.test.ts +381 -0
  116. package/src/composites/while.ts +149 -0
  117. package/src/data-store/index.ts +10 -0
  118. package/src/data-store/memory-store.ts +161 -0
  119. package/src/data-store/types.ts +94 -0
  120. package/src/debug/breakpoint.test.ts +47 -0
  121. package/src/debug/breakpoint.ts +30 -0
  122. package/src/debug/index.ts +17 -0
  123. package/src/debug/resume-point.test.ts +49 -0
  124. package/src/debug/resume-point.ts +29 -0
  125. package/src/decorators/delay.schema.ts +21 -0
  126. package/src/decorators/delay.test.ts +261 -0
  127. package/src/decorators/delay.ts +140 -0
  128. package/src/decorators/force-result.schema.ts +32 -0
  129. package/src/decorators/force-result.test.ts +133 -0
  130. package/src/decorators/force-result.ts +63 -0
  131. package/src/decorators/index.ts +13 -0
  132. package/src/decorators/invert.schema.ts +19 -0
  133. package/src/decorators/invert.test.ts +135 -0
  134. package/src/decorators/invert.ts +42 -0
  135. package/src/decorators/keep-running.schema.ts +20 -0
  136. package/src/decorators/keep-running.test.ts +105 -0
  137. package/src/decorators/keep-running.ts +49 -0
  138. package/src/decorators/precondition.schema.ts +19 -0
  139. package/src/decorators/precondition.test.ts +351 -0
  140. package/src/decorators/precondition.ts +139 -0
  141. package/src/decorators/repeat.schema.ts +21 -0
  142. package/src/decorators/repeat.test.ts +187 -0
  143. package/src/decorators/repeat.ts +94 -0
  144. package/src/decorators/run-once.schema.ts +19 -0
  145. package/src/decorators/run-once.test.ts +140 -0
  146. package/src/decorators/run-once.ts +61 -0
  147. package/src/decorators/soft-assert.schema.ts +19 -0
  148. package/src/decorators/soft-assert.test.ts +107 -0
  149. package/src/decorators/soft-assert.ts +68 -0
  150. package/src/decorators/timeout.schema.ts +21 -0
  151. package/src/decorators/timeout.test.ts +274 -0
  152. package/src/decorators/timeout.ts +159 -0
  153. package/src/errors.test.ts +63 -0
  154. package/src/errors.ts +34 -0
  155. package/src/events.test.ts +347 -0
  156. package/src/events.ts +183 -0
  157. package/src/index.ts +80 -0
  158. package/src/integrations/index.ts +30 -0
  159. package/src/integrations/integration-action.test.ts +571 -0
  160. package/src/integrations/integration-action.ts +233 -0
  161. package/src/integrations/piece-executor.ts +320 -0
  162. package/src/observability/execution-tracker.ts +320 -0
  163. package/src/observability/index.ts +23 -0
  164. package/src/observability/sinks.ts +138 -0
  165. package/src/observability/types.ts +130 -0
  166. package/src/registry-utils.ts +147 -0
  167. package/src/registry.test.ts +466 -0
  168. package/src/registry.ts +334 -0
  169. package/src/schemas/base.schema.ts +104 -0
  170. package/src/schemas/index.ts +223 -0
  171. package/src/schemas/integration.test.ts +238 -0
  172. package/src/schemas/tree-definition.schema.ts +170 -0
  173. package/src/schemas/validation.test.ts +146 -0
  174. package/src/schemas/validation.ts +122 -0
  175. package/src/scripting/index.ts +22 -0
  176. package/src/templates/template-loader.test.ts +281 -0
  177. package/src/templates/template-loader.ts +152 -0
  178. package/src/temporal-integration.test.ts +213 -0
  179. package/src/test-nodes.ts +259 -0
  180. package/src/types.ts +503 -0
  181. package/src/utilities/index.ts +17 -0
  182. package/src/utilities/log-message.test.ts +275 -0
  183. package/src/utilities/log-message.ts +134 -0
  184. package/src/utilities/regex-extract.test.ts +138 -0
  185. package/src/utilities/regex-extract.ts +108 -0
  186. package/src/utilities/variable-resolver.test.ts +416 -0
  187. package/src/utilities/variable-resolver.ts +318 -0
  188. package/src/utils/error-handler.test.ts +117 -0
  189. package/src/utils/error-handler.ts +48 -0
  190. package/src/utils/signal-check.test.ts +234 -0
  191. package/src/utils/signal-check.ts +140 -0
  192. package/src/yaml/errors.ts +143 -0
  193. package/src/yaml/index.ts +30 -0
  194. package/src/yaml/loader.ts +39 -0
  195. package/src/yaml/parser.ts +286 -0
  196. package/src/yaml/validation/semantic-validator.ts +196 -0
  197. package/templates/google-sheets/insert-row.yaml +76 -0
  198. package/templates/notification-sender.yaml +33 -0
  199. package/templates/order-validation.yaml +44 -0
  200. package/tsconfig.json +24 -0
  201. package/vitest.config.ts +25 -0
  202. package/workflows/order-processor.yaml +59 -0
  203. package/workflows/process-order-workflow.yaml +142 -0
@@ -0,0 +1,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
+ }