@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,339 @@
1
+ """
2
+ Python Activity Implementations for btree workflows
3
+
4
+ These activities handle data processing operations that benefit from Python's
5
+ superior data libraries (pandas, openpyxl, rapidfuzz).
6
+
7
+ Activities:
8
+ - parse_file: Parse CSV/Excel files into structured data
9
+ - generate_file: Generate CSV/Excel/JSON files from data
10
+ - execute_python_script: Execute Python code with blackboard access
11
+ """
12
+
13
+ import os
14
+ import json
15
+ import tempfile
16
+ from typing import Any, Dict, List, Optional
17
+ from temporalio import activity
18
+ import pandas as pd
19
+ import numpy as np
20
+ from rapidfuzz import fuzz, process
21
+
22
+
23
+ # ─────────────────────────────────────────────────────────────────────────────
24
+ # ParseFile Activity
25
+ # ─────────────────────────────────────────────────────────────────────────────
26
+
27
+ @activity.defn
28
+ async def parse_file(request: Dict[str, Any]) -> Dict[str, Any]:
29
+ """
30
+ Parse CSV/Excel file into structured data.
31
+
32
+ Args:
33
+ request: Dictionary containing:
34
+ - file: Path to file
35
+ - format: File format (csv, xlsx, xls, auto)
36
+ - sheetName: Sheet name for Excel (optional)
37
+ - columnMapping: Column rename mapping (optional)
38
+ - options: Parse options (optional)
39
+ - skipRows: Number of rows to skip
40
+ - trim: Trim whitespace from values
41
+ - emptyAsNull: Treat empty strings as None
42
+ - dateColumns: Columns to parse as dates
43
+ - dateFormat: Date format string
44
+
45
+ Returns:
46
+ Dictionary containing:
47
+ - data: List of row dictionaries
48
+ - rowCount: Number of rows parsed
49
+ - columns: List of column names
50
+ """
51
+ file_path = request.get("file")
52
+ format_type = request.get("format", "auto")
53
+ sheet_name = request.get("sheetName")
54
+ column_mapping = request.get("columnMapping", {})
55
+ options = request.get("options", {})
56
+
57
+ activity.logger.info(f"Parsing file: {file_path} (format: {format_type})")
58
+
59
+ # Auto-detect format from file extension
60
+ if format_type == "auto":
61
+ ext = os.path.splitext(file_path)[1].lower()
62
+ if ext in [".xlsx", ".xls"]:
63
+ format_type = "xlsx" if ext == ".xlsx" else "xls"
64
+ else:
65
+ format_type = "csv"
66
+
67
+ # Read file into DataFrame
68
+ try:
69
+ if format_type == "csv":
70
+ df = pd.read_csv(
71
+ file_path,
72
+ skiprows=options.get("skipRows", 0),
73
+ skipinitialspace=options.get("trim", True),
74
+ )
75
+ else: # xlsx or xls
76
+ df = pd.read_excel(
77
+ file_path,
78
+ sheet_name=sheet_name or 0,
79
+ skiprows=options.get("skipRows", 0),
80
+ engine="openpyxl" if format_type == "xlsx" else None,
81
+ )
82
+ except FileNotFoundError:
83
+ raise ValueError(f"File not found: {file_path}")
84
+ except Exception as e:
85
+ raise ValueError(f"Failed to read file {file_path}: {str(e)}")
86
+
87
+ # Apply column mapping (rename columns)
88
+ if column_mapping:
89
+ df = df.rename(columns=column_mapping)
90
+
91
+ # Trim whitespace from string columns
92
+ if options.get("trim", True):
93
+ for col in df.select_dtypes(include=["object"]).columns:
94
+ df[col] = df[col].apply(lambda x: x.strip() if isinstance(x, str) else x)
95
+
96
+ # Convert empty strings to None
97
+ if options.get("emptyAsNull", False):
98
+ df = df.replace("", None)
99
+
100
+ # Parse date columns
101
+ date_columns = options.get("dateColumns", [])
102
+ date_format = options.get("dateFormat")
103
+ for col in date_columns:
104
+ if col in df.columns:
105
+ try:
106
+ df[col] = pd.to_datetime(df[col], format=date_format)
107
+ # Convert to ISO format string for JSON serialization
108
+ df[col] = df[col].dt.strftime("%Y-%m-%dT%H:%M:%S")
109
+ except Exception as e:
110
+ activity.logger.warning(f"Failed to parse date column {col}: {e}")
111
+
112
+ # Replace NaN/NaT with None for JSON serialization
113
+ df = df.where(pd.notnull(df), None)
114
+
115
+ # Convert to list of dictionaries
116
+ data = df.to_dict("records")
117
+
118
+ activity.logger.info(f"Parsed {len(data)} rows, columns: {list(df.columns)}")
119
+
120
+ return {
121
+ "data": data,
122
+ "rowCount": len(data),
123
+ "columns": list(df.columns),
124
+ }
125
+
126
+
127
+ # ─────────────────────────────────────────────────────────────────────────────
128
+ # GenerateFile Activity
129
+ # ─────────────────────────────────────────────────────────────────────────────
130
+
131
+ @activity.defn
132
+ async def generate_file(request: Dict[str, Any]) -> Dict[str, Any]:
133
+ """
134
+ Generate CSV/Excel/JSON file from data.
135
+
136
+ Args:
137
+ request: Dictionary containing:
138
+ - format: Output format (csv, xlsx, json)
139
+ - data: List of row dictionaries
140
+ - columns: Column definitions (optional)
141
+ - header: Display header name
142
+ - key: Data key
143
+ - width: Column width (Excel only)
144
+ - filename: Output filename
145
+ - storage: Storage type (temp, persistent)
146
+
147
+ Returns:
148
+ Dictionary containing:
149
+ - filename: Generated filename
150
+ - contentType: MIME type
151
+ - size: File size in bytes
152
+ - path: Full file path
153
+ - url: Download URL (if persistent)
154
+ """
155
+ format_type = request.get("format")
156
+ data = request.get("data", [])
157
+ columns = request.get("columns")
158
+ filename = request.get("filename")
159
+ storage = request.get("storage", "temp")
160
+
161
+ activity.logger.info(f"Generating {format_type} file: {filename} ({len(data)} rows)")
162
+
163
+ # Create DataFrame from data
164
+ df = pd.DataFrame(data)
165
+
166
+ # Reorder and rename columns if specified
167
+ if columns:
168
+ col_order = [c["key"] for c in columns if c["key"] in df.columns]
169
+ df = df[col_order]
170
+ header_map = {c["key"]: c["header"] for c in columns}
171
+ df = df.rename(columns=header_map)
172
+
173
+ # Determine output directory
174
+ if storage == "temp":
175
+ output_dir = tempfile.gettempdir()
176
+ else:
177
+ output_dir = os.environ.get("PERSISTENT_STORAGE", "/tmp/persistent")
178
+ os.makedirs(output_dir, exist_ok=True)
179
+
180
+ file_path = os.path.join(output_dir, filename)
181
+
182
+ # Write file based on format
183
+ if format_type == "csv":
184
+ df.to_csv(file_path, index=False)
185
+ content_type = "text/csv"
186
+ elif format_type == "xlsx":
187
+ # Write with openpyxl for xlsx support
188
+ with pd.ExcelWriter(file_path, engine="openpyxl") as writer:
189
+ df.to_excel(writer, index=False, sheet_name="Sheet1")
190
+
191
+ # Apply column widths if specified
192
+ if columns:
193
+ worksheet = writer.sheets["Sheet1"]
194
+ for i, col_def in enumerate(columns):
195
+ if "width" in col_def:
196
+ # openpyxl uses 1-based column indexing
197
+ col_letter = chr(65 + i) # A, B, C, etc.
198
+ worksheet.column_dimensions[col_letter].width = col_def["width"]
199
+
200
+ content_type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
201
+ else: # json
202
+ df.to_json(file_path, orient="records", indent=2)
203
+ content_type = "application/json"
204
+
205
+ # Get file size
206
+ size = os.path.getsize(file_path)
207
+
208
+ # Generate URL for persistent storage
209
+ url = None
210
+ if storage == "persistent":
211
+ # In a real implementation, this would upload to S3/GCS and return a signed URL
212
+ url = f"/files/{filename}"
213
+
214
+ activity.logger.info(f"Generated file: {file_path} ({size} bytes)")
215
+
216
+ return {
217
+ "filename": filename,
218
+ "contentType": content_type,
219
+ "size": size,
220
+ "path": file_path,
221
+ "url": url,
222
+ }
223
+
224
+
225
+ # ─────────────────────────────────────────────────────────────────────────────
226
+ # PythonScript Activity
227
+ # ─────────────────────────────────────────────────────────────────────────────
228
+
229
+ @activity.defn
230
+ async def execute_python_script(request: Dict[str, Any]) -> Dict[str, Any]:
231
+ """
232
+ Execute Python code with blackboard access.
233
+
234
+ The code has access to:
235
+ - bb: dict - Blackboard state (read/write)
236
+ - input: dict - Workflow input (read-only)
237
+ - env: dict - Environment variables
238
+ - pd: pandas module
239
+ - np: numpy module
240
+ - fuzz: rapidfuzz.fuzz module (for fuzzy string matching)
241
+ - process: rapidfuzz.process module (for fuzzy extraction)
242
+
243
+ Args:
244
+ request: Dictionary containing:
245
+ - code: Python code to execute
246
+ - blackboard: Current blackboard state
247
+ - input: Workflow input (optional)
248
+ - env: Environment variables (optional)
249
+ - timeout: Execution timeout in ms (optional)
250
+
251
+ Returns:
252
+ Dictionary containing:
253
+ - blackboard: Modified blackboard state
254
+ - stdout: Captured stdout (if any)
255
+ - stderr: Captured stderr (if any)
256
+ """
257
+ code = request.get("code", "")
258
+ bb = request.get("blackboard", {})
259
+ input_data = request.get("input", {})
260
+ env = request.get("env", {})
261
+
262
+ activity.logger.info(f"Executing Python script ({len(code)} chars)")
263
+
264
+ # Create execution context with available libraries
265
+ local_vars: Dict[str, Any] = {
266
+ "bb": bb,
267
+ "input": input_data,
268
+ "env": env,
269
+ "pd": pd,
270
+ "np": np,
271
+ "fuzz": fuzz,
272
+ "process": process,
273
+ }
274
+
275
+ # Capture stdout/stderr
276
+ import io
277
+ import sys
278
+
279
+ stdout_capture = io.StringIO()
280
+ stderr_capture = io.StringIO()
281
+
282
+ old_stdout = sys.stdout
283
+ old_stderr = sys.stderr
284
+
285
+ try:
286
+ sys.stdout = stdout_capture
287
+ sys.stderr = stderr_capture
288
+
289
+ # Execute user code
290
+ # Note: In production, consider using a sandboxed execution environment
291
+ exec(code, {"__builtins__": __builtins__}, local_vars)
292
+
293
+ except Exception as e:
294
+ activity.logger.error(f"Python script execution failed: {e}")
295
+ raise ValueError(f"Script execution failed: {str(e)}")
296
+ finally:
297
+ sys.stdout = old_stdout
298
+ sys.stderr = old_stderr
299
+
300
+ stdout_output = stdout_capture.getvalue()
301
+ stderr_output = stderr_capture.getvalue()
302
+
303
+ if stdout_output:
304
+ activity.logger.info(f"Script stdout: {stdout_output[:500]}")
305
+ if stderr_output:
306
+ activity.logger.warning(f"Script stderr: {stderr_output[:500]}")
307
+
308
+ # Convert any numpy/pandas types to JSON-serializable types
309
+ result_bb = _make_json_serializable(local_vars["bb"])
310
+
311
+ activity.logger.info(f"Script completed, {len(result_bb)} blackboard keys")
312
+
313
+ return {
314
+ "blackboard": result_bb,
315
+ "stdout": stdout_output,
316
+ "stderr": stderr_output,
317
+ }
318
+
319
+
320
+ def _make_json_serializable(obj: Any) -> Any:
321
+ """Convert numpy/pandas types to JSON-serializable Python types."""
322
+ if isinstance(obj, dict):
323
+ return {k: _make_json_serializable(v) for k, v in obj.items()}
324
+ elif isinstance(obj, list):
325
+ return [_make_json_serializable(item) for item in obj]
326
+ elif isinstance(obj, pd.DataFrame):
327
+ return obj.to_dict("records")
328
+ elif isinstance(obj, pd.Series):
329
+ return obj.tolist()
330
+ elif isinstance(obj, np.ndarray):
331
+ return obj.tolist()
332
+ elif isinstance(obj, (np.integer, np.floating)):
333
+ return obj.item()
334
+ elif isinstance(obj, np.bool_):
335
+ return bool(obj)
336
+ elif pd.isna(obj):
337
+ return None
338
+ else:
339
+ return obj
@@ -0,0 +1,12 @@
1
+ # Temporal Python SDK
2
+ temporalio>=1.0.0
3
+
4
+ # Data processing
5
+ pandas>=2.0.0
6
+ numpy>=1.24.0
7
+
8
+ # String matching / fuzzy matching
9
+ rapidfuzz>=3.0.0
10
+
11
+ # Excel file support
12
+ openpyxl>=3.1.0
@@ -0,0 +1,106 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Python Worker for btree workflows
4
+
5
+ This worker handles data processing activities that benefit from Python's
6
+ superior data libraries (pandas, openpyxl, rapidfuzz).
7
+
8
+ Usage:
9
+ pip install -r requirements.txt
10
+ python worker.py
11
+
12
+ Environment variables:
13
+ TEMPORAL_HOST: Temporal server host (default: localhost:7233)
14
+ TEMPORAL_NAMESPACE: Temporal namespace (default: default)
15
+ TASK_QUEUE: Task queue name (default: btree-workflows)
16
+ PERSISTENT_STORAGE: Path for persistent file storage (default: /tmp/persistent)
17
+ """
18
+
19
+ import asyncio
20
+ import logging
21
+ import os
22
+ import signal
23
+ import sys
24
+
25
+ from temporalio.client import Client
26
+ from temporalio.worker import Worker
27
+
28
+ from activities import parse_file, generate_file, execute_python_script
29
+
30
+
31
+ # Configure logging
32
+ logging.basicConfig(
33
+ level=logging.INFO,
34
+ format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
35
+ datefmt="%Y-%m-%d %H:%M:%S",
36
+ )
37
+ logger = logging.getLogger(__name__)
38
+
39
+
40
+ async def main():
41
+ """Main entry point for the Python worker."""
42
+ # Configuration from environment
43
+ temporal_host = os.environ.get("TEMPORAL_HOST", "localhost:7233")
44
+ namespace = os.environ.get("TEMPORAL_NAMESPACE", "default")
45
+ task_queue = os.environ.get("TASK_QUEUE", "btree-workflows")
46
+
47
+ logger.info(f"Connecting to Temporal at {temporal_host} (namespace: {namespace})")
48
+
49
+ try:
50
+ # Connect to Temporal
51
+ client = await Client.connect(temporal_host, namespace=namespace)
52
+ logger.info("Connected to Temporal server")
53
+
54
+ # Create worker with Python activities
55
+ worker = Worker(
56
+ client,
57
+ task_queue=task_queue,
58
+ activities=[
59
+ parse_file,
60
+ generate_file,
61
+ execute_python_script,
62
+ ],
63
+ )
64
+
65
+ logger.info(f"Starting Python worker on task queue: {task_queue}")
66
+ logger.info("Registered activities:")
67
+ logger.info(" - parse_file: Parse CSV/Excel files into structured data")
68
+ logger.info(" - generate_file: Generate CSV/Excel/JSON files from data")
69
+ logger.info(" - execute_python_script: Execute Python code with pandas/numpy")
70
+
71
+ # Handle graceful shutdown
72
+ shutdown_event = asyncio.Event()
73
+
74
+ def signal_handler(signum, frame):
75
+ logger.info(f"Received signal {signum}, initiating graceful shutdown...")
76
+ shutdown_event.set()
77
+
78
+ signal.signal(signal.SIGINT, signal_handler)
79
+ signal.signal(signal.SIGTERM, signal_handler)
80
+
81
+ # Run worker until shutdown
82
+ async with worker:
83
+ logger.info("Python worker started successfully")
84
+ await shutdown_event.wait()
85
+
86
+ logger.info("Python worker shut down gracefully")
87
+
88
+ except Exception as e:
89
+ logger.error(f"Worker failed: {e}")
90
+ sys.exit(1)
91
+
92
+
93
+ if __name__ == "__main__":
94
+ print("""
95
+ ╔═══════════════════════════════════════════════════════════════════════════════╗
96
+ ║ btree Python Worker ║
97
+ ║ ║
98
+ ║ Activities: ║
99
+ ║ - parse_file: Parse CSV/Excel files into structured data ║
100
+ ║ - generate_file: Generate CSV/Excel/JSON files from data ║
101
+ ║ - execute_python_script: Execute Python code with pandas/numpy/rapidfuzz ║
102
+ ║ ║
103
+ ║ Press Ctrl+C to stop ║
104
+ ╚═══════════════════════════════════════════════════════════════════════════════╝
105
+ """)
106
+ asyncio.run(main())
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Temporal Worker
3
+ * Registers and runs behavior tree workflows with activity support
4
+ */
5
+
6
+ import { NativeConnection, Worker, bundleWorkflowCode } from "@temporalio/worker";
7
+ import { fileURLToPath } from "url";
8
+ import { dirname, join } from "path";
9
+
10
+ // Import activities
11
+ import * as activities from "./activities.js";
12
+
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+
16
+ async function run() {
17
+ console.log("🚀 Starting Temporal worker for behavior tree workflows...");
18
+
19
+ const connection = await NativeConnection.connect({
20
+ address: "localhost:7233",
21
+ });
22
+
23
+ // Bundle workflows ahead of time with better control
24
+ console.log("📦 Bundling workflows...");
25
+ const { code } = await bundleWorkflowCode({
26
+ workflowsPath: join(__dirname, "workflows.ts"),
27
+ // Ignore modules that are used by btree but not needed in workflow context
28
+ // Note: 'vm' is used by js-interpreter but not at runtime in the workflow
29
+ ignoreModules: ["fs", "fs/promises", "path", "vm"],
30
+ webpackConfigHook: (config) => {
31
+ config.target = "webworker";
32
+ if (config.output) {
33
+ config.output.publicPath = "";
34
+ config.output.globalObject = "globalThis";
35
+ }
36
+ // Force single bundle without code splitting
37
+ config.optimization = {
38
+ minimize: false,
39
+ splitChunks: false,
40
+ runtimeChunk: false,
41
+ };
42
+ return config;
43
+ },
44
+ });
45
+
46
+ console.log("✅ Workflows bundled successfully");
47
+
48
+ const worker = await Worker.create({
49
+ connection,
50
+ namespace: "default",
51
+ workflowBundle: { code },
52
+ taskQueue: "btree-workflows",
53
+ activities, // Register activity implementations
54
+ });
55
+
56
+ console.log("✅ Worker started successfully!");
57
+ console.log("📋 Task Queue: btree-workflows");
58
+ console.log("🔄 Listening for workflow tasks...\n");
59
+
60
+ await worker.run();
61
+ }
62
+
63
+ run().catch((err) => {
64
+ console.error("❌ Worker error:", err);
65
+ process.exit(1);
66
+ });
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Workflow exports for Temporal worker
3
+ * All workflows are now YAML-based for declarative workflow definitions
4
+ */
5
+
6
+ export { yamlWorkflow } from "./yaml-workflow-loader.js";
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Universal YAML Workflow Loader
3
+ * Executes any YAML-defined workflow in Temporal with activity support
4
+ */
5
+
6
+ import { proxyActivities } from "@temporalio/workflow";
7
+ import {
8
+ BehaviorTree,
9
+ Registry,
10
+ registerStandardNodes,
11
+ loadTreeFromYaml,
12
+ type WorkflowArgs,
13
+ type WorkflowResult,
14
+ type BtreeActivities,
15
+ type TokenProvider,
16
+ type PieceAuth,
17
+ } from "../../dist/index.js";
18
+
19
+ // Import activity types for proxy creation
20
+ import type * as activitiesModule from "./activities.js";
21
+
22
+ // Create activity proxies - these route calls to the activity worker
23
+ const activities = proxyActivities<typeof activitiesModule>({
24
+ startToCloseTimeout: "30s",
25
+ retry: {
26
+ maximumAttempts: 3,
27
+ },
28
+ });
29
+
30
+ // Create the BtreeActivities object that nodes expect
31
+ const btreeActivities: BtreeActivities = {
32
+ executePieceAction: activities.executePieceActionActivity,
33
+ executePythonScript: activities.executePythonScriptActivity,
34
+ parseFile: activities.parseFileActivity,
35
+ generateFile: activities.generateFileActivity,
36
+ };
37
+
38
+ /**
39
+ * Mock token provider for testing
40
+ * In production, this would fetch real OAuth tokens from controlplane
41
+ */
42
+ const mockTokenProvider: TokenProvider = async (
43
+ _context,
44
+ provider,
45
+ _connectionId
46
+ ): Promise<PieceAuth> => {
47
+ // For testing, return mock tokens
48
+ // In production, this would call controlplane to get real tokens
49
+ console.log(`[TokenProvider] Fetching token for provider: ${provider}`);
50
+ return {
51
+ access_token: `mock_token_for_${provider}_${Date.now()}`,
52
+ };
53
+ };
54
+
55
+ /**
56
+ * Extended workflow args with YAML content
57
+ */
58
+ export interface YamlWorkflowArgs extends WorkflowArgs {
59
+ yamlContent: string;
60
+ }
61
+
62
+ /**
63
+ * Universal YAML workflow executor
64
+ * Loads and executes any YAML workflow definition
65
+ *
66
+ * Usage:
67
+ * ```typescript
68
+ * const result = await client.workflow.execute(yamlWorkflow, {
69
+ * args: [{
70
+ * input: {},
71
+ * treeRegistry: new Registry(),
72
+ * yamlContent: readFileSync('./my-workflow.yaml', 'utf-8')
73
+ * }]
74
+ * });
75
+ * ```
76
+ */
77
+ export async function yamlWorkflow(
78
+ args: YamlWorkflowArgs,
79
+ ): Promise<WorkflowResult> {
80
+ if (!args.yamlContent) {
81
+ throw new Error("yamlContent is required in workflow arguments");
82
+ }
83
+
84
+ // Create registry and register all standard built-in nodes
85
+ const registry = new Registry();
86
+ registerStandardNodes(registry);
87
+
88
+ // Users can register custom nodes here:
89
+ // registry.register("MyCustomNode", MyCustomNode, { category: "action" });
90
+
91
+ // Parse and validate YAML
92
+ const root = loadTreeFromYaml(args.yamlContent, registry);
93
+
94
+ // Convert to Temporal workflow
95
+ const tree = new BehaviorTree(root);
96
+ const workflow = tree.toWorkflow();
97
+
98
+ // Execute with original args (without yamlContent), activities, and tokenProvider
99
+ return workflow({
100
+ input: args.input,
101
+ treeRegistry: args.treeRegistry,
102
+ activities: btreeActivities,
103
+ tokenProvider: mockTokenProvider,
104
+ });
105
+ }