@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,117 @@
|
|
|
1
|
+
# Temporal Activity Test
|
|
2
|
+
|
|
3
|
+
This directory contains examples for running btree workflows with Temporal, including activity execution for I/O operations.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
1. **Temporal Server** - Running locally on `localhost:7233`
|
|
8
|
+
```bash
|
|
9
|
+
# Option 1: Use Temporal CLI (recommended for testing)
|
|
10
|
+
brew install temporal
|
|
11
|
+
temporal server start-dev
|
|
12
|
+
|
|
13
|
+
# Option 2: Use Docker
|
|
14
|
+
docker-compose up -d
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
2. **Build btree** - The dist folder must be built
|
|
18
|
+
```bash
|
|
19
|
+
cd /path/to/btree
|
|
20
|
+
npm run build
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Running the Test
|
|
24
|
+
|
|
25
|
+
### 1. Start the Worker (Terminal 1)
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
cd /path/to/btree/examples/temporal
|
|
29
|
+
|
|
30
|
+
# For mock activities (no real API calls)
|
|
31
|
+
BTREE_MOCK_ACTIVITIES=true npx tsx worker.ts
|
|
32
|
+
|
|
33
|
+
# For real activities (requires actual credentials)
|
|
34
|
+
# GOOGLE_SHEETS_ACCESS_TOKEN=xxx npx tsx worker.ts
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
You should see:
|
|
38
|
+
```
|
|
39
|
+
🚀 Starting Temporal worker for behavior tree workflows...
|
|
40
|
+
📦 Bundling workflows...
|
|
41
|
+
✅ Workflows bundled successfully
|
|
42
|
+
✅ Worker started successfully!
|
|
43
|
+
📋 Task Queue: btree-workflows
|
|
44
|
+
🔄 Listening for workflow tasks...
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Run the Client (Terminal 2)
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
cd /path/to/btree/examples/temporal
|
|
51
|
+
npx tsx client.ts
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
You should see:
|
|
55
|
+
```
|
|
56
|
+
🔌 Connecting to Temporal server at localhost:7233...
|
|
57
|
+
✅ Connected to Temporal server
|
|
58
|
+
|
|
59
|
+
============================================================
|
|
60
|
+
Workflow: Activity Test (IntegrationAction via Activity)
|
|
61
|
+
============================================================
|
|
62
|
+
✅ Result: { status: 'SUCCESS', output: { ... } }
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## What's Being Tested
|
|
66
|
+
|
|
67
|
+
The activity test workflow (`06-activity-test.yaml`) demonstrates:
|
|
68
|
+
|
|
69
|
+
1. **IntegrationAction with Activities** - The `IntegrationAction` node uses `context.activities.executePieceAction` instead of inline execution, making it deterministic for Temporal replay.
|
|
70
|
+
|
|
71
|
+
2. **Mock Token Provider** - The `yaml-workflow-loader.ts` includes a mock token provider for testing without real OAuth credentials.
|
|
72
|
+
|
|
73
|
+
3. **Mock Activity Responses** - When `BTREE_MOCK_ACTIVITIES=true`, activities return simulated responses instead of making real API calls.
|
|
74
|
+
|
|
75
|
+
## Files
|
|
76
|
+
|
|
77
|
+
| File | Description |
|
|
78
|
+
|------|-------------|
|
|
79
|
+
| `worker.ts` | Temporal worker that registers workflows and activities |
|
|
80
|
+
| `client.ts` | Client that executes workflows |
|
|
81
|
+
| `activities.ts` | Activity implementations (run outside workflow sandbox) |
|
|
82
|
+
| `yaml-workflow-loader.ts` | Workflow that loads YAML and passes activities to btree |
|
|
83
|
+
| `workflows.ts` | Re-exports workflows for Temporal bundler |
|
|
84
|
+
|
|
85
|
+
## Activity Flow
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
Client Worker (Workflow) Worker (Activity)
|
|
89
|
+
| | |
|
|
90
|
+
| execute(workflow) | |
|
|
91
|
+
|------------------------>| |
|
|
92
|
+
| | |
|
|
93
|
+
| | IntegrationAction.tick() |
|
|
94
|
+
| | -> context.activities |
|
|
95
|
+
| | .executePieceAction() |
|
|
96
|
+
| | |
|
|
97
|
+
| |----------- activity -------->|
|
|
98
|
+
| | |
|
|
99
|
+
| | | executePieceActionActivity()
|
|
100
|
+
| | | -> executePieceAction()
|
|
101
|
+
| | | -> API call (or mock)
|
|
102
|
+
| | |
|
|
103
|
+
| |<---------- result -----------|
|
|
104
|
+
| | |
|
|
105
|
+
| | Store result in blackboard |
|
|
106
|
+
| | |
|
|
107
|
+
|<-- WorkflowResult ------| |
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Node Behavior Summary
|
|
111
|
+
|
|
112
|
+
| Node | Activity Required | Standalone Fallback |
|
|
113
|
+
|------|-------------------|---------------------|
|
|
114
|
+
| `IntegrationAction` | Optional | Yes (inline execution) |
|
|
115
|
+
| `PythonScript` | **Required** | No (needs Python worker) |
|
|
116
|
+
| `ParseFile` | **Required** | No (needs file I/O) |
|
|
117
|
+
| `GenerateFile` | **Required** | No (needs file I/O) |
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Activity implementations for btree workflows
|
|
3
|
+
*
|
|
4
|
+
* These run in the worker process (outside the workflow sandbox)
|
|
5
|
+
* and handle all I/O operations deterministically for Temporal.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
executePieceAction,
|
|
10
|
+
type PieceActivityRequest,
|
|
11
|
+
type PythonScriptRequest,
|
|
12
|
+
type PythonScriptResult,
|
|
13
|
+
type ParseFileRequest,
|
|
14
|
+
type ParseFileResult,
|
|
15
|
+
type GenerateFileRequest,
|
|
16
|
+
type GenerateFileResult,
|
|
17
|
+
type HttpRequestActivity,
|
|
18
|
+
type HttpResponseActivity,
|
|
19
|
+
} from "../../dist/index.js";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Execute an Active Pieces action
|
|
23
|
+
* This wraps the btree executePieceAction for use as a Temporal activity
|
|
24
|
+
*/
|
|
25
|
+
export async function executePieceActionActivity(
|
|
26
|
+
request: PieceActivityRequest
|
|
27
|
+
): Promise<unknown> {
|
|
28
|
+
console.log(
|
|
29
|
+
`[Activity] executePieceAction: ${request.provider}/${request.action}`
|
|
30
|
+
);
|
|
31
|
+
console.log(`[Activity] Inputs:`, JSON.stringify(request.inputs, null, 2));
|
|
32
|
+
|
|
33
|
+
// For testing without actual credentials, we can mock responses
|
|
34
|
+
if (process.env.BTREE_MOCK_ACTIVITIES === "true") {
|
|
35
|
+
console.log(`[Activity] MOCK MODE - returning simulated response`);
|
|
36
|
+
return mockPieceAction(request);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Real execution via Active Pieces
|
|
40
|
+
const result = await executePieceAction(request);
|
|
41
|
+
console.log(`[Activity] Result:`, JSON.stringify(result, null, 2));
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Execute Python code (placeholder - would need Python worker)
|
|
47
|
+
*/
|
|
48
|
+
export async function executePythonScriptActivity(
|
|
49
|
+
request: PythonScriptRequest
|
|
50
|
+
): Promise<PythonScriptResult> {
|
|
51
|
+
console.log(`[Activity] executePythonScript`);
|
|
52
|
+
console.log(`[Activity] Code length: ${request.code.length} chars`);
|
|
53
|
+
|
|
54
|
+
// Mock implementation for testing
|
|
55
|
+
if (process.env.BTREE_MOCK_ACTIVITIES === "true") {
|
|
56
|
+
console.log(`[Activity] MOCK MODE - returning simulated Python result`);
|
|
57
|
+
return {
|
|
58
|
+
blackboard: request.blackboard,
|
|
59
|
+
stdout: "Mock Python execution",
|
|
60
|
+
stderr: "",
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Real implementation would call a Python worker via gRPC or subprocess
|
|
65
|
+
throw new Error(
|
|
66
|
+
"Real Python execution requires a Python worker. Set BTREE_MOCK_ACTIVITIES=true for testing."
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Parse a file (CSV/Excel)
|
|
72
|
+
*/
|
|
73
|
+
export async function parseFileActivity(
|
|
74
|
+
request: ParseFileRequest
|
|
75
|
+
): Promise<ParseFileResult> {
|
|
76
|
+
console.log(`[Activity] parseFile: ${request.file}`);
|
|
77
|
+
|
|
78
|
+
// Mock implementation for testing
|
|
79
|
+
if (process.env.BTREE_MOCK_ACTIVITIES === "true") {
|
|
80
|
+
console.log(`[Activity] MOCK MODE - returning simulated parsed data`);
|
|
81
|
+
return {
|
|
82
|
+
data: [
|
|
83
|
+
{ orderId: "ORD-001", product: "Widget", quantity: 5, price: 10.99 },
|
|
84
|
+
{ orderId: "ORD-002", product: "Gadget", quantity: 3, price: 24.99 },
|
|
85
|
+
{ orderId: "ORD-003", product: "Gizmo", quantity: 10, price: 5.49 },
|
|
86
|
+
],
|
|
87
|
+
rowCount: 3,
|
|
88
|
+
columns: ["orderId", "product", "quantity", "price"],
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Real implementation would use papaparse for CSV, xlsx for Excel
|
|
93
|
+
throw new Error(
|
|
94
|
+
"Real file parsing requires file system access. Set BTREE_MOCK_ACTIVITIES=true for testing."
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Generate a file (CSV/Excel/JSON)
|
|
100
|
+
*/
|
|
101
|
+
export async function generateFileActivity(
|
|
102
|
+
request: GenerateFileRequest
|
|
103
|
+
): Promise<GenerateFileResult> {
|
|
104
|
+
console.log(`[Activity] generateFile: ${request.filename} (${request.format})`);
|
|
105
|
+
console.log(`[Activity] Data rows: ${request.data.length}`);
|
|
106
|
+
|
|
107
|
+
// Mock implementation for testing
|
|
108
|
+
if (process.env.BTREE_MOCK_ACTIVITIES === "true") {
|
|
109
|
+
console.log(`[Activity] MOCK MODE - returning simulated file metadata`);
|
|
110
|
+
return {
|
|
111
|
+
filename: request.filename,
|
|
112
|
+
contentType: getContentType(request.format),
|
|
113
|
+
size: JSON.stringify(request.data).length,
|
|
114
|
+
path: `/tmp/${request.filename}`,
|
|
115
|
+
url:
|
|
116
|
+
request.storage === "persistent"
|
|
117
|
+
? `https://storage.example.com/files/${request.filename}`
|
|
118
|
+
: undefined,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Real implementation would use json2csv, xlsx, etc.
|
|
123
|
+
throw new Error(
|
|
124
|
+
"Real file generation requires file system access. Set BTREE_MOCK_ACTIVITIES=true for testing."
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Fetch a URL (HTTP request)
|
|
130
|
+
* Uses native fetch() API with timeout support via AbortController
|
|
131
|
+
*/
|
|
132
|
+
export async function fetchUrlActivity(
|
|
133
|
+
request: HttpRequestActivity
|
|
134
|
+
): Promise<HttpResponseActivity> {
|
|
135
|
+
console.log(`[Activity] fetchUrl: ${request.method} ${request.url}`);
|
|
136
|
+
|
|
137
|
+
// Mock implementation for testing
|
|
138
|
+
if (process.env.BTREE_MOCK_ACTIVITIES === "true") {
|
|
139
|
+
console.log(`[Activity] MOCK MODE - returning simulated HTTP response`);
|
|
140
|
+
return mockFetchUrl(request);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Real implementation using native fetch
|
|
144
|
+
const controller = new AbortController();
|
|
145
|
+
const timeoutId = request.timeout
|
|
146
|
+
? setTimeout(() => controller.abort(), request.timeout)
|
|
147
|
+
: null;
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const fetchOptions: RequestInit = {
|
|
151
|
+
method: request.method,
|
|
152
|
+
headers: request.headers,
|
|
153
|
+
signal: controller.signal,
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
// Add body for non-GET requests
|
|
157
|
+
if (request.body !== undefined && request.method !== "GET") {
|
|
158
|
+
fetchOptions.body =
|
|
159
|
+
typeof request.body === "string"
|
|
160
|
+
? request.body
|
|
161
|
+
: JSON.stringify(request.body);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const response = await fetch(request.url, fetchOptions);
|
|
165
|
+
|
|
166
|
+
// Extract headers into a plain object
|
|
167
|
+
const headers: Record<string, string> = {};
|
|
168
|
+
response.headers.forEach((value, key) => {
|
|
169
|
+
headers[key] = value;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Parse response based on content type
|
|
173
|
+
const contentType = response.headers.get("content-type") || "";
|
|
174
|
+
let data: unknown;
|
|
175
|
+
|
|
176
|
+
if (contentType.includes("application/json")) {
|
|
177
|
+
data = await response.json();
|
|
178
|
+
} else if (contentType.includes("text/")) {
|
|
179
|
+
data = await response.text();
|
|
180
|
+
} else {
|
|
181
|
+
// For binary data, convert to base64
|
|
182
|
+
const buffer = await response.arrayBuffer();
|
|
183
|
+
data = Buffer.from(buffer).toString("base64");
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
console.log(`[Activity] fetchUrl response: ${response.status}`);
|
|
187
|
+
return {
|
|
188
|
+
status: response.status,
|
|
189
|
+
headers,
|
|
190
|
+
data,
|
|
191
|
+
};
|
|
192
|
+
} catch (error) {
|
|
193
|
+
if (error instanceof Error && error.name === "AbortError") {
|
|
194
|
+
throw new Error(`Request timed out after ${request.timeout}ms`);
|
|
195
|
+
}
|
|
196
|
+
throw error;
|
|
197
|
+
} finally {
|
|
198
|
+
if (timeoutId) {
|
|
199
|
+
clearTimeout(timeoutId);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
205
|
+
// Mock Helpers
|
|
206
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
function mockPieceAction(request: PieceActivityRequest): unknown {
|
|
209
|
+
const { provider, action, inputs } = request;
|
|
210
|
+
|
|
211
|
+
// Google Sheets mocks
|
|
212
|
+
if (provider === "google-sheets" || provider === "google") {
|
|
213
|
+
if (action === "append_row" || action === "insert_row") {
|
|
214
|
+
return {
|
|
215
|
+
spreadsheetId: inputs.spreadsheetId,
|
|
216
|
+
updatedRange: "Sheet1!A1:C1",
|
|
217
|
+
updatedRows: 1,
|
|
218
|
+
updatedColumns: (inputs.values as unknown[])?.length || 3,
|
|
219
|
+
updatedCells: (inputs.values as unknown[])?.length || 3,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (action === "get_values" || action === "read_rows") {
|
|
223
|
+
return {
|
|
224
|
+
values: [
|
|
225
|
+
["Header1", "Header2", "Header3"],
|
|
226
|
+
["Value1", "Value2", "Value3"],
|
|
227
|
+
["Value4", "Value5", "Value6"],
|
|
228
|
+
],
|
|
229
|
+
};
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Slack mocks
|
|
234
|
+
if (provider === "slack") {
|
|
235
|
+
if (action === "send_message" || action === "post_message") {
|
|
236
|
+
return {
|
|
237
|
+
ok: true,
|
|
238
|
+
channel: inputs.channel || "#general",
|
|
239
|
+
ts: `${Date.now()}.000000`,
|
|
240
|
+
message: {
|
|
241
|
+
text: inputs.text || inputs.message,
|
|
242
|
+
ts: `${Date.now()}.000000`,
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// OpenAI mocks
|
|
249
|
+
if (provider === "openai") {
|
|
250
|
+
if (action === "chat" || action === "ask_chatgpt") {
|
|
251
|
+
return {
|
|
252
|
+
id: `chatcmpl-${Date.now()}`,
|
|
253
|
+
choices: [
|
|
254
|
+
{
|
|
255
|
+
message: {
|
|
256
|
+
role: "assistant",
|
|
257
|
+
content: "This is a mock response from the AI assistant.",
|
|
258
|
+
},
|
|
259
|
+
finish_reason: "stop",
|
|
260
|
+
},
|
|
261
|
+
],
|
|
262
|
+
usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Default mock response
|
|
268
|
+
return {
|
|
269
|
+
success: true,
|
|
270
|
+
provider,
|
|
271
|
+
action,
|
|
272
|
+
timestamp: new Date().toISOString(),
|
|
273
|
+
mock: true,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function mockFetchUrl(request: HttpRequestActivity): HttpResponseActivity {
|
|
278
|
+
const { url, method } = request;
|
|
279
|
+
|
|
280
|
+
// Simulate different API responses based on URL patterns
|
|
281
|
+
if (url.includes("/users")) {
|
|
282
|
+
if (method === "GET") {
|
|
283
|
+
return {
|
|
284
|
+
status: 200,
|
|
285
|
+
headers: { "content-type": "application/json" },
|
|
286
|
+
data: {
|
|
287
|
+
id: 1,
|
|
288
|
+
name: "Mock User",
|
|
289
|
+
email: "mock@example.com",
|
|
290
|
+
},
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
if (method === "POST") {
|
|
294
|
+
return {
|
|
295
|
+
status: 201,
|
|
296
|
+
headers: { "content-type": "application/json" },
|
|
297
|
+
data: {
|
|
298
|
+
id: 123,
|
|
299
|
+
success: true,
|
|
300
|
+
message: "User created",
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (url.includes("/orders")) {
|
|
307
|
+
if (method === "POST") {
|
|
308
|
+
return {
|
|
309
|
+
status: 201,
|
|
310
|
+
headers: { "content-type": "application/json" },
|
|
311
|
+
data: {
|
|
312
|
+
orderId: `ORD-${Date.now()}`,
|
|
313
|
+
status: "created",
|
|
314
|
+
timestamp: new Date().toISOString(),
|
|
315
|
+
},
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
if (method === "GET") {
|
|
319
|
+
return {
|
|
320
|
+
status: 200,
|
|
321
|
+
headers: { "content-type": "application/json" },
|
|
322
|
+
data: {
|
|
323
|
+
orders: [
|
|
324
|
+
{ id: "ORD-001", total: 99.99, status: "shipped" },
|
|
325
|
+
{ id: "ORD-002", total: 149.99, status: "pending" },
|
|
326
|
+
],
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (url.includes("/error")) {
|
|
333
|
+
return {
|
|
334
|
+
status: 500,
|
|
335
|
+
headers: { "content-type": "application/json" },
|
|
336
|
+
data: { error: "Internal Server Error", mock: true },
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
if (url.includes("/not-found")) {
|
|
341
|
+
return {
|
|
342
|
+
status: 404,
|
|
343
|
+
headers: { "content-type": "application/json" },
|
|
344
|
+
data: { error: "Not Found", mock: true },
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Default success response
|
|
349
|
+
return {
|
|
350
|
+
status: 200,
|
|
351
|
+
headers: { "content-type": "application/json" },
|
|
352
|
+
data: {
|
|
353
|
+
success: true,
|
|
354
|
+
url,
|
|
355
|
+
method,
|
|
356
|
+
timestamp: new Date().toISOString(),
|
|
357
|
+
mock: true,
|
|
358
|
+
},
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function getContentType(format: string): string {
|
|
363
|
+
switch (format) {
|
|
364
|
+
case "csv":
|
|
365
|
+
return "text/csv";
|
|
366
|
+
case "xlsx":
|
|
367
|
+
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
368
|
+
case "json":
|
|
369
|
+
return "application/json";
|
|
370
|
+
default:
|
|
371
|
+
return "application/octet-stream";
|
|
372
|
+
}
|
|
373
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Temporal Client
|
|
3
|
+
* Executes YAML-defined behavior tree workflows on Temporal server
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { readFileSync } from "fs";
|
|
7
|
+
import { fileURLToPath } from "url";
|
|
8
|
+
import { dirname, join } from "path";
|
|
9
|
+
import { Connection, Client } from "@temporalio/client";
|
|
10
|
+
import { Registry } from "../../dist/index.js";
|
|
11
|
+
import type { YamlWorkflowArgs } from "./yaml-workflow-loader.js";
|
|
12
|
+
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = dirname(__filename);
|
|
15
|
+
|
|
16
|
+
async function run() {
|
|
17
|
+
console.log("🔌 Connecting to Temporal server at localhost:7233...");
|
|
18
|
+
|
|
19
|
+
const connection = await Connection.connect({
|
|
20
|
+
address: "localhost:7233",
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const client = new Client({ connection });
|
|
24
|
+
console.log("✅ Connected to Temporal server\n");
|
|
25
|
+
|
|
26
|
+
// Create tree registry (required for SubTree nodes)
|
|
27
|
+
const treeRegistry = new Registry();
|
|
28
|
+
|
|
29
|
+
// Load YAML workflows
|
|
30
|
+
const workflows = [
|
|
31
|
+
{
|
|
32
|
+
name: "Simple Sequence",
|
|
33
|
+
file: "../yaml-workflows/01-simple-sequence.yaml",
|
|
34
|
+
id: "simple-sequence",
|
|
35
|
+
input: {},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "Parallel with Timeout",
|
|
39
|
+
file: "../yaml-workflows/02-parallel-timeout.yaml",
|
|
40
|
+
id: "parallel-timeout",
|
|
41
|
+
input: {},
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
name: "Activity Test (IntegrationAction via Activity)",
|
|
45
|
+
file: "../yaml-workflows/07-activity-simple-test.yaml",
|
|
46
|
+
id: "activity-test",
|
|
47
|
+
input: {
|
|
48
|
+
spreadsheetId: "test-spreadsheet-123",
|
|
49
|
+
orderId: "ORD-" + Date.now(),
|
|
50
|
+
customerName: "John Doe",
|
|
51
|
+
amount: 99.99,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
// {
|
|
55
|
+
// name: "Order Processing",
|
|
56
|
+
// file: "../yaml-workflows/05-order-processing.yaml",
|
|
57
|
+
// id: "order-processing",
|
|
58
|
+
// input: {},
|
|
59
|
+
// },
|
|
60
|
+
// Complex workflows (comment out for initial test)
|
|
61
|
+
// {
|
|
62
|
+
// name: "E-commerce Checkout",
|
|
63
|
+
// file: "../yaml-workflows/03-ecommerce-checkout.yaml",
|
|
64
|
+
// id: "ecommerce-checkout",
|
|
65
|
+
// input: {},
|
|
66
|
+
// },
|
|
67
|
+
// {
|
|
68
|
+
// name: "AI Agent Workflow",
|
|
69
|
+
// file: "../yaml-workflows/04-ai-agent-workflow.yaml",
|
|
70
|
+
// id: "ai-agent",
|
|
71
|
+
// input: {},
|
|
72
|
+
// },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
// Execute each YAML workflow
|
|
76
|
+
for (const workflow of workflows) {
|
|
77
|
+
console.log("=".repeat(60));
|
|
78
|
+
console.log(`Workflow: ${workflow.name}`);
|
|
79
|
+
console.log("=".repeat(60));
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
// Load YAML content from file
|
|
83
|
+
const yamlContent = readFileSync(join(__dirname, workflow.file), "utf-8");
|
|
84
|
+
|
|
85
|
+
// Create workflow args with YAML content
|
|
86
|
+
const args: YamlWorkflowArgs = {
|
|
87
|
+
input: workflow.input || {},
|
|
88
|
+
treeRegistry,
|
|
89
|
+
yamlContent,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
// Execute workflow
|
|
93
|
+
const result = await client.workflow.execute("yamlWorkflow", {
|
|
94
|
+
taskQueue: "btree-workflows",
|
|
95
|
+
workflowId: `${workflow.id}-${Date.now()}`,
|
|
96
|
+
args: [args],
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
console.log("✅ Result:", result);
|
|
100
|
+
console.log("\n");
|
|
101
|
+
} catch (error) {
|
|
102
|
+
console.error(`❌ Workflow failed:`, error);
|
|
103
|
+
console.log("\n");
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
console.log("=".repeat(60));
|
|
108
|
+
console.log("🎉 All YAML workflows completed!");
|
|
109
|
+
console.log("=".repeat(60));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
run().catch((err) => {
|
|
113
|
+
console.error("❌ Client error:", err);
|
|
114
|
+
process.exit(1);
|
|
115
|
+
});
|