@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,320 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExecutionTracker - Aggregates node events into queryable state
|
|
3
|
+
* Used by workflow query handlers to expose real-time execution progress
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { NodeEventType } from "../events.js";
|
|
7
|
+
import type {
|
|
8
|
+
ExecutionProgress,
|
|
9
|
+
NodeState,
|
|
10
|
+
StructuredError,
|
|
11
|
+
TimelineEntry,
|
|
12
|
+
ObservableNodeEvent,
|
|
13
|
+
} from "./types.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Tracks execution state by subscribing to node events
|
|
17
|
+
* Provides query methods for progress, node states, errors, and timeline
|
|
18
|
+
*/
|
|
19
|
+
export class ExecutionTracker {
|
|
20
|
+
private nodeStates = new Map<string, NodeState>();
|
|
21
|
+
private timeline: TimelineEntry[] = [];
|
|
22
|
+
private errors: StructuredError[] = [];
|
|
23
|
+
private pathTaken: string[] = [];
|
|
24
|
+
private startedAt: number = Date.now();
|
|
25
|
+
private currentNodeId: string | null = null;
|
|
26
|
+
private totalNodes: number;
|
|
27
|
+
private finished: boolean = false;
|
|
28
|
+
|
|
29
|
+
constructor(totalNodes: number) {
|
|
30
|
+
this.totalNodes = totalNodes;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Process a node event and update internal state
|
|
35
|
+
* Called by NodeEventEmitter subscription
|
|
36
|
+
*/
|
|
37
|
+
onNodeEvent(event: ObservableNodeEvent): void {
|
|
38
|
+
const state = this.getOrCreateState(
|
|
39
|
+
event.nodeId,
|
|
40
|
+
event.nodeType,
|
|
41
|
+
event.nodeName,
|
|
42
|
+
event.nodePath
|
|
43
|
+
);
|
|
44
|
+
|
|
45
|
+
const eventType = event.type as NodeEventType;
|
|
46
|
+
|
|
47
|
+
switch (eventType) {
|
|
48
|
+
case NodeEventType.TICK_START:
|
|
49
|
+
this.handleTickStart(state, event);
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case NodeEventType.TICK_END:
|
|
53
|
+
this.handleTickEnd(state, event);
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case NodeEventType.ERROR:
|
|
57
|
+
this.handleError(state, event);
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
case NodeEventType.LOG:
|
|
61
|
+
// Log events don't affect tracking state
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case NodeEventType.HALT:
|
|
65
|
+
case NodeEventType.RESET:
|
|
66
|
+
// Could track these for debugging, but not critical for progress
|
|
67
|
+
break;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
private handleTickStart(state: NodeState, event: ObservableNodeEvent): void {
|
|
72
|
+
state.status = "running";
|
|
73
|
+
state.tickCount++;
|
|
74
|
+
state.lastTickAt = event.timestamp;
|
|
75
|
+
this.currentNodeId = event.nodeId;
|
|
76
|
+
|
|
77
|
+
this.timeline.push({
|
|
78
|
+
nodeId: event.nodeId,
|
|
79
|
+
nodeType: event.nodeType,
|
|
80
|
+
nodeName: event.nodeName,
|
|
81
|
+
nodePath: event.nodePath ?? "",
|
|
82
|
+
event: "start",
|
|
83
|
+
timestamp: event.timestamp,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
private handleTickEnd(state: NodeState, event: ObservableNodeEvent): void {
|
|
88
|
+
const status = event.data?.status;
|
|
89
|
+
const durationMs = event.data?.durationMs;
|
|
90
|
+
|
|
91
|
+
// Map NodeStatus strings to our simpler status
|
|
92
|
+
if (status === "SUCCESS") {
|
|
93
|
+
state.status = "success";
|
|
94
|
+
if (!this.pathTaken.includes(event.nodeId)) {
|
|
95
|
+
this.pathTaken.push(event.nodeId);
|
|
96
|
+
}
|
|
97
|
+
} else if (status === "FAILURE") {
|
|
98
|
+
state.status = "failure";
|
|
99
|
+
} else if (status === "RUNNING") {
|
|
100
|
+
state.status = "running";
|
|
101
|
+
} else {
|
|
102
|
+
state.status = "idle";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (durationMs !== undefined) {
|
|
106
|
+
state.durationMs = durationMs;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
this.timeline.push({
|
|
110
|
+
nodeId: event.nodeId,
|
|
111
|
+
nodeType: event.nodeType,
|
|
112
|
+
nodeName: event.nodeName,
|
|
113
|
+
nodePath: event.nodePath ?? "",
|
|
114
|
+
event: "end",
|
|
115
|
+
timestamp: event.timestamp,
|
|
116
|
+
status: state.status,
|
|
117
|
+
durationMs: state.durationMs,
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Clear current node if this was it
|
|
121
|
+
if (this.currentNodeId === event.nodeId && state.status !== "running") {
|
|
122
|
+
this.currentNodeId = null;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
private handleError(state: NodeState, event: ObservableNodeEvent): void {
|
|
127
|
+
state.status = "failure";
|
|
128
|
+
const errorData = event.data?.error;
|
|
129
|
+
state.lastError = errorData?.message ?? "Unknown error";
|
|
130
|
+
|
|
131
|
+
const structuredError: StructuredError = {
|
|
132
|
+
nodeId: event.nodeId,
|
|
133
|
+
nodeType: event.nodeType,
|
|
134
|
+
nodeName: event.nodeName,
|
|
135
|
+
nodePath: event.nodePath ?? "",
|
|
136
|
+
message: errorData?.message ?? "Unknown error",
|
|
137
|
+
stack: errorData?.stack,
|
|
138
|
+
timestamp: event.timestamp,
|
|
139
|
+
blackboardSnapshot: event.data?.blackboard ?? {},
|
|
140
|
+
nodeInput: errorData?.input,
|
|
141
|
+
recoverable: this.isRecoverable(event.nodeType),
|
|
142
|
+
suggestedFix: this.suggestFix(event),
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
this.errors.push(structuredError);
|
|
146
|
+
|
|
147
|
+
this.timeline.push({
|
|
148
|
+
nodeId: event.nodeId,
|
|
149
|
+
nodeType: event.nodeType,
|
|
150
|
+
nodeName: event.nodeName,
|
|
151
|
+
nodePath: event.nodePath ?? "",
|
|
152
|
+
event: "error",
|
|
153
|
+
timestamp: event.timestamp,
|
|
154
|
+
error: {
|
|
155
|
+
message: structuredError.message,
|
|
156
|
+
stack: structuredError.stack,
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Mark execution as finished
|
|
163
|
+
*/
|
|
164
|
+
markFinished(): void {
|
|
165
|
+
this.finished = true;
|
|
166
|
+
this.currentNodeId = null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Get overall execution progress
|
|
171
|
+
*/
|
|
172
|
+
getProgress(): ExecutionProgress {
|
|
173
|
+
const states = [...this.nodeStates.values()];
|
|
174
|
+
const completed = states.filter((n) => n.status === "success").length;
|
|
175
|
+
const failed = states.filter((n) => n.status === "failure").length;
|
|
176
|
+
|
|
177
|
+
let status: "running" | "completed" | "failed";
|
|
178
|
+
if (failed > 0) {
|
|
179
|
+
status = "failed";
|
|
180
|
+
} else if (this.finished) {
|
|
181
|
+
status = "completed";
|
|
182
|
+
} else if (this.currentNodeId) {
|
|
183
|
+
status = "running";
|
|
184
|
+
} else {
|
|
185
|
+
status = "completed";
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
totalNodes: this.totalNodes,
|
|
190
|
+
completedNodes: completed,
|
|
191
|
+
failedNodes: failed,
|
|
192
|
+
currentNodeId: this.currentNodeId,
|
|
193
|
+
currentNodeType: this.nodeStates.get(this.currentNodeId ?? "")?.type ?? null,
|
|
194
|
+
pathTaken: [...this.pathTaken],
|
|
195
|
+
startedAt: this.startedAt,
|
|
196
|
+
lastActivityAt:
|
|
197
|
+
this.timeline[this.timeline.length - 1]?.timestamp ?? this.startedAt,
|
|
198
|
+
status,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Get all node states
|
|
204
|
+
*/
|
|
205
|
+
getNodeStates(): Map<string, NodeState> {
|
|
206
|
+
return new Map(this.nodeStates);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Get node states as a plain object (for JSON serialization)
|
|
211
|
+
*/
|
|
212
|
+
getNodeStatesObject(): Record<string, NodeState> {
|
|
213
|
+
const result: Record<string, NodeState> = {};
|
|
214
|
+
for (const [id, state] of this.nodeStates) {
|
|
215
|
+
result[id] = { ...state };
|
|
216
|
+
}
|
|
217
|
+
return result;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Get all errors that occurred during execution
|
|
222
|
+
*/
|
|
223
|
+
getErrors(): StructuredError[] {
|
|
224
|
+
return [...this.errors];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Get the full execution timeline
|
|
229
|
+
*/
|
|
230
|
+
getTimeline(): TimelineEntry[] {
|
|
231
|
+
return [...this.timeline];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get state for a specific node
|
|
236
|
+
*/
|
|
237
|
+
getNodeState(nodeId: string): NodeState | undefined {
|
|
238
|
+
return this.nodeStates.get(nodeId);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
private getOrCreateState(
|
|
242
|
+
id: string,
|
|
243
|
+
type: string,
|
|
244
|
+
name: string,
|
|
245
|
+
path?: string
|
|
246
|
+
): NodeState {
|
|
247
|
+
if (!this.nodeStates.has(id)) {
|
|
248
|
+
this.nodeStates.set(id, {
|
|
249
|
+
id,
|
|
250
|
+
type,
|
|
251
|
+
name,
|
|
252
|
+
path: path ?? "",
|
|
253
|
+
status: "idle",
|
|
254
|
+
tickCount: 0,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return this.nodeStates.get(id)!;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Check if an error type is potentially recoverable with retry
|
|
262
|
+
*/
|
|
263
|
+
private isRecoverable(nodeType: string): boolean {
|
|
264
|
+
// Nodes that often fail due to transient issues
|
|
265
|
+
const recoverableTypes = [
|
|
266
|
+
"FetchUrl",
|
|
267
|
+
"HttpRequest",
|
|
268
|
+
"CodeExecution",
|
|
269
|
+
"ParseFile",
|
|
270
|
+
"GenerateFile",
|
|
271
|
+
"IntegrationAction",
|
|
272
|
+
];
|
|
273
|
+
return recoverableTypes.includes(nodeType);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Suggest a fix based on error patterns
|
|
278
|
+
*/
|
|
279
|
+
private suggestFix(event: ObservableNodeEvent): string | undefined {
|
|
280
|
+
const msg = event.data?.error?.message ?? "";
|
|
281
|
+
const nodeType = event.nodeType;
|
|
282
|
+
|
|
283
|
+
// Timeout errors
|
|
284
|
+
if (msg.includes("timeout") || msg.includes("TIMEOUT") || msg.includes("timed out")) {
|
|
285
|
+
return "Consider increasing timeout or adding retry decorator";
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Undefined/null access
|
|
289
|
+
if (msg.includes("undefined") || msg.includes("null") || msg.includes("Cannot read property")) {
|
|
290
|
+
return "Check if required blackboard key exists before accessing";
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Network errors
|
|
294
|
+
if (msg.includes("network") || msg.includes("ECONNREFUSED") || msg.includes("fetch failed")) {
|
|
295
|
+
return "Check URL accessibility, consider adding retry decorator";
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Authentication errors
|
|
299
|
+
if (msg.includes("401") || msg.includes("403") || msg.includes("unauthorized")) {
|
|
300
|
+
return "Check authentication credentials and permissions";
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// File not found
|
|
304
|
+
if (msg.includes("not found") || msg.includes("ENOENT") || msg.includes("404")) {
|
|
305
|
+
return "Verify file path or URL exists";
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// Code execution errors
|
|
309
|
+
if (nodeType === "CodeExecution") {
|
|
310
|
+
if (msg.includes("SyntaxError")) {
|
|
311
|
+
return "Fix syntax error in code";
|
|
312
|
+
}
|
|
313
|
+
if (msg.includes("ReferenceError")) {
|
|
314
|
+
return "Variable not defined - check getBB() keys";
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return undefined;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability module for workflow execution tracking
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - ExecutionTracker: Aggregates events into queryable state
|
|
6
|
+
* - Types: ExecutionProgress, NodeState, StructuredError, TimelineEntry
|
|
7
|
+
* - Sinks: ObservabilitySinks for Temporal workflow event export
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export { ExecutionTracker } from "./execution-tracker.js";
|
|
11
|
+
export type {
|
|
12
|
+
ExecutionProgress,
|
|
13
|
+
NodeState,
|
|
14
|
+
StructuredError,
|
|
15
|
+
TimelineEntry,
|
|
16
|
+
ObservableNodeEvent,
|
|
17
|
+
} from "./types.js";
|
|
18
|
+
export type {
|
|
19
|
+
ObservabilitySinks,
|
|
20
|
+
SinkHandlerConfig,
|
|
21
|
+
InjectedObservabilitySinks,
|
|
22
|
+
} from "./sinks.js";
|
|
23
|
+
export { createObservabilitySinkHandler } from "./sinks.js";
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Workflow Sink types for exporting events from Temporal workflows
|
|
3
|
+
* Sinks allow workflows to export data without affecting determinism
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { Sinks } from "@temporalio/workflow";
|
|
7
|
+
import type { ObservableNodeEvent } from "./types.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Observability sinks for workflow event export
|
|
11
|
+
*
|
|
12
|
+
* Usage in workflow:
|
|
13
|
+
* ```typescript
|
|
14
|
+
* import { proxySinks } from '@temporalio/workflow';
|
|
15
|
+
* import type { ObservabilitySinks } from '@wayfarer-ai/btree-workflows';
|
|
16
|
+
*
|
|
17
|
+
* const { events } = proxySinks<ObservabilitySinks>();
|
|
18
|
+
* events.push(nodeEvent); // Fire-and-forget
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The sink handler runs in the worker (outside the deterministic sandbox)
|
|
22
|
+
* and can safely perform I/O like database writes or triggering agents.
|
|
23
|
+
*/
|
|
24
|
+
export interface ObservabilitySinks extends Sinks {
|
|
25
|
+
events: {
|
|
26
|
+
/**
|
|
27
|
+
* Push a node event to external observers
|
|
28
|
+
* This is fire-and-forget - workflow execution continues immediately
|
|
29
|
+
*/
|
|
30
|
+
push(event: ObservableNodeEvent): void;
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Configuration for creating sink handlers
|
|
36
|
+
*/
|
|
37
|
+
export interface SinkHandlerConfig {
|
|
38
|
+
/**
|
|
39
|
+
* Called when a node event is received
|
|
40
|
+
* Can be async - workflow doesn't wait for completion
|
|
41
|
+
*/
|
|
42
|
+
onEvent?: (
|
|
43
|
+
workflowId: string,
|
|
44
|
+
runId: string,
|
|
45
|
+
event: ObservableNodeEvent
|
|
46
|
+
) => void | Promise<void>;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Called specifically for ERROR events
|
|
50
|
+
* Use this to trigger analyzer agent or alerting
|
|
51
|
+
*/
|
|
52
|
+
onError?: (
|
|
53
|
+
workflowId: string,
|
|
54
|
+
runId: string,
|
|
55
|
+
event: ObservableNodeEvent
|
|
56
|
+
) => void | Promise<void>;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Whether to log events to console (default: false)
|
|
60
|
+
*/
|
|
61
|
+
logEvents?: boolean;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Sink function signature for InjectedSinks
|
|
66
|
+
*/
|
|
67
|
+
export interface SinkFunction<T extends (...args: any[]) => any> {
|
|
68
|
+
fn(info: { workflowId: string; runId: string }, ...args: Parameters<T>): ReturnType<T>;
|
|
69
|
+
callDuringReplay: boolean;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Type for InjectedSinks<ObservabilitySinks>
|
|
74
|
+
*/
|
|
75
|
+
export type InjectedObservabilitySinks = {
|
|
76
|
+
events: {
|
|
77
|
+
push: SinkFunction<(event: ObservableNodeEvent) => void>;
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Create sink handler functions for InjectedSinks
|
|
83
|
+
*
|
|
84
|
+
* Usage in worker:
|
|
85
|
+
* ```typescript
|
|
86
|
+
* import { createObservabilitySinkHandler } from '@wayfarer-ai/btree-workflows';
|
|
87
|
+
*
|
|
88
|
+
* const sinks = createObservabilitySinkHandler({
|
|
89
|
+
* onEvent: (workflowId, runId, event) => {
|
|
90
|
+
* // Store event to database
|
|
91
|
+
* },
|
|
92
|
+
* onError: (workflowId, runId, event) => {
|
|
93
|
+
* // Trigger analyzer agent
|
|
94
|
+
* },
|
|
95
|
+
* });
|
|
96
|
+
*
|
|
97
|
+
* const worker = await Worker.create({ sinks, ... });
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function createObservabilitySinkHandler(
|
|
101
|
+
config: SinkHandlerConfig = {}
|
|
102
|
+
): InjectedObservabilitySinks {
|
|
103
|
+
return {
|
|
104
|
+
events: {
|
|
105
|
+
push: {
|
|
106
|
+
fn: (
|
|
107
|
+
workflowInfo: { workflowId: string; runId: string },
|
|
108
|
+
event: ObservableNodeEvent
|
|
109
|
+
) => {
|
|
110
|
+
const { workflowId, runId } = workflowInfo;
|
|
111
|
+
|
|
112
|
+
// Log if configured
|
|
113
|
+
if (config.logEvents) {
|
|
114
|
+
console.log(
|
|
115
|
+
`[Sink] [${workflowId}] ${event.type}: ${event.nodeId} (${event.nodeType})`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Call general event handler
|
|
120
|
+
if (config.onEvent) {
|
|
121
|
+
Promise.resolve(config.onEvent(workflowId, runId, event)).catch(
|
|
122
|
+
(err) => console.error("[Sink] Error in onEvent handler:", err)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Call error handler for ERROR events
|
|
127
|
+
if (event.type === "error" && config.onError) {
|
|
128
|
+
Promise.resolve(config.onError(workflowId, runId, event)).catch(
|
|
129
|
+
(err) => console.error("[Sink] Error in onError handler:", err)
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
},
|
|
133
|
+
// Don't call during replay - events were already processed
|
|
134
|
+
callDuringReplay: false,
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
};
|
|
138
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Observability types for workflow execution tracking
|
|
3
|
+
* Used by ExecutionTracker and Analyzer Agent
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Overall execution progress summary
|
|
8
|
+
*/
|
|
9
|
+
export interface ExecutionProgress {
|
|
10
|
+
/** Total number of nodes in the tree */
|
|
11
|
+
totalNodes: number;
|
|
12
|
+
/** Number of nodes that completed successfully */
|
|
13
|
+
completedNodes: number;
|
|
14
|
+
/** Number of nodes that failed */
|
|
15
|
+
failedNodes: number;
|
|
16
|
+
/** Currently executing node ID (null if not running) */
|
|
17
|
+
currentNodeId: string | null;
|
|
18
|
+
/** Type of currently executing node */
|
|
19
|
+
currentNodeType: string | null;
|
|
20
|
+
/** Ordered list of node IDs that have been executed */
|
|
21
|
+
pathTaken: string[];
|
|
22
|
+
/** Timestamp when execution started */
|
|
23
|
+
startedAt: number;
|
|
24
|
+
/** Timestamp of last activity */
|
|
25
|
+
lastActivityAt: number;
|
|
26
|
+
/** Overall status */
|
|
27
|
+
status: "running" | "completed" | "failed";
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* State of a single node during execution
|
|
32
|
+
*/
|
|
33
|
+
export interface NodeState {
|
|
34
|
+
/** Node ID */
|
|
35
|
+
id: string;
|
|
36
|
+
/** Node type (e.g., "Sequence", "CodeExecution") */
|
|
37
|
+
type: string;
|
|
38
|
+
/** Node name */
|
|
39
|
+
name: string;
|
|
40
|
+
/** Tree path (e.g., "/0/1/2") */
|
|
41
|
+
path: string;
|
|
42
|
+
/** Current status */
|
|
43
|
+
status: "idle" | "running" | "success" | "failure";
|
|
44
|
+
/** Last error message if failed */
|
|
45
|
+
lastError?: string;
|
|
46
|
+
/** Number of times this node has been ticked */
|
|
47
|
+
tickCount: number;
|
|
48
|
+
/** Timestamp of last tick */
|
|
49
|
+
lastTickAt?: number;
|
|
50
|
+
/** Duration of last tick in ms */
|
|
51
|
+
durationMs?: number;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Structured error with rich context for debugging
|
|
56
|
+
*/
|
|
57
|
+
export interface StructuredError {
|
|
58
|
+
/** Node ID where error occurred */
|
|
59
|
+
nodeId: string;
|
|
60
|
+
/** Node type */
|
|
61
|
+
nodeType: string;
|
|
62
|
+
/** Node name */
|
|
63
|
+
nodeName: string;
|
|
64
|
+
/** Tree path (e.g., "/0/1/2") */
|
|
65
|
+
nodePath: string;
|
|
66
|
+
/** Error message */
|
|
67
|
+
message: string;
|
|
68
|
+
/** Error stack trace (if available) */
|
|
69
|
+
stack?: string;
|
|
70
|
+
/** Timestamp when error occurred */
|
|
71
|
+
timestamp: number;
|
|
72
|
+
/** Blackboard snapshot at time of error */
|
|
73
|
+
blackboardSnapshot: Record<string, unknown>;
|
|
74
|
+
/** Input that was passed to the node */
|
|
75
|
+
nodeInput?: unknown;
|
|
76
|
+
/** Whether this error is potentially recoverable with retry */
|
|
77
|
+
recoverable: boolean;
|
|
78
|
+
/** Suggested fix based on error analysis */
|
|
79
|
+
suggestedFix?: string;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Entry in the execution timeline
|
|
84
|
+
*/
|
|
85
|
+
export interface TimelineEntry {
|
|
86
|
+
/** Node ID */
|
|
87
|
+
nodeId: string;
|
|
88
|
+
/** Node type */
|
|
89
|
+
nodeType: string;
|
|
90
|
+
/** Node name */
|
|
91
|
+
nodeName: string;
|
|
92
|
+
/** Tree path */
|
|
93
|
+
nodePath: string;
|
|
94
|
+
/** Event type */
|
|
95
|
+
event: "start" | "end" | "error";
|
|
96
|
+
/** Timestamp */
|
|
97
|
+
timestamp: number;
|
|
98
|
+
/** Final status (for end events) */
|
|
99
|
+
status?: string;
|
|
100
|
+
/** Duration in ms (for end events) */
|
|
101
|
+
durationMs?: number;
|
|
102
|
+
/** Error details (for error events) */
|
|
103
|
+
error?: {
|
|
104
|
+
message: string;
|
|
105
|
+
stack?: string;
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Extended NodeEvent with path and blackboard context
|
|
111
|
+
* Used internally by ExecutionTracker
|
|
112
|
+
*/
|
|
113
|
+
export interface ObservableNodeEvent {
|
|
114
|
+
type: string;
|
|
115
|
+
nodeId: string;
|
|
116
|
+
nodeName: string;
|
|
117
|
+
nodeType: string;
|
|
118
|
+
nodePath?: string;
|
|
119
|
+
timestamp: number;
|
|
120
|
+
data?: {
|
|
121
|
+
status?: string;
|
|
122
|
+
durationMs?: number;
|
|
123
|
+
error?: {
|
|
124
|
+
message: string;
|
|
125
|
+
stack?: string;
|
|
126
|
+
input?: unknown;
|
|
127
|
+
};
|
|
128
|
+
blackboard?: Record<string, unknown>;
|
|
129
|
+
};
|
|
130
|
+
}
|