@q1k-oss/btree-workflows 0.0.1

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