@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,149 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* While node - Loop while condition is true
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CompositeNode } from "../base-node.js";
|
|
6
|
+
import { ConfigurationError } from "../errors.js";
|
|
7
|
+
import {
|
|
8
|
+
type TemporalContext,
|
|
9
|
+
type NodeConfiguration,
|
|
10
|
+
NodeStatus,
|
|
11
|
+
type TreeNode,
|
|
12
|
+
} from "../types.js";
|
|
13
|
+
import { checkSignal } from "../utils/signal-check.js";
|
|
14
|
+
|
|
15
|
+
export interface WhileConfiguration extends NodeConfiguration {
|
|
16
|
+
maxIterations?: number; // Safety limit
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* While loops while the condition returns SUCCESS.
|
|
21
|
+
* Structure:
|
|
22
|
+
* - First child = condition
|
|
23
|
+
* - Second child = body
|
|
24
|
+
*/
|
|
25
|
+
export class While extends CompositeNode {
|
|
26
|
+
private maxIterations: number;
|
|
27
|
+
private currentIteration: number = 0;
|
|
28
|
+
private condition?: TreeNode;
|
|
29
|
+
private body?: TreeNode;
|
|
30
|
+
private bodyStarted: boolean = false;
|
|
31
|
+
|
|
32
|
+
constructor(config: WhileConfiguration) {
|
|
33
|
+
super(config);
|
|
34
|
+
this.maxIterations = config.maxIterations ?? 1000;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
addChild(child: TreeNode): void {
|
|
38
|
+
if (!this.condition) {
|
|
39
|
+
this.condition = child;
|
|
40
|
+
this._children.push(child);
|
|
41
|
+
child.parent = this;
|
|
42
|
+
} else if (!this.body) {
|
|
43
|
+
this.body = child;
|
|
44
|
+
this._children.push(child);
|
|
45
|
+
child.parent = this;
|
|
46
|
+
} else {
|
|
47
|
+
throw new ConfigurationError(
|
|
48
|
+
"While can have maximum 2 children (condition, body)",
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async executeTick(context: TemporalContext): Promise<NodeStatus> {
|
|
54
|
+
if (!this.condition) {
|
|
55
|
+
throw new ConfigurationError("While requires a condition child");
|
|
56
|
+
}
|
|
57
|
+
if (!this.body) {
|
|
58
|
+
throw new ConfigurationError("While requires a body child");
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this.log(
|
|
62
|
+
`Starting while loop (iteration ${this.currentIteration}/${this.maxIterations})`,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// Loop while condition is SUCCESS
|
|
66
|
+
while (this.currentIteration < this.maxIterations) {
|
|
67
|
+
// Check for cancellation before each iteration
|
|
68
|
+
checkSignal(context.signal);
|
|
69
|
+
|
|
70
|
+
// Only check condition if body hasn't started for this iteration
|
|
71
|
+
if (!this.bodyStarted) {
|
|
72
|
+
// Evaluate condition
|
|
73
|
+
this.log(`Evaluating condition (iteration ${this.currentIteration})`);
|
|
74
|
+
const conditionStatus = await this.condition.tick(context);
|
|
75
|
+
|
|
76
|
+
if (conditionStatus === NodeStatus.RUNNING) {
|
|
77
|
+
this.log("Condition is running");
|
|
78
|
+
this._status = NodeStatus.RUNNING;
|
|
79
|
+
return NodeStatus.RUNNING;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (conditionStatus === NodeStatus.FAILURE) {
|
|
83
|
+
this.log("Condition failed - exiting loop");
|
|
84
|
+
this._status = NodeStatus.SUCCESS;
|
|
85
|
+
this.currentIteration = 0;
|
|
86
|
+
this.bodyStarted = false;
|
|
87
|
+
return NodeStatus.SUCCESS;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Condition succeeded, mark body as started
|
|
91
|
+
this.bodyStarted = true;
|
|
92
|
+
} else {
|
|
93
|
+
this.log(
|
|
94
|
+
`Body already started for iteration ${this.currentIteration} - continuing execution`,
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// Execute body
|
|
99
|
+
this.log(`Executing body (iteration ${this.currentIteration})`);
|
|
100
|
+
const bodyStatus = await this.body.tick(context);
|
|
101
|
+
|
|
102
|
+
switch (bodyStatus) {
|
|
103
|
+
case NodeStatus.SUCCESS:
|
|
104
|
+
this.log("Body succeeded - continuing loop");
|
|
105
|
+
this.currentIteration++;
|
|
106
|
+
this.bodyStarted = false; // Reset for next iteration
|
|
107
|
+
this.condition.reset(); // Reset for next iteration
|
|
108
|
+
this.body.reset();
|
|
109
|
+
break;
|
|
110
|
+
|
|
111
|
+
case NodeStatus.FAILURE:
|
|
112
|
+
this.log("Body failed - While fails");
|
|
113
|
+
this._status = NodeStatus.FAILURE;
|
|
114
|
+
this.currentIteration = 0;
|
|
115
|
+
this.bodyStarted = false;
|
|
116
|
+
return NodeStatus.FAILURE;
|
|
117
|
+
|
|
118
|
+
case NodeStatus.RUNNING:
|
|
119
|
+
this.log("Body is running");
|
|
120
|
+
this._status = NodeStatus.RUNNING;
|
|
121
|
+
return NodeStatus.RUNNING;
|
|
122
|
+
|
|
123
|
+
default:
|
|
124
|
+
throw new Error(`Unexpected status from body: ${bodyStatus}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Max iterations reached
|
|
129
|
+
this.log(`Max iterations (${this.maxIterations}) reached`);
|
|
130
|
+
this._status = NodeStatus.FAILURE;
|
|
131
|
+
this.currentIteration = 0;
|
|
132
|
+
this.bodyStarted = false;
|
|
133
|
+
return NodeStatus.FAILURE;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
protected onReset(): void {
|
|
137
|
+
super.onReset();
|
|
138
|
+
this.log("Resetting - clearing body started flag");
|
|
139
|
+
this.currentIteration = 0;
|
|
140
|
+
this.bodyStarted = false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
protected onHalt(): void {
|
|
144
|
+
super.onHalt();
|
|
145
|
+
this.log("Halting - clearing body started flag");
|
|
146
|
+
this.currentIteration = 0;
|
|
147
|
+
this.bodyStarted = false;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataStore module exports
|
|
3
|
+
*
|
|
4
|
+
* Provides interfaces and implementations for large data storage
|
|
5
|
+
* outside workflow execution context.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export type { DataStore, DataRef, PutOptions } from "./types.js";
|
|
9
|
+
export { isDataRef } from "./types.js";
|
|
10
|
+
export { MemoryDataStore } from "./memory-store.js";
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-Memory DataStore Implementation
|
|
3
|
+
*
|
|
4
|
+
* Simple in-memory storage for testing and development.
|
|
5
|
+
* Data is lost when the process terminates.
|
|
6
|
+
*
|
|
7
|
+
* Features:
|
|
8
|
+
* - TTL support with automatic cleanup
|
|
9
|
+
* - Size tracking
|
|
10
|
+
* - Thread-safe for single Node.js process
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { DataStore, DataRef, PutOptions } from "./types.js";
|
|
14
|
+
|
|
15
|
+
interface StoredEntry {
|
|
16
|
+
data: unknown;
|
|
17
|
+
sizeBytes: number;
|
|
18
|
+
expiresAt?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* In-memory DataStore implementation
|
|
23
|
+
* Suitable for tests and local development
|
|
24
|
+
*/
|
|
25
|
+
export class MemoryDataStore implements DataStore {
|
|
26
|
+
private storage = new Map<string, StoredEntry>();
|
|
27
|
+
private cleanupInterval: ReturnType<typeof setInterval> | null = null;
|
|
28
|
+
|
|
29
|
+
constructor(options?: { cleanupIntervalMs?: number }) {
|
|
30
|
+
// Start periodic cleanup for expired entries
|
|
31
|
+
const cleanupMs = options?.cleanupIntervalMs ?? 60000; // 1 minute default
|
|
32
|
+
if (cleanupMs > 0) {
|
|
33
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), cleanupMs);
|
|
34
|
+
// Don't prevent process exit
|
|
35
|
+
if (this.cleanupInterval.unref) {
|
|
36
|
+
this.cleanupInterval.unref();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async put(key: string, data: unknown, options?: PutOptions): Promise<DataRef> {
|
|
42
|
+
const serialized = JSON.stringify(data);
|
|
43
|
+
const sizeBytes = Buffer.byteLength(serialized, "utf8");
|
|
44
|
+
|
|
45
|
+
const entry: StoredEntry = {
|
|
46
|
+
data,
|
|
47
|
+
sizeBytes,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
if (options?.ttlSeconds) {
|
|
51
|
+
entry.expiresAt = Date.now() + options.ttlSeconds * 1000;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
this.storage.set(key, entry);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
store: "memory",
|
|
58
|
+
key,
|
|
59
|
+
sizeBytes,
|
|
60
|
+
expiresAt: entry.expiresAt,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async get(ref: DataRef): Promise<unknown> {
|
|
65
|
+
if (ref.store !== "memory") {
|
|
66
|
+
throw new Error(`MemoryDataStore cannot retrieve from store: ${ref.store}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const entry = this.storage.get(ref.key);
|
|
70
|
+
|
|
71
|
+
if (!entry) {
|
|
72
|
+
throw new Error(`Data not found for key: ${ref.key}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Check expiration
|
|
76
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
77
|
+
this.storage.delete(ref.key);
|
|
78
|
+
throw new Error(`Data expired for key: ${ref.key}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Return a deep clone to prevent accidental mutation
|
|
82
|
+
return structuredClone(entry.data);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async delete(ref: DataRef): Promise<void> {
|
|
86
|
+
if (ref.store !== "memory") {
|
|
87
|
+
throw new Error(`MemoryDataStore cannot delete from store: ${ref.store}`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.storage.delete(ref.key);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async exists(ref: DataRef): Promise<boolean> {
|
|
94
|
+
if (ref.store !== "memory") {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const entry = this.storage.get(ref.key);
|
|
99
|
+
|
|
100
|
+
if (!entry) {
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Check expiration
|
|
105
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
106
|
+
this.storage.delete(ref.key);
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Clear all stored data
|
|
115
|
+
* Useful for test cleanup
|
|
116
|
+
*/
|
|
117
|
+
clear(): void {
|
|
118
|
+
this.storage.clear();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Get the number of stored entries
|
|
123
|
+
*/
|
|
124
|
+
size(): number {
|
|
125
|
+
return this.storage.size;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get total bytes stored
|
|
130
|
+
*/
|
|
131
|
+
totalBytes(): number {
|
|
132
|
+
let total = 0;
|
|
133
|
+
for (const entry of this.storage.values()) {
|
|
134
|
+
total += entry.sizeBytes;
|
|
135
|
+
}
|
|
136
|
+
return total;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Stop the cleanup interval
|
|
141
|
+
* Call this when done with the store to prevent memory leaks in tests
|
|
142
|
+
*/
|
|
143
|
+
dispose(): void {
|
|
144
|
+
if (this.cleanupInterval) {
|
|
145
|
+
clearInterval(this.cleanupInterval);
|
|
146
|
+
this.cleanupInterval = null;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Remove expired entries
|
|
152
|
+
*/
|
|
153
|
+
private cleanup(): void {
|
|
154
|
+
const now = Date.now();
|
|
155
|
+
for (const [key, entry] of this.storage.entries()) {
|
|
156
|
+
if (entry.expiresAt && now > entry.expiresAt) {
|
|
157
|
+
this.storage.delete(key);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DataStore Interface
|
|
3
|
+
*
|
|
4
|
+
* Provides an abstraction for storing and retrieving large data payloads
|
|
5
|
+
* outside the workflow execution context. This keeps the Temporal workflow
|
|
6
|
+
* history lean while allowing workflows to process large datasets.
|
|
7
|
+
*
|
|
8
|
+
* Implementations:
|
|
9
|
+
* - MemoryDataStore: In-memory storage for tests
|
|
10
|
+
* - GCSDataStore: Google Cloud Storage for production (in controlplane)
|
|
11
|
+
* - RedisDataStore: Redis storage for caching (future)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Reference to data stored in a DataStore
|
|
16
|
+
* This lightweight reference can be passed through workflows/activities
|
|
17
|
+
*/
|
|
18
|
+
export interface DataRef {
|
|
19
|
+
/** Storage backend identifier */
|
|
20
|
+
store: "gcs" | "s3" | "redis" | "memory";
|
|
21
|
+
/** Unique key for retrieving the data */
|
|
22
|
+
key: string;
|
|
23
|
+
/** Size of stored data in bytes (for monitoring/optimization) */
|
|
24
|
+
sizeBytes?: number;
|
|
25
|
+
/** Unix timestamp when data expires (optional TTL) */
|
|
26
|
+
expiresAt?: number;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Options for storing data
|
|
31
|
+
*/
|
|
32
|
+
export interface PutOptions {
|
|
33
|
+
/** Time-to-live in seconds (data auto-deleted after expiry) */
|
|
34
|
+
ttlSeconds?: number;
|
|
35
|
+
/** Content type hint for serialization */
|
|
36
|
+
contentType?: "json" | "csv" | "binary";
|
|
37
|
+
/** Associated workflow ID for grouping/cleanup */
|
|
38
|
+
workflowId?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* DataStore interface for storing and retrieving large payloads
|
|
43
|
+
*/
|
|
44
|
+
export interface DataStore {
|
|
45
|
+
/**
|
|
46
|
+
* Store data and return a reference
|
|
47
|
+
* @param key - Unique identifier for the data
|
|
48
|
+
* @param data - Data to store (will be JSON serialized)
|
|
49
|
+
* @param options - Storage options (TTL, content type, etc.)
|
|
50
|
+
* @returns Reference to the stored data
|
|
51
|
+
*/
|
|
52
|
+
put(key: string, data: unknown, options?: PutOptions): Promise<DataRef>;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Retrieve data by reference
|
|
56
|
+
* @param ref - Reference returned from put()
|
|
57
|
+
* @returns The stored data (JSON deserialized)
|
|
58
|
+
* @throws Error if data not found or expired
|
|
59
|
+
*/
|
|
60
|
+
get(ref: DataRef): Promise<unknown>;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Delete data by reference
|
|
64
|
+
* @param ref - Reference to delete
|
|
65
|
+
*/
|
|
66
|
+
delete(ref: DataRef): Promise<void>;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Check if data exists
|
|
70
|
+
* @param ref - Reference to check
|
|
71
|
+
* @returns true if data exists and hasn't expired
|
|
72
|
+
*/
|
|
73
|
+
exists(ref: DataRef): Promise<boolean>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Type guard to check if a value is a DataRef
|
|
78
|
+
* @param value - Value to check
|
|
79
|
+
* @returns true if value is a DataRef
|
|
80
|
+
*/
|
|
81
|
+
export function isDataRef(value: unknown): value is DataRef {
|
|
82
|
+
if (typeof value !== "object" || value === null) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const obj = value as Record<string, unknown>;
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
typeof obj.store === "string" &&
|
|
90
|
+
["gcs", "s3", "redis", "memory"].includes(obj.store) &&
|
|
91
|
+
typeof obj.key === "string" &&
|
|
92
|
+
obj.key.length > 0
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for Breakpoint node
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { ScopedBlackboard } from "../blackboard.js";
|
|
7
|
+
import { Registry } from "../registry.js";
|
|
8
|
+
import { NodeStatus, type TemporalContext } from "../types.js";
|
|
9
|
+
import { Breakpoint } from "./breakpoint.js";
|
|
10
|
+
|
|
11
|
+
describe("Breakpoint", () => {
|
|
12
|
+
const createContext = (): TemporalContext => ({
|
|
13
|
+
blackboard: new ScopedBlackboard(),
|
|
14
|
+
treeRegistry: new Registry(),
|
|
15
|
+
signal: new AbortController().signal,
|
|
16
|
+
timestamp: Date.now(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should return RUNNING to signal pause", async () => {
|
|
20
|
+
const node = new Breakpoint({ id: "test-break" });
|
|
21
|
+
const context = createContext();
|
|
22
|
+
|
|
23
|
+
const result = await node.tick(context);
|
|
24
|
+
|
|
25
|
+
expect(result).toBe(NodeStatus.RUNNING);
|
|
26
|
+
expect(node.status()).toBe(NodeStatus.RUNNING);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should store the breakpointId", () => {
|
|
30
|
+
const node = new Breakpoint({ id: "my-breakpoint" });
|
|
31
|
+
|
|
32
|
+
expect(node.breakpointId).toBe("my-breakpoint");
|
|
33
|
+
expect(node.id).toBe("breakpoint-my-breakpoint");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should consistently return RUNNING", async () => {
|
|
37
|
+
const node = new Breakpoint({ id: "pause-here" });
|
|
38
|
+
const context = createContext();
|
|
39
|
+
|
|
40
|
+
// Execute multiple times - should always return RUNNING
|
|
41
|
+
const result1 = await node.tick(context);
|
|
42
|
+
const result2 = await node.tick(context);
|
|
43
|
+
|
|
44
|
+
expect(result1).toBe(NodeStatus.RUNNING);
|
|
45
|
+
expect(result2).toBe(NodeStatus.RUNNING);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Breakpoint - Pauses execution without failure
|
|
3
|
+
* Returns RUNNING to signal pause (not FAILURE)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ActionNode } from "../base-node.js";
|
|
7
|
+
import { type TemporalContext, NodeStatus } from "../types.js";
|
|
8
|
+
|
|
9
|
+
export interface BreakpointConfig {
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Breakpoint pauses execution without marking it as a failure.
|
|
15
|
+
* Returns RUNNING to signal that execution should pause here.
|
|
16
|
+
* The session can then be resumed from this point.
|
|
17
|
+
*/
|
|
18
|
+
export class Breakpoint extends ActionNode {
|
|
19
|
+
readonly breakpointId: string;
|
|
20
|
+
|
|
21
|
+
constructor(config: BreakpointConfig) {
|
|
22
|
+
super({ id: `breakpoint-${config.id}` });
|
|
23
|
+
this.breakpointId = config.id;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async executeTick(_context: TemporalContext): Promise<NodeStatus> {
|
|
27
|
+
this._status = NodeStatus.RUNNING;
|
|
28
|
+
return NodeStatus.RUNNING;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug nodes for behavior tree debugging and resume functionality
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
// Breakpoint is not exported - it's still in development
|
|
6
|
+
// The current implementation causes infinite loops with tickWhileRunning
|
|
7
|
+
// See docs/backlog/DEBUG_MODE.md for the full implementation plan
|
|
8
|
+
// export {
|
|
9
|
+
// Breakpoint,
|
|
10
|
+
// BreakpointSchema,
|
|
11
|
+
// type BreakpointConfig,
|
|
12
|
+
// } from "./breakpoint.js";
|
|
13
|
+
|
|
14
|
+
export {
|
|
15
|
+
ResumePoint,
|
|
16
|
+
type ResumePointConfig,
|
|
17
|
+
} from "./resume-point.js";
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for ResumePoint node
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, expect, it } from "vitest";
|
|
6
|
+
import { ScopedBlackboard } from "../blackboard.js";
|
|
7
|
+
import { Registry } from "../registry.js";
|
|
8
|
+
import { NodeStatus, type TemporalContext } from "../types.js";
|
|
9
|
+
import { ResumePoint } from "./resume-point.js";
|
|
10
|
+
|
|
11
|
+
describe("ResumePoint", () => {
|
|
12
|
+
const createContext = (): TemporalContext => ({
|
|
13
|
+
blackboard: new ScopedBlackboard(),
|
|
14
|
+
treeRegistry: new Registry(),
|
|
15
|
+
signal: new AbortController().signal,
|
|
16
|
+
timestamp: Date.now(),
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("should always return SUCCESS", async () => {
|
|
20
|
+
const node = new ResumePoint({ id: "test-resume" });
|
|
21
|
+
const context = createContext();
|
|
22
|
+
|
|
23
|
+
const result = await node.tick(context);
|
|
24
|
+
|
|
25
|
+
expect(result).toBe(NodeStatus.SUCCESS);
|
|
26
|
+
expect(node.status()).toBe(NodeStatus.SUCCESS);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it("should store the resumePointId", () => {
|
|
30
|
+
const node = new ResumePoint({ id: "my-custom-id" });
|
|
31
|
+
|
|
32
|
+
expect(node.resumePointId).toBe("my-custom-id");
|
|
33
|
+
expect(node.id).toBe("resume-point-my-custom-id");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("should not affect tree execution flow", async () => {
|
|
37
|
+
const node = new ResumePoint({ id: "marker" });
|
|
38
|
+
const context = createContext();
|
|
39
|
+
|
|
40
|
+
// Execute multiple times - should always succeed
|
|
41
|
+
const result1 = await node.tick(context);
|
|
42
|
+
const result2 = await node.tick(context);
|
|
43
|
+
const result3 = await node.tick(context);
|
|
44
|
+
|
|
45
|
+
expect(result1).toBe(NodeStatus.SUCCESS);
|
|
46
|
+
expect(result2).toBe(NodeStatus.SUCCESS);
|
|
47
|
+
expect(result3).toBe(NodeStatus.SUCCESS);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ResumePoint - Marker node for resume location
|
|
3
|
+
* Always returns SUCCESS, used purely as a findable marker for LLM-based resume
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { ActionNode } from "../base-node.js";
|
|
7
|
+
import { type TemporalContext, NodeStatus } from "../types.js";
|
|
8
|
+
|
|
9
|
+
export interface ResumePointConfig {
|
|
10
|
+
id: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* ResumePoint is a marker node that LLM agents can insert to indicate
|
|
15
|
+
* where execution should resume from. It always returns SUCCESS.
|
|
16
|
+
*/
|
|
17
|
+
export class ResumePoint extends ActionNode {
|
|
18
|
+
readonly resumePointId: string;
|
|
19
|
+
|
|
20
|
+
constructor(config: ResumePointConfig) {
|
|
21
|
+
super({ id: `resume-point-${config.id}` });
|
|
22
|
+
this.resumePointId = config.id;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async executeTick(_context: TemporalContext): Promise<NodeStatus> {
|
|
26
|
+
this._status = NodeStatus.SUCCESS;
|
|
27
|
+
return NodeStatus.SUCCESS;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Delay decorator configuration schema
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import { createNodeSchema, validations } from "../schemas/base.schema.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Schema for Delay decorator configuration
|
|
10
|
+
* Validates that delayMs is non-negative (can be 0)
|
|
11
|
+
*/
|
|
12
|
+
export const delayConfigurationSchema = createNodeSchema("Delay", {
|
|
13
|
+
delayMs: validations.nonNegativeNumber("delayMs"),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validated Delay configuration type
|
|
18
|
+
*/
|
|
19
|
+
export type ValidatedDelayConfiguration = z.infer<
|
|
20
|
+
typeof delayConfigurationSchema
|
|
21
|
+
>;
|