@q1k-oss/behaviour-tree-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/LICENSE +21 -0
- package/README.md +920 -0
- package/dist/index.cjs +5011 -0
- package/dist/index.d.cts +3320 -0
- package/dist/index.d.ts +3320 -0
- package/dist/index.js +4879 -0
- package/package.json +68 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,5011 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
ActionNode: () => ActionNode,
|
|
34
|
+
AlwaysCondition: () => AlwaysCondition,
|
|
35
|
+
BaseNode: () => BaseNode,
|
|
36
|
+
BehaviorTree: () => BehaviorTree,
|
|
37
|
+
CheckCondition: () => CheckCondition,
|
|
38
|
+
CodeExecution: () => CodeExecution,
|
|
39
|
+
CompositeNode: () => CompositeNode,
|
|
40
|
+
ConditionNode: () => ConditionNode,
|
|
41
|
+
Conditional: () => Conditional,
|
|
42
|
+
ConfigValidationError: () => ConfigValidationError,
|
|
43
|
+
ConfigurationError: () => ConfigurationError,
|
|
44
|
+
CounterAction: () => CounterAction,
|
|
45
|
+
DecoratorNode: () => DecoratorNode,
|
|
46
|
+
Delay: () => Delay,
|
|
47
|
+
ExecutionTracker: () => ExecutionTracker,
|
|
48
|
+
FailureNode: () => FailureNode,
|
|
49
|
+
Fallback: () => Fallback,
|
|
50
|
+
ForEach: () => ForEach,
|
|
51
|
+
ForceFailure: () => ForceFailure,
|
|
52
|
+
ForceSuccess: () => ForceSuccess,
|
|
53
|
+
GenerateFile: () => GenerateFile,
|
|
54
|
+
HttpRequest: () => HttpRequest,
|
|
55
|
+
IntegrationAction: () => IntegrationAction,
|
|
56
|
+
Invert: () => Invert,
|
|
57
|
+
KeepRunningUntilFailure: () => KeepRunningUntilFailure,
|
|
58
|
+
LogMessage: () => LogMessage,
|
|
59
|
+
MemoryDataStore: () => MemoryDataStore,
|
|
60
|
+
MemorySequence: () => MemorySequence,
|
|
61
|
+
MockAction: () => MockAction,
|
|
62
|
+
NodeEventEmitter: () => NodeEventEmitter,
|
|
63
|
+
NodeEventType: () => NodeEventType,
|
|
64
|
+
NodeStatus: () => NodeStatus,
|
|
65
|
+
Parallel: () => Parallel,
|
|
66
|
+
ParseFile: () => ParseFile,
|
|
67
|
+
Precondition: () => Precondition,
|
|
68
|
+
PrintAction: () => PrintAction,
|
|
69
|
+
PythonScript: () => PythonScript,
|
|
70
|
+
ReactiveSequence: () => ReactiveSequence,
|
|
71
|
+
Recovery: () => Recovery,
|
|
72
|
+
RegexExtract: () => RegexExtract,
|
|
73
|
+
Registry: () => Registry,
|
|
74
|
+
Repeat: () => Repeat,
|
|
75
|
+
ResumePoint: () => ResumePoint,
|
|
76
|
+
RunOnce: () => RunOnce,
|
|
77
|
+
RunningNode: () => RunningNode,
|
|
78
|
+
SchemaRegistry: () => SchemaRegistry,
|
|
79
|
+
ScopedBlackboard: () => ScopedBlackboard,
|
|
80
|
+
Selector: () => Selector,
|
|
81
|
+
SemanticValidationError: () => SemanticValidationError,
|
|
82
|
+
Sequence: () => Sequence,
|
|
83
|
+
SequenceWithMemory: () => SequenceWithMemory,
|
|
84
|
+
SoftAssert: () => SoftAssert,
|
|
85
|
+
StructureValidationError: () => StructureValidationError,
|
|
86
|
+
SubTree: () => SubTree,
|
|
87
|
+
SuccessNode: () => SuccessNode,
|
|
88
|
+
Timeout: () => Timeout,
|
|
89
|
+
ValidationError: () => ValidationError,
|
|
90
|
+
ValidationErrors: () => ValidationErrors,
|
|
91
|
+
WaitAction: () => WaitAction,
|
|
92
|
+
While: () => While,
|
|
93
|
+
YamlSyntaxError: () => YamlSyntaxError,
|
|
94
|
+
clearPieceCache: () => clearPieceCache,
|
|
95
|
+
createNodeSchema: () => createNodeSchema,
|
|
96
|
+
createObservabilitySinkHandler: () => createObservabilitySinkHandler,
|
|
97
|
+
envTokenProvider: () => envTokenProvider,
|
|
98
|
+
executePieceAction: () => executePieceAction,
|
|
99
|
+
extractVariables: () => extractVariables,
|
|
100
|
+
getTemplateIds: () => getTemplateIds,
|
|
101
|
+
hasTemplate: () => hasTemplate,
|
|
102
|
+
hasVariables: () => hasVariables,
|
|
103
|
+
isDataRef: () => isDataRef,
|
|
104
|
+
isPieceInstalled: () => isPieceInstalled,
|
|
105
|
+
listPieceActions: () => listPieceActions,
|
|
106
|
+
loadTemplate: () => loadTemplate,
|
|
107
|
+
loadTemplatesFromDirectory: () => loadTemplatesFromDirectory,
|
|
108
|
+
loadTreeFromFile: () => loadTreeFromFile,
|
|
109
|
+
loadTreeFromYaml: () => loadTreeFromYaml,
|
|
110
|
+
nodeConfigurationSchema: () => nodeConfigurationSchema,
|
|
111
|
+
parseYaml: () => parseYaml,
|
|
112
|
+
registerStandardNodes: () => registerStandardNodes,
|
|
113
|
+
resolveString: () => resolveString,
|
|
114
|
+
resolveValue: () => resolveValue,
|
|
115
|
+
safeValidateConfiguration: () => safeValidateConfiguration,
|
|
116
|
+
schemaRegistry: () => schemaRegistry,
|
|
117
|
+
semanticValidator: () => semanticValidator,
|
|
118
|
+
toYaml: () => toYaml,
|
|
119
|
+
treeDefinitionSchema: () => treeDefinitionSchema,
|
|
120
|
+
validateChildCount: () => validateChildCount,
|
|
121
|
+
validateChildCountRange: () => validateChildCountRange,
|
|
122
|
+
validateCompositeChildren: () => validateCompositeChildren,
|
|
123
|
+
validateConfiguration: () => validateConfiguration,
|
|
124
|
+
validateDecoratorChildren: () => validateDecoratorChildren,
|
|
125
|
+
validateTreeDefinition: () => validateTreeDefinition,
|
|
126
|
+
validateYaml: () => validateYaml,
|
|
127
|
+
validations: () => validations,
|
|
128
|
+
zodErrorToConfigurationError: () => zodErrorToConfigurationError
|
|
129
|
+
});
|
|
130
|
+
module.exports = __toCommonJS(index_exports);
|
|
131
|
+
|
|
132
|
+
// src/events.ts
|
|
133
|
+
var NodeEventType = /* @__PURE__ */ ((NodeEventType2) => {
|
|
134
|
+
NodeEventType2["TICK_START"] = "tick_start";
|
|
135
|
+
NodeEventType2["TICK_END"] = "tick_end";
|
|
136
|
+
NodeEventType2["STATUS_CHANGE"] = "status_change";
|
|
137
|
+
NodeEventType2["ERROR"] = "error";
|
|
138
|
+
NodeEventType2["HALT"] = "halt";
|
|
139
|
+
NodeEventType2["RESET"] = "reset";
|
|
140
|
+
NodeEventType2["LOG"] = "log";
|
|
141
|
+
return NodeEventType2;
|
|
142
|
+
})(NodeEventType || {});
|
|
143
|
+
var NodeEventEmitter = class {
|
|
144
|
+
listeners = /* @__PURE__ */ new Map();
|
|
145
|
+
allListeners = /* @__PURE__ */ new Set();
|
|
146
|
+
/**
|
|
147
|
+
* Subscribe to a specific event type
|
|
148
|
+
* @param type - The event type to listen for
|
|
149
|
+
* @param callback - Function to call when event occurs
|
|
150
|
+
*/
|
|
151
|
+
on(type, callback) {
|
|
152
|
+
if (!this.listeners.has(type)) {
|
|
153
|
+
this.listeners.set(type, /* @__PURE__ */ new Set());
|
|
154
|
+
}
|
|
155
|
+
this.listeners.get(type)?.add(callback);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Subscribe to all event types
|
|
159
|
+
* @param callback - Function to call for any event
|
|
160
|
+
*/
|
|
161
|
+
onAll(callback) {
|
|
162
|
+
this.allListeners.add(callback);
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Unsubscribe from a specific event type
|
|
166
|
+
* @param type - The event type to stop listening for
|
|
167
|
+
* @param callback - The callback to remove
|
|
168
|
+
*/
|
|
169
|
+
off(type, callback) {
|
|
170
|
+
this.listeners.get(type)?.delete(callback);
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Unsubscribe from all events
|
|
174
|
+
* @param callback - The callback to remove
|
|
175
|
+
*/
|
|
176
|
+
offAll(callback) {
|
|
177
|
+
this.allListeners.delete(callback);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Emit an event to all registered listeners
|
|
181
|
+
* Errors in callbacks are caught and logged to prevent breaking execution
|
|
182
|
+
* @param event - The event to emit
|
|
183
|
+
*/
|
|
184
|
+
emit(event) {
|
|
185
|
+
const typeListeners = this.listeners.get(event.type);
|
|
186
|
+
if (typeListeners) {
|
|
187
|
+
for (const callback of typeListeners) {
|
|
188
|
+
try {
|
|
189
|
+
callback(event);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(`Error in event callback for ${event.type}:`, error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
for (const callback of this.allListeners) {
|
|
196
|
+
try {
|
|
197
|
+
callback(event);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error("Error in event callback (onAll):", error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Remove all listeners
|
|
205
|
+
*/
|
|
206
|
+
clear() {
|
|
207
|
+
this.listeners.clear();
|
|
208
|
+
this.allListeners.clear();
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Get count of listeners for a specific type
|
|
212
|
+
* @param type - The event type to check
|
|
213
|
+
* @returns Number of listeners
|
|
214
|
+
*/
|
|
215
|
+
listenerCount(type) {
|
|
216
|
+
return this.listeners.get(type)?.size || 0;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Get count of "all events" listeners
|
|
220
|
+
* @returns Number of listeners
|
|
221
|
+
*/
|
|
222
|
+
allListenerCount() {
|
|
223
|
+
return this.allListeners.size;
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Check if there are any listeners
|
|
227
|
+
* @returns True if any listeners are registered
|
|
228
|
+
*/
|
|
229
|
+
hasListeners() {
|
|
230
|
+
return this.listeners.size > 0 || this.allListeners.size > 0;
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Returns an Effect Stream of all events
|
|
234
|
+
* REMOVED: Effect-TS integration removed in Phase 1
|
|
235
|
+
* For streaming events in Temporal workflows, use native event emitter pattern
|
|
236
|
+
*/
|
|
237
|
+
// toStream(): Stream.Stream<NodeEvent<unknown>> {
|
|
238
|
+
// return Stream.asyncPush<NodeEvent<unknown>>((emit) =>
|
|
239
|
+
// Effect.acquireRelease(
|
|
240
|
+
// Effect.sync(() => {
|
|
241
|
+
// const callback = (event: NodeEvent<unknown>) => {
|
|
242
|
+
// emit.single(event);
|
|
243
|
+
// };
|
|
244
|
+
// this.onAll(callback);
|
|
245
|
+
// return callback;
|
|
246
|
+
// }),
|
|
247
|
+
// (callback) => Effect.sync(() => this.offAll(callback)),
|
|
248
|
+
// ),
|
|
249
|
+
// );
|
|
250
|
+
// }
|
|
251
|
+
/**
|
|
252
|
+
* Returns an AsyncIterable of all events
|
|
253
|
+
* REMOVED: Effect-TS dependency removed in Phase 1
|
|
254
|
+
* Implement using native async generators if needed
|
|
255
|
+
*/
|
|
256
|
+
// toAsyncIterable(): AsyncIterable<NodeEvent<unknown>> {
|
|
257
|
+
// return Stream.toAsyncIterable(this.toStream());
|
|
258
|
+
// }
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
// src/types.ts
|
|
262
|
+
var NodeStatus = /* @__PURE__ */ ((NodeStatus2) => {
|
|
263
|
+
NodeStatus2["SUCCESS"] = "SUCCESS";
|
|
264
|
+
NodeStatus2["FAILURE"] = "FAILURE";
|
|
265
|
+
NodeStatus2["RUNNING"] = "RUNNING";
|
|
266
|
+
NodeStatus2["IDLE"] = "IDLE";
|
|
267
|
+
return NodeStatus2;
|
|
268
|
+
})(NodeStatus || {});
|
|
269
|
+
|
|
270
|
+
// src/errors.ts
|
|
271
|
+
var ConfigurationError = class _ConfigurationError extends Error {
|
|
272
|
+
constructor(message, hint) {
|
|
273
|
+
super(message);
|
|
274
|
+
this.hint = hint;
|
|
275
|
+
this.name = "ConfigurationError";
|
|
276
|
+
if (Error.captureStackTrace) {
|
|
277
|
+
Error.captureStackTrace(this, _ConfigurationError);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
isConfigurationError = true;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/utils/signal-check.ts
|
|
284
|
+
var OperationCancelledError = class _OperationCancelledError extends Error {
|
|
285
|
+
constructor(message = "Operation was cancelled") {
|
|
286
|
+
super(message);
|
|
287
|
+
this.name = "OperationCancelledError";
|
|
288
|
+
if (Error.captureStackTrace) {
|
|
289
|
+
Error.captureStackTrace(this, _OperationCancelledError);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
function checkSignal(signal, message) {
|
|
294
|
+
if (signal?.aborted) {
|
|
295
|
+
throw new OperationCancelledError(message);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// src/utils/error-handler.ts
|
|
300
|
+
function handleNodeError(error) {
|
|
301
|
+
if (error instanceof ConfigurationError) {
|
|
302
|
+
throw error;
|
|
303
|
+
}
|
|
304
|
+
if (error instanceof OperationCancelledError) {
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
return "FAILURE" /* FAILURE */;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// src/base-node.ts
|
|
311
|
+
var BaseNode = class {
|
|
312
|
+
id;
|
|
313
|
+
name;
|
|
314
|
+
type;
|
|
315
|
+
_status = "IDLE" /* IDLE */;
|
|
316
|
+
_lastError;
|
|
317
|
+
config;
|
|
318
|
+
_eventEmitter;
|
|
319
|
+
parent;
|
|
320
|
+
children;
|
|
321
|
+
constructor(config) {
|
|
322
|
+
this.id = config.id;
|
|
323
|
+
this.name = config.name || config.id;
|
|
324
|
+
this.type = this.constructor.name;
|
|
325
|
+
this.config = config;
|
|
326
|
+
}
|
|
327
|
+
halt() {
|
|
328
|
+
console.log(`[${this.type}:${this.name}] Halting...`);
|
|
329
|
+
this._eventEmitter?.emit({
|
|
330
|
+
type: "halt" /* HALT */,
|
|
331
|
+
nodeId: this.id,
|
|
332
|
+
nodeName: this.name,
|
|
333
|
+
nodeType: this.type,
|
|
334
|
+
timestamp: Date.now()
|
|
335
|
+
});
|
|
336
|
+
if (this._status === "RUNNING" /* RUNNING */) {
|
|
337
|
+
this.onHalt();
|
|
338
|
+
this._status = "IDLE" /* IDLE */;
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
reset() {
|
|
342
|
+
console.log(`[${this.type}:${this.name}] Resetting...`);
|
|
343
|
+
this._eventEmitter?.emit({
|
|
344
|
+
type: "reset" /* RESET */,
|
|
345
|
+
nodeId: this.id,
|
|
346
|
+
nodeName: this.name,
|
|
347
|
+
nodeType: this.type,
|
|
348
|
+
timestamp: Date.now()
|
|
349
|
+
});
|
|
350
|
+
this._status = "IDLE" /* IDLE */;
|
|
351
|
+
this._lastError = void 0;
|
|
352
|
+
this.onReset();
|
|
353
|
+
}
|
|
354
|
+
status() {
|
|
355
|
+
return this._status;
|
|
356
|
+
}
|
|
357
|
+
get lastError() {
|
|
358
|
+
return this._lastError;
|
|
359
|
+
}
|
|
360
|
+
providedPorts() {
|
|
361
|
+
return [];
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Hook for derived classes to implement custom halt logic
|
|
365
|
+
*/
|
|
366
|
+
onHalt() {
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Hook for derived classes to implement custom reset logic
|
|
370
|
+
*/
|
|
371
|
+
onReset() {
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Helper to get input value from blackboard
|
|
375
|
+
*/
|
|
376
|
+
getInput(context, key, defaultValue) {
|
|
377
|
+
const fullKey = this.config[key] || key;
|
|
378
|
+
return context.blackboard.getPort(fullKey, defaultValue);
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Helper to set output value to blackboard
|
|
382
|
+
*/
|
|
383
|
+
setOutput(context, key, value) {
|
|
384
|
+
const fullKey = this.config[key] || key;
|
|
385
|
+
context.blackboard.setPort(fullKey, value);
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Log helper for debugging
|
|
389
|
+
*/
|
|
390
|
+
log(message, ...args) {
|
|
391
|
+
console.log(`[${this.type}:${this.name}] ${message}`, ...args);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
var ActionNode = class extends BaseNode {
|
|
395
|
+
/**
|
|
396
|
+
* Clone this action node
|
|
397
|
+
* Leaf nodes have no children to clone
|
|
398
|
+
*/
|
|
399
|
+
clone() {
|
|
400
|
+
const ClonedClass = this.constructor;
|
|
401
|
+
return new ClonedClass({ ...this.config });
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Tick with resumable execution support for leaf nodes
|
|
405
|
+
* Uses async/await for Promise-based async/RUNNING semantics
|
|
406
|
+
* All errors are caught and converted to NodeStatus.FAILURE
|
|
407
|
+
*/
|
|
408
|
+
async tick(context) {
|
|
409
|
+
try {
|
|
410
|
+
this._eventEmitter = context.eventEmitter;
|
|
411
|
+
context.eventEmitter?.emit({
|
|
412
|
+
type: "tick_start" /* TICK_START */,
|
|
413
|
+
nodeId: this.id,
|
|
414
|
+
nodeName: this.name,
|
|
415
|
+
nodeType: this.type,
|
|
416
|
+
timestamp: Date.now()
|
|
417
|
+
});
|
|
418
|
+
const status = await this.executeTick(context);
|
|
419
|
+
this._status = status;
|
|
420
|
+
context.eventEmitter?.emit({
|
|
421
|
+
type: "tick_end" /* TICK_END */,
|
|
422
|
+
nodeId: this.id,
|
|
423
|
+
nodeName: this.name,
|
|
424
|
+
nodeType: this.type,
|
|
425
|
+
timestamp: Date.now(),
|
|
426
|
+
data: { status }
|
|
427
|
+
});
|
|
428
|
+
return status;
|
|
429
|
+
} catch (error) {
|
|
430
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
431
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
432
|
+
this._lastError = errorMessage;
|
|
433
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
434
|
+
context.eventEmitter?.emit({
|
|
435
|
+
type: "error" /* ERROR */,
|
|
436
|
+
nodeId: this.id,
|
|
437
|
+
nodeName: this.name,
|
|
438
|
+
nodeType: this.type,
|
|
439
|
+
timestamp: Date.now(),
|
|
440
|
+
data: {
|
|
441
|
+
error: {
|
|
442
|
+
message: errorMessage,
|
|
443
|
+
stack: errorStack
|
|
444
|
+
},
|
|
445
|
+
blackboard: context.blackboard?.toJSON?.() ?? {}
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
context.eventEmitter?.emit({
|
|
449
|
+
type: "tick_end" /* TICK_END */,
|
|
450
|
+
nodeId: this.id,
|
|
451
|
+
nodeName: this.name,
|
|
452
|
+
nodeType: this.type,
|
|
453
|
+
timestamp: Date.now(),
|
|
454
|
+
data: { status: "FAILURE" /* FAILURE */ }
|
|
455
|
+
});
|
|
456
|
+
return handleNodeError(error);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
};
|
|
460
|
+
var ConditionNode = class extends BaseNode {
|
|
461
|
+
/**
|
|
462
|
+
* Clone this condition node
|
|
463
|
+
* Leaf nodes have no children to clone
|
|
464
|
+
*/
|
|
465
|
+
clone() {
|
|
466
|
+
const ClonedClass = this.constructor;
|
|
467
|
+
return new ClonedClass({ ...this.config });
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Tick with resumable execution support for leaf nodes
|
|
471
|
+
* Uses async/await for Promise-based async/RUNNING semantics
|
|
472
|
+
* All errors are caught and converted to NodeStatus.FAILURE
|
|
473
|
+
*/
|
|
474
|
+
async tick(context) {
|
|
475
|
+
try {
|
|
476
|
+
this._eventEmitter = context.eventEmitter;
|
|
477
|
+
context.eventEmitter?.emit({
|
|
478
|
+
type: "tick_start" /* TICK_START */,
|
|
479
|
+
nodeId: this.id,
|
|
480
|
+
nodeName: this.name,
|
|
481
|
+
nodeType: this.type,
|
|
482
|
+
timestamp: Date.now()
|
|
483
|
+
});
|
|
484
|
+
const status = await this.executeTick(context);
|
|
485
|
+
this._status = status;
|
|
486
|
+
context.eventEmitter?.emit({
|
|
487
|
+
type: "tick_end" /* TICK_END */,
|
|
488
|
+
nodeId: this.id,
|
|
489
|
+
nodeName: this.name,
|
|
490
|
+
nodeType: this.type,
|
|
491
|
+
timestamp: Date.now(),
|
|
492
|
+
data: { status }
|
|
493
|
+
});
|
|
494
|
+
return status;
|
|
495
|
+
} catch (error) {
|
|
496
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
497
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
498
|
+
this._lastError = errorMessage;
|
|
499
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
500
|
+
context.eventEmitter?.emit({
|
|
501
|
+
type: "error" /* ERROR */,
|
|
502
|
+
nodeId: this.id,
|
|
503
|
+
nodeName: this.name,
|
|
504
|
+
nodeType: this.type,
|
|
505
|
+
timestamp: Date.now(),
|
|
506
|
+
data: {
|
|
507
|
+
error: {
|
|
508
|
+
message: errorMessage,
|
|
509
|
+
stack: errorStack
|
|
510
|
+
},
|
|
511
|
+
blackboard: context.blackboard?.toJSON?.() ?? {}
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
context.eventEmitter?.emit({
|
|
515
|
+
type: "tick_end" /* TICK_END */,
|
|
516
|
+
nodeId: this.id,
|
|
517
|
+
nodeName: this.name,
|
|
518
|
+
nodeType: this.type,
|
|
519
|
+
timestamp: Date.now(),
|
|
520
|
+
data: { status: "FAILURE" /* FAILURE */ }
|
|
521
|
+
});
|
|
522
|
+
return handleNodeError(error);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
var DecoratorNode = class extends BaseNode {
|
|
527
|
+
child;
|
|
528
|
+
/**
|
|
529
|
+
* Clone this decorator node including its child
|
|
530
|
+
*/
|
|
531
|
+
clone() {
|
|
532
|
+
const ClonedClass = this.constructor;
|
|
533
|
+
const cloned = new ClonedClass({ ...this.config });
|
|
534
|
+
if (this.child) {
|
|
535
|
+
cloned.setChild(this.child.clone());
|
|
536
|
+
}
|
|
537
|
+
return cloned;
|
|
538
|
+
}
|
|
539
|
+
/**
|
|
540
|
+
* Tick with resumable execution support - decorators can be resume points
|
|
541
|
+
* Uses async/await for Promise-based async/RUNNING semantics
|
|
542
|
+
* All errors are caught and converted to NodeStatus.FAILURE
|
|
543
|
+
*/
|
|
544
|
+
async tick(context) {
|
|
545
|
+
try {
|
|
546
|
+
context.eventEmitter?.emit({
|
|
547
|
+
type: "tick_start" /* TICK_START */,
|
|
548
|
+
nodeId: this.id,
|
|
549
|
+
nodeName: this.name,
|
|
550
|
+
nodeType: this.type,
|
|
551
|
+
timestamp: Date.now()
|
|
552
|
+
});
|
|
553
|
+
const status = await this.executeTick(context);
|
|
554
|
+
context.eventEmitter?.emit({
|
|
555
|
+
type: "tick_end" /* TICK_END */,
|
|
556
|
+
nodeId: this.id,
|
|
557
|
+
nodeName: this.name,
|
|
558
|
+
nodeType: this.type,
|
|
559
|
+
timestamp: Date.now(),
|
|
560
|
+
data: { status }
|
|
561
|
+
});
|
|
562
|
+
return status;
|
|
563
|
+
} catch (error) {
|
|
564
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
565
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
566
|
+
this._lastError = errorMessage;
|
|
567
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
568
|
+
context.eventEmitter?.emit({
|
|
569
|
+
type: "error" /* ERROR */,
|
|
570
|
+
nodeId: this.id,
|
|
571
|
+
nodeName: this.name,
|
|
572
|
+
nodeType: this.type,
|
|
573
|
+
timestamp: Date.now(),
|
|
574
|
+
data: {
|
|
575
|
+
error: {
|
|
576
|
+
message: errorMessage,
|
|
577
|
+
stack: errorStack
|
|
578
|
+
},
|
|
579
|
+
blackboard: context.blackboard?.toJSON?.() ?? {}
|
|
580
|
+
}
|
|
581
|
+
});
|
|
582
|
+
context.eventEmitter?.emit({
|
|
583
|
+
type: "tick_end" /* TICK_END */,
|
|
584
|
+
nodeId: this.id,
|
|
585
|
+
nodeName: this.name,
|
|
586
|
+
nodeType: this.type,
|
|
587
|
+
timestamp: Date.now(),
|
|
588
|
+
data: { status: "FAILURE" /* FAILURE */ }
|
|
589
|
+
});
|
|
590
|
+
return handleNodeError(error);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
setChild(child) {
|
|
594
|
+
if (!child) {
|
|
595
|
+
throw new Error("Cannot set undefined child on decorator node");
|
|
596
|
+
}
|
|
597
|
+
this.child = child;
|
|
598
|
+
this.children = [child];
|
|
599
|
+
child.parent = this;
|
|
600
|
+
}
|
|
601
|
+
halt() {
|
|
602
|
+
super.halt();
|
|
603
|
+
if (this.child && this.child.status() === "RUNNING" /* RUNNING */) {
|
|
604
|
+
this.child.halt();
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
reset() {
|
|
608
|
+
super.reset();
|
|
609
|
+
if (this.child) {
|
|
610
|
+
this.child.reset();
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
var CompositeNode = class extends BaseNode {
|
|
615
|
+
_children = [];
|
|
616
|
+
/**
|
|
617
|
+
* Clone this composite node including all children
|
|
618
|
+
*/
|
|
619
|
+
clone() {
|
|
620
|
+
const ClonedClass = this.constructor;
|
|
621
|
+
const cloned = new ClonedClass({ ...this.config });
|
|
622
|
+
const clonedChildren = this._children.map((child) => child.clone());
|
|
623
|
+
cloned.addChildren(clonedChildren);
|
|
624
|
+
return cloned;
|
|
625
|
+
}
|
|
626
|
+
/**
|
|
627
|
+
* Tick with resumable execution support - composites can be resume points
|
|
628
|
+
* Uses async/await for Promise-based async/RUNNING semantics
|
|
629
|
+
* All errors are caught and converted to NodeStatus.FAILURE
|
|
630
|
+
*/
|
|
631
|
+
async tick(context) {
|
|
632
|
+
try {
|
|
633
|
+
context.eventEmitter?.emit({
|
|
634
|
+
type: "tick_start" /* TICK_START */,
|
|
635
|
+
nodeId: this.id,
|
|
636
|
+
nodeName: this.name,
|
|
637
|
+
nodeType: this.type,
|
|
638
|
+
timestamp: Date.now()
|
|
639
|
+
});
|
|
640
|
+
const status = await this.executeTick(context);
|
|
641
|
+
context.eventEmitter?.emit({
|
|
642
|
+
type: "tick_end" /* TICK_END */,
|
|
643
|
+
nodeId: this.id,
|
|
644
|
+
nodeName: this.name,
|
|
645
|
+
nodeType: this.type,
|
|
646
|
+
timestamp: Date.now(),
|
|
647
|
+
data: { status }
|
|
648
|
+
});
|
|
649
|
+
return status;
|
|
650
|
+
} catch (error) {
|
|
651
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
652
|
+
const errorStack = error instanceof Error ? error.stack : void 0;
|
|
653
|
+
this._lastError = errorMessage;
|
|
654
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
655
|
+
context.eventEmitter?.emit({
|
|
656
|
+
type: "error" /* ERROR */,
|
|
657
|
+
nodeId: this.id,
|
|
658
|
+
nodeName: this.name,
|
|
659
|
+
nodeType: this.type,
|
|
660
|
+
timestamp: Date.now(),
|
|
661
|
+
data: {
|
|
662
|
+
error: {
|
|
663
|
+
message: errorMessage,
|
|
664
|
+
stack: errorStack
|
|
665
|
+
},
|
|
666
|
+
blackboard: context.blackboard?.toJSON?.() ?? {}
|
|
667
|
+
}
|
|
668
|
+
});
|
|
669
|
+
context.eventEmitter?.emit({
|
|
670
|
+
type: "tick_end" /* TICK_END */,
|
|
671
|
+
nodeId: this.id,
|
|
672
|
+
nodeName: this.name,
|
|
673
|
+
nodeType: this.type,
|
|
674
|
+
timestamp: Date.now(),
|
|
675
|
+
data: { status: "FAILURE" /* FAILURE */ }
|
|
676
|
+
});
|
|
677
|
+
return handleNodeError(error);
|
|
678
|
+
}
|
|
679
|
+
}
|
|
680
|
+
addChild(child) {
|
|
681
|
+
if (!child) {
|
|
682
|
+
throw new Error("Cannot add undefined child to composite node");
|
|
683
|
+
}
|
|
684
|
+
this._children.push(child);
|
|
685
|
+
this.children = this._children;
|
|
686
|
+
child.parent = this;
|
|
687
|
+
}
|
|
688
|
+
addChildren(children) {
|
|
689
|
+
children.forEach((child) => {
|
|
690
|
+
this.addChild(child);
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
halt() {
|
|
694
|
+
super.halt();
|
|
695
|
+
for (const child of this._children) {
|
|
696
|
+
if (child.status() === "RUNNING" /* RUNNING */) {
|
|
697
|
+
child.halt();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
reset() {
|
|
702
|
+
super.reset();
|
|
703
|
+
for (const child of this._children) {
|
|
704
|
+
child.reset();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
haltChildren(startIndex = 0) {
|
|
708
|
+
for (let i = startIndex; i < this._children.length; i++) {
|
|
709
|
+
const child = this._children[i];
|
|
710
|
+
if (child && child.status() === "RUNNING" /* RUNNING */) {
|
|
711
|
+
child.halt();
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
};
|
|
716
|
+
|
|
717
|
+
// src/blackboard.ts
|
|
718
|
+
var ScopedBlackboard = class _ScopedBlackboard {
|
|
719
|
+
data = {};
|
|
720
|
+
parent = null;
|
|
721
|
+
scopeName;
|
|
722
|
+
childScopes = /* @__PURE__ */ new Map();
|
|
723
|
+
constructor(scopeName = "root", parent = null) {
|
|
724
|
+
this.scopeName = scopeName;
|
|
725
|
+
this.parent = parent;
|
|
726
|
+
}
|
|
727
|
+
get(key) {
|
|
728
|
+
if (key in this.data) {
|
|
729
|
+
return this.data[key];
|
|
730
|
+
}
|
|
731
|
+
if (this.parent) {
|
|
732
|
+
return this.parent.get(key);
|
|
733
|
+
}
|
|
734
|
+
return void 0;
|
|
735
|
+
}
|
|
736
|
+
set(key, value) {
|
|
737
|
+
this.data[key] = value;
|
|
738
|
+
}
|
|
739
|
+
has(key) {
|
|
740
|
+
if (key in this.data) {
|
|
741
|
+
return true;
|
|
742
|
+
}
|
|
743
|
+
if (this.parent) {
|
|
744
|
+
return this.parent.has(key);
|
|
745
|
+
}
|
|
746
|
+
return false;
|
|
747
|
+
}
|
|
748
|
+
delete(key) {
|
|
749
|
+
delete this.data[key];
|
|
750
|
+
}
|
|
751
|
+
clear() {
|
|
752
|
+
this.data = {};
|
|
753
|
+
this.childScopes.clear();
|
|
754
|
+
}
|
|
755
|
+
createScope(name) {
|
|
756
|
+
if (this.childScopes.has(name)) {
|
|
757
|
+
const scope = this.childScopes.get(name);
|
|
758
|
+
if (!scope) {
|
|
759
|
+
throw new Error(`Scope ${name} not found`);
|
|
760
|
+
}
|
|
761
|
+
return scope;
|
|
762
|
+
}
|
|
763
|
+
const childScope = new _ScopedBlackboard(name, this);
|
|
764
|
+
this.childScopes.set(name, childScope);
|
|
765
|
+
return childScope;
|
|
766
|
+
}
|
|
767
|
+
getParentScope() {
|
|
768
|
+
return this.parent;
|
|
769
|
+
}
|
|
770
|
+
getScopeName() {
|
|
771
|
+
return this.scopeName;
|
|
772
|
+
}
|
|
773
|
+
getPort(key, defaultValue) {
|
|
774
|
+
const value = this.get(key);
|
|
775
|
+
if (value === void 0 && defaultValue !== void 0) {
|
|
776
|
+
return defaultValue;
|
|
777
|
+
}
|
|
778
|
+
return value;
|
|
779
|
+
}
|
|
780
|
+
setPort(key, value) {
|
|
781
|
+
this.set(key, value);
|
|
782
|
+
}
|
|
783
|
+
keys() {
|
|
784
|
+
const localKeys = Object.keys(this.data);
|
|
785
|
+
const parentKeys = this.parent ? this.parent.keys() : [];
|
|
786
|
+
const allKeys = /* @__PURE__ */ new Set([...localKeys, ...parentKeys]);
|
|
787
|
+
return Array.from(allKeys);
|
|
788
|
+
}
|
|
789
|
+
entries() {
|
|
790
|
+
const result = [];
|
|
791
|
+
const processedKeys = /* @__PURE__ */ new Set();
|
|
792
|
+
for (const [key, value] of Object.entries(this.data)) {
|
|
793
|
+
result.push([key, value]);
|
|
794
|
+
processedKeys.add(key);
|
|
795
|
+
}
|
|
796
|
+
if (this.parent) {
|
|
797
|
+
for (const [key, value] of this.parent.entries()) {
|
|
798
|
+
if (!processedKeys.has(key)) {
|
|
799
|
+
result.push([key, value]);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
return result;
|
|
804
|
+
}
|
|
805
|
+
toJSON() {
|
|
806
|
+
return { ...this.data };
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* Create a deep clone of this blackboard for snapshots
|
|
810
|
+
* Uses structured cloning for deep copy
|
|
811
|
+
*/
|
|
812
|
+
clone() {
|
|
813
|
+
const cloned = new _ScopedBlackboard(this.scopeName, this.parent);
|
|
814
|
+
cloned.data = structuredClone(this.data);
|
|
815
|
+
this.childScopes.forEach((childScope, name) => {
|
|
816
|
+
cloned.childScopes.set(name, childScope.clone());
|
|
817
|
+
});
|
|
818
|
+
return cloned;
|
|
819
|
+
}
|
|
820
|
+
/**
|
|
821
|
+
* Get the full scope path (e.g., "root.child.grandchild")
|
|
822
|
+
*/
|
|
823
|
+
getFullScopePath() {
|
|
824
|
+
const path2 = [this.scopeName];
|
|
825
|
+
let current = this.parent;
|
|
826
|
+
while (current) {
|
|
827
|
+
path2.unshift(current.scopeName);
|
|
828
|
+
current = current.parent;
|
|
829
|
+
}
|
|
830
|
+
return path2.join(".");
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Debug utility to print the blackboard hierarchy
|
|
834
|
+
*/
|
|
835
|
+
debug(indent = 0) {
|
|
836
|
+
const prefix = " ".repeat(indent);
|
|
837
|
+
console.log(`${prefix}Scope: ${this.scopeName}`);
|
|
838
|
+
for (const [key, value] of Object.entries(this.data)) {
|
|
839
|
+
console.log(`${prefix} ${key}: ${JSON.stringify(value)}`);
|
|
840
|
+
}
|
|
841
|
+
for (const [_name, childScope] of this.childScopes) {
|
|
842
|
+
childScope.debug(indent + 1);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
};
|
|
846
|
+
|
|
847
|
+
// src/behavior-tree.ts
|
|
848
|
+
var BehaviorTree = class _BehaviorTree {
|
|
849
|
+
root;
|
|
850
|
+
pathIndex = /* @__PURE__ */ new Map();
|
|
851
|
+
idIndex = /* @__PURE__ */ new Map();
|
|
852
|
+
constructor(root) {
|
|
853
|
+
this.root = root;
|
|
854
|
+
this.buildNodeIndex();
|
|
855
|
+
}
|
|
856
|
+
/**
|
|
857
|
+
* Parse a path with tree ID prefix.
|
|
858
|
+
* Format: #TreeID/node/path
|
|
859
|
+
*
|
|
860
|
+
* @param fullPath Path string starting with # followed by tree ID
|
|
861
|
+
* @returns Object with treeId and nodePath
|
|
862
|
+
* @throws Error if path format is invalid
|
|
863
|
+
*
|
|
864
|
+
* Valid examples:
|
|
865
|
+
* - "#SimpleTest/0/1" -> { treeId: "SimpleTest", nodePath: "/0/1" }
|
|
866
|
+
* - "#MyTree/" -> { treeId: "MyTree", nodePath: "/" }
|
|
867
|
+
* - "#OnlyTree" -> { treeId: "OnlyTree", nodePath: "/" }
|
|
868
|
+
*
|
|
869
|
+
* Invalid examples:
|
|
870
|
+
* - "/0/1" - missing #TreeID prefix
|
|
871
|
+
* - "#/0/1" - empty tree ID
|
|
872
|
+
* - "#" - empty tree ID
|
|
873
|
+
*/
|
|
874
|
+
static parsePathWithTreeId(fullPath) {
|
|
875
|
+
if (!fullPath.startsWith("#")) {
|
|
876
|
+
throw new Error(
|
|
877
|
+
`Invalid path format: '${fullPath}'. Path must start with #TreeID (e.g., #SimpleTest/0/1)`
|
|
878
|
+
);
|
|
879
|
+
}
|
|
880
|
+
const slashIndex = fullPath.indexOf("/");
|
|
881
|
+
let treeId;
|
|
882
|
+
let nodePath;
|
|
883
|
+
if (slashIndex === -1) {
|
|
884
|
+
treeId = fullPath.slice(1);
|
|
885
|
+
nodePath = "/";
|
|
886
|
+
} else {
|
|
887
|
+
treeId = fullPath.slice(1, slashIndex);
|
|
888
|
+
nodePath = fullPath.slice(slashIndex);
|
|
889
|
+
}
|
|
890
|
+
if (!treeId || treeId.trim() === "") {
|
|
891
|
+
throw new Error(
|
|
892
|
+
`Invalid path: tree ID cannot be empty in '${fullPath}'. Expected format: #TreeID/path`
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
if (!nodePath.startsWith("/")) {
|
|
896
|
+
throw new Error(
|
|
897
|
+
`Invalid path: node path must start with '/' in '${fullPath}'`
|
|
898
|
+
);
|
|
899
|
+
}
|
|
900
|
+
return { treeId, nodePath };
|
|
901
|
+
}
|
|
902
|
+
/**
|
|
903
|
+
* Get the root node of the tree
|
|
904
|
+
*/
|
|
905
|
+
getRoot() {
|
|
906
|
+
return this.root;
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Find a node by its path
|
|
910
|
+
* Path format: / for root, /0 for first child, /0/1 for second child of first child
|
|
911
|
+
*/
|
|
912
|
+
findNodeByPath(path2) {
|
|
913
|
+
return this.pathIndex.get(path2) || null;
|
|
914
|
+
}
|
|
915
|
+
/**
|
|
916
|
+
* Find a node by its ID (if it has one)
|
|
917
|
+
* Convenience method for nodes with explicit IDs
|
|
918
|
+
*/
|
|
919
|
+
findNodeById(nodeId) {
|
|
920
|
+
return this.idIndex.get(nodeId) || null;
|
|
921
|
+
}
|
|
922
|
+
/**
|
|
923
|
+
* Get the path for a given node
|
|
924
|
+
* Returns null if the node is not in the tree
|
|
925
|
+
*/
|
|
926
|
+
getNodePath(targetNode) {
|
|
927
|
+
for (const [path2, node] of this.pathIndex.entries()) {
|
|
928
|
+
if (node === targetNode) {
|
|
929
|
+
return path2;
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
return null;
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Get the path for a node by its ID
|
|
936
|
+
* More reliable than instance-based lookup
|
|
937
|
+
* Returns null if the node is not in the tree
|
|
938
|
+
*/
|
|
939
|
+
getNodePathById(nodeId) {
|
|
940
|
+
const node = this.findNodeById(nodeId);
|
|
941
|
+
if (!node) return null;
|
|
942
|
+
for (const [path2, indexedNode] of this.pathIndex.entries()) {
|
|
943
|
+
if (indexedNode.id === nodeId) {
|
|
944
|
+
return path2;
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
return null;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Clone this BehaviorTree (deep clones the underlying TreeNode)
|
|
951
|
+
*/
|
|
952
|
+
clone() {
|
|
953
|
+
const clonedRoot = this.root.clone();
|
|
954
|
+
return new _BehaviorTree(clonedRoot);
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Convert this BehaviorTree to a Temporal workflow function
|
|
958
|
+
* Returns a workflow function that can be registered with Temporal
|
|
959
|
+
*
|
|
960
|
+
* @returns A Temporal workflow function that executes this behavior tree
|
|
961
|
+
*
|
|
962
|
+
* @example
|
|
963
|
+
* ```typescript
|
|
964
|
+
* import { BehaviorTree } from '@wayfarer-ai/btree';
|
|
965
|
+
* import { Sequence } from '@wayfarer-ai/btree';
|
|
966
|
+
* import { PrintAction } from '@wayfarer-ai/btree';
|
|
967
|
+
*
|
|
968
|
+
* const root = new Sequence({ id: 'root' });
|
|
969
|
+
* root.addChild(new PrintAction({ id: 'step1', message: 'Hello' }));
|
|
970
|
+
* root.addChild(new PrintAction({ id: 'step2', message: 'World' }));
|
|
971
|
+
*
|
|
972
|
+
* const tree = new BehaviorTree(root);
|
|
973
|
+
* const workflow = tree.toWorkflow();
|
|
974
|
+
*
|
|
975
|
+
* // Register with Temporal worker
|
|
976
|
+
* // Worker.create({ workflows: { myWorkflow: workflow }, ... })
|
|
977
|
+
* ```
|
|
978
|
+
*/
|
|
979
|
+
toWorkflow() {
|
|
980
|
+
const root = this.root;
|
|
981
|
+
return async function behaviorTreeWorkflow(args) {
|
|
982
|
+
const context = {
|
|
983
|
+
blackboard: new ScopedBlackboard(),
|
|
984
|
+
treeRegistry: args.treeRegistry,
|
|
985
|
+
timestamp: Date.now(),
|
|
986
|
+
sessionId: args.sessionId || `session-${Date.now()}`,
|
|
987
|
+
// Store input immutably for ${input.key} resolution
|
|
988
|
+
input: args.input ? Object.freeze({ ...args.input }) : void 0,
|
|
989
|
+
// Pass activities for I/O operations (deterministic Temporal execution)
|
|
990
|
+
activities: args.activities,
|
|
991
|
+
// Pass tokenProvider for IntegrationAction authentication
|
|
992
|
+
tokenProvider: args.tokenProvider
|
|
993
|
+
};
|
|
994
|
+
if (args.input) {
|
|
995
|
+
for (const [key, value] of Object.entries(args.input)) {
|
|
996
|
+
context.blackboard.set(key, value);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
const status = await root.tick(context);
|
|
1000
|
+
return {
|
|
1001
|
+
status,
|
|
1002
|
+
output: context.blackboard.toJSON()
|
|
1003
|
+
};
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Replace a node at the given path with a new node
|
|
1008
|
+
* Updates parent-child relationships and rebuilds the index
|
|
1009
|
+
*/
|
|
1010
|
+
replaceNodeAtPath(path2, newNode) {
|
|
1011
|
+
const oldNode = this.findNodeByPath(path2);
|
|
1012
|
+
if (!oldNode) {
|
|
1013
|
+
throw new Error(`Node not found at path: ${path2}`);
|
|
1014
|
+
}
|
|
1015
|
+
if (path2 === "/") {
|
|
1016
|
+
this.root = newNode;
|
|
1017
|
+
newNode.parent = void 0;
|
|
1018
|
+
} else {
|
|
1019
|
+
const pathParts = path2.split("/").filter((p) => p);
|
|
1020
|
+
const lastPart = pathParts[pathParts.length - 1];
|
|
1021
|
+
if (!lastPart) {
|
|
1022
|
+
throw new Error(`Invalid path format: ${path2}`);
|
|
1023
|
+
}
|
|
1024
|
+
const childIndex = parseInt(lastPart, 10);
|
|
1025
|
+
const parent = oldNode.parent;
|
|
1026
|
+
if (!parent || !parent.children) {
|
|
1027
|
+
throw new Error(`Cannot replace node: invalid parent at path ${path2}`);
|
|
1028
|
+
}
|
|
1029
|
+
parent.children[childIndex] = newNode;
|
|
1030
|
+
newNode.parent = parent;
|
|
1031
|
+
}
|
|
1032
|
+
this.buildNodeIndex();
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Build the node index with path-based and ID-based lookups
|
|
1036
|
+
*/
|
|
1037
|
+
buildNodeIndex() {
|
|
1038
|
+
this.pathIndex.clear();
|
|
1039
|
+
this.idIndex.clear();
|
|
1040
|
+
const indexNode = (node, path2) => {
|
|
1041
|
+
this.pathIndex.set(path2, node);
|
|
1042
|
+
if (node.id) {
|
|
1043
|
+
this.idIndex.set(node.id, node);
|
|
1044
|
+
}
|
|
1045
|
+
if (node.children) {
|
|
1046
|
+
node.children.forEach((child, index) => {
|
|
1047
|
+
const childPath = path2 === "/" ? `/${index}` : `${path2}/${index}`;
|
|
1048
|
+
indexNode(child, childPath);
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
indexNode(this.root, "/");
|
|
1053
|
+
}
|
|
1054
|
+
};
|
|
1055
|
+
|
|
1056
|
+
// src/composites/conditional.ts
|
|
1057
|
+
var Conditional = class extends CompositeNode {
|
|
1058
|
+
condition;
|
|
1059
|
+
thenBranch;
|
|
1060
|
+
elseBranch;
|
|
1061
|
+
conditionEvaluated = false;
|
|
1062
|
+
selectedBranch;
|
|
1063
|
+
/**
|
|
1064
|
+
* Override addChild to enforce conditional structure
|
|
1065
|
+
*/
|
|
1066
|
+
addChild(child) {
|
|
1067
|
+
if (!this.condition) {
|
|
1068
|
+
this.condition = child;
|
|
1069
|
+
this._children.push(child);
|
|
1070
|
+
child.parent = this;
|
|
1071
|
+
} else if (!this.thenBranch) {
|
|
1072
|
+
this.thenBranch = child;
|
|
1073
|
+
this._children.push(child);
|
|
1074
|
+
child.parent = this;
|
|
1075
|
+
} else if (!this.elseBranch) {
|
|
1076
|
+
this.elseBranch = child;
|
|
1077
|
+
this._children.push(child);
|
|
1078
|
+
child.parent = this;
|
|
1079
|
+
} else {
|
|
1080
|
+
throw new ConfigurationError(
|
|
1081
|
+
"Conditional can have maximum 3 children (condition, then, else)"
|
|
1082
|
+
);
|
|
1083
|
+
}
|
|
1084
|
+
}
|
|
1085
|
+
async executeTick(context) {
|
|
1086
|
+
checkSignal(context.signal);
|
|
1087
|
+
if (!this.condition) {
|
|
1088
|
+
throw new Error("Conditional requires at least a condition child");
|
|
1089
|
+
}
|
|
1090
|
+
if (!this.thenBranch) {
|
|
1091
|
+
throw new Error(
|
|
1092
|
+
"Conditional requires at least condition and then branch"
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
if (!this.conditionEvaluated) {
|
|
1096
|
+
this.log("Evaluating condition");
|
|
1097
|
+
const conditionStatus = await this.condition.tick(context);
|
|
1098
|
+
switch (conditionStatus) {
|
|
1099
|
+
case "SUCCESS" /* SUCCESS */:
|
|
1100
|
+
this.log("Condition succeeded - will execute then branch");
|
|
1101
|
+
this.selectedBranch = this.thenBranch;
|
|
1102
|
+
this.conditionEvaluated = true;
|
|
1103
|
+
break;
|
|
1104
|
+
case "FAILURE" /* FAILURE */:
|
|
1105
|
+
if (this.elseBranch) {
|
|
1106
|
+
this.log("Condition failed - will execute else branch");
|
|
1107
|
+
this.selectedBranch = this.elseBranch;
|
|
1108
|
+
this.conditionEvaluated = true;
|
|
1109
|
+
} else {
|
|
1110
|
+
this.log("Condition failed - no else branch, returning FAILURE");
|
|
1111
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
1112
|
+
return "FAILURE" /* FAILURE */;
|
|
1113
|
+
}
|
|
1114
|
+
break;
|
|
1115
|
+
case "RUNNING" /* RUNNING */:
|
|
1116
|
+
this.log("Condition is running");
|
|
1117
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
1118
|
+
return "RUNNING" /* RUNNING */;
|
|
1119
|
+
default:
|
|
1120
|
+
throw new Error(
|
|
1121
|
+
`Unexpected status from condition: ${conditionStatus}`
|
|
1122
|
+
);
|
|
1123
|
+
}
|
|
1124
|
+
} else {
|
|
1125
|
+
this.log("Condition already evaluated - continuing branch execution");
|
|
1126
|
+
}
|
|
1127
|
+
if (!this.selectedBranch) {
|
|
1128
|
+
throw new Error("No branch selected for execution");
|
|
1129
|
+
}
|
|
1130
|
+
const branchStatus = await this.selectedBranch.tick(context);
|
|
1131
|
+
this._status = branchStatus;
|
|
1132
|
+
if (branchStatus !== "RUNNING" /* RUNNING */) {
|
|
1133
|
+
this.log("Branch completed - resetting condition check flag");
|
|
1134
|
+
this.conditionEvaluated = false;
|
|
1135
|
+
this.selectedBranch = void 0;
|
|
1136
|
+
}
|
|
1137
|
+
return branchStatus;
|
|
1138
|
+
}
|
|
1139
|
+
onHalt() {
|
|
1140
|
+
this.log("Halting - resetting condition check flag");
|
|
1141
|
+
this.conditionEvaluated = false;
|
|
1142
|
+
this.selectedBranch = void 0;
|
|
1143
|
+
super.onHalt();
|
|
1144
|
+
}
|
|
1145
|
+
onReset() {
|
|
1146
|
+
this.log("Resetting - clearing condition check flag");
|
|
1147
|
+
this.conditionEvaluated = false;
|
|
1148
|
+
this.selectedBranch = void 0;
|
|
1149
|
+
}
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
// src/composites/for-each.ts
|
|
1153
|
+
var ForEach = class extends CompositeNode {
|
|
1154
|
+
collectionKey;
|
|
1155
|
+
itemKey;
|
|
1156
|
+
indexKey;
|
|
1157
|
+
currentIndex = 0;
|
|
1158
|
+
constructor(config) {
|
|
1159
|
+
super(config);
|
|
1160
|
+
this.collectionKey = config.collectionKey;
|
|
1161
|
+
this.itemKey = config.itemKey;
|
|
1162
|
+
this.indexKey = config.indexKey;
|
|
1163
|
+
}
|
|
1164
|
+
async executeTick(context) {
|
|
1165
|
+
if (this._children.length === 0) {
|
|
1166
|
+
throw new ConfigurationError(
|
|
1167
|
+
"ForEach requires at least one child (body)"
|
|
1168
|
+
);
|
|
1169
|
+
}
|
|
1170
|
+
const body = this._children[0];
|
|
1171
|
+
if (!body) {
|
|
1172
|
+
throw new ConfigurationError(
|
|
1173
|
+
"ForEach requires at least one child (body)"
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
const collection = context.blackboard.get(this.collectionKey);
|
|
1177
|
+
if (!collection) {
|
|
1178
|
+
this.log(`Collection '${this.collectionKey}' not found in blackboard`);
|
|
1179
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
1180
|
+
return "FAILURE" /* FAILURE */;
|
|
1181
|
+
}
|
|
1182
|
+
if (!Array.isArray(collection)) {
|
|
1183
|
+
throw new Error(`Collection '${this.collectionKey}' is not an array`);
|
|
1184
|
+
}
|
|
1185
|
+
if (collection.length === 0) {
|
|
1186
|
+
this.log("Collection is empty - returning SUCCESS");
|
|
1187
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
1188
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1189
|
+
}
|
|
1190
|
+
this.log(
|
|
1191
|
+
`Iterating over collection (${collection.length} items), starting at index ${this.currentIndex}`
|
|
1192
|
+
);
|
|
1193
|
+
while (this.currentIndex < collection.length) {
|
|
1194
|
+
checkSignal(context.signal);
|
|
1195
|
+
const item = collection[this.currentIndex];
|
|
1196
|
+
context.blackboard.set(this.itemKey, item);
|
|
1197
|
+
if (this.indexKey) {
|
|
1198
|
+
context.blackboard.set(this.indexKey, this.currentIndex);
|
|
1199
|
+
}
|
|
1200
|
+
this.log(
|
|
1201
|
+
`Processing item ${this.currentIndex}: ${JSON.stringify(item)}`
|
|
1202
|
+
);
|
|
1203
|
+
const bodyStatus = await body.tick(context);
|
|
1204
|
+
switch (bodyStatus) {
|
|
1205
|
+
case "SUCCESS" /* SUCCESS */:
|
|
1206
|
+
this.log(`Item ${this.currentIndex} succeeded`);
|
|
1207
|
+
this.currentIndex++;
|
|
1208
|
+
body.reset();
|
|
1209
|
+
break;
|
|
1210
|
+
case "FAILURE" /* FAILURE */:
|
|
1211
|
+
this.log(`Item ${this.currentIndex} failed - ForEach fails`);
|
|
1212
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
1213
|
+
this.currentIndex = 0;
|
|
1214
|
+
return "FAILURE" /* FAILURE */;
|
|
1215
|
+
case "RUNNING" /* RUNNING */:
|
|
1216
|
+
this.log(`Item ${this.currentIndex} is running`);
|
|
1217
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
1218
|
+
return "RUNNING" /* RUNNING */;
|
|
1219
|
+
// Will resume from this index next tick
|
|
1220
|
+
default:
|
|
1221
|
+
throw new Error(`Unexpected status from body: ${bodyStatus}`);
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
this.log("All items processed successfully");
|
|
1225
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
1226
|
+
this.currentIndex = 0;
|
|
1227
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1228
|
+
}
|
|
1229
|
+
onReset() {
|
|
1230
|
+
super.onReset();
|
|
1231
|
+
this.currentIndex = 0;
|
|
1232
|
+
}
|
|
1233
|
+
onHalt() {
|
|
1234
|
+
super.onHalt();
|
|
1235
|
+
this.currentIndex = 0;
|
|
1236
|
+
}
|
|
1237
|
+
};
|
|
1238
|
+
|
|
1239
|
+
// src/composites/sequence.ts
|
|
1240
|
+
var Sequence = class extends CompositeNode {
|
|
1241
|
+
currentChildIndex = 0;
|
|
1242
|
+
async executeTick(context) {
|
|
1243
|
+
this.log("Ticking with", this._children.length, "children");
|
|
1244
|
+
if (this._children.length === 0) {
|
|
1245
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1246
|
+
}
|
|
1247
|
+
while (this.currentChildIndex < this._children.length) {
|
|
1248
|
+
checkSignal(context.signal);
|
|
1249
|
+
const child = this._children[this.currentChildIndex];
|
|
1250
|
+
if (!child) {
|
|
1251
|
+
throw new Error(
|
|
1252
|
+
`Child at index ${this.currentChildIndex} is undefined`
|
|
1253
|
+
);
|
|
1254
|
+
}
|
|
1255
|
+
this.log(`Ticking child ${this.currentChildIndex}: ${child.name}`);
|
|
1256
|
+
const childStatus = await child.tick(context);
|
|
1257
|
+
switch (childStatus) {
|
|
1258
|
+
case "SUCCESS" /* SUCCESS */:
|
|
1259
|
+
this.log(`Child ${child.name} succeeded`);
|
|
1260
|
+
this.currentChildIndex++;
|
|
1261
|
+
break;
|
|
1262
|
+
case "FAILURE" /* FAILURE */:
|
|
1263
|
+
this.log(`Child ${child.name} failed - sequence fails`);
|
|
1264
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
1265
|
+
this.currentChildIndex = 0;
|
|
1266
|
+
return "FAILURE" /* FAILURE */;
|
|
1267
|
+
case "RUNNING" /* RUNNING */:
|
|
1268
|
+
this.log(`Child ${child.name} is running`);
|
|
1269
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
1270
|
+
return "RUNNING" /* RUNNING */;
|
|
1271
|
+
default:
|
|
1272
|
+
throw new Error(`Unexpected status from child: ${childStatus}`);
|
|
1273
|
+
}
|
|
1274
|
+
}
|
|
1275
|
+
this.log("All children succeeded");
|
|
1276
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
1277
|
+
this.currentChildIndex = 0;
|
|
1278
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1279
|
+
}
|
|
1280
|
+
onHalt() {
|
|
1281
|
+
this.haltChildren(this.currentChildIndex);
|
|
1282
|
+
this.currentChildIndex = 0;
|
|
1283
|
+
}
|
|
1284
|
+
onReset() {
|
|
1285
|
+
this.currentChildIndex = 0;
|
|
1286
|
+
}
|
|
1287
|
+
};
|
|
1288
|
+
|
|
1289
|
+
// src/composites/memory-sequence.ts
|
|
1290
|
+
var MemorySequence = class extends Sequence {
|
|
1291
|
+
completedChildren = /* @__PURE__ */ new Set();
|
|
1292
|
+
async executeTick(context) {
|
|
1293
|
+
this.log(
|
|
1294
|
+
`Ticking with ${this._children.length} children (${this.completedChildren.size} completed)`
|
|
1295
|
+
);
|
|
1296
|
+
if (this._children.length === 0) {
|
|
1297
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1298
|
+
}
|
|
1299
|
+
for (let i = 0; i < this._children.length; i++) {
|
|
1300
|
+
checkSignal(context.signal);
|
|
1301
|
+
const child = this._children[i];
|
|
1302
|
+
if (!child) {
|
|
1303
|
+
throw new ConfigurationError(`Child at index ${i} is undefined`);
|
|
1304
|
+
}
|
|
1305
|
+
if (this.completedChildren.has(child.id)) {
|
|
1306
|
+
this.log(`Skipping completed child: ${child.name}`);
|
|
1307
|
+
continue;
|
|
1308
|
+
}
|
|
1309
|
+
this.log(`Ticking child ${i}: ${child.name}`);
|
|
1310
|
+
const childStatus = await child.tick(context);
|
|
1311
|
+
switch (childStatus) {
|
|
1312
|
+
case "SUCCESS" /* SUCCESS */:
|
|
1313
|
+
this.log(`Child ${child.name} succeeded - remembering`);
|
|
1314
|
+
this.completedChildren.add(child.id);
|
|
1315
|
+
break;
|
|
1316
|
+
case "FAILURE" /* FAILURE */:
|
|
1317
|
+
this.log(`Child ${child.name} failed - sequence fails`);
|
|
1318
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
1319
|
+
return "FAILURE" /* FAILURE */;
|
|
1320
|
+
case "RUNNING" /* RUNNING */:
|
|
1321
|
+
this.log(`Child ${child.name} is running`);
|
|
1322
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
1323
|
+
return "RUNNING" /* RUNNING */;
|
|
1324
|
+
default:
|
|
1325
|
+
throw new Error(`Unexpected status from child: ${childStatus}`);
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
this.log("All children succeeded");
|
|
1329
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
1330
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1331
|
+
}
|
|
1332
|
+
onReset() {
|
|
1333
|
+
super.onReset();
|
|
1334
|
+
this.log("Clearing completed children memory");
|
|
1335
|
+
this.completedChildren.clear();
|
|
1336
|
+
}
|
|
1337
|
+
onHalt() {
|
|
1338
|
+
super.onHalt();
|
|
1339
|
+
}
|
|
1340
|
+
};
|
|
1341
|
+
var SequenceWithMemory = class extends MemorySequence {
|
|
1342
|
+
constructor(config) {
|
|
1343
|
+
super({ ...config, type: "SequenceWithMemory" });
|
|
1344
|
+
}
|
|
1345
|
+
};
|
|
1346
|
+
|
|
1347
|
+
// src/composites/parallel.ts
|
|
1348
|
+
var Parallel = class extends CompositeNode {
|
|
1349
|
+
strategy;
|
|
1350
|
+
successThreshold;
|
|
1351
|
+
failureThreshold;
|
|
1352
|
+
constructor(config) {
|
|
1353
|
+
super(config);
|
|
1354
|
+
this.strategy = config.strategy ?? "strict";
|
|
1355
|
+
this.successThreshold = config.successThreshold;
|
|
1356
|
+
this.failureThreshold = config.failureThreshold;
|
|
1357
|
+
}
|
|
1358
|
+
async executeTick(context) {
|
|
1359
|
+
this.log(
|
|
1360
|
+
`Ticking with ${this._children.length} children (strategy: ${this.strategy})`
|
|
1361
|
+
);
|
|
1362
|
+
if (this._children.length === 0) {
|
|
1363
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1364
|
+
}
|
|
1365
|
+
const childrenToTick = this._children.filter((child) => {
|
|
1366
|
+
const status = child.status();
|
|
1367
|
+
return status === "IDLE" /* IDLE */ || status === "RUNNING" /* RUNNING */;
|
|
1368
|
+
});
|
|
1369
|
+
this.log(
|
|
1370
|
+
`Ticking ${childrenToTick.length}/${this._children.length} children (others completed)`
|
|
1371
|
+
);
|
|
1372
|
+
checkSignal(context.signal);
|
|
1373
|
+
if (childrenToTick.length > 0) {
|
|
1374
|
+
await Promise.all(childrenToTick.map((child) => child.tick(context)));
|
|
1375
|
+
}
|
|
1376
|
+
const allStatuses = this._children.map((child) => child.status());
|
|
1377
|
+
const hasRunning = allStatuses.some(
|
|
1378
|
+
(status) => status === "RUNNING" /* RUNNING */
|
|
1379
|
+
);
|
|
1380
|
+
if (hasRunning) {
|
|
1381
|
+
this.log("At least one child returned RUNNING");
|
|
1382
|
+
return "RUNNING" /* RUNNING */;
|
|
1383
|
+
}
|
|
1384
|
+
const successes = allStatuses.filter(
|
|
1385
|
+
(status) => status === "SUCCESS" /* SUCCESS */
|
|
1386
|
+
).length;
|
|
1387
|
+
const failures = allStatuses.filter(
|
|
1388
|
+
(status) => status === "FAILURE" /* FAILURE */
|
|
1389
|
+
).length;
|
|
1390
|
+
this.log(`Results - Successes: ${successes}, Failures: ${failures}`);
|
|
1391
|
+
if (this.successThreshold !== void 0 && successes >= this.successThreshold) {
|
|
1392
|
+
this.log(
|
|
1393
|
+
`Success threshold met: ${successes}/${this.successThreshold} -> SUCCESS`
|
|
1394
|
+
);
|
|
1395
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1396
|
+
}
|
|
1397
|
+
if (this.failureThreshold !== void 0 && failures >= this.failureThreshold) {
|
|
1398
|
+
this.log(
|
|
1399
|
+
`Failure threshold met: ${failures}/${this.failureThreshold} -> FAILURE`
|
|
1400
|
+
);
|
|
1401
|
+
return "FAILURE" /* FAILURE */;
|
|
1402
|
+
}
|
|
1403
|
+
if (this.strategy === "strict") {
|
|
1404
|
+
const finalStatus = successes === this._children.length ? "SUCCESS" /* SUCCESS */ : "FAILURE" /* FAILURE */;
|
|
1405
|
+
this.log(
|
|
1406
|
+
`Strategy 'strict': ${successes}/${this._children.length} succeeded -> ${finalStatus}`
|
|
1407
|
+
);
|
|
1408
|
+
return finalStatus;
|
|
1409
|
+
} else {
|
|
1410
|
+
const finalStatus = successes > 0 ? "SUCCESS" /* SUCCESS */ : "FAILURE" /* FAILURE */;
|
|
1411
|
+
this.log(`Strategy 'any': ${successes} succeeded -> ${finalStatus}`);
|
|
1412
|
+
return finalStatus;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
onHalt() {
|
|
1416
|
+
this.log("Halting parallel execution");
|
|
1417
|
+
for (const child of this._children) {
|
|
1418
|
+
if (child.status() === "RUNNING" /* RUNNING */) {
|
|
1419
|
+
child.halt();
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
onReset() {
|
|
1424
|
+
this.log("Resetting parallel state");
|
|
1425
|
+
}
|
|
1426
|
+
};
|
|
1427
|
+
|
|
1428
|
+
// src/composites/reactive-sequence.ts
|
|
1429
|
+
var ReactiveSequence = class extends Sequence {
|
|
1430
|
+
async executeTick(context) {
|
|
1431
|
+
this.log("Ticking (reactive - always starts from beginning)");
|
|
1432
|
+
if (this._children.length === 0) {
|
|
1433
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1434
|
+
}
|
|
1435
|
+
for (let i = 0; i < this._children.length; i++) {
|
|
1436
|
+
checkSignal(context.signal);
|
|
1437
|
+
const child = this._children[i];
|
|
1438
|
+
if (!child) {
|
|
1439
|
+
throw new ConfigurationError(`Child at index ${i} is undefined`);
|
|
1440
|
+
}
|
|
1441
|
+
this.log(`Ticking child ${i}: ${child.name}`);
|
|
1442
|
+
const childStatus = await child.tick(context);
|
|
1443
|
+
switch (childStatus) {
|
|
1444
|
+
case "SUCCESS" /* SUCCESS */:
|
|
1445
|
+
this.log(`Child ${child.name} succeeded`);
|
|
1446
|
+
break;
|
|
1447
|
+
case "FAILURE" /* FAILURE */:
|
|
1448
|
+
this.log(`Child ${child.name} failed - sequence fails`);
|
|
1449
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
1450
|
+
return "FAILURE" /* FAILURE */;
|
|
1451
|
+
case "RUNNING" /* RUNNING */:
|
|
1452
|
+
this.log(`Child ${child.name} is running`);
|
|
1453
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
1454
|
+
return "RUNNING" /* RUNNING */;
|
|
1455
|
+
default:
|
|
1456
|
+
throw new Error(`Unexpected status from child: ${childStatus}`);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
this.log("All children succeeded");
|
|
1460
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
1461
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Override to prevent parent Sequence from resetting currentChildIndex
|
|
1465
|
+
* (ReactiveSequence doesn't use currentChildIndex)
|
|
1466
|
+
*/
|
|
1467
|
+
onReset() {
|
|
1468
|
+
this._status = "IDLE" /* IDLE */;
|
|
1469
|
+
for (const child of this._children) {
|
|
1470
|
+
child.reset();
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
};
|
|
1474
|
+
|
|
1475
|
+
// src/composites/recovery.ts
|
|
1476
|
+
var Recovery = class extends CompositeNode {
|
|
1477
|
+
tryBranch;
|
|
1478
|
+
catchBranch;
|
|
1479
|
+
finallyBranch;
|
|
1480
|
+
addChild(child) {
|
|
1481
|
+
if (!this.tryBranch) {
|
|
1482
|
+
this.tryBranch = child;
|
|
1483
|
+
this._children.push(child);
|
|
1484
|
+
child.parent = this;
|
|
1485
|
+
} else if (!this.catchBranch) {
|
|
1486
|
+
this.catchBranch = child;
|
|
1487
|
+
this._children.push(child);
|
|
1488
|
+
child.parent = this;
|
|
1489
|
+
} else if (!this.finallyBranch) {
|
|
1490
|
+
this.finallyBranch = child;
|
|
1491
|
+
this._children.push(child);
|
|
1492
|
+
child.parent = this;
|
|
1493
|
+
} else {
|
|
1494
|
+
throw new ConfigurationError(
|
|
1495
|
+
"Recovery can have maximum 3 children (try, catch, finally)"
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
async executeTick(context) {
|
|
1500
|
+
checkSignal(context.signal);
|
|
1501
|
+
if (!this.tryBranch) {
|
|
1502
|
+
throw new ConfigurationError("Recovery requires at least a try branch");
|
|
1503
|
+
}
|
|
1504
|
+
this.log("Executing try branch");
|
|
1505
|
+
const tryResult = await this.tryBranch.tick(context);
|
|
1506
|
+
let mainResult;
|
|
1507
|
+
if (tryResult === "FAILURE" /* FAILURE */ && this.catchBranch) {
|
|
1508
|
+
this.log("Try branch failed - executing catch branch");
|
|
1509
|
+
mainResult = await this.catchBranch.tick(context);
|
|
1510
|
+
} else {
|
|
1511
|
+
mainResult = tryResult;
|
|
1512
|
+
}
|
|
1513
|
+
if (this.finallyBranch) {
|
|
1514
|
+
this.log("Executing finally branch");
|
|
1515
|
+
await this.finallyBranch.tick(context);
|
|
1516
|
+
this.log("Finally branch completed");
|
|
1517
|
+
}
|
|
1518
|
+
this._status = mainResult;
|
|
1519
|
+
return mainResult;
|
|
1520
|
+
}
|
|
1521
|
+
};
|
|
1522
|
+
|
|
1523
|
+
// src/composites/selector.ts
|
|
1524
|
+
var Selector = class extends CompositeNode {
|
|
1525
|
+
currentChildIndex = 0;
|
|
1526
|
+
async executeTick(context) {
|
|
1527
|
+
this.log("Ticking with", this._children.length, "children");
|
|
1528
|
+
if (this._children.length === 0) {
|
|
1529
|
+
return "FAILURE" /* FAILURE */;
|
|
1530
|
+
}
|
|
1531
|
+
while (this.currentChildIndex < this._children.length) {
|
|
1532
|
+
checkSignal(context.signal);
|
|
1533
|
+
const child = this._children[this.currentChildIndex];
|
|
1534
|
+
if (!child) {
|
|
1535
|
+
throw new Error(
|
|
1536
|
+
`Child at index ${this.currentChildIndex} is undefined`
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
this.log(`Ticking child ${this.currentChildIndex}: ${child.name}`);
|
|
1540
|
+
const childStatus = await child.tick(context);
|
|
1541
|
+
switch (childStatus) {
|
|
1542
|
+
case "SUCCESS" /* SUCCESS */:
|
|
1543
|
+
this.log(`Child ${child.name} succeeded - selector succeeds`);
|
|
1544
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
1545
|
+
this.currentChildIndex = 0;
|
|
1546
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1547
|
+
case "FAILURE" /* FAILURE */:
|
|
1548
|
+
this.log(`Child ${child.name} failed`);
|
|
1549
|
+
this.currentChildIndex++;
|
|
1550
|
+
break;
|
|
1551
|
+
case "RUNNING" /* RUNNING */:
|
|
1552
|
+
this.log(`Child ${child.name} is running`);
|
|
1553
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
1554
|
+
return "RUNNING" /* RUNNING */;
|
|
1555
|
+
default:
|
|
1556
|
+
throw new Error(`Unexpected status from child: ${childStatus}`);
|
|
1557
|
+
}
|
|
1558
|
+
}
|
|
1559
|
+
this.log("All children failed");
|
|
1560
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
1561
|
+
this.currentChildIndex = 0;
|
|
1562
|
+
return "FAILURE" /* FAILURE */;
|
|
1563
|
+
}
|
|
1564
|
+
onHalt() {
|
|
1565
|
+
this.haltChildren(this.currentChildIndex);
|
|
1566
|
+
this.currentChildIndex = 0;
|
|
1567
|
+
}
|
|
1568
|
+
onReset() {
|
|
1569
|
+
this.currentChildIndex = 0;
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
var Fallback = class extends Selector {
|
|
1573
|
+
constructor(config) {
|
|
1574
|
+
super({ ...config, type: "Fallback" });
|
|
1575
|
+
}
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
// src/utilities/variable-resolver.ts
|
|
1579
|
+
var VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/g;
|
|
1580
|
+
var HAS_VARIABLE_PATTERN = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/;
|
|
1581
|
+
var FULL_MATCH_PATTERN = /^\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}$|^\$\{([a-zA-Z0-9_.]+)\}$/;
|
|
1582
|
+
var safeProcessEnv = () => {
|
|
1583
|
+
try {
|
|
1584
|
+
return typeof process !== "undefined" && process?.env ? process.env : {};
|
|
1585
|
+
} catch {
|
|
1586
|
+
return {};
|
|
1587
|
+
}
|
|
1588
|
+
};
|
|
1589
|
+
function resolveString(str, ctx, opts = {}) {
|
|
1590
|
+
const { preserveUndefined = true, envSource = safeProcessEnv() } = opts;
|
|
1591
|
+
const fullMatch = str.match(FULL_MATCH_PATTERN);
|
|
1592
|
+
if (fullMatch) {
|
|
1593
|
+
const namespace = fullMatch[1];
|
|
1594
|
+
const namespacedKey = fullMatch[2];
|
|
1595
|
+
const simpleKey = fullMatch[3];
|
|
1596
|
+
const key = namespacedKey || simpleKey;
|
|
1597
|
+
const ns = namespace || "bb";
|
|
1598
|
+
if (key) {
|
|
1599
|
+
const value = resolveVariable(ns, key, ctx, envSource);
|
|
1600
|
+
if (value !== void 0) {
|
|
1601
|
+
return value;
|
|
1602
|
+
}
|
|
1603
|
+
return preserveUndefined ? str : void 0;
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
return str.replace(VARIABLE_PATTERN, (match, namespace, namespacedKey, simpleKey) => {
|
|
1607
|
+
const key = namespacedKey || simpleKey;
|
|
1608
|
+
const ns = namespace || "bb";
|
|
1609
|
+
const value = resolveVariable(ns, key, ctx, envSource);
|
|
1610
|
+
if (value === void 0) {
|
|
1611
|
+
return preserveUndefined ? match : "";
|
|
1612
|
+
}
|
|
1613
|
+
if (value === null) {
|
|
1614
|
+
return "null";
|
|
1615
|
+
}
|
|
1616
|
+
if (typeof value === "object") {
|
|
1617
|
+
try {
|
|
1618
|
+
return JSON.stringify(value);
|
|
1619
|
+
} catch {
|
|
1620
|
+
return String(value);
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
return String(value);
|
|
1624
|
+
});
|
|
1625
|
+
}
|
|
1626
|
+
function resolveValue(value, ctx, opts = {}) {
|
|
1627
|
+
if (typeof value === "string") {
|
|
1628
|
+
return resolveString(value, ctx, opts);
|
|
1629
|
+
}
|
|
1630
|
+
if (Array.isArray(value)) {
|
|
1631
|
+
return value.map((item) => resolveValue(item, ctx, opts));
|
|
1632
|
+
}
|
|
1633
|
+
if (value !== null && typeof value === "object") {
|
|
1634
|
+
const resolved = {};
|
|
1635
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1636
|
+
resolved[key] = resolveValue(val, ctx, opts);
|
|
1637
|
+
}
|
|
1638
|
+
return resolved;
|
|
1639
|
+
}
|
|
1640
|
+
return value;
|
|
1641
|
+
}
|
|
1642
|
+
function resolveVariable(namespace, key, ctx, envSource) {
|
|
1643
|
+
switch (namespace) {
|
|
1644
|
+
case "input":
|
|
1645
|
+
return getNestedValue(ctx.input, key);
|
|
1646
|
+
case "bb":
|
|
1647
|
+
return getNestedBlackboardValue(ctx.blackboard, key);
|
|
1648
|
+
case "env":
|
|
1649
|
+
return envSource[key];
|
|
1650
|
+
case "param":
|
|
1651
|
+
if (ctx.testData) {
|
|
1652
|
+
const parts = key.split(".");
|
|
1653
|
+
const firstPart = parts[0];
|
|
1654
|
+
if (firstPart) {
|
|
1655
|
+
let value = ctx.testData.get(firstPart);
|
|
1656
|
+
for (let i = 1; i < parts.length && value !== void 0; i++) {
|
|
1657
|
+
const part = parts[i];
|
|
1658
|
+
if (part && typeof value === "object" && value !== null) {
|
|
1659
|
+
value = value[part];
|
|
1660
|
+
} else {
|
|
1661
|
+
return void 0;
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return value;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
return void 0;
|
|
1668
|
+
default:
|
|
1669
|
+
return getNestedBlackboardValue(ctx.blackboard, key);
|
|
1670
|
+
}
|
|
1671
|
+
}
|
|
1672
|
+
function getNestedValue(obj, path2) {
|
|
1673
|
+
if (obj === void 0 || obj === null) {
|
|
1674
|
+
return void 0;
|
|
1675
|
+
}
|
|
1676
|
+
if (typeof obj !== "object") {
|
|
1677
|
+
return void 0;
|
|
1678
|
+
}
|
|
1679
|
+
const parts = path2.split(".");
|
|
1680
|
+
let value = obj;
|
|
1681
|
+
for (const part of parts) {
|
|
1682
|
+
if (value === void 0 || value === null) {
|
|
1683
|
+
return void 0;
|
|
1684
|
+
}
|
|
1685
|
+
if (typeof value !== "object") {
|
|
1686
|
+
return void 0;
|
|
1687
|
+
}
|
|
1688
|
+
value = value[part];
|
|
1689
|
+
}
|
|
1690
|
+
return value;
|
|
1691
|
+
}
|
|
1692
|
+
function getNestedBlackboardValue(blackboard, path2) {
|
|
1693
|
+
const parts = path2.split(".");
|
|
1694
|
+
const firstPart = parts[0];
|
|
1695
|
+
if (!firstPart) {
|
|
1696
|
+
return void 0;
|
|
1697
|
+
}
|
|
1698
|
+
let value = blackboard.get(firstPart);
|
|
1699
|
+
for (let i = 1; i < parts.length && value !== void 0; i++) {
|
|
1700
|
+
const part = parts[i];
|
|
1701
|
+
if (part && typeof value === "object" && value !== null) {
|
|
1702
|
+
value = value[part];
|
|
1703
|
+
} else {
|
|
1704
|
+
return void 0;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return value;
|
|
1708
|
+
}
|
|
1709
|
+
function hasVariables(str) {
|
|
1710
|
+
return HAS_VARIABLE_PATTERN.test(str);
|
|
1711
|
+
}
|
|
1712
|
+
function extractVariables(str) {
|
|
1713
|
+
const variables = [];
|
|
1714
|
+
const pattern = /\$\{(input|bb|env|param)\.([a-zA-Z0-9_.]+)\}|\$\{([a-zA-Z0-9_.]+)\}/g;
|
|
1715
|
+
let match;
|
|
1716
|
+
while ((match = pattern.exec(str)) !== null) {
|
|
1717
|
+
const namespace = match[1] || "bb";
|
|
1718
|
+
const key = match[2] || match[3];
|
|
1719
|
+
if (key) {
|
|
1720
|
+
variables.push({ namespace, key });
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
return variables;
|
|
1724
|
+
}
|
|
1725
|
+
|
|
1726
|
+
// src/composites/sub-tree.ts
|
|
1727
|
+
var SubTree = class extends ActionNode {
|
|
1728
|
+
treeId;
|
|
1729
|
+
params;
|
|
1730
|
+
outputs;
|
|
1731
|
+
clonedTree;
|
|
1732
|
+
// Cached tree instance
|
|
1733
|
+
constructor(config) {
|
|
1734
|
+
super(config);
|
|
1735
|
+
this.treeId = config.treeId;
|
|
1736
|
+
this.params = config.params ?? {};
|
|
1737
|
+
this.outputs = config.outputs ?? [];
|
|
1738
|
+
}
|
|
1739
|
+
async executeTick(context) {
|
|
1740
|
+
checkSignal(context.signal);
|
|
1741
|
+
if (!this.clonedTree) {
|
|
1742
|
+
if (!context.treeRegistry.hasTree(this.treeId)) {
|
|
1743
|
+
throw new Error(
|
|
1744
|
+
`SubTree tree '${this.treeId}' not found in registry. Available trees: ${context.treeRegistry.getAllTreeIds().join(", ") || "none"}`
|
|
1745
|
+
);
|
|
1746
|
+
}
|
|
1747
|
+
const clonedBehaviorTree = context.treeRegistry.cloneTree(this.treeId);
|
|
1748
|
+
this.clonedTree = clonedBehaviorTree.getRoot();
|
|
1749
|
+
this.log(`Cloned SubTree tree '${this.treeId}' from registry`);
|
|
1750
|
+
}
|
|
1751
|
+
const subtreeScope = context.blackboard.createScope(`subtree_${this.id}`);
|
|
1752
|
+
this.log(`Created scoped blackboard: ${subtreeScope.getFullScopePath()}`);
|
|
1753
|
+
if (Object.keys(this.params).length > 0) {
|
|
1754
|
+
const varCtx = {
|
|
1755
|
+
blackboard: context.blackboard,
|
|
1756
|
+
input: context.input,
|
|
1757
|
+
testData: context.testData
|
|
1758
|
+
};
|
|
1759
|
+
const resolvedParams = resolveValue(this.params, varCtx);
|
|
1760
|
+
for (const [key, value] of Object.entries(resolvedParams)) {
|
|
1761
|
+
subtreeScope.set(key, value);
|
|
1762
|
+
this.log(`Set param '${key}' in subtree scope`);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
const scopedContext = {
|
|
1766
|
+
...context,
|
|
1767
|
+
blackboard: subtreeScope
|
|
1768
|
+
};
|
|
1769
|
+
try {
|
|
1770
|
+
this.log(`Executing SubTree tree '${this.treeId}'`);
|
|
1771
|
+
const status = await this.clonedTree.tick(scopedContext);
|
|
1772
|
+
if (this.outputs.length > 0 && (status === "SUCCESS" /* SUCCESS */ || status === "RUNNING" /* RUNNING */)) {
|
|
1773
|
+
for (const outputKey of this.outputs) {
|
|
1774
|
+
if (subtreeScope.has(outputKey)) {
|
|
1775
|
+
const value = subtreeScope.get(outputKey);
|
|
1776
|
+
context.blackboard.set(outputKey, value);
|
|
1777
|
+
this.log(`Exported output '${outputKey}' to parent scope`);
|
|
1778
|
+
} else {
|
|
1779
|
+
this.log(`Output '${outputKey}' not found in subtree scope, skipping`);
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
this.log(
|
|
1784
|
+
`SubTree tree '${this.treeId}' completed with status: ${status}`
|
|
1785
|
+
);
|
|
1786
|
+
return status;
|
|
1787
|
+
} catch (error) {
|
|
1788
|
+
this.log(`SubTree tree '${this.treeId}' failed with error: ${error}`);
|
|
1789
|
+
throw error;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
/**
|
|
1793
|
+
* Override clone to include cloned tree
|
|
1794
|
+
*/
|
|
1795
|
+
clone() {
|
|
1796
|
+
const ClonedClass = this.constructor;
|
|
1797
|
+
const cloned = new ClonedClass({ ...this.config });
|
|
1798
|
+
return cloned;
|
|
1799
|
+
}
|
|
1800
|
+
/**
|
|
1801
|
+
* Override halt to halt the referenced tree
|
|
1802
|
+
*/
|
|
1803
|
+
halt() {
|
|
1804
|
+
super.halt();
|
|
1805
|
+
if (this.clonedTree && this.clonedTree.status() === "RUNNING" /* RUNNING */) {
|
|
1806
|
+
this.clonedTree.halt();
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
/**
|
|
1810
|
+
* Override reset to reset the referenced tree
|
|
1811
|
+
*/
|
|
1812
|
+
reset() {
|
|
1813
|
+
super.reset();
|
|
1814
|
+
if (this.clonedTree) {
|
|
1815
|
+
this.clonedTree.reset();
|
|
1816
|
+
}
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
|
|
1820
|
+
// src/composites/while.ts
|
|
1821
|
+
var While = class extends CompositeNode {
|
|
1822
|
+
maxIterations;
|
|
1823
|
+
currentIteration = 0;
|
|
1824
|
+
condition;
|
|
1825
|
+
body;
|
|
1826
|
+
bodyStarted = false;
|
|
1827
|
+
constructor(config) {
|
|
1828
|
+
super(config);
|
|
1829
|
+
this.maxIterations = config.maxIterations ?? 1e3;
|
|
1830
|
+
}
|
|
1831
|
+
addChild(child) {
|
|
1832
|
+
if (!this.condition) {
|
|
1833
|
+
this.condition = child;
|
|
1834
|
+
this._children.push(child);
|
|
1835
|
+
child.parent = this;
|
|
1836
|
+
} else if (!this.body) {
|
|
1837
|
+
this.body = child;
|
|
1838
|
+
this._children.push(child);
|
|
1839
|
+
child.parent = this;
|
|
1840
|
+
} else {
|
|
1841
|
+
throw new ConfigurationError(
|
|
1842
|
+
"While can have maximum 2 children (condition, body)"
|
|
1843
|
+
);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
async executeTick(context) {
|
|
1847
|
+
if (!this.condition) {
|
|
1848
|
+
throw new ConfigurationError("While requires a condition child");
|
|
1849
|
+
}
|
|
1850
|
+
if (!this.body) {
|
|
1851
|
+
throw new ConfigurationError("While requires a body child");
|
|
1852
|
+
}
|
|
1853
|
+
this.log(
|
|
1854
|
+
`Starting while loop (iteration ${this.currentIteration}/${this.maxIterations})`
|
|
1855
|
+
);
|
|
1856
|
+
while (this.currentIteration < this.maxIterations) {
|
|
1857
|
+
checkSignal(context.signal);
|
|
1858
|
+
if (!this.bodyStarted) {
|
|
1859
|
+
this.log(`Evaluating condition (iteration ${this.currentIteration})`);
|
|
1860
|
+
const conditionStatus = await this.condition.tick(context);
|
|
1861
|
+
if (conditionStatus === "RUNNING" /* RUNNING */) {
|
|
1862
|
+
this.log("Condition is running");
|
|
1863
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
1864
|
+
return "RUNNING" /* RUNNING */;
|
|
1865
|
+
}
|
|
1866
|
+
if (conditionStatus === "FAILURE" /* FAILURE */) {
|
|
1867
|
+
this.log("Condition failed - exiting loop");
|
|
1868
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
1869
|
+
this.currentIteration = 0;
|
|
1870
|
+
this.bodyStarted = false;
|
|
1871
|
+
return "SUCCESS" /* SUCCESS */;
|
|
1872
|
+
}
|
|
1873
|
+
this.bodyStarted = true;
|
|
1874
|
+
} else {
|
|
1875
|
+
this.log(
|
|
1876
|
+
`Body already started for iteration ${this.currentIteration} - continuing execution`
|
|
1877
|
+
);
|
|
1878
|
+
}
|
|
1879
|
+
this.log(`Executing body (iteration ${this.currentIteration})`);
|
|
1880
|
+
const bodyStatus = await this.body.tick(context);
|
|
1881
|
+
switch (bodyStatus) {
|
|
1882
|
+
case "SUCCESS" /* SUCCESS */:
|
|
1883
|
+
this.log("Body succeeded - continuing loop");
|
|
1884
|
+
this.currentIteration++;
|
|
1885
|
+
this.bodyStarted = false;
|
|
1886
|
+
this.condition.reset();
|
|
1887
|
+
this.body.reset();
|
|
1888
|
+
break;
|
|
1889
|
+
case "FAILURE" /* FAILURE */:
|
|
1890
|
+
this.log("Body failed - While fails");
|
|
1891
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
1892
|
+
this.currentIteration = 0;
|
|
1893
|
+
this.bodyStarted = false;
|
|
1894
|
+
return "FAILURE" /* FAILURE */;
|
|
1895
|
+
case "RUNNING" /* RUNNING */:
|
|
1896
|
+
this.log("Body is running");
|
|
1897
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
1898
|
+
return "RUNNING" /* RUNNING */;
|
|
1899
|
+
default:
|
|
1900
|
+
throw new Error(`Unexpected status from body: ${bodyStatus}`);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
this.log(`Max iterations (${this.maxIterations}) reached`);
|
|
1904
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
1905
|
+
this.currentIteration = 0;
|
|
1906
|
+
this.bodyStarted = false;
|
|
1907
|
+
return "FAILURE" /* FAILURE */;
|
|
1908
|
+
}
|
|
1909
|
+
onReset() {
|
|
1910
|
+
super.onReset();
|
|
1911
|
+
this.log("Resetting - clearing body started flag");
|
|
1912
|
+
this.currentIteration = 0;
|
|
1913
|
+
this.bodyStarted = false;
|
|
1914
|
+
}
|
|
1915
|
+
onHalt() {
|
|
1916
|
+
super.onHalt();
|
|
1917
|
+
this.log("Halting - clearing body started flag");
|
|
1918
|
+
this.currentIteration = 0;
|
|
1919
|
+
this.bodyStarted = false;
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
1922
|
+
|
|
1923
|
+
// src/decorators/delay.ts
|
|
1924
|
+
var import_workflow = require("@temporalio/workflow");
|
|
1925
|
+
var Delay = class extends DecoratorNode {
|
|
1926
|
+
delayMs;
|
|
1927
|
+
delayStartTime = null;
|
|
1928
|
+
useTemporalAPI = null;
|
|
1929
|
+
// Cached detection result
|
|
1930
|
+
constructor(config) {
|
|
1931
|
+
super(config);
|
|
1932
|
+
this.delayMs = config.delayMs;
|
|
1933
|
+
if (this.delayMs < 0) {
|
|
1934
|
+
throw new ConfigurationError(
|
|
1935
|
+
`${this.name}: Delay must be non-negative (got ${this.delayMs})`
|
|
1936
|
+
);
|
|
1937
|
+
}
|
|
1938
|
+
}
|
|
1939
|
+
async executeTick(context) {
|
|
1940
|
+
checkSignal(context.signal);
|
|
1941
|
+
if (!this.child) {
|
|
1942
|
+
throw new ConfigurationError(`${this.name}: Decorator must have a child`);
|
|
1943
|
+
}
|
|
1944
|
+
if (this.delayMs === 0) {
|
|
1945
|
+
return await this.child.tick(context);
|
|
1946
|
+
}
|
|
1947
|
+
if (this.useTemporalAPI === null) {
|
|
1948
|
+
try {
|
|
1949
|
+
this.log(`Starting delay of ${this.delayMs}ms`);
|
|
1950
|
+
await (0, import_workflow.sleep)(this.delayMs);
|
|
1951
|
+
this.useTemporalAPI = true;
|
|
1952
|
+
this.log("Delay completed, executing child");
|
|
1953
|
+
checkSignal(context.signal);
|
|
1954
|
+
const childStatus2 = await this.child.tick(context);
|
|
1955
|
+
this._status = childStatus2;
|
|
1956
|
+
return childStatus2;
|
|
1957
|
+
} catch (err) {
|
|
1958
|
+
this.useTemporalAPI = false;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
if (this.useTemporalAPI === true) {
|
|
1962
|
+
this.log(`Starting delay of ${this.delayMs}ms`);
|
|
1963
|
+
await (0, import_workflow.sleep)(this.delayMs);
|
|
1964
|
+
this.log("Delay completed, executing child");
|
|
1965
|
+
checkSignal(context.signal);
|
|
1966
|
+
const childStatus2 = await this.child.tick(context);
|
|
1967
|
+
this._status = childStatus2;
|
|
1968
|
+
return childStatus2;
|
|
1969
|
+
}
|
|
1970
|
+
if (this.child.status() === "RUNNING" /* RUNNING */) {
|
|
1971
|
+
checkSignal(context.signal);
|
|
1972
|
+
const childStatus2 = await this.child.tick(context);
|
|
1973
|
+
if (childStatus2 !== "RUNNING" /* RUNNING */) {
|
|
1974
|
+
this.delayStartTime = null;
|
|
1975
|
+
}
|
|
1976
|
+
this._status = childStatus2;
|
|
1977
|
+
return childStatus2;
|
|
1978
|
+
}
|
|
1979
|
+
if (this.delayStartTime === null) {
|
|
1980
|
+
this.delayStartTime = Date.now();
|
|
1981
|
+
this.log(`Starting delay of ${this.delayMs}ms`);
|
|
1982
|
+
}
|
|
1983
|
+
const elapsed = Date.now() - this.delayStartTime;
|
|
1984
|
+
if (elapsed < this.delayMs) {
|
|
1985
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
1986
|
+
return "RUNNING" /* RUNNING */;
|
|
1987
|
+
}
|
|
1988
|
+
this.log("Delay completed, executing child");
|
|
1989
|
+
checkSignal(context.signal);
|
|
1990
|
+
const childStatus = await this.child.tick(context);
|
|
1991
|
+
if (childStatus !== "RUNNING" /* RUNNING */) {
|
|
1992
|
+
this.delayStartTime = null;
|
|
1993
|
+
}
|
|
1994
|
+
this._status = childStatus;
|
|
1995
|
+
return childStatus;
|
|
1996
|
+
}
|
|
1997
|
+
onHalt() {
|
|
1998
|
+
this.delayStartTime = null;
|
|
1999
|
+
}
|
|
2000
|
+
onReset() {
|
|
2001
|
+
this.delayStartTime = null;
|
|
2002
|
+
this.useTemporalAPI = null;
|
|
2003
|
+
}
|
|
2004
|
+
};
|
|
2005
|
+
|
|
2006
|
+
// src/decorators/force-result.ts
|
|
2007
|
+
var ForceSuccess = class extends DecoratorNode {
|
|
2008
|
+
async executeTick(context) {
|
|
2009
|
+
checkSignal(context.signal);
|
|
2010
|
+
if (!this.child) {
|
|
2011
|
+
throw new ConfigurationError("ForceSuccess requires a child");
|
|
2012
|
+
}
|
|
2013
|
+
const childStatus = await this.child.tick(context);
|
|
2014
|
+
if (childStatus === "RUNNING" /* RUNNING */) {
|
|
2015
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
2016
|
+
return "RUNNING" /* RUNNING */;
|
|
2017
|
+
}
|
|
2018
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
2019
|
+
return "SUCCESS" /* SUCCESS */;
|
|
2020
|
+
}
|
|
2021
|
+
};
|
|
2022
|
+
var ForceFailure = class extends DecoratorNode {
|
|
2023
|
+
async executeTick(context) {
|
|
2024
|
+
checkSignal(context.signal);
|
|
2025
|
+
if (!this.child) {
|
|
2026
|
+
throw new ConfigurationError("ForceFailure requires a child");
|
|
2027
|
+
}
|
|
2028
|
+
const childStatus = await this.child.tick(context);
|
|
2029
|
+
if (childStatus === "RUNNING" /* RUNNING */) {
|
|
2030
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
2031
|
+
return "RUNNING" /* RUNNING */;
|
|
2032
|
+
}
|
|
2033
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
2034
|
+
return "FAILURE" /* FAILURE */;
|
|
2035
|
+
}
|
|
2036
|
+
};
|
|
2037
|
+
|
|
2038
|
+
// src/decorators/invert.ts
|
|
2039
|
+
var Invert = class extends DecoratorNode {
|
|
2040
|
+
async executeTick(context) {
|
|
2041
|
+
checkSignal(context.signal);
|
|
2042
|
+
if (!this.child) {
|
|
2043
|
+
throw new ConfigurationError(`${this.name}: Decorator must have a child`);
|
|
2044
|
+
}
|
|
2045
|
+
this.log("Ticking child");
|
|
2046
|
+
const childStatus = await this.child.tick(context);
|
|
2047
|
+
switch (childStatus) {
|
|
2048
|
+
case "SUCCESS" /* SUCCESS */:
|
|
2049
|
+
this.log("Child succeeded - returning FAILURE");
|
|
2050
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
2051
|
+
return "FAILURE" /* FAILURE */;
|
|
2052
|
+
case "FAILURE" /* FAILURE */:
|
|
2053
|
+
this.log("Child failed - returning SUCCESS");
|
|
2054
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
2055
|
+
return "SUCCESS" /* SUCCESS */;
|
|
2056
|
+
case "RUNNING" /* RUNNING */:
|
|
2057
|
+
this.log("Child is running");
|
|
2058
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
2059
|
+
return "RUNNING" /* RUNNING */;
|
|
2060
|
+
default:
|
|
2061
|
+
return childStatus;
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
};
|
|
2065
|
+
|
|
2066
|
+
// src/decorators/keep-running.ts
|
|
2067
|
+
var KeepRunningUntilFailure = class extends DecoratorNode {
|
|
2068
|
+
async executeTick(context) {
|
|
2069
|
+
checkSignal(context.signal);
|
|
2070
|
+
if (!this.child) {
|
|
2071
|
+
throw new ConfigurationError(
|
|
2072
|
+
"KeepRunningUntilFailure requires a child"
|
|
2073
|
+
);
|
|
2074
|
+
}
|
|
2075
|
+
const result = await this.child.tick(context);
|
|
2076
|
+
switch (result) {
|
|
2077
|
+
case "SUCCESS" /* SUCCESS */:
|
|
2078
|
+
this.log("Child succeeded - resetting and continuing");
|
|
2079
|
+
this.child.reset();
|
|
2080
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
2081
|
+
return "RUNNING" /* RUNNING */;
|
|
2082
|
+
case "FAILURE" /* FAILURE */:
|
|
2083
|
+
this.log("Child failed - goal achieved");
|
|
2084
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
2085
|
+
return "SUCCESS" /* SUCCESS */;
|
|
2086
|
+
case "RUNNING" /* RUNNING */:
|
|
2087
|
+
this.log("Child is running");
|
|
2088
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
2089
|
+
return "RUNNING" /* RUNNING */;
|
|
2090
|
+
default:
|
|
2091
|
+
throw new Error(`Unexpected status from child: ${result}`);
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
};
|
|
2095
|
+
|
|
2096
|
+
// src/decorators/precondition.ts
|
|
2097
|
+
var Precondition = class extends DecoratorNode {
|
|
2098
|
+
preconditions = [];
|
|
2099
|
+
preconditionsChecked = false;
|
|
2100
|
+
/**
|
|
2101
|
+
* Add a precondition to check before main execution
|
|
2102
|
+
*/
|
|
2103
|
+
addPrecondition(condition, resolver, required = true) {
|
|
2104
|
+
this.preconditions.push({ condition, resolver, required });
|
|
2105
|
+
}
|
|
2106
|
+
async executeTick(context) {
|
|
2107
|
+
checkSignal(context.signal);
|
|
2108
|
+
if (!this.child) {
|
|
2109
|
+
throw new ConfigurationError("Precondition requires a child");
|
|
2110
|
+
}
|
|
2111
|
+
if (!this.preconditionsChecked) {
|
|
2112
|
+
for (let i = 0; i < this.preconditions.length; i++) {
|
|
2113
|
+
checkSignal(context.signal);
|
|
2114
|
+
const precond = this.preconditions[i];
|
|
2115
|
+
if (!precond) {
|
|
2116
|
+
continue;
|
|
2117
|
+
}
|
|
2118
|
+
this.log(
|
|
2119
|
+
`Checking precondition ${i + 1}/${this.preconditions.length}`
|
|
2120
|
+
);
|
|
2121
|
+
const conditionResult = await precond.condition.tick(context);
|
|
2122
|
+
if (conditionResult === "RUNNING" /* RUNNING */) {
|
|
2123
|
+
this.log(`Precondition ${i + 1} is running`);
|
|
2124
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
2125
|
+
return "RUNNING" /* RUNNING */;
|
|
2126
|
+
}
|
|
2127
|
+
if (conditionResult === "FAILURE" /* FAILURE */) {
|
|
2128
|
+
this.log(`Precondition ${i + 1} failed`);
|
|
2129
|
+
if (precond.resolver) {
|
|
2130
|
+
this.log(`Attempting to resolve precondition ${i + 1}`);
|
|
2131
|
+
const resolverResult = await precond.resolver.tick(context);
|
|
2132
|
+
if (resolverResult === "RUNNING" /* RUNNING */) {
|
|
2133
|
+
this.log(`Resolver ${i + 1} is running`);
|
|
2134
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
2135
|
+
return "RUNNING" /* RUNNING */;
|
|
2136
|
+
}
|
|
2137
|
+
if (resolverResult === "SUCCESS" /* SUCCESS */) {
|
|
2138
|
+
this.log(`Precondition ${i + 1} resolved successfully`);
|
|
2139
|
+
const recheckResult = await precond.condition.tick(context);
|
|
2140
|
+
if (recheckResult !== "SUCCESS" /* SUCCESS */) {
|
|
2141
|
+
if (precond.required) {
|
|
2142
|
+
this.log(
|
|
2143
|
+
`Precondition ${i + 1} still not met after resolution`
|
|
2144
|
+
);
|
|
2145
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
2146
|
+
return "FAILURE" /* FAILURE */;
|
|
2147
|
+
} else {
|
|
2148
|
+
this.log(`Optional precondition ${i + 1} skipped`);
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
} else if (precond.required) {
|
|
2152
|
+
this.log(`Failed to resolve required precondition ${i + 1}`);
|
|
2153
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
2154
|
+
return "FAILURE" /* FAILURE */;
|
|
2155
|
+
}
|
|
2156
|
+
} else if (precond.required) {
|
|
2157
|
+
this.log(`Required precondition ${i + 1} not met (no resolver)`);
|
|
2158
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
2159
|
+
return "FAILURE" /* FAILURE */;
|
|
2160
|
+
} else {
|
|
2161
|
+
this.log(`Optional precondition ${i + 1} skipped`);
|
|
2162
|
+
}
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
this.preconditionsChecked = true;
|
|
2166
|
+
this.log("All preconditions met - executing main child");
|
|
2167
|
+
} else {
|
|
2168
|
+
this.log("Preconditions already verified - continuing child execution");
|
|
2169
|
+
}
|
|
2170
|
+
checkSignal(context.signal);
|
|
2171
|
+
const result = await this.child.tick(context);
|
|
2172
|
+
this._status = result;
|
|
2173
|
+
if (result !== "RUNNING" /* RUNNING */) {
|
|
2174
|
+
this.log("Child completed - resetting precondition check flag");
|
|
2175
|
+
this.preconditionsChecked = false;
|
|
2176
|
+
}
|
|
2177
|
+
return result;
|
|
2178
|
+
}
|
|
2179
|
+
onHalt() {
|
|
2180
|
+
this.log("Halting - resetting precondition check flag");
|
|
2181
|
+
this.preconditionsChecked = false;
|
|
2182
|
+
super.onHalt();
|
|
2183
|
+
}
|
|
2184
|
+
onReset() {
|
|
2185
|
+
this.log("Resetting - clearing precondition check flag");
|
|
2186
|
+
this.preconditionsChecked = false;
|
|
2187
|
+
}
|
|
2188
|
+
};
|
|
2189
|
+
|
|
2190
|
+
// src/decorators/repeat.ts
|
|
2191
|
+
var Repeat = class extends DecoratorNode {
|
|
2192
|
+
numCycles;
|
|
2193
|
+
currentCycle = 0;
|
|
2194
|
+
constructor(config) {
|
|
2195
|
+
super(config);
|
|
2196
|
+
this.numCycles = config.numCycles;
|
|
2197
|
+
}
|
|
2198
|
+
async executeTick(context) {
|
|
2199
|
+
checkSignal(context.signal);
|
|
2200
|
+
if (!this.child) {
|
|
2201
|
+
throw new ConfigurationError("Repeat requires a child");
|
|
2202
|
+
}
|
|
2203
|
+
this.log(`Repeat cycle ${this.currentCycle}/${this.numCycles}`);
|
|
2204
|
+
if (this.currentCycle >= this.numCycles) {
|
|
2205
|
+
this.log("All cycles completed");
|
|
2206
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
2207
|
+
this.currentCycle = 0;
|
|
2208
|
+
return "SUCCESS" /* SUCCESS */;
|
|
2209
|
+
}
|
|
2210
|
+
const result = await this.child.tick(context);
|
|
2211
|
+
switch (result) {
|
|
2212
|
+
case "SUCCESS" /* SUCCESS */:
|
|
2213
|
+
this.log(`Cycle ${this.currentCycle} succeeded`);
|
|
2214
|
+
this.currentCycle++;
|
|
2215
|
+
if (this.currentCycle < this.numCycles) {
|
|
2216
|
+
this.child.reset();
|
|
2217
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
2218
|
+
return "RUNNING" /* RUNNING */;
|
|
2219
|
+
} else {
|
|
2220
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
2221
|
+
this.currentCycle = 0;
|
|
2222
|
+
return "SUCCESS" /* SUCCESS */;
|
|
2223
|
+
}
|
|
2224
|
+
case "FAILURE" /* FAILURE */:
|
|
2225
|
+
this.log(`Cycle ${this.currentCycle} failed`);
|
|
2226
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
2227
|
+
this.currentCycle = 0;
|
|
2228
|
+
return "FAILURE" /* FAILURE */;
|
|
2229
|
+
case "RUNNING" /* RUNNING */:
|
|
2230
|
+
this.log(`Cycle ${this.currentCycle} is running`);
|
|
2231
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
2232
|
+
return "RUNNING" /* RUNNING */;
|
|
2233
|
+
default:
|
|
2234
|
+
throw new Error(`Unexpected status from child: ${result}`);
|
|
2235
|
+
}
|
|
2236
|
+
}
|
|
2237
|
+
onReset() {
|
|
2238
|
+
super.onReset();
|
|
2239
|
+
this.currentCycle = 0;
|
|
2240
|
+
}
|
|
2241
|
+
onHalt() {
|
|
2242
|
+
super.onHalt();
|
|
2243
|
+
this.currentCycle = 0;
|
|
2244
|
+
}
|
|
2245
|
+
};
|
|
2246
|
+
|
|
2247
|
+
// src/decorators/run-once.ts
|
|
2248
|
+
var RunOnce = class extends DecoratorNode {
|
|
2249
|
+
hasRun = false;
|
|
2250
|
+
cachedResult;
|
|
2251
|
+
async executeTick(context) {
|
|
2252
|
+
checkSignal(context.signal);
|
|
2253
|
+
if (!this.child) {
|
|
2254
|
+
throw new ConfigurationError("RunOnce requires a child");
|
|
2255
|
+
}
|
|
2256
|
+
if (this.hasRun) {
|
|
2257
|
+
this.log(
|
|
2258
|
+
`Already executed, returning cached result: ${this.cachedResult}`
|
|
2259
|
+
);
|
|
2260
|
+
if (this.cachedResult === void 0) {
|
|
2261
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
2262
|
+
return "RUNNING" /* RUNNING */;
|
|
2263
|
+
}
|
|
2264
|
+
this._status = this.cachedResult;
|
|
2265
|
+
return this.cachedResult;
|
|
2266
|
+
}
|
|
2267
|
+
this.log("First execution - ticking child");
|
|
2268
|
+
const result = await this.child.tick(context);
|
|
2269
|
+
if (result !== "RUNNING" /* RUNNING */) {
|
|
2270
|
+
this.hasRun = true;
|
|
2271
|
+
this.cachedResult = result;
|
|
2272
|
+
this.log(`Caching result: ${result}`);
|
|
2273
|
+
} else {
|
|
2274
|
+
this.log("Child is running - will retry on next tick");
|
|
2275
|
+
}
|
|
2276
|
+
this._status = result;
|
|
2277
|
+
return result;
|
|
2278
|
+
}
|
|
2279
|
+
onReset() {
|
|
2280
|
+
super.onReset();
|
|
2281
|
+
this.hasRun = false;
|
|
2282
|
+
this.cachedResult = void 0;
|
|
2283
|
+
}
|
|
2284
|
+
};
|
|
2285
|
+
|
|
2286
|
+
// src/decorators/soft-assert.ts
|
|
2287
|
+
var SoftAssert = class extends DecoratorNode {
|
|
2288
|
+
failures = [];
|
|
2289
|
+
async executeTick(context) {
|
|
2290
|
+
checkSignal(context.signal);
|
|
2291
|
+
if (!this.child) {
|
|
2292
|
+
throw new ConfigurationError("SoftAssert requires a child");
|
|
2293
|
+
}
|
|
2294
|
+
const result = await this.child.tick(context);
|
|
2295
|
+
if (result === "FAILURE" /* FAILURE */) {
|
|
2296
|
+
const failure = {
|
|
2297
|
+
timestamp: Date.now(),
|
|
2298
|
+
message: `Soft assertion failed: ${this.child.name}`
|
|
2299
|
+
};
|
|
2300
|
+
this.failures.push(failure);
|
|
2301
|
+
this.log(`Soft assertion failed (continuing): ${this.child.name}`);
|
|
2302
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
2303
|
+
return "SUCCESS" /* SUCCESS */;
|
|
2304
|
+
}
|
|
2305
|
+
this._status = result;
|
|
2306
|
+
return result;
|
|
2307
|
+
}
|
|
2308
|
+
/**
|
|
2309
|
+
* Get all recorded failures
|
|
2310
|
+
*/
|
|
2311
|
+
getFailures() {
|
|
2312
|
+
return [...this.failures];
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* Check if any assertions have failed
|
|
2316
|
+
*/
|
|
2317
|
+
hasFailures() {
|
|
2318
|
+
return this.failures.length > 0;
|
|
2319
|
+
}
|
|
2320
|
+
onReset() {
|
|
2321
|
+
super.onReset();
|
|
2322
|
+
this.failures = [];
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
|
|
2326
|
+
// src/decorators/timeout.ts
|
|
2327
|
+
var import_workflow2 = require("@temporalio/workflow");
|
|
2328
|
+
var Timeout = class extends DecoratorNode {
|
|
2329
|
+
timeoutMs;
|
|
2330
|
+
startTime = null;
|
|
2331
|
+
useTemporalAPI = null;
|
|
2332
|
+
// Cached detection result
|
|
2333
|
+
constructor(config) {
|
|
2334
|
+
super(config);
|
|
2335
|
+
this.timeoutMs = config.timeoutMs;
|
|
2336
|
+
if (this.timeoutMs <= 0) {
|
|
2337
|
+
throw new ConfigurationError(
|
|
2338
|
+
`${this.name}: Timeout must be positive (got ${this.timeoutMs})`
|
|
2339
|
+
);
|
|
2340
|
+
}
|
|
2341
|
+
}
|
|
2342
|
+
async executeTick(context) {
|
|
2343
|
+
checkSignal(context.signal);
|
|
2344
|
+
if (!this.child) {
|
|
2345
|
+
throw new ConfigurationError(`${this.name}: Decorator must have a child`);
|
|
2346
|
+
}
|
|
2347
|
+
if (this.useTemporalAPI === null) {
|
|
2348
|
+
try {
|
|
2349
|
+
this.log(`Starting timeout for ${this.timeoutMs}ms`);
|
|
2350
|
+
const childStatus2 = await import_workflow2.CancellationScope.withTimeout(
|
|
2351
|
+
this.timeoutMs,
|
|
2352
|
+
async () => {
|
|
2353
|
+
return await this.child.tick(context);
|
|
2354
|
+
}
|
|
2355
|
+
);
|
|
2356
|
+
this.useTemporalAPI = true;
|
|
2357
|
+
this._status = childStatus2;
|
|
2358
|
+
this.log(`Child completed with ${childStatus2}`);
|
|
2359
|
+
return childStatus2;
|
|
2360
|
+
} catch (err) {
|
|
2361
|
+
if ((0, import_workflow2.isCancellation)(err)) {
|
|
2362
|
+
this.useTemporalAPI = true;
|
|
2363
|
+
this.log(`Timeout after ${this.timeoutMs}ms`);
|
|
2364
|
+
if (this.child.status() === "RUNNING" /* RUNNING */) {
|
|
2365
|
+
this.child.halt();
|
|
2366
|
+
}
|
|
2367
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
2368
|
+
return "FAILURE" /* FAILURE */;
|
|
2369
|
+
}
|
|
2370
|
+
this.useTemporalAPI = false;
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
if (this.useTemporalAPI === true) {
|
|
2374
|
+
try {
|
|
2375
|
+
this.log(`Starting timeout for ${this.timeoutMs}ms`);
|
|
2376
|
+
const childStatus2 = await import_workflow2.CancellationScope.withTimeout(
|
|
2377
|
+
this.timeoutMs,
|
|
2378
|
+
async () => {
|
|
2379
|
+
return await this.child.tick(context);
|
|
2380
|
+
}
|
|
2381
|
+
);
|
|
2382
|
+
this._status = childStatus2;
|
|
2383
|
+
this.log(`Child completed with ${childStatus2}`);
|
|
2384
|
+
return childStatus2;
|
|
2385
|
+
} catch (err) {
|
|
2386
|
+
if ((0, import_workflow2.isCancellation)(err)) {
|
|
2387
|
+
this.log(`Timeout after ${this.timeoutMs}ms`);
|
|
2388
|
+
if (this.child.status() === "RUNNING" /* RUNNING */) {
|
|
2389
|
+
this.child.halt();
|
|
2390
|
+
}
|
|
2391
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
2392
|
+
return "FAILURE" /* FAILURE */;
|
|
2393
|
+
}
|
|
2394
|
+
throw err;
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
if (this.startTime === null) {
|
|
2398
|
+
this.startTime = Date.now();
|
|
2399
|
+
this.log(`Starting timeout for ${this.timeoutMs}ms`);
|
|
2400
|
+
}
|
|
2401
|
+
const elapsed = Date.now() - this.startTime;
|
|
2402
|
+
if (elapsed >= this.timeoutMs) {
|
|
2403
|
+
this.log(`Timeout after ${this.timeoutMs}ms`);
|
|
2404
|
+
this.startTime = null;
|
|
2405
|
+
if (this.child.status() === "RUNNING" /* RUNNING */) {
|
|
2406
|
+
this.child.halt();
|
|
2407
|
+
}
|
|
2408
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
2409
|
+
return "FAILURE" /* FAILURE */;
|
|
2410
|
+
}
|
|
2411
|
+
const childStatus = await this.child.tick(context);
|
|
2412
|
+
if (childStatus !== "RUNNING" /* RUNNING */) {
|
|
2413
|
+
this.startTime = null;
|
|
2414
|
+
}
|
|
2415
|
+
this._status = childStatus;
|
|
2416
|
+
return childStatus;
|
|
2417
|
+
}
|
|
2418
|
+
onHalt() {
|
|
2419
|
+
this.startTime = null;
|
|
2420
|
+
if (this.child && this.child.status() === "RUNNING" /* RUNNING */) {
|
|
2421
|
+
this.child.halt();
|
|
2422
|
+
}
|
|
2423
|
+
}
|
|
2424
|
+
onReset() {
|
|
2425
|
+
this.startTime = null;
|
|
2426
|
+
this.useTemporalAPI = null;
|
|
2427
|
+
}
|
|
2428
|
+
};
|
|
2429
|
+
|
|
2430
|
+
// src/schemas/base.schema.ts
|
|
2431
|
+
var import_zod = require("zod");
|
|
2432
|
+
var nodeConfigurationSchema = import_zod.z.object({
|
|
2433
|
+
id: import_zod.z.string().min(1, "Node ID cannot be empty"),
|
|
2434
|
+
name: import_zod.z.string().optional()
|
|
2435
|
+
}).passthrough();
|
|
2436
|
+
function createNodeSchema(nodeType, fields) {
|
|
2437
|
+
return nodeConfigurationSchema.extend(fields).describe(`${nodeType} configuration`);
|
|
2438
|
+
}
|
|
2439
|
+
var validations = {
|
|
2440
|
+
/**
|
|
2441
|
+
* Positive number validator (> 0)
|
|
2442
|
+
*/
|
|
2443
|
+
positiveNumber: (fieldName) => import_zod.z.number().positive(`${fieldName} must be positive`),
|
|
2444
|
+
/**
|
|
2445
|
+
* Non-negative number validator (>= 0)
|
|
2446
|
+
*/
|
|
2447
|
+
nonNegativeNumber: (fieldName) => import_zod.z.number().nonnegative(`${fieldName} must be non-negative`),
|
|
2448
|
+
/**
|
|
2449
|
+
* Positive integer validator (> 0, whole number)
|
|
2450
|
+
*/
|
|
2451
|
+
positiveInteger: (fieldName) => import_zod.z.number().int().positive(`${fieldName} must be a positive integer`),
|
|
2452
|
+
/**
|
|
2453
|
+
* Non-negative integer validator (>= 0, whole number)
|
|
2454
|
+
*/
|
|
2455
|
+
nonNegativeInteger: (fieldName) => import_zod.z.number().int().nonnegative(`${fieldName} must be a non-negative integer`),
|
|
2456
|
+
/**
|
|
2457
|
+
* Blackboard key validator (non-empty string)
|
|
2458
|
+
*/
|
|
2459
|
+
blackboardKey: import_zod.z.string().min(1, "Blackboard key cannot be empty"),
|
|
2460
|
+
/**
|
|
2461
|
+
* Tree ID validator (non-empty string for SubTree references)
|
|
2462
|
+
*/
|
|
2463
|
+
treeId: import_zod.z.string().min(1, "Tree ID cannot be empty"),
|
|
2464
|
+
/**
|
|
2465
|
+
* Duration in milliseconds validator (non-negative)
|
|
2466
|
+
*/
|
|
2467
|
+
durationMs: (fieldName = "duration") => import_zod.z.number().nonnegative(`${fieldName} must be non-negative milliseconds`)
|
|
2468
|
+
};
|
|
2469
|
+
|
|
2470
|
+
// src/decorators/timeout.schema.ts
|
|
2471
|
+
var timeoutConfigurationSchema = createNodeSchema("Timeout", {
|
|
2472
|
+
timeoutMs: validations.positiveNumber("timeoutMs")
|
|
2473
|
+
});
|
|
2474
|
+
|
|
2475
|
+
// src/decorators/delay.schema.ts
|
|
2476
|
+
var delayConfigurationSchema = createNodeSchema("Delay", {
|
|
2477
|
+
delayMs: validations.nonNegativeNumber("delayMs")
|
|
2478
|
+
});
|
|
2479
|
+
|
|
2480
|
+
// src/decorators/repeat.schema.ts
|
|
2481
|
+
var repeatConfigurationSchema = createNodeSchema("Repeat", {
|
|
2482
|
+
numCycles: validations.positiveInteger("numCycles")
|
|
2483
|
+
});
|
|
2484
|
+
|
|
2485
|
+
// src/decorators/invert.schema.ts
|
|
2486
|
+
var invertConfigurationSchema = nodeConfigurationSchema;
|
|
2487
|
+
|
|
2488
|
+
// src/decorators/force-result.schema.ts
|
|
2489
|
+
var forceSuccessConfigurationSchema = nodeConfigurationSchema;
|
|
2490
|
+
var forceFailureConfigurationSchema = nodeConfigurationSchema;
|
|
2491
|
+
|
|
2492
|
+
// src/decorators/precondition.schema.ts
|
|
2493
|
+
var preconditionConfigurationSchema = nodeConfigurationSchema;
|
|
2494
|
+
|
|
2495
|
+
// src/decorators/run-once.schema.ts
|
|
2496
|
+
var runOnceConfigurationSchema = nodeConfigurationSchema;
|
|
2497
|
+
|
|
2498
|
+
// src/decorators/keep-running.schema.ts
|
|
2499
|
+
var keepRunningUntilFailureConfigurationSchema = nodeConfigurationSchema;
|
|
2500
|
+
|
|
2501
|
+
// src/decorators/soft-assert.schema.ts
|
|
2502
|
+
var softAssertConfigurationSchema = nodeConfigurationSchema;
|
|
2503
|
+
|
|
2504
|
+
// src/composites/parallel.schema.ts
|
|
2505
|
+
var import_zod2 = require("zod");
|
|
2506
|
+
var parallelStrategySchema = import_zod2.z.enum(["strict", "any"]);
|
|
2507
|
+
var parallelConfigurationSchema = createNodeSchema("Parallel", {
|
|
2508
|
+
strategy: parallelStrategySchema.optional().default("strict"),
|
|
2509
|
+
successThreshold: validations.positiveInteger("successThreshold").optional(),
|
|
2510
|
+
failureThreshold: validations.positiveInteger("failureThreshold").optional()
|
|
2511
|
+
});
|
|
2512
|
+
|
|
2513
|
+
// src/composites/for-each.schema.ts
|
|
2514
|
+
var forEachConfigurationSchema = createNodeSchema("ForEach", {
|
|
2515
|
+
collectionKey: validations.blackboardKey,
|
|
2516
|
+
itemKey: validations.blackboardKey,
|
|
2517
|
+
indexKey: validations.blackboardKey.optional()
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2520
|
+
// src/composites/while.schema.ts
|
|
2521
|
+
var whileConfigurationSchema = createNodeSchema("While", {
|
|
2522
|
+
maxIterations: validations.positiveInteger("maxIterations").optional().default(1e3)
|
|
2523
|
+
});
|
|
2524
|
+
|
|
2525
|
+
// src/composites/sub-tree.schema.ts
|
|
2526
|
+
var subTreeConfigurationSchema = createNodeSchema("SubTree", {
|
|
2527
|
+
treeId: validations.treeId
|
|
2528
|
+
});
|
|
2529
|
+
|
|
2530
|
+
// src/composites/sequence.schema.ts
|
|
2531
|
+
var sequenceConfigurationSchema = nodeConfigurationSchema;
|
|
2532
|
+
|
|
2533
|
+
// src/composites/selector.schema.ts
|
|
2534
|
+
var selectorConfigurationSchema = nodeConfigurationSchema;
|
|
2535
|
+
|
|
2536
|
+
// src/composites/conditional.schema.ts
|
|
2537
|
+
var conditionalConfigurationSchema = nodeConfigurationSchema;
|
|
2538
|
+
|
|
2539
|
+
// src/composites/reactive-sequence.schema.ts
|
|
2540
|
+
var reactiveSequenceConfigurationSchema = nodeConfigurationSchema;
|
|
2541
|
+
|
|
2542
|
+
// src/composites/memory-sequence.schema.ts
|
|
2543
|
+
var memorySequenceConfigurationSchema = nodeConfigurationSchema;
|
|
2544
|
+
|
|
2545
|
+
// src/composites/recovery.schema.ts
|
|
2546
|
+
var recoveryConfigurationSchema = nodeConfigurationSchema;
|
|
2547
|
+
|
|
2548
|
+
// src/schemas/validation.ts
|
|
2549
|
+
var import_zod3 = require("zod");
|
|
2550
|
+
function zodErrorToConfigurationError(error, nodeType, nodeId) {
|
|
2551
|
+
const nodeIdentifier = nodeId ? `${nodeType}:${nodeId}` : nodeType;
|
|
2552
|
+
const issues = error.issues.map((issue) => {
|
|
2553
|
+
const path2 = issue.path.join(".");
|
|
2554
|
+
return ` - ${path2 ? path2 + ": " : ""}${issue.message}`;
|
|
2555
|
+
}).join("\n");
|
|
2556
|
+
const message = `Invalid configuration for ${nodeIdentifier}:
|
|
2557
|
+
${issues}`;
|
|
2558
|
+
const hint = "Check the node configuration and ensure all required fields are provided with valid values.";
|
|
2559
|
+
return new ConfigurationError(message, hint);
|
|
2560
|
+
}
|
|
2561
|
+
function validateConfiguration(schema, config, nodeType, nodeId) {
|
|
2562
|
+
try {
|
|
2563
|
+
return schema.parse(config);
|
|
2564
|
+
} catch (error) {
|
|
2565
|
+
if (error instanceof import_zod3.z.ZodError) {
|
|
2566
|
+
throw zodErrorToConfigurationError(error, nodeType, nodeId);
|
|
2567
|
+
}
|
|
2568
|
+
throw error;
|
|
2569
|
+
}
|
|
2570
|
+
}
|
|
2571
|
+
function safeValidateConfiguration(schema, config, nodeType, nodeId) {
|
|
2572
|
+
const result = schema.safeParse(config);
|
|
2573
|
+
if (result.success) {
|
|
2574
|
+
return { success: true, data: result.data };
|
|
2575
|
+
} else {
|
|
2576
|
+
return {
|
|
2577
|
+
success: false,
|
|
2578
|
+
error: zodErrorToConfigurationError(result.error, nodeType, nodeId)
|
|
2579
|
+
};
|
|
2580
|
+
}
|
|
2581
|
+
}
|
|
2582
|
+
|
|
2583
|
+
// src/schemas/tree-definition.schema.ts
|
|
2584
|
+
var import_zod4 = require("zod");
|
|
2585
|
+
var treeDefSchemaObject = import_zod4.z.object({
|
|
2586
|
+
type: import_zod4.z.string().min(1, "Node type is required"),
|
|
2587
|
+
id: import_zod4.z.string().optional(),
|
|
2588
|
+
name: import_zod4.z.string().optional(),
|
|
2589
|
+
props: import_zod4.z.record(import_zod4.z.string(), import_zod4.z.unknown()).optional(),
|
|
2590
|
+
children: import_zod4.z.array(
|
|
2591
|
+
import_zod4.z.lazy(() => treeDefinitionSchema)
|
|
2592
|
+
).optional()
|
|
2593
|
+
});
|
|
2594
|
+
var treeDefinitionSchema = treeDefSchemaObject;
|
|
2595
|
+
function validateTreeDefinition(definition) {
|
|
2596
|
+
return treeDefinitionSchema.parse(definition);
|
|
2597
|
+
}
|
|
2598
|
+
function validateDecoratorChildren(nodeType, children) {
|
|
2599
|
+
const childCount = children?.length || 0;
|
|
2600
|
+
if (childCount !== 1) {
|
|
2601
|
+
throw new Error(
|
|
2602
|
+
`Decorator ${nodeType} must have exactly one child (got ${childCount})`
|
|
2603
|
+
);
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
function validateCompositeChildren(nodeType, children, minChildren = 0) {
|
|
2607
|
+
const count = children?.length || 0;
|
|
2608
|
+
if (count < minChildren) {
|
|
2609
|
+
throw new Error(
|
|
2610
|
+
`Composite ${nodeType} requires at least ${minChildren} children (got ${count})`
|
|
2611
|
+
);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
function validateChildCount(nodeType, children, expectedCount = 0) {
|
|
2615
|
+
const count = children?.length || 0;
|
|
2616
|
+
if (count !== expectedCount) {
|
|
2617
|
+
throw new Error(
|
|
2618
|
+
`${nodeType} requires exactly ${expectedCount} children (got ${count})`
|
|
2619
|
+
);
|
|
2620
|
+
}
|
|
2621
|
+
}
|
|
2622
|
+
function validateChildCountRange(nodeType, children, minChildren = 0, maxChildren) {
|
|
2623
|
+
const count = children?.length || 0;
|
|
2624
|
+
if (count < minChildren) {
|
|
2625
|
+
throw new Error(
|
|
2626
|
+
`${nodeType} requires at least ${minChildren} children (got ${count})`
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
if (maxChildren !== void 0 && count > maxChildren) {
|
|
2630
|
+
throw new Error(
|
|
2631
|
+
`${nodeType} allows at most ${maxChildren} children (got ${count})`
|
|
2632
|
+
);
|
|
2633
|
+
}
|
|
2634
|
+
}
|
|
2635
|
+
|
|
2636
|
+
// src/schemas/index.ts
|
|
2637
|
+
var SchemaRegistry = class {
|
|
2638
|
+
schemas = /* @__PURE__ */ new Map();
|
|
2639
|
+
constructor() {
|
|
2640
|
+
this.registerAllSchemas();
|
|
2641
|
+
}
|
|
2642
|
+
/**
|
|
2643
|
+
* Register all node schemas
|
|
2644
|
+
* Called automatically on construction
|
|
2645
|
+
*/
|
|
2646
|
+
registerAllSchemas() {
|
|
2647
|
+
this.register("Timeout", timeoutConfigurationSchema);
|
|
2648
|
+
this.register("Delay", delayConfigurationSchema);
|
|
2649
|
+
this.register("Repeat", repeatConfigurationSchema);
|
|
2650
|
+
this.register("Invert", invertConfigurationSchema);
|
|
2651
|
+
this.register("ForceSuccess", forceSuccessConfigurationSchema);
|
|
2652
|
+
this.register("ForceFailure", forceFailureConfigurationSchema);
|
|
2653
|
+
this.register("Precondition", preconditionConfigurationSchema);
|
|
2654
|
+
this.register("RunOnce", runOnceConfigurationSchema);
|
|
2655
|
+
this.register(
|
|
2656
|
+
"KeepRunningUntilFailure",
|
|
2657
|
+
keepRunningUntilFailureConfigurationSchema
|
|
2658
|
+
);
|
|
2659
|
+
this.register("SoftAssert", softAssertConfigurationSchema);
|
|
2660
|
+
this.register("Parallel", parallelConfigurationSchema);
|
|
2661
|
+
this.register("ForEach", forEachConfigurationSchema);
|
|
2662
|
+
this.register("While", whileConfigurationSchema);
|
|
2663
|
+
this.register("SubTree", subTreeConfigurationSchema);
|
|
2664
|
+
this.register("Sequence", sequenceConfigurationSchema);
|
|
2665
|
+
this.register("Selector", selectorConfigurationSchema);
|
|
2666
|
+
this.register("Conditional", conditionalConfigurationSchema);
|
|
2667
|
+
this.register("ReactiveSequence", reactiveSequenceConfigurationSchema);
|
|
2668
|
+
this.register("MemorySequence", memorySequenceConfigurationSchema);
|
|
2669
|
+
this.register("Recovery", recoveryConfigurationSchema);
|
|
2670
|
+
}
|
|
2671
|
+
/**
|
|
2672
|
+
* Register a validation schema for a node type
|
|
2673
|
+
*
|
|
2674
|
+
* @param nodeType - Name of the node type (e.g., 'Timeout', 'Sequence')
|
|
2675
|
+
* @param schema - Zod schema for validating this node type's configuration
|
|
2676
|
+
* @throws Error if node type is already registered
|
|
2677
|
+
*
|
|
2678
|
+
* @example
|
|
2679
|
+
* ```typescript
|
|
2680
|
+
* schemaRegistry.register('Timeout', timeoutConfigurationSchema);
|
|
2681
|
+
* ```
|
|
2682
|
+
*/
|
|
2683
|
+
register(nodeType, schema) {
|
|
2684
|
+
if (this.schemas.has(nodeType)) {
|
|
2685
|
+
throw new Error(`Schema for node type '${nodeType}' already registered`);
|
|
2686
|
+
}
|
|
2687
|
+
this.schemas.set(nodeType, schema);
|
|
2688
|
+
}
|
|
2689
|
+
/**
|
|
2690
|
+
* Get schema for a node type
|
|
2691
|
+
* Returns base schema if no specific schema registered
|
|
2692
|
+
*
|
|
2693
|
+
* @param nodeType - Name of the node type
|
|
2694
|
+
* @returns Zod schema for the node type (or base schema if not found)
|
|
2695
|
+
*
|
|
2696
|
+
* @example
|
|
2697
|
+
* ```typescript
|
|
2698
|
+
* const schema = schemaRegistry.getSchema('Timeout');
|
|
2699
|
+
* const validated = schema.parse({ id: 'test', timeoutMs: 1000 });
|
|
2700
|
+
* ```
|
|
2701
|
+
*/
|
|
2702
|
+
getSchema(nodeType) {
|
|
2703
|
+
return this.schemas.get(nodeType) || nodeConfigurationSchema;
|
|
2704
|
+
}
|
|
2705
|
+
/**
|
|
2706
|
+
* Check if a schema is registered for a node type
|
|
2707
|
+
*
|
|
2708
|
+
* @param nodeType - Name of the node type
|
|
2709
|
+
* @returns True if schema is registered, false otherwise
|
|
2710
|
+
*/
|
|
2711
|
+
hasSchema(nodeType) {
|
|
2712
|
+
return this.schemas.has(nodeType);
|
|
2713
|
+
}
|
|
2714
|
+
/**
|
|
2715
|
+
* Get all registered node types
|
|
2716
|
+
*
|
|
2717
|
+
* @returns Array of registered node type names
|
|
2718
|
+
*/
|
|
2719
|
+
getRegisteredTypes() {
|
|
2720
|
+
return Array.from(this.schemas.keys());
|
|
2721
|
+
}
|
|
2722
|
+
/**
|
|
2723
|
+
* Validate configuration for a specific node type
|
|
2724
|
+
* Throws ZodError if validation fails
|
|
2725
|
+
*
|
|
2726
|
+
* @param nodeType - Name of the node type
|
|
2727
|
+
* @param config - Configuration object to validate
|
|
2728
|
+
* @returns Validated and parsed configuration
|
|
2729
|
+
* @throws ZodError if validation fails
|
|
2730
|
+
*
|
|
2731
|
+
* @example
|
|
2732
|
+
* ```typescript
|
|
2733
|
+
* const config = schemaRegistry.validate('Timeout', {
|
|
2734
|
+
* id: 'test',
|
|
2735
|
+
* timeoutMs: 1000
|
|
2736
|
+
* });
|
|
2737
|
+
* ```
|
|
2738
|
+
*/
|
|
2739
|
+
validate(nodeType, config) {
|
|
2740
|
+
const schema = this.getSchema(nodeType);
|
|
2741
|
+
return schema.parse(config);
|
|
2742
|
+
}
|
|
2743
|
+
/**
|
|
2744
|
+
* Safe validation that returns success/error result
|
|
2745
|
+
* Does not throw errors
|
|
2746
|
+
*
|
|
2747
|
+
* @param nodeType - Name of the node type
|
|
2748
|
+
* @param config - Configuration object to validate
|
|
2749
|
+
* @returns Result with success/error
|
|
2750
|
+
*
|
|
2751
|
+
* @example
|
|
2752
|
+
* ```typescript
|
|
2753
|
+
* const result = schemaRegistry.safeParse('Timeout', { id: 'test', timeoutMs: -100 });
|
|
2754
|
+
* if (result.success) {
|
|
2755
|
+
* console.log(result.data);
|
|
2756
|
+
* } else {
|
|
2757
|
+
* console.error(result.error);
|
|
2758
|
+
* }
|
|
2759
|
+
* ```
|
|
2760
|
+
*/
|
|
2761
|
+
safeParse(nodeType, config) {
|
|
2762
|
+
const schema = this.getSchema(nodeType);
|
|
2763
|
+
return schema.safeParse(config);
|
|
2764
|
+
}
|
|
2765
|
+
/**
|
|
2766
|
+
* Clear all registered schemas
|
|
2767
|
+
* Useful for testing
|
|
2768
|
+
*/
|
|
2769
|
+
clear() {
|
|
2770
|
+
this.schemas.clear();
|
|
2771
|
+
}
|
|
2772
|
+
};
|
|
2773
|
+
var schemaRegistry = new SchemaRegistry();
|
|
2774
|
+
|
|
2775
|
+
// src/registry.ts
|
|
2776
|
+
var Registry = class {
|
|
2777
|
+
nodeTypes = /* @__PURE__ */ new Map();
|
|
2778
|
+
nodeMetadata = /* @__PURE__ */ new Map();
|
|
2779
|
+
behaviorTrees = /* @__PURE__ */ new Map();
|
|
2780
|
+
constructor() {
|
|
2781
|
+
this.log("Registry created");
|
|
2782
|
+
}
|
|
2783
|
+
/**
|
|
2784
|
+
* Register a node type with the registry
|
|
2785
|
+
*/
|
|
2786
|
+
register(type, ctor, metadata) {
|
|
2787
|
+
if (this.nodeTypes.has(type)) {
|
|
2788
|
+
throw new Error(`Node type '${type}' is already registered`);
|
|
2789
|
+
}
|
|
2790
|
+
this.nodeTypes.set(type, ctor);
|
|
2791
|
+
const fullMetadata = {
|
|
2792
|
+
type,
|
|
2793
|
+
category: metadata?.category || "action",
|
|
2794
|
+
description: metadata?.description,
|
|
2795
|
+
ports: metadata?.ports || []
|
|
2796
|
+
};
|
|
2797
|
+
this.nodeMetadata.set(type, fullMetadata);
|
|
2798
|
+
this.log(`Registered node type: ${type} (${fullMetadata.category})`);
|
|
2799
|
+
}
|
|
2800
|
+
/**
|
|
2801
|
+
* Create a node instance by type
|
|
2802
|
+
* Validates configuration against schema before creating node
|
|
2803
|
+
*/
|
|
2804
|
+
create(type, config) {
|
|
2805
|
+
const Constructor = this.nodeTypes.get(type);
|
|
2806
|
+
if (!Constructor) {
|
|
2807
|
+
throw new Error(
|
|
2808
|
+
`Unknown node type: '${type}'. Available types: ${this.getRegisteredTypes().join(", ")}`
|
|
2809
|
+
);
|
|
2810
|
+
}
|
|
2811
|
+
const validatedConfig = validateConfiguration(
|
|
2812
|
+
schemaRegistry.getSchema(type),
|
|
2813
|
+
config,
|
|
2814
|
+
type,
|
|
2815
|
+
config.id
|
|
2816
|
+
);
|
|
2817
|
+
this.log(`Creating node of type: ${type} with id: ${validatedConfig.id}`);
|
|
2818
|
+
return new Constructor(validatedConfig);
|
|
2819
|
+
}
|
|
2820
|
+
/**
|
|
2821
|
+
* Check if a node type is registered
|
|
2822
|
+
*/
|
|
2823
|
+
has(type) {
|
|
2824
|
+
return this.nodeTypes.has(type);
|
|
2825
|
+
}
|
|
2826
|
+
/**
|
|
2827
|
+
* Get metadata for a node type
|
|
2828
|
+
*/
|
|
2829
|
+
getMetadata(type) {
|
|
2830
|
+
return this.nodeMetadata.get(type);
|
|
2831
|
+
}
|
|
2832
|
+
/**
|
|
2833
|
+
* Get all registered node types
|
|
2834
|
+
*/
|
|
2835
|
+
getRegisteredTypes() {
|
|
2836
|
+
return Array.from(this.nodeTypes.keys());
|
|
2837
|
+
}
|
|
2838
|
+
/**
|
|
2839
|
+
* Get registered types by category
|
|
2840
|
+
*/
|
|
2841
|
+
getTypesByCategory(category) {
|
|
2842
|
+
const types = [];
|
|
2843
|
+
for (const [type, metadata] of this.nodeMetadata) {
|
|
2844
|
+
if (metadata.category === category) {
|
|
2845
|
+
types.push(type);
|
|
2846
|
+
}
|
|
2847
|
+
}
|
|
2848
|
+
return types;
|
|
2849
|
+
}
|
|
2850
|
+
/**
|
|
2851
|
+
* Clear all registrations (node types, metadata, and behavior trees)
|
|
2852
|
+
*/
|
|
2853
|
+
clear() {
|
|
2854
|
+
this.nodeTypes.clear();
|
|
2855
|
+
this.nodeMetadata.clear();
|
|
2856
|
+
this.behaviorTrees.clear();
|
|
2857
|
+
this.log("Registry cleared (nodes and trees)");
|
|
2858
|
+
}
|
|
2859
|
+
/**
|
|
2860
|
+
* Register a behavior tree instance with source file metadata.
|
|
2861
|
+
* Used for reusable trees like elements, step groups, and test cases.
|
|
2862
|
+
*
|
|
2863
|
+
* @param id Unique identifier for the tree
|
|
2864
|
+
* @param tree BehaviorTree instance to register
|
|
2865
|
+
* @param sourceFile Path to the source .sigma file
|
|
2866
|
+
* @throws Error if a tree with the same ID is already registered
|
|
2867
|
+
*/
|
|
2868
|
+
registerTree(id, tree, sourceFile) {
|
|
2869
|
+
if (this.behaviorTrees.has(id)) {
|
|
2870
|
+
throw new Error(`Behavior tree '${id}' is already registered`);
|
|
2871
|
+
}
|
|
2872
|
+
this.behaviorTrees.set(id, { tree, sourceFile });
|
|
2873
|
+
this.log(`Registered behavior tree: ${id}`);
|
|
2874
|
+
}
|
|
2875
|
+
/**
|
|
2876
|
+
* Unregister a behavior tree by ID.
|
|
2877
|
+
* Useful for cleanup in long-running processes or tests.
|
|
2878
|
+
*
|
|
2879
|
+
* @param id Tree ID to unregister
|
|
2880
|
+
* @returns true if tree was found and removed, false otherwise
|
|
2881
|
+
*/
|
|
2882
|
+
unregisterTree(id) {
|
|
2883
|
+
const deleted = this.behaviorTrees.delete(id);
|
|
2884
|
+
if (deleted) {
|
|
2885
|
+
this.log(`Unregistered behavior tree: ${id}`);
|
|
2886
|
+
}
|
|
2887
|
+
return deleted;
|
|
2888
|
+
}
|
|
2889
|
+
/**
|
|
2890
|
+
* Replace an existing behavior tree registration.
|
|
2891
|
+
* If the tree doesn't exist, it will be registered as new.
|
|
2892
|
+
*
|
|
2893
|
+
* @param id Tree ID to replace
|
|
2894
|
+
* @param tree New BehaviorTree instance
|
|
2895
|
+
* @param sourceFile Path to the source .sigma file
|
|
2896
|
+
*/
|
|
2897
|
+
replaceTree(id, tree, sourceFile) {
|
|
2898
|
+
const existed = this.behaviorTrees.has(id);
|
|
2899
|
+
this.behaviorTrees.set(id, { tree, sourceFile });
|
|
2900
|
+
this.log(`${existed ? "Replaced" : "Registered"} behavior tree: ${id}`);
|
|
2901
|
+
}
|
|
2902
|
+
/**
|
|
2903
|
+
* Get a behavior tree instance by ID
|
|
2904
|
+
*/
|
|
2905
|
+
getTree(id) {
|
|
2906
|
+
return this.behaviorTrees.get(id)?.tree;
|
|
2907
|
+
}
|
|
2908
|
+
/**
|
|
2909
|
+
* Get the source file path for a behavior tree
|
|
2910
|
+
*/
|
|
2911
|
+
getTreeSourceFile(id) {
|
|
2912
|
+
return this.behaviorTrees.get(id)?.sourceFile;
|
|
2913
|
+
}
|
|
2914
|
+
/**
|
|
2915
|
+
* Get all trees that belong to a specific source file
|
|
2916
|
+
*/
|
|
2917
|
+
getTreesForFile(filePath) {
|
|
2918
|
+
const result = /* @__PURE__ */ new Map();
|
|
2919
|
+
for (const [id, entry] of this.behaviorTrees) {
|
|
2920
|
+
if (entry.sourceFile === filePath) {
|
|
2921
|
+
result.set(id, entry.tree);
|
|
2922
|
+
}
|
|
2923
|
+
}
|
|
2924
|
+
return result;
|
|
2925
|
+
}
|
|
2926
|
+
/**
|
|
2927
|
+
* Check if a behavior tree is registered
|
|
2928
|
+
*/
|
|
2929
|
+
hasTree(id) {
|
|
2930
|
+
return this.behaviorTrees.has(id);
|
|
2931
|
+
}
|
|
2932
|
+
/**
|
|
2933
|
+
* Clone a registered behavior tree for instantiation
|
|
2934
|
+
* Returns the cloned BehaviorTree (caller should use getRoot() for TreeNode)
|
|
2935
|
+
*/
|
|
2936
|
+
cloneTree(id) {
|
|
2937
|
+
const entry = this.behaviorTrees.get(id);
|
|
2938
|
+
if (!entry) {
|
|
2939
|
+
throw new Error(
|
|
2940
|
+
`Behavior tree '${id}' not found. Available trees: ${this.getAllTreeIds().join(", ") || "none"}`
|
|
2941
|
+
);
|
|
2942
|
+
}
|
|
2943
|
+
this.log(`Cloning behavior tree: ${id}`);
|
|
2944
|
+
return entry.tree.clone();
|
|
2945
|
+
}
|
|
2946
|
+
/**
|
|
2947
|
+
* Get all registered behavior tree IDs
|
|
2948
|
+
*/
|
|
2949
|
+
getAllTreeIds() {
|
|
2950
|
+
return Array.from(this.behaviorTrees.keys());
|
|
2951
|
+
}
|
|
2952
|
+
/**
|
|
2953
|
+
* Clear all registered behavior trees
|
|
2954
|
+
*/
|
|
2955
|
+
clearTrees() {
|
|
2956
|
+
this.behaviorTrees.clear();
|
|
2957
|
+
this.log("Cleared all behavior trees");
|
|
2958
|
+
}
|
|
2959
|
+
/**
|
|
2960
|
+
* Create a tree from a JSON definition
|
|
2961
|
+
* Validates tree structure before creating nodes
|
|
2962
|
+
*/
|
|
2963
|
+
createTree(definition) {
|
|
2964
|
+
const validatedDef = treeDefinitionSchema.parse(definition);
|
|
2965
|
+
if (!validatedDef.type) {
|
|
2966
|
+
throw new Error("Node definition must have a type");
|
|
2967
|
+
}
|
|
2968
|
+
const config = {
|
|
2969
|
+
id: validatedDef.id || `${validatedDef.type}_${Date.now()}`,
|
|
2970
|
+
name: validatedDef.name || validatedDef.id,
|
|
2971
|
+
...validatedDef.props
|
|
2972
|
+
};
|
|
2973
|
+
const node = this.create(validatedDef.type, config);
|
|
2974
|
+
if (validatedDef.children && Array.isArray(validatedDef.children)) {
|
|
2975
|
+
if ("setChild" in node && typeof node.setChild === "function") {
|
|
2976
|
+
if (validatedDef.children.length !== 1) {
|
|
2977
|
+
throw new Error(
|
|
2978
|
+
`Decorator ${validatedDef.type} must have exactly one child`
|
|
2979
|
+
);
|
|
2980
|
+
}
|
|
2981
|
+
const child = this.createTree(validatedDef.children[0]);
|
|
2982
|
+
node.setChild(child);
|
|
2983
|
+
} else if ("addChildren" in node && typeof node.addChildren === "function") {
|
|
2984
|
+
const children = validatedDef.children.map(
|
|
2985
|
+
(childDef) => this.createTree(childDef)
|
|
2986
|
+
);
|
|
2987
|
+
node.addChildren(
|
|
2988
|
+
children
|
|
2989
|
+
);
|
|
2990
|
+
}
|
|
2991
|
+
}
|
|
2992
|
+
return node;
|
|
2993
|
+
}
|
|
2994
|
+
/**
|
|
2995
|
+
* Safe tree creation that returns success/error result
|
|
2996
|
+
* Useful for user-facing tools that need graceful error handling
|
|
2997
|
+
*
|
|
2998
|
+
* @param definition - Tree definition to create
|
|
2999
|
+
* @returns Success result with tree or failure result with error
|
|
3000
|
+
*
|
|
3001
|
+
* @example
|
|
3002
|
+
* ```typescript
|
|
3003
|
+
* const result = registry.safeCreateTree({
|
|
3004
|
+
* type: 'Sequence',
|
|
3005
|
+
* id: 'root',
|
|
3006
|
+
* children: [...]
|
|
3007
|
+
* });
|
|
3008
|
+
*
|
|
3009
|
+
* if (result.success) {
|
|
3010
|
+
* console.log('Tree created:', result.tree);
|
|
3011
|
+
* } else {
|
|
3012
|
+
* console.error('Failed:', result.error.message);
|
|
3013
|
+
* }
|
|
3014
|
+
* ```
|
|
3015
|
+
*/
|
|
3016
|
+
safeCreateTree(definition) {
|
|
3017
|
+
try {
|
|
3018
|
+
const tree = this.createTree(definition);
|
|
3019
|
+
return { success: true, tree };
|
|
3020
|
+
} catch (error) {
|
|
3021
|
+
return {
|
|
3022
|
+
success: false,
|
|
3023
|
+
error: error instanceof Error ? error : new Error(String(error))
|
|
3024
|
+
};
|
|
3025
|
+
}
|
|
3026
|
+
}
|
|
3027
|
+
log(message) {
|
|
3028
|
+
console.log(`[Registry] ${message}`);
|
|
3029
|
+
}
|
|
3030
|
+
};
|
|
3031
|
+
|
|
3032
|
+
// src/test-nodes.ts
|
|
3033
|
+
var PrintAction = class extends ActionNode {
|
|
3034
|
+
message;
|
|
3035
|
+
constructor(config) {
|
|
3036
|
+
super(config);
|
|
3037
|
+
this.message = config.message || "Hello from PrintAction!";
|
|
3038
|
+
}
|
|
3039
|
+
async executeTick(context) {
|
|
3040
|
+
this.log(`Executing: "${this.message}"`);
|
|
3041
|
+
if (this.config.outputKey && typeof this.config.outputKey === "string") {
|
|
3042
|
+
context.blackboard.set(this.config.outputKey, this.message);
|
|
3043
|
+
}
|
|
3044
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
3045
|
+
return "SUCCESS" /* SUCCESS */;
|
|
3046
|
+
}
|
|
3047
|
+
};
|
|
3048
|
+
var WaitAction = class extends ActionNode {
|
|
3049
|
+
waitMs;
|
|
3050
|
+
startTime = null;
|
|
3051
|
+
constructor(config) {
|
|
3052
|
+
super(config);
|
|
3053
|
+
this.waitMs = config.waitMs || 1e3;
|
|
3054
|
+
}
|
|
3055
|
+
async executeTick(_context) {
|
|
3056
|
+
if (this.startTime === null) {
|
|
3057
|
+
this.startTime = Date.now();
|
|
3058
|
+
this.log(`Starting wait for ${this.waitMs}ms`);
|
|
3059
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
3060
|
+
return "RUNNING" /* RUNNING */;
|
|
3061
|
+
}
|
|
3062
|
+
const elapsed = Date.now() - this.startTime;
|
|
3063
|
+
if (elapsed < this.waitMs) {
|
|
3064
|
+
this.log(`Waiting... ${this.waitMs - elapsed}ms remaining`);
|
|
3065
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
3066
|
+
return "RUNNING" /* RUNNING */;
|
|
3067
|
+
}
|
|
3068
|
+
this.log(`Wait completed after ${elapsed}ms`);
|
|
3069
|
+
this.startTime = null;
|
|
3070
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
3071
|
+
return "SUCCESS" /* SUCCESS */;
|
|
3072
|
+
}
|
|
3073
|
+
onReset() {
|
|
3074
|
+
this.startTime = null;
|
|
3075
|
+
}
|
|
3076
|
+
onHalt() {
|
|
3077
|
+
this.startTime = null;
|
|
3078
|
+
}
|
|
3079
|
+
};
|
|
3080
|
+
var CounterAction = class extends ActionNode {
|
|
3081
|
+
counterKey;
|
|
3082
|
+
increment;
|
|
3083
|
+
constructor(config) {
|
|
3084
|
+
super(config);
|
|
3085
|
+
this.counterKey = config.counterKey || "counter";
|
|
3086
|
+
this.increment = config.increment || 1;
|
|
3087
|
+
}
|
|
3088
|
+
async executeTick(context) {
|
|
3089
|
+
const currentValue = context.blackboard.get(this.counterKey) || 0;
|
|
3090
|
+
const newValue = currentValue + this.increment;
|
|
3091
|
+
context.blackboard.set(this.counterKey, newValue);
|
|
3092
|
+
this.log(
|
|
3093
|
+
`Counter '${this.counterKey}' incremented from ${currentValue} to ${newValue}`
|
|
3094
|
+
);
|
|
3095
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
3096
|
+
return "SUCCESS" /* SUCCESS */;
|
|
3097
|
+
}
|
|
3098
|
+
};
|
|
3099
|
+
var MockAction = class extends ActionNode {
|
|
3100
|
+
returnStatus;
|
|
3101
|
+
ticksBeforeComplete;
|
|
3102
|
+
currentTicks = 0;
|
|
3103
|
+
constructor(config) {
|
|
3104
|
+
super(config);
|
|
3105
|
+
this.returnStatus = config.returnStatus || "SUCCESS" /* SUCCESS */;
|
|
3106
|
+
this.ticksBeforeComplete = config.ticksBeforeComplete || 1;
|
|
3107
|
+
}
|
|
3108
|
+
async executeTick(_context) {
|
|
3109
|
+
this.currentTicks++;
|
|
3110
|
+
if (this.currentTicks < this.ticksBeforeComplete) {
|
|
3111
|
+
this.log(
|
|
3112
|
+
`Running... (tick ${this.currentTicks}/${this.ticksBeforeComplete})`
|
|
3113
|
+
);
|
|
3114
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
3115
|
+
return "RUNNING" /* RUNNING */;
|
|
3116
|
+
}
|
|
3117
|
+
this.log(`Completing with status: ${this.returnStatus}`);
|
|
3118
|
+
this.currentTicks = 0;
|
|
3119
|
+
this._status = this.returnStatus;
|
|
3120
|
+
return this.returnStatus;
|
|
3121
|
+
}
|
|
3122
|
+
onReset() {
|
|
3123
|
+
this.currentTicks = 0;
|
|
3124
|
+
}
|
|
3125
|
+
onHalt() {
|
|
3126
|
+
this.currentTicks = 0;
|
|
3127
|
+
}
|
|
3128
|
+
};
|
|
3129
|
+
var CheckCondition = class extends ConditionNode {
|
|
3130
|
+
key;
|
|
3131
|
+
operator;
|
|
3132
|
+
value;
|
|
3133
|
+
constructor(config) {
|
|
3134
|
+
super(config);
|
|
3135
|
+
this.key = config.key;
|
|
3136
|
+
this.operator = config.operator || "==";
|
|
3137
|
+
this.value = config.value;
|
|
3138
|
+
}
|
|
3139
|
+
async executeTick(context) {
|
|
3140
|
+
const actualValue = context.blackboard.get(this.key);
|
|
3141
|
+
let result = false;
|
|
3142
|
+
switch (this.operator) {
|
|
3143
|
+
case "==":
|
|
3144
|
+
result = actualValue === this.value;
|
|
3145
|
+
break;
|
|
3146
|
+
case "!=":
|
|
3147
|
+
result = actualValue !== this.value;
|
|
3148
|
+
break;
|
|
3149
|
+
case ">":
|
|
3150
|
+
result = actualValue > this.value;
|
|
3151
|
+
break;
|
|
3152
|
+
case "<":
|
|
3153
|
+
result = actualValue < this.value;
|
|
3154
|
+
break;
|
|
3155
|
+
case ">=":
|
|
3156
|
+
result = actualValue >= this.value;
|
|
3157
|
+
break;
|
|
3158
|
+
case "<=":
|
|
3159
|
+
result = actualValue <= this.value;
|
|
3160
|
+
break;
|
|
3161
|
+
}
|
|
3162
|
+
this.log(
|
|
3163
|
+
`Checking: ${this.key} ${this.operator} ${this.value} => ${actualValue} ${this.operator} ${this.value} = ${result}`
|
|
3164
|
+
);
|
|
3165
|
+
this._status = result ? "SUCCESS" /* SUCCESS */ : "FAILURE" /* FAILURE */;
|
|
3166
|
+
return this._status;
|
|
3167
|
+
}
|
|
3168
|
+
};
|
|
3169
|
+
var AlwaysCondition = class extends ConditionNode {
|
|
3170
|
+
returnStatus;
|
|
3171
|
+
constructor(config) {
|
|
3172
|
+
super(config);
|
|
3173
|
+
this.returnStatus = config.returnStatus || "SUCCESS" /* SUCCESS */;
|
|
3174
|
+
}
|
|
3175
|
+
async executeTick(_context) {
|
|
3176
|
+
this.log(`Returning ${this.returnStatus}`);
|
|
3177
|
+
this._status = this.returnStatus;
|
|
3178
|
+
return this.returnStatus;
|
|
3179
|
+
}
|
|
3180
|
+
};
|
|
3181
|
+
var SuccessNode = class extends ActionNode {
|
|
3182
|
+
async executeTick(_context) {
|
|
3183
|
+
this.log("Executing (SUCCESS)");
|
|
3184
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
3185
|
+
return "SUCCESS" /* SUCCESS */;
|
|
3186
|
+
}
|
|
3187
|
+
};
|
|
3188
|
+
var FailureNode = class extends ActionNode {
|
|
3189
|
+
async executeTick(_context) {
|
|
3190
|
+
this.log("Executing (FAILURE)");
|
|
3191
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
3192
|
+
return "FAILURE" /* FAILURE */;
|
|
3193
|
+
}
|
|
3194
|
+
};
|
|
3195
|
+
var RunningNode = class extends ActionNode {
|
|
3196
|
+
async executeTick(_context) {
|
|
3197
|
+
this.log("Executing (RUNNING)");
|
|
3198
|
+
this._status = "RUNNING" /* RUNNING */;
|
|
3199
|
+
return "RUNNING" /* RUNNING */;
|
|
3200
|
+
}
|
|
3201
|
+
};
|
|
3202
|
+
|
|
3203
|
+
// src/utilities/log-message.ts
|
|
3204
|
+
var LogMessage = class extends ActionNode {
|
|
3205
|
+
message;
|
|
3206
|
+
level;
|
|
3207
|
+
constructor(config) {
|
|
3208
|
+
super(config);
|
|
3209
|
+
this.message = config.message;
|
|
3210
|
+
this.level = config.level || "info";
|
|
3211
|
+
}
|
|
3212
|
+
async executeTick(context) {
|
|
3213
|
+
try {
|
|
3214
|
+
const resolvedMessage = this.resolveMessage(this.message, context);
|
|
3215
|
+
switch (this.level) {
|
|
3216
|
+
case "warn":
|
|
3217
|
+
console.warn(`[LogMessage:${this.name}] ${resolvedMessage}`);
|
|
3218
|
+
break;
|
|
3219
|
+
case "error":
|
|
3220
|
+
console.error(`[LogMessage:${this.name}] ${resolvedMessage}`);
|
|
3221
|
+
break;
|
|
3222
|
+
case "debug":
|
|
3223
|
+
console.debug(`[LogMessage:${this.name}] ${resolvedMessage}`);
|
|
3224
|
+
break;
|
|
3225
|
+
default:
|
|
3226
|
+
console.log(`[LogMessage:${this.name}] ${resolvedMessage}`);
|
|
3227
|
+
break;
|
|
3228
|
+
}
|
|
3229
|
+
context.eventEmitter?.emit({
|
|
3230
|
+
type: "log" /* LOG */,
|
|
3231
|
+
nodeId: this.id,
|
|
3232
|
+
nodeName: this.name,
|
|
3233
|
+
nodeType: this.type,
|
|
3234
|
+
timestamp: Date.now(),
|
|
3235
|
+
data: { level: this.level, message: resolvedMessage }
|
|
3236
|
+
});
|
|
3237
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
3238
|
+
return "SUCCESS" /* SUCCESS */;
|
|
3239
|
+
} catch (error) {
|
|
3240
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3241
|
+
console.error(
|
|
3242
|
+
`[LogMessage:${this.name}] Failed to log message: ${errorMessage}`
|
|
3243
|
+
);
|
|
3244
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
3245
|
+
return "FAILURE" /* FAILURE */;
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
/**
|
|
3249
|
+
* Resolve variable references in message string
|
|
3250
|
+
* Supports ${key}, ${input.key}, ${bb.key}, ${env.KEY}, ${param.key}
|
|
3251
|
+
*/
|
|
3252
|
+
resolveMessage(message, context) {
|
|
3253
|
+
const varCtx = {
|
|
3254
|
+
blackboard: context.blackboard,
|
|
3255
|
+
input: context.input,
|
|
3256
|
+
testData: context.testData
|
|
3257
|
+
};
|
|
3258
|
+
const resolved = resolveString(message, varCtx);
|
|
3259
|
+
if (typeof resolved === "string") {
|
|
3260
|
+
return resolved;
|
|
3261
|
+
}
|
|
3262
|
+
if (resolved === null) {
|
|
3263
|
+
return "null";
|
|
3264
|
+
}
|
|
3265
|
+
if (typeof resolved === "object") {
|
|
3266
|
+
try {
|
|
3267
|
+
return JSON.stringify(resolved);
|
|
3268
|
+
} catch {
|
|
3269
|
+
return String(resolved);
|
|
3270
|
+
}
|
|
3271
|
+
}
|
|
3272
|
+
return String(resolved);
|
|
3273
|
+
}
|
|
3274
|
+
};
|
|
3275
|
+
|
|
3276
|
+
// src/utilities/regex-extract.ts
|
|
3277
|
+
var RegexExtract = class extends ActionNode {
|
|
3278
|
+
input;
|
|
3279
|
+
pattern;
|
|
3280
|
+
outputKey;
|
|
3281
|
+
flags;
|
|
3282
|
+
matchIndex;
|
|
3283
|
+
constructor(config) {
|
|
3284
|
+
super(config);
|
|
3285
|
+
this.input = config.input;
|
|
3286
|
+
this.pattern = config.pattern;
|
|
3287
|
+
this.outputKey = config.outputKey;
|
|
3288
|
+
this.flags = config.flags || "g";
|
|
3289
|
+
this.matchIndex = config.matchIndex;
|
|
3290
|
+
}
|
|
3291
|
+
async executeTick(context) {
|
|
3292
|
+
try {
|
|
3293
|
+
const text = context.blackboard.get(this.input);
|
|
3294
|
+
if (text === void 0 || text === null) {
|
|
3295
|
+
throw new ConfigurationError(
|
|
3296
|
+
`Input '${this.input}' not found in blackboard`
|
|
3297
|
+
);
|
|
3298
|
+
}
|
|
3299
|
+
if (typeof text !== "string") {
|
|
3300
|
+
throw new ConfigurationError(
|
|
3301
|
+
`Input '${this.input}' must be a string, got ${typeof text}`
|
|
3302
|
+
);
|
|
3303
|
+
}
|
|
3304
|
+
let regex;
|
|
3305
|
+
try {
|
|
3306
|
+
regex = new RegExp(this.pattern, this.flags);
|
|
3307
|
+
} catch (error) {
|
|
3308
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3309
|
+
throw new Error(`Invalid regex pattern: ${errorMessage}`);
|
|
3310
|
+
}
|
|
3311
|
+
const matches = text.match(regex) || [];
|
|
3312
|
+
let result;
|
|
3313
|
+
if (this.matchIndex !== void 0 && this.matchIndex >= 0) {
|
|
3314
|
+
result = matches[this.matchIndex] || null;
|
|
3315
|
+
this.log(`Extracted match at index ${this.matchIndex}: ${result}`);
|
|
3316
|
+
} else {
|
|
3317
|
+
result = matches;
|
|
3318
|
+
this.log(`Extracted ${matches.length} match(es) from input`);
|
|
3319
|
+
}
|
|
3320
|
+
this.setOutput(context, this.outputKey, result);
|
|
3321
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
3322
|
+
return "SUCCESS" /* SUCCESS */;
|
|
3323
|
+
} catch (error) {
|
|
3324
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
3325
|
+
this.log(`RegexExtract failed: ${errorMessage}`);
|
|
3326
|
+
this._status = "FAILURE" /* FAILURE */;
|
|
3327
|
+
return "FAILURE" /* FAILURE */;
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
};
|
|
3331
|
+
|
|
3332
|
+
// src/integrations/piece-executor.ts
|
|
3333
|
+
var PROVIDER_TO_PIECE = {
|
|
3334
|
+
// Google services
|
|
3335
|
+
"google": "google-sheets",
|
|
3336
|
+
"google-sheets": "google-sheets",
|
|
3337
|
+
"google-drive": "google-drive",
|
|
3338
|
+
"google-calendar": "google-calendar",
|
|
3339
|
+
"gmail": "gmail",
|
|
3340
|
+
// Communication
|
|
3341
|
+
"slack": "slack",
|
|
3342
|
+
"discord": "discord",
|
|
3343
|
+
"telegram": "telegram-bot",
|
|
3344
|
+
"twilio": "twilio",
|
|
3345
|
+
// AI/ML
|
|
3346
|
+
"openai": "openai",
|
|
3347
|
+
"anthropic": "anthropic",
|
|
3348
|
+
// CRM & Sales
|
|
3349
|
+
"hubspot": "hubspot",
|
|
3350
|
+
"salesforce": "salesforce",
|
|
3351
|
+
// Productivity
|
|
3352
|
+
"notion": "notion",
|
|
3353
|
+
"airtable": "airtable",
|
|
3354
|
+
"asana": "asana",
|
|
3355
|
+
"trello": "trello",
|
|
3356
|
+
// Development
|
|
3357
|
+
"github": "github",
|
|
3358
|
+
"gitlab": "gitlab",
|
|
3359
|
+
// E-commerce
|
|
3360
|
+
"shopify": "shopify",
|
|
3361
|
+
"stripe": "stripe",
|
|
3362
|
+
// Other
|
|
3363
|
+
"http": "http",
|
|
3364
|
+
"webhook": "http"
|
|
3365
|
+
};
|
|
3366
|
+
var pieceCache = /* @__PURE__ */ new Map();
|
|
3367
|
+
function getPiecePackageName(provider) {
|
|
3368
|
+
const pieceName = PROVIDER_TO_PIECE[provider.toLowerCase()];
|
|
3369
|
+
if (pieceName) {
|
|
3370
|
+
return `@activepieces/piece-${pieceName}`;
|
|
3371
|
+
}
|
|
3372
|
+
return `@activepieces/piece-${provider.toLowerCase()}`;
|
|
3373
|
+
}
|
|
3374
|
+
var PIECE_EXPORT_NAMES = {
|
|
3375
|
+
"@activepieces/piece-google-sheets": "googleSheets",
|
|
3376
|
+
"@activepieces/piece-slack": "slack",
|
|
3377
|
+
"@activepieces/piece-openai": "openai",
|
|
3378
|
+
"@activepieces/piece-discord": "discord",
|
|
3379
|
+
"@activepieces/piece-notion": "notion",
|
|
3380
|
+
"@activepieces/piece-github": "github",
|
|
3381
|
+
"@activepieces/piece-http": "http"
|
|
3382
|
+
};
|
|
3383
|
+
async function loadPiece(packageName) {
|
|
3384
|
+
if (pieceCache.has(packageName)) {
|
|
3385
|
+
return pieceCache.get(packageName);
|
|
3386
|
+
}
|
|
3387
|
+
try {
|
|
3388
|
+
const pieceModule = await import(packageName);
|
|
3389
|
+
let piece = pieceModule;
|
|
3390
|
+
const exportName = PIECE_EXPORT_NAMES[packageName];
|
|
3391
|
+
if (exportName && pieceModule[exportName]) {
|
|
3392
|
+
piece = pieceModule[exportName];
|
|
3393
|
+
} else {
|
|
3394
|
+
for (const key of Object.keys(pieceModule)) {
|
|
3395
|
+
const exported = pieceModule[key];
|
|
3396
|
+
if (exported && typeof exported === "object" && "_actions" in exported) {
|
|
3397
|
+
piece = exported;
|
|
3398
|
+
break;
|
|
3399
|
+
}
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
pieceCache.set(packageName, piece);
|
|
3403
|
+
return piece;
|
|
3404
|
+
} catch (error) {
|
|
3405
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
3406
|
+
if (message.includes("Cannot find module")) {
|
|
3407
|
+
throw new Error(
|
|
3408
|
+
`Active Pieces package '${packageName}' is not installed. Run: npm install ${packageName}`
|
|
3409
|
+
);
|
|
3410
|
+
}
|
|
3411
|
+
throw new Error(`Failed to load piece package '${packageName}': ${message}`);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
async function executePieceAction(request) {
|
|
3415
|
+
const { provider, action, inputs, auth } = request;
|
|
3416
|
+
const packageName = getPiecePackageName(provider);
|
|
3417
|
+
const piece = await loadPiece(packageName);
|
|
3418
|
+
let actions;
|
|
3419
|
+
if (piece._actions && typeof piece._actions === "object") {
|
|
3420
|
+
actions = piece._actions;
|
|
3421
|
+
} else if (piece.actions && typeof piece.actions === "object") {
|
|
3422
|
+
actions = piece.actions;
|
|
3423
|
+
} else if (piece.default && typeof piece.default === "object") {
|
|
3424
|
+
const defaultExport = piece.default;
|
|
3425
|
+
actions = defaultExport._actions || defaultExport.actions;
|
|
3426
|
+
}
|
|
3427
|
+
if (!actions) {
|
|
3428
|
+
throw new Error(
|
|
3429
|
+
`Piece '${packageName}' does not export actions. This may not be a valid Active Pieces package.`
|
|
3430
|
+
);
|
|
3431
|
+
}
|
|
3432
|
+
const actionDef = actions[action];
|
|
3433
|
+
if (!actionDef) {
|
|
3434
|
+
const availableActions = Object.keys(actions).join(", ");
|
|
3435
|
+
throw new Error(
|
|
3436
|
+
`Action '${action}' not found in piece '${packageName}'. Available actions: ${availableActions}`
|
|
3437
|
+
);
|
|
3438
|
+
}
|
|
3439
|
+
if (typeof actionDef.run !== "function") {
|
|
3440
|
+
throw new Error(
|
|
3441
|
+
`Action '${action}' in piece '${packageName}' does not have a run method`
|
|
3442
|
+
);
|
|
3443
|
+
}
|
|
3444
|
+
const runFn = actionDef.run;
|
|
3445
|
+
const result = await runFn({
|
|
3446
|
+
auth,
|
|
3447
|
+
propsValue: inputs,
|
|
3448
|
+
// Additional context that some pieces might need
|
|
3449
|
+
store: createMockStore(),
|
|
3450
|
+
files: createMockFiles()
|
|
3451
|
+
});
|
|
3452
|
+
return result;
|
|
3453
|
+
}
|
|
3454
|
+
function createMockStore() {
|
|
3455
|
+
const storage = {};
|
|
3456
|
+
return {
|
|
3457
|
+
async get(key) {
|
|
3458
|
+
return storage[key];
|
|
3459
|
+
},
|
|
3460
|
+
async put(key, value) {
|
|
3461
|
+
storage[key] = value;
|
|
3462
|
+
},
|
|
3463
|
+
async delete(key) {
|
|
3464
|
+
delete storage[key];
|
|
3465
|
+
}
|
|
3466
|
+
};
|
|
3467
|
+
}
|
|
3468
|
+
function createMockFiles() {
|
|
3469
|
+
return {
|
|
3470
|
+
async write(params) {
|
|
3471
|
+
console.log(`[PieceExecutor] Mock file write: ${params.fileName}`);
|
|
3472
|
+
return `mock://files/${params.fileName}`;
|
|
3473
|
+
}
|
|
3474
|
+
};
|
|
3475
|
+
}
|
|
3476
|
+
async function listPieceActions(provider) {
|
|
3477
|
+
const packageName = getPiecePackageName(provider);
|
|
3478
|
+
const piece = await loadPiece(packageName);
|
|
3479
|
+
let actions;
|
|
3480
|
+
if (piece._actions && typeof piece._actions === "object") {
|
|
3481
|
+
actions = piece._actions;
|
|
3482
|
+
} else if (piece.actions && typeof piece.actions === "object") {
|
|
3483
|
+
actions = piece.actions;
|
|
3484
|
+
} else if (piece.default && typeof piece.default === "object") {
|
|
3485
|
+
const defaultExport = piece.default;
|
|
3486
|
+
actions = defaultExport._actions || defaultExport.actions;
|
|
3487
|
+
}
|
|
3488
|
+
if (!actions) {
|
|
3489
|
+
return [];
|
|
3490
|
+
}
|
|
3491
|
+
return Object.entries(actions).map(([name, def]) => {
|
|
3492
|
+
const actionDef = def;
|
|
3493
|
+
return {
|
|
3494
|
+
name,
|
|
3495
|
+
displayName: actionDef.displayName,
|
|
3496
|
+
description: actionDef.description
|
|
3497
|
+
};
|
|
3498
|
+
});
|
|
3499
|
+
}
|
|
3500
|
+
async function isPieceInstalled(provider) {
|
|
3501
|
+
const packageName = getPiecePackageName(provider);
|
|
3502
|
+
try {
|
|
3503
|
+
await loadPiece(packageName);
|
|
3504
|
+
return true;
|
|
3505
|
+
} catch {
|
|
3506
|
+
return false;
|
|
3507
|
+
}
|
|
3508
|
+
}
|
|
3509
|
+
function clearPieceCache() {
|
|
3510
|
+
pieceCache.clear();
|
|
3511
|
+
}
|
|
3512
|
+
|
|
3513
|
+
// src/integrations/integration-action.ts
|
|
3514
|
+
var IntegrationAction = class extends ActionNode {
|
|
3515
|
+
provider;
|
|
3516
|
+
action;
|
|
3517
|
+
inputs;
|
|
3518
|
+
connectionId;
|
|
3519
|
+
storeResult;
|
|
3520
|
+
resultKey;
|
|
3521
|
+
constructor(config) {
|
|
3522
|
+
super(config);
|
|
3523
|
+
if (!config.provider) {
|
|
3524
|
+
throw new ConfigurationError("IntegrationAction requires provider");
|
|
3525
|
+
}
|
|
3526
|
+
if (!config.action) {
|
|
3527
|
+
throw new ConfigurationError("IntegrationAction requires action");
|
|
3528
|
+
}
|
|
3529
|
+
this.provider = config.provider;
|
|
3530
|
+
this.action = config.action;
|
|
3531
|
+
this.inputs = config.inputs || {};
|
|
3532
|
+
this.connectionId = config.connectionId;
|
|
3533
|
+
this.storeResult = config.storeResult !== false;
|
|
3534
|
+
this.resultKey = config.resultKey || `${this.id}.result`;
|
|
3535
|
+
}
|
|
3536
|
+
async executeTick(context) {
|
|
3537
|
+
const integrationContext = context;
|
|
3538
|
+
if (!integrationContext.tokenProvider) {
|
|
3539
|
+
this._lastError = "No token provider configured in context. Set context.tokenProvider to fetch OAuth tokens.";
|
|
3540
|
+
this.log(`Error: ${this._lastError}`);
|
|
3541
|
+
return "FAILURE" /* FAILURE */;
|
|
3542
|
+
}
|
|
3543
|
+
try {
|
|
3544
|
+
const resolvedInputs = this.resolveInputs(context);
|
|
3545
|
+
this.log(`Resolved inputs: ${JSON.stringify(resolvedInputs)}`);
|
|
3546
|
+
const auth = await integrationContext.tokenProvider(
|
|
3547
|
+
context,
|
|
3548
|
+
this.provider,
|
|
3549
|
+
this.connectionId
|
|
3550
|
+
);
|
|
3551
|
+
this.log(`Got authentication for provider: ${this.provider}`);
|
|
3552
|
+
const result = await this.executeAction(context, resolvedInputs, auth);
|
|
3553
|
+
if (this.storeResult) {
|
|
3554
|
+
context.blackboard.set(this.resultKey, result);
|
|
3555
|
+
this.log(`Stored result in blackboard: ${this.resultKey}`);
|
|
3556
|
+
}
|
|
3557
|
+
this.log(
|
|
3558
|
+
`Integration action completed: ${this.provider}/${this.action}`
|
|
3559
|
+
);
|
|
3560
|
+
return "SUCCESS" /* SUCCESS */;
|
|
3561
|
+
} catch (error) {
|
|
3562
|
+
this._lastError = error instanceof Error ? error.message : String(error);
|
|
3563
|
+
this.log(`Integration action failed: ${this._lastError}`);
|
|
3564
|
+
return "FAILURE" /* FAILURE */;
|
|
3565
|
+
}
|
|
3566
|
+
}
|
|
3567
|
+
/**
|
|
3568
|
+
* Dual-mode execution:
|
|
3569
|
+
* - Activity mode: Use context.activities (deterministic for Temporal)
|
|
3570
|
+
* - Standalone mode: Inline executePieceAction (for testing)
|
|
3571
|
+
*/
|
|
3572
|
+
async executeAction(context, inputs, auth) {
|
|
3573
|
+
const request = {
|
|
3574
|
+
provider: this.provider,
|
|
3575
|
+
action: this.action,
|
|
3576
|
+
inputs,
|
|
3577
|
+
auth
|
|
3578
|
+
};
|
|
3579
|
+
if (context.activities?.executePieceAction) {
|
|
3580
|
+
this.log(`Executing via activity: ${this.provider}/${this.action}`);
|
|
3581
|
+
return context.activities.executePieceAction(request);
|
|
3582
|
+
}
|
|
3583
|
+
this.log(`Executing inline (standalone): ${this.provider}/${this.action}`);
|
|
3584
|
+
return executePieceAction(request);
|
|
3585
|
+
}
|
|
3586
|
+
/**
|
|
3587
|
+
* Resolve variable references in inputs
|
|
3588
|
+
* Supports ${input.key}, ${bb.key}, ${env.KEY}, ${param.key}
|
|
3589
|
+
*/
|
|
3590
|
+
resolveInputs(context) {
|
|
3591
|
+
const varCtx = {
|
|
3592
|
+
blackboard: context.blackboard,
|
|
3593
|
+
input: context.input,
|
|
3594
|
+
testData: context.testData
|
|
3595
|
+
};
|
|
3596
|
+
return resolveValue(this.inputs, varCtx, { preserveUndefined: false });
|
|
3597
|
+
}
|
|
3598
|
+
};
|
|
3599
|
+
var envTokenProvider = async (_context, provider) => {
|
|
3600
|
+
const normalizedProvider = provider.toUpperCase().replace(/-/g, "_");
|
|
3601
|
+
const accessToken = process.env[`${normalizedProvider}_ACCESS_TOKEN`];
|
|
3602
|
+
if (accessToken) {
|
|
3603
|
+
return { access_token: accessToken };
|
|
3604
|
+
}
|
|
3605
|
+
const apiKey = process.env[`${normalizedProvider}_API_KEY`];
|
|
3606
|
+
if (apiKey) {
|
|
3607
|
+
return { api_key: apiKey };
|
|
3608
|
+
}
|
|
3609
|
+
throw new Error(
|
|
3610
|
+
`No token found for provider ${provider}. Set ${normalizedProvider}_ACCESS_TOKEN or ${normalizedProvider}_API_KEY environment variable.`
|
|
3611
|
+
);
|
|
3612
|
+
};
|
|
3613
|
+
|
|
3614
|
+
// src/actions/python-script.ts
|
|
3615
|
+
var PythonScript = class extends ActionNode {
|
|
3616
|
+
code;
|
|
3617
|
+
packages;
|
|
3618
|
+
timeout;
|
|
3619
|
+
allowedEnvVars;
|
|
3620
|
+
constructor(config) {
|
|
3621
|
+
super(config);
|
|
3622
|
+
if (!config.code) {
|
|
3623
|
+
throw new ConfigurationError("PythonScript requires code");
|
|
3624
|
+
}
|
|
3625
|
+
this.code = config.code;
|
|
3626
|
+
this.packages = config.packages || [];
|
|
3627
|
+
this.timeout = config.timeout || 6e4;
|
|
3628
|
+
this.allowedEnvVars = config.allowedEnvVars || [];
|
|
3629
|
+
}
|
|
3630
|
+
async executeTick(context) {
|
|
3631
|
+
if (!context.activities?.executePythonScript) {
|
|
3632
|
+
this._lastError = "PythonScript requires activities.executePythonScript to be configured. This activity executes Python code in a separate worker process.";
|
|
3633
|
+
this.log(`Error: ${this._lastError}`);
|
|
3634
|
+
return "FAILURE" /* FAILURE */;
|
|
3635
|
+
}
|
|
3636
|
+
try {
|
|
3637
|
+
const request = {
|
|
3638
|
+
code: this.resolveCode(context),
|
|
3639
|
+
blackboard: context.blackboard.toJSON(),
|
|
3640
|
+
input: context.input ? { ...context.input } : void 0,
|
|
3641
|
+
env: this.getAllowedEnv(),
|
|
3642
|
+
timeout: this.timeout
|
|
3643
|
+
};
|
|
3644
|
+
this.log(`Executing Python script (${this.code.length} chars, timeout: ${this.timeout}ms)`);
|
|
3645
|
+
const result = await context.activities.executePythonScript(request);
|
|
3646
|
+
for (const [key, value] of Object.entries(result.blackboard)) {
|
|
3647
|
+
context.blackboard.set(key, value);
|
|
3648
|
+
}
|
|
3649
|
+
this.log(`Python script completed, ${Object.keys(result.blackboard).length} blackboard keys updated`);
|
|
3650
|
+
if (result.stdout) {
|
|
3651
|
+
this.log(`Python stdout: ${result.stdout}`);
|
|
3652
|
+
}
|
|
3653
|
+
if (result.stderr) {
|
|
3654
|
+
this.log(`Python stderr: ${result.stderr}`);
|
|
3655
|
+
}
|
|
3656
|
+
return "SUCCESS" /* SUCCESS */;
|
|
3657
|
+
} catch (error) {
|
|
3658
|
+
this._lastError = error instanceof Error ? error.message : String(error);
|
|
3659
|
+
this.log(`Python script failed: ${this._lastError}`);
|
|
3660
|
+
return "FAILURE" /* FAILURE */;
|
|
3661
|
+
}
|
|
3662
|
+
}
|
|
3663
|
+
/**
|
|
3664
|
+
* Resolve variable references in the Python code
|
|
3665
|
+
* Allows dynamic code templates like:
|
|
3666
|
+
* code: "bb['output_key'] = '${input.prefix}_result'"
|
|
3667
|
+
*/
|
|
3668
|
+
resolveCode(context) {
|
|
3669
|
+
const varCtx = {
|
|
3670
|
+
blackboard: context.blackboard,
|
|
3671
|
+
input: context.input,
|
|
3672
|
+
testData: context.testData
|
|
3673
|
+
};
|
|
3674
|
+
return resolveString(this.code, varCtx);
|
|
3675
|
+
}
|
|
3676
|
+
/**
|
|
3677
|
+
* Get allowed environment variables to pass to Python
|
|
3678
|
+
*/
|
|
3679
|
+
getAllowedEnv() {
|
|
3680
|
+
const env = {};
|
|
3681
|
+
for (const varName of this.allowedEnvVars) {
|
|
3682
|
+
const value = process.env[varName];
|
|
3683
|
+
if (value !== void 0) {
|
|
3684
|
+
env[varName] = value;
|
|
3685
|
+
}
|
|
3686
|
+
}
|
|
3687
|
+
return env;
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
|
|
3691
|
+
// src/actions/parse-file.ts
|
|
3692
|
+
var ParseFile = class extends ActionNode {
|
|
3693
|
+
file;
|
|
3694
|
+
format;
|
|
3695
|
+
sheetName;
|
|
3696
|
+
columnMapping;
|
|
3697
|
+
outputKey;
|
|
3698
|
+
options;
|
|
3699
|
+
constructor(config) {
|
|
3700
|
+
super(config);
|
|
3701
|
+
if (!config.file) {
|
|
3702
|
+
throw new ConfigurationError("ParseFile requires file");
|
|
3703
|
+
}
|
|
3704
|
+
if (!config.outputKey) {
|
|
3705
|
+
throw new ConfigurationError("ParseFile requires outputKey");
|
|
3706
|
+
}
|
|
3707
|
+
this.file = config.file;
|
|
3708
|
+
this.format = config.format || "auto";
|
|
3709
|
+
this.sheetName = config.sheetName;
|
|
3710
|
+
this.columnMapping = config.columnMapping;
|
|
3711
|
+
this.outputKey = config.outputKey;
|
|
3712
|
+
this.options = config.options;
|
|
3713
|
+
}
|
|
3714
|
+
async executeTick(context) {
|
|
3715
|
+
if (!context.activities?.parseFile) {
|
|
3716
|
+
this._lastError = "ParseFile requires activities.parseFile to be configured. This activity handles file I/O outside the workflow sandbox.";
|
|
3717
|
+
this.log(`Error: ${this._lastError}`);
|
|
3718
|
+
return "FAILURE" /* FAILURE */;
|
|
3719
|
+
}
|
|
3720
|
+
try {
|
|
3721
|
+
const varCtx = {
|
|
3722
|
+
blackboard: context.blackboard,
|
|
3723
|
+
input: context.input,
|
|
3724
|
+
testData: context.testData
|
|
3725
|
+
};
|
|
3726
|
+
const resolvedFile = resolveValue(this.file, varCtx);
|
|
3727
|
+
const request = {
|
|
3728
|
+
file: resolvedFile,
|
|
3729
|
+
format: this.format,
|
|
3730
|
+
sheetName: this.sheetName,
|
|
3731
|
+
columnMapping: this.columnMapping,
|
|
3732
|
+
options: this.options
|
|
3733
|
+
};
|
|
3734
|
+
this.log(`Parsing file: ${resolvedFile} (format: ${this.format || "auto"})`);
|
|
3735
|
+
const result = await context.activities.parseFile(request);
|
|
3736
|
+
context.blackboard.set(this.outputKey, result.data);
|
|
3737
|
+
this.log(
|
|
3738
|
+
`Parsed ${result.rowCount} rows from ${resolvedFile}, columns: [${result.columns.join(", ")}]`
|
|
3739
|
+
);
|
|
3740
|
+
return "SUCCESS" /* SUCCESS */;
|
|
3741
|
+
} catch (error) {
|
|
3742
|
+
this._lastError = error instanceof Error ? error.message : String(error);
|
|
3743
|
+
this.log(`Parse file failed: ${this._lastError}`);
|
|
3744
|
+
return "FAILURE" /* FAILURE */;
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
};
|
|
3748
|
+
|
|
3749
|
+
// src/actions/generate-file.ts
|
|
3750
|
+
var GenerateFile = class extends ActionNode {
|
|
3751
|
+
format;
|
|
3752
|
+
dataKey;
|
|
3753
|
+
columns;
|
|
3754
|
+
filename;
|
|
3755
|
+
storage;
|
|
3756
|
+
outputKey;
|
|
3757
|
+
constructor(config) {
|
|
3758
|
+
super(config);
|
|
3759
|
+
if (!config.format) {
|
|
3760
|
+
throw new ConfigurationError("GenerateFile requires format");
|
|
3761
|
+
}
|
|
3762
|
+
if (!config.dataKey) {
|
|
3763
|
+
throw new ConfigurationError("GenerateFile requires dataKey");
|
|
3764
|
+
}
|
|
3765
|
+
if (!config.filename) {
|
|
3766
|
+
throw new ConfigurationError("GenerateFile requires filename");
|
|
3767
|
+
}
|
|
3768
|
+
if (!config.storage) {
|
|
3769
|
+
throw new ConfigurationError("GenerateFile requires storage");
|
|
3770
|
+
}
|
|
3771
|
+
if (!config.outputKey) {
|
|
3772
|
+
throw new ConfigurationError("GenerateFile requires outputKey");
|
|
3773
|
+
}
|
|
3774
|
+
this.format = config.format;
|
|
3775
|
+
this.dataKey = config.dataKey;
|
|
3776
|
+
this.columns = config.columns;
|
|
3777
|
+
this.filename = config.filename;
|
|
3778
|
+
this.storage = config.storage;
|
|
3779
|
+
this.outputKey = config.outputKey;
|
|
3780
|
+
}
|
|
3781
|
+
async executeTick(context) {
|
|
3782
|
+
if (!context.activities?.generateFile) {
|
|
3783
|
+
this._lastError = "GenerateFile requires activities.generateFile to be configured. This activity handles file I/O outside the workflow sandbox.";
|
|
3784
|
+
this.log(`Error: ${this._lastError}`);
|
|
3785
|
+
return "FAILURE" /* FAILURE */;
|
|
3786
|
+
}
|
|
3787
|
+
try {
|
|
3788
|
+
const varCtx = {
|
|
3789
|
+
blackboard: context.blackboard,
|
|
3790
|
+
input: context.input,
|
|
3791
|
+
testData: context.testData
|
|
3792
|
+
};
|
|
3793
|
+
const data = context.blackboard.get(this.dataKey);
|
|
3794
|
+
if (!Array.isArray(data)) {
|
|
3795
|
+
this._lastError = `Data at '${this.dataKey}' is not an array (got ${typeof data})`;
|
|
3796
|
+
this.log(`Error: ${this._lastError}`);
|
|
3797
|
+
return "FAILURE" /* FAILURE */;
|
|
3798
|
+
}
|
|
3799
|
+
const resolvedFilename = resolveValue(this.filename, varCtx);
|
|
3800
|
+
const request = {
|
|
3801
|
+
format: this.format,
|
|
3802
|
+
data,
|
|
3803
|
+
columns: this.columns,
|
|
3804
|
+
filename: resolvedFilename,
|
|
3805
|
+
storage: this.storage
|
|
3806
|
+
};
|
|
3807
|
+
this.log(
|
|
3808
|
+
`Generating ${this.format} file: ${resolvedFilename} (${data.length} rows, storage: ${this.storage})`
|
|
3809
|
+
);
|
|
3810
|
+
const result = await context.activities.generateFile(request);
|
|
3811
|
+
context.blackboard.set(this.outputKey, result);
|
|
3812
|
+
this.log(
|
|
3813
|
+
`Generated file: ${result.filename} (${result.size} bytes, path: ${result.path})`
|
|
3814
|
+
);
|
|
3815
|
+
return "SUCCESS" /* SUCCESS */;
|
|
3816
|
+
} catch (error) {
|
|
3817
|
+
this._lastError = error instanceof Error ? error.message : String(error);
|
|
3818
|
+
this.log(`Generate file failed: ${this._lastError}`);
|
|
3819
|
+
return "FAILURE" /* FAILURE */;
|
|
3820
|
+
}
|
|
3821
|
+
}
|
|
3822
|
+
};
|
|
3823
|
+
|
|
3824
|
+
// src/actions/http-request.ts
|
|
3825
|
+
var HttpRequest = class extends ActionNode {
|
|
3826
|
+
url;
|
|
3827
|
+
method;
|
|
3828
|
+
headers;
|
|
3829
|
+
body;
|
|
3830
|
+
responseType;
|
|
3831
|
+
timeout;
|
|
3832
|
+
retry;
|
|
3833
|
+
outputKey;
|
|
3834
|
+
constructor(config) {
|
|
3835
|
+
super(config);
|
|
3836
|
+
if (!config.url) {
|
|
3837
|
+
throw new ConfigurationError("HttpRequest requires url");
|
|
3838
|
+
}
|
|
3839
|
+
if (!config.outputKey) {
|
|
3840
|
+
throw new ConfigurationError("HttpRequest requires outputKey");
|
|
3841
|
+
}
|
|
3842
|
+
this.url = config.url;
|
|
3843
|
+
this.method = config.method || "GET";
|
|
3844
|
+
this.headers = config.headers;
|
|
3845
|
+
this.body = config.body;
|
|
3846
|
+
this.responseType = config.responseType || "json";
|
|
3847
|
+
this.timeout = config.timeout;
|
|
3848
|
+
this.retry = config.retry;
|
|
3849
|
+
this.outputKey = config.outputKey;
|
|
3850
|
+
}
|
|
3851
|
+
async executeTick(context) {
|
|
3852
|
+
if (!context.activities?.fetchUrl) {
|
|
3853
|
+
this._lastError = "HttpRequest requires activities.fetchUrl to be configured. This activity handles HTTP I/O outside the workflow sandbox.";
|
|
3854
|
+
this.log(`Error: ${this._lastError}`);
|
|
3855
|
+
return "FAILURE" /* FAILURE */;
|
|
3856
|
+
}
|
|
3857
|
+
try {
|
|
3858
|
+
const varCtx = {
|
|
3859
|
+
blackboard: context.blackboard,
|
|
3860
|
+
input: context.input,
|
|
3861
|
+
testData: context.testData
|
|
3862
|
+
};
|
|
3863
|
+
const resolvedUrl = resolveValue(this.url, varCtx);
|
|
3864
|
+
const resolvedHeaders = this.headers ? resolveValue(this.headers, varCtx) : void 0;
|
|
3865
|
+
const resolvedBody = this.body !== void 0 ? resolveValue(this.body, varCtx) : void 0;
|
|
3866
|
+
const request = {
|
|
3867
|
+
url: resolvedUrl,
|
|
3868
|
+
method: this.method || "GET",
|
|
3869
|
+
headers: resolvedHeaders,
|
|
3870
|
+
body: resolvedBody,
|
|
3871
|
+
timeout: this.timeout
|
|
3872
|
+
};
|
|
3873
|
+
this.log(`HTTP ${request.method} ${resolvedUrl}`);
|
|
3874
|
+
let response;
|
|
3875
|
+
let lastError;
|
|
3876
|
+
const maxAttempts = this.retry?.maxAttempts || 1;
|
|
3877
|
+
const backoffMs = this.retry?.backoffMs || 1e3;
|
|
3878
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
3879
|
+
try {
|
|
3880
|
+
response = await context.activities.fetchUrl(request);
|
|
3881
|
+
lastError = void 0;
|
|
3882
|
+
break;
|
|
3883
|
+
} catch (error) {
|
|
3884
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
3885
|
+
if (attempt < maxAttempts) {
|
|
3886
|
+
const delay = backoffMs * Math.pow(2, attempt - 1);
|
|
3887
|
+
this.log(`Request failed, retrying in ${delay}ms (attempt ${attempt}/${maxAttempts})`);
|
|
3888
|
+
await this.sleep(delay);
|
|
3889
|
+
}
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
if (lastError) {
|
|
3893
|
+
throw lastError;
|
|
3894
|
+
}
|
|
3895
|
+
if (!response) {
|
|
3896
|
+
throw new Error("No response received");
|
|
3897
|
+
}
|
|
3898
|
+
let parsedData = response.data;
|
|
3899
|
+
if (this.responseType === "json" && typeof response.data === "string") {
|
|
3900
|
+
try {
|
|
3901
|
+
parsedData = JSON.parse(response.data);
|
|
3902
|
+
} catch {
|
|
3903
|
+
this.log("Response is not valid JSON, keeping as string");
|
|
3904
|
+
}
|
|
3905
|
+
}
|
|
3906
|
+
context.blackboard.set(this.outputKey, {
|
|
3907
|
+
status: response.status,
|
|
3908
|
+
headers: response.headers,
|
|
3909
|
+
data: parsedData
|
|
3910
|
+
});
|
|
3911
|
+
this.log(
|
|
3912
|
+
`HTTP ${request.method} ${resolvedUrl} -> ${response.status}`
|
|
3913
|
+
);
|
|
3914
|
+
return response.status >= 200 && response.status < 300 ? "SUCCESS" /* SUCCESS */ : "FAILURE" /* FAILURE */;
|
|
3915
|
+
} catch (error) {
|
|
3916
|
+
this._lastError = error instanceof Error ? error.message : String(error);
|
|
3917
|
+
this.log(`HTTP request failed: ${this._lastError}`);
|
|
3918
|
+
return "FAILURE" /* FAILURE */;
|
|
3919
|
+
}
|
|
3920
|
+
}
|
|
3921
|
+
/**
|
|
3922
|
+
* Sleep for the specified duration
|
|
3923
|
+
*/
|
|
3924
|
+
sleep(ms) {
|
|
3925
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3926
|
+
}
|
|
3927
|
+
};
|
|
3928
|
+
|
|
3929
|
+
// src/data-store/types.ts
|
|
3930
|
+
function isDataRef(value) {
|
|
3931
|
+
if (typeof value !== "object" || value === null) {
|
|
3932
|
+
return false;
|
|
3933
|
+
}
|
|
3934
|
+
const obj = value;
|
|
3935
|
+
return typeof obj.store === "string" && ["gcs", "s3", "redis", "memory"].includes(obj.store) && typeof obj.key === "string" && obj.key.length > 0;
|
|
3936
|
+
}
|
|
3937
|
+
|
|
3938
|
+
// src/actions/code-execution.ts
|
|
3939
|
+
var CodeExecution = class extends ActionNode {
|
|
3940
|
+
code;
|
|
3941
|
+
language;
|
|
3942
|
+
timeout;
|
|
3943
|
+
packages;
|
|
3944
|
+
constructor(config) {
|
|
3945
|
+
super(config);
|
|
3946
|
+
if (!config.code) {
|
|
3947
|
+
throw new ConfigurationError("CodeExecution requires code");
|
|
3948
|
+
}
|
|
3949
|
+
if (!config.language) {
|
|
3950
|
+
throw new ConfigurationError("CodeExecution requires language");
|
|
3951
|
+
}
|
|
3952
|
+
if (!["javascript", "python"].includes(config.language)) {
|
|
3953
|
+
throw new ConfigurationError(
|
|
3954
|
+
`CodeExecution language must be 'javascript' or 'python', got: ${config.language}`
|
|
3955
|
+
);
|
|
3956
|
+
}
|
|
3957
|
+
this.code = config.code;
|
|
3958
|
+
this.language = config.language;
|
|
3959
|
+
this.timeout = config.timeout ?? 3e4;
|
|
3960
|
+
this.packages = config.packages ?? [];
|
|
3961
|
+
}
|
|
3962
|
+
async executeTick(context) {
|
|
3963
|
+
if (!context.activities?.executeCode) {
|
|
3964
|
+
this._lastError = "CodeExecution requires activities.executeCode to be configured. This activity handles sandboxed code execution via Microsandbox.";
|
|
3965
|
+
this.log(`Error: ${this._lastError}`);
|
|
3966
|
+
return "FAILURE" /* FAILURE */;
|
|
3967
|
+
}
|
|
3968
|
+
try {
|
|
3969
|
+
const dataRefs = {};
|
|
3970
|
+
const inlineContext = {};
|
|
3971
|
+
for (const [key, value] of Object.entries(context.blackboard.toJSON())) {
|
|
3972
|
+
if (isDataRef(value)) {
|
|
3973
|
+
dataRefs[key] = value;
|
|
3974
|
+
} else {
|
|
3975
|
+
inlineContext[key] = value;
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
const request = {
|
|
3979
|
+
code: this.code,
|
|
3980
|
+
language: this.language,
|
|
3981
|
+
dataRefs,
|
|
3982
|
+
context: inlineContext,
|
|
3983
|
+
input: context.input ? { ...context.input } : void 0,
|
|
3984
|
+
timeout: this.timeout,
|
|
3985
|
+
packages: this.packages.length > 0 ? this.packages : void 0,
|
|
3986
|
+
workflowId: context.workflowInfo?.workflowId
|
|
3987
|
+
};
|
|
3988
|
+
this.log(
|
|
3989
|
+
`Executing ${this.language} code (timeout: ${this.timeout}ms, packages: ${this.packages.length})`
|
|
3990
|
+
);
|
|
3991
|
+
const result = await context.activities.executeCode(request);
|
|
3992
|
+
if (result.logs.length > 0) {
|
|
3993
|
+
for (const log of result.logs) {
|
|
3994
|
+
this.log(`[sandbox] ${log}`);
|
|
3995
|
+
}
|
|
3996
|
+
}
|
|
3997
|
+
for (const [key, value] of Object.entries(result.values)) {
|
|
3998
|
+
context.blackboard.set(key, value);
|
|
3999
|
+
}
|
|
4000
|
+
for (const [key, ref] of Object.entries(result.dataRefs)) {
|
|
4001
|
+
context.blackboard.set(key, ref);
|
|
4002
|
+
}
|
|
4003
|
+
this.log(
|
|
4004
|
+
`Code execution completed in ${result.executionTimeMs}ms (${Object.keys(result.values).length} values, ${Object.keys(result.dataRefs).length} refs)`
|
|
4005
|
+
);
|
|
4006
|
+
return "SUCCESS" /* SUCCESS */;
|
|
4007
|
+
} catch (error) {
|
|
4008
|
+
let actualMessage = error instanceof Error ? error.message : String(error);
|
|
4009
|
+
if (error && typeof error === "object") {
|
|
4010
|
+
const err = error;
|
|
4011
|
+
if (err.cause?.cause?.message) {
|
|
4012
|
+
actualMessage = err.cause.cause.message;
|
|
4013
|
+
} else if (err.cause?.message) {
|
|
4014
|
+
actualMessage = err.cause.message;
|
|
4015
|
+
}
|
|
4016
|
+
}
|
|
4017
|
+
this.log(`Code execution failed: ${actualMessage}`);
|
|
4018
|
+
throw new Error(`Code execution failed: ${actualMessage}`);
|
|
4019
|
+
}
|
|
4020
|
+
}
|
|
4021
|
+
};
|
|
4022
|
+
|
|
4023
|
+
// src/registry-utils.ts
|
|
4024
|
+
function registerStandardNodes(registry) {
|
|
4025
|
+
registry.register("Sequence", Sequence, { category: "composite" });
|
|
4026
|
+
registry.register("Selector", Selector, { category: "composite" });
|
|
4027
|
+
registry.register("Parallel", Parallel, { category: "composite" });
|
|
4028
|
+
registry.register("Conditional", Conditional, {
|
|
4029
|
+
category: "composite"
|
|
4030
|
+
});
|
|
4031
|
+
registry.register("ForEach", ForEach, { category: "composite" });
|
|
4032
|
+
registry.register("While", While, { category: "composite" });
|
|
4033
|
+
registry.register("Recovery", Recovery, { category: "composite" });
|
|
4034
|
+
registry.register("ReactiveSequence", ReactiveSequence, {
|
|
4035
|
+
category: "composite"
|
|
4036
|
+
});
|
|
4037
|
+
registry.register("MemorySequence", MemorySequence, {
|
|
4038
|
+
category: "composite"
|
|
4039
|
+
});
|
|
4040
|
+
registry.register("SubTree", SubTree, { category: "composite" });
|
|
4041
|
+
registry.register("Timeout", Timeout, { category: "decorator" });
|
|
4042
|
+
registry.register("Delay", Delay, { category: "decorator" });
|
|
4043
|
+
registry.register("Repeat", Repeat, { category: "decorator" });
|
|
4044
|
+
registry.register("Invert", Invert, { category: "decorator" });
|
|
4045
|
+
registry.register("ForceSuccess", ForceSuccess, {
|
|
4046
|
+
category: "decorator"
|
|
4047
|
+
});
|
|
4048
|
+
registry.register("ForceFailure", ForceFailure, {
|
|
4049
|
+
category: "decorator"
|
|
4050
|
+
});
|
|
4051
|
+
registry.register("RunOnce", RunOnce, { category: "decorator" });
|
|
4052
|
+
registry.register("KeepRunningUntilFailure", KeepRunningUntilFailure, {
|
|
4053
|
+
category: "decorator"
|
|
4054
|
+
});
|
|
4055
|
+
registry.register("Precondition", Precondition, {
|
|
4056
|
+
category: "decorator"
|
|
4057
|
+
});
|
|
4058
|
+
registry.register("SoftAssert", SoftAssert, { category: "decorator" });
|
|
4059
|
+
registry.register("PrintAction", PrintAction, { category: "action" });
|
|
4060
|
+
registry.register("MockAction", MockAction, { category: "action" });
|
|
4061
|
+
registry.register("SuccessNode", SuccessNode, { category: "action" });
|
|
4062
|
+
registry.register("FailureNode", FailureNode, { category: "action" });
|
|
4063
|
+
registry.register("RunningNode", RunningNode, { category: "action" });
|
|
4064
|
+
registry.register("CounterAction", CounterAction, {
|
|
4065
|
+
category: "action"
|
|
4066
|
+
});
|
|
4067
|
+
registry.register("CheckCondition", CheckCondition, {
|
|
4068
|
+
category: "condition"
|
|
4069
|
+
});
|
|
4070
|
+
registry.register("AlwaysCondition", AlwaysCondition, {
|
|
4071
|
+
category: "condition"
|
|
4072
|
+
});
|
|
4073
|
+
registry.register("WaitAction", WaitAction, { category: "action" });
|
|
4074
|
+
registry.register("LogMessage", LogMessage, { category: "action" });
|
|
4075
|
+
registry.register("RegexExtract", RegexExtract, { category: "action" });
|
|
4076
|
+
registry.register("IntegrationAction", IntegrationAction, { category: "action" });
|
|
4077
|
+
registry.register("PythonScript", PythonScript, { category: "action" });
|
|
4078
|
+
registry.register("ParseFile", ParseFile, { category: "action" });
|
|
4079
|
+
registry.register("GenerateFile", GenerateFile, { category: "action" });
|
|
4080
|
+
registry.register("HttpRequest", HttpRequest, { category: "action" });
|
|
4081
|
+
registry.register("CodeExecution", CodeExecution, { category: "action" });
|
|
4082
|
+
}
|
|
4083
|
+
|
|
4084
|
+
// src/data-store/memory-store.ts
|
|
4085
|
+
var MemoryDataStore = class {
|
|
4086
|
+
storage = /* @__PURE__ */ new Map();
|
|
4087
|
+
cleanupInterval = null;
|
|
4088
|
+
constructor(options) {
|
|
4089
|
+
const cleanupMs = options?.cleanupIntervalMs ?? 6e4;
|
|
4090
|
+
if (cleanupMs > 0) {
|
|
4091
|
+
this.cleanupInterval = setInterval(() => this.cleanup(), cleanupMs);
|
|
4092
|
+
if (this.cleanupInterval.unref) {
|
|
4093
|
+
this.cleanupInterval.unref();
|
|
4094
|
+
}
|
|
4095
|
+
}
|
|
4096
|
+
}
|
|
4097
|
+
async put(key, data, options) {
|
|
4098
|
+
const serialized = JSON.stringify(data);
|
|
4099
|
+
const sizeBytes = Buffer.byteLength(serialized, "utf8");
|
|
4100
|
+
const entry = {
|
|
4101
|
+
data,
|
|
4102
|
+
sizeBytes
|
|
4103
|
+
};
|
|
4104
|
+
if (options?.ttlSeconds) {
|
|
4105
|
+
entry.expiresAt = Date.now() + options.ttlSeconds * 1e3;
|
|
4106
|
+
}
|
|
4107
|
+
this.storage.set(key, entry);
|
|
4108
|
+
return {
|
|
4109
|
+
store: "memory",
|
|
4110
|
+
key,
|
|
4111
|
+
sizeBytes,
|
|
4112
|
+
expiresAt: entry.expiresAt
|
|
4113
|
+
};
|
|
4114
|
+
}
|
|
4115
|
+
async get(ref) {
|
|
4116
|
+
if (ref.store !== "memory") {
|
|
4117
|
+
throw new Error(`MemoryDataStore cannot retrieve from store: ${ref.store}`);
|
|
4118
|
+
}
|
|
4119
|
+
const entry = this.storage.get(ref.key);
|
|
4120
|
+
if (!entry) {
|
|
4121
|
+
throw new Error(`Data not found for key: ${ref.key}`);
|
|
4122
|
+
}
|
|
4123
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
4124
|
+
this.storage.delete(ref.key);
|
|
4125
|
+
throw new Error(`Data expired for key: ${ref.key}`);
|
|
4126
|
+
}
|
|
4127
|
+
return structuredClone(entry.data);
|
|
4128
|
+
}
|
|
4129
|
+
async delete(ref) {
|
|
4130
|
+
if (ref.store !== "memory") {
|
|
4131
|
+
throw new Error(`MemoryDataStore cannot delete from store: ${ref.store}`);
|
|
4132
|
+
}
|
|
4133
|
+
this.storage.delete(ref.key);
|
|
4134
|
+
}
|
|
4135
|
+
async exists(ref) {
|
|
4136
|
+
if (ref.store !== "memory") {
|
|
4137
|
+
return false;
|
|
4138
|
+
}
|
|
4139
|
+
const entry = this.storage.get(ref.key);
|
|
4140
|
+
if (!entry) {
|
|
4141
|
+
return false;
|
|
4142
|
+
}
|
|
4143
|
+
if (entry.expiresAt && Date.now() > entry.expiresAt) {
|
|
4144
|
+
this.storage.delete(ref.key);
|
|
4145
|
+
return false;
|
|
4146
|
+
}
|
|
4147
|
+
return true;
|
|
4148
|
+
}
|
|
4149
|
+
/**
|
|
4150
|
+
* Clear all stored data
|
|
4151
|
+
* Useful for test cleanup
|
|
4152
|
+
*/
|
|
4153
|
+
clear() {
|
|
4154
|
+
this.storage.clear();
|
|
4155
|
+
}
|
|
4156
|
+
/**
|
|
4157
|
+
* Get the number of stored entries
|
|
4158
|
+
*/
|
|
4159
|
+
size() {
|
|
4160
|
+
return this.storage.size;
|
|
4161
|
+
}
|
|
4162
|
+
/**
|
|
4163
|
+
* Get total bytes stored
|
|
4164
|
+
*/
|
|
4165
|
+
totalBytes() {
|
|
4166
|
+
let total = 0;
|
|
4167
|
+
for (const entry of this.storage.values()) {
|
|
4168
|
+
total += entry.sizeBytes;
|
|
4169
|
+
}
|
|
4170
|
+
return total;
|
|
4171
|
+
}
|
|
4172
|
+
/**
|
|
4173
|
+
* Stop the cleanup interval
|
|
4174
|
+
* Call this when done with the store to prevent memory leaks in tests
|
|
4175
|
+
*/
|
|
4176
|
+
dispose() {
|
|
4177
|
+
if (this.cleanupInterval) {
|
|
4178
|
+
clearInterval(this.cleanupInterval);
|
|
4179
|
+
this.cleanupInterval = null;
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
/**
|
|
4183
|
+
* Remove expired entries
|
|
4184
|
+
*/
|
|
4185
|
+
cleanup() {
|
|
4186
|
+
const now = Date.now();
|
|
4187
|
+
for (const [key, entry] of this.storage.entries()) {
|
|
4188
|
+
if (entry.expiresAt && now > entry.expiresAt) {
|
|
4189
|
+
this.storage.delete(key);
|
|
4190
|
+
}
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
4193
|
+
};
|
|
4194
|
+
|
|
4195
|
+
// src/debug/resume-point.ts
|
|
4196
|
+
var ResumePoint = class extends ActionNode {
|
|
4197
|
+
resumePointId;
|
|
4198
|
+
constructor(config) {
|
|
4199
|
+
super({ id: `resume-point-${config.id}` });
|
|
4200
|
+
this.resumePointId = config.id;
|
|
4201
|
+
}
|
|
4202
|
+
async executeTick(_context) {
|
|
4203
|
+
this._status = "SUCCESS" /* SUCCESS */;
|
|
4204
|
+
return "SUCCESS" /* SUCCESS */;
|
|
4205
|
+
}
|
|
4206
|
+
};
|
|
4207
|
+
|
|
4208
|
+
// src/yaml/parser.ts
|
|
4209
|
+
var yaml = __toESM(require("js-yaml"), 1);
|
|
4210
|
+
|
|
4211
|
+
// src/yaml/errors.ts
|
|
4212
|
+
var ValidationError = class extends Error {
|
|
4213
|
+
constructor(message, path2, suggestion) {
|
|
4214
|
+
super(message);
|
|
4215
|
+
this.path = path2;
|
|
4216
|
+
this.suggestion = suggestion;
|
|
4217
|
+
this.name = "ValidationError";
|
|
4218
|
+
}
|
|
4219
|
+
/**
|
|
4220
|
+
* Format error message with path and suggestion
|
|
4221
|
+
*/
|
|
4222
|
+
format() {
|
|
4223
|
+
let formatted = this.message;
|
|
4224
|
+
if (this.path) {
|
|
4225
|
+
formatted = `${this.path}: ${formatted}`;
|
|
4226
|
+
}
|
|
4227
|
+
if (this.suggestion) {
|
|
4228
|
+
formatted += `
|
|
4229
|
+
Suggestion: ${this.suggestion}`;
|
|
4230
|
+
}
|
|
4231
|
+
return formatted;
|
|
4232
|
+
}
|
|
4233
|
+
};
|
|
4234
|
+
var YamlSyntaxError = class extends ValidationError {
|
|
4235
|
+
constructor(message, line, column, suggestion) {
|
|
4236
|
+
super(message, void 0, suggestion);
|
|
4237
|
+
this.line = line;
|
|
4238
|
+
this.column = column;
|
|
4239
|
+
this.name = "YamlSyntaxError";
|
|
4240
|
+
}
|
|
4241
|
+
format() {
|
|
4242
|
+
let formatted = this.message;
|
|
4243
|
+
if (this.line !== void 0) {
|
|
4244
|
+
formatted = `Line ${this.line}${this.column !== void 0 ? `, Column ${this.column}` : ""}: ${formatted}`;
|
|
4245
|
+
}
|
|
4246
|
+
if (this.suggestion) {
|
|
4247
|
+
formatted += `
|
|
4248
|
+
Suggestion: ${this.suggestion}`;
|
|
4249
|
+
}
|
|
4250
|
+
return formatted;
|
|
4251
|
+
}
|
|
4252
|
+
};
|
|
4253
|
+
var StructureValidationError = class extends ValidationError {
|
|
4254
|
+
constructor(message, path2, suggestion) {
|
|
4255
|
+
super(message, path2, suggestion);
|
|
4256
|
+
this.name = "StructureValidationError";
|
|
4257
|
+
}
|
|
4258
|
+
};
|
|
4259
|
+
var ConfigValidationError = class extends ValidationError {
|
|
4260
|
+
constructor(message, nodeType, path2, suggestion) {
|
|
4261
|
+
super(message, path2, suggestion);
|
|
4262
|
+
this.nodeType = nodeType;
|
|
4263
|
+
this.name = "ConfigValidationError";
|
|
4264
|
+
}
|
|
4265
|
+
format() {
|
|
4266
|
+
let formatted = `Invalid configuration for node type '${this.nodeType}'`;
|
|
4267
|
+
if (this.path) {
|
|
4268
|
+
formatted += ` at ${this.path}`;
|
|
4269
|
+
}
|
|
4270
|
+
formatted += `:
|
|
4271
|
+
${this.message}`;
|
|
4272
|
+
if (this.suggestion) {
|
|
4273
|
+
formatted += `
|
|
4274
|
+
Suggestion: ${this.suggestion}`;
|
|
4275
|
+
}
|
|
4276
|
+
return formatted;
|
|
4277
|
+
}
|
|
4278
|
+
};
|
|
4279
|
+
var SemanticValidationError = class extends ValidationError {
|
|
4280
|
+
constructor(message, path2, suggestion) {
|
|
4281
|
+
super(message, path2, suggestion);
|
|
4282
|
+
this.name = "SemanticValidationError";
|
|
4283
|
+
}
|
|
4284
|
+
};
|
|
4285
|
+
var ValidationErrors = class extends Error {
|
|
4286
|
+
constructor(errors) {
|
|
4287
|
+
super(`Validation failed with ${errors.length} error(s)`);
|
|
4288
|
+
this.errors = errors;
|
|
4289
|
+
this.name = "ValidationErrors";
|
|
4290
|
+
}
|
|
4291
|
+
/**
|
|
4292
|
+
* Format all errors as a single message
|
|
4293
|
+
*/
|
|
4294
|
+
format() {
|
|
4295
|
+
const header = `YAML validation failed
|
|
4296
|
+
|
|
4297
|
+
Issues found:`;
|
|
4298
|
+
const issues = this.errors.map((error, index) => {
|
|
4299
|
+
const formatted = error.format();
|
|
4300
|
+
return ` ${index + 1}. ${formatted.split("\n").join("\n ")}`;
|
|
4301
|
+
}).join("\n\n");
|
|
4302
|
+
return `${header}
|
|
4303
|
+
${issues}`;
|
|
4304
|
+
}
|
|
4305
|
+
};
|
|
4306
|
+
|
|
4307
|
+
// src/yaml/validation/semantic-validator.ts
|
|
4308
|
+
var SemanticValidator = class {
|
|
4309
|
+
/**
|
|
4310
|
+
* Validate semantic rules for a tree definition
|
|
4311
|
+
*
|
|
4312
|
+
* @param definition - Tree definition to validate
|
|
4313
|
+
* @param registry - Registry to check node types and tree references
|
|
4314
|
+
* @returns Array of validation errors (empty if valid)
|
|
4315
|
+
*/
|
|
4316
|
+
validate(definition, registry) {
|
|
4317
|
+
const errors = [];
|
|
4318
|
+
const seenIds = /* @__PURE__ */ new Set();
|
|
4319
|
+
const subTreePath = [];
|
|
4320
|
+
this.validateNode(definition, registry, seenIds, subTreePath, "", errors);
|
|
4321
|
+
return errors;
|
|
4322
|
+
}
|
|
4323
|
+
/**
|
|
4324
|
+
* Recursively validate a node and its children
|
|
4325
|
+
*/
|
|
4326
|
+
validateNode(node, registry, seenIds, subTreePath, path2, errors) {
|
|
4327
|
+
const nodePath = path2 ? `${path2}.${node.id || node.type}` : node.id || node.type;
|
|
4328
|
+
if (!registry.has(node.type)) {
|
|
4329
|
+
errors.push(
|
|
4330
|
+
new SemanticValidationError(
|
|
4331
|
+
`Unknown node type '${node.type}'`,
|
|
4332
|
+
nodePath,
|
|
4333
|
+
`Available types: ${registry.getRegisteredTypes().join(", ")}`
|
|
4334
|
+
)
|
|
4335
|
+
);
|
|
4336
|
+
return;
|
|
4337
|
+
}
|
|
4338
|
+
if (node.id) {
|
|
4339
|
+
if (seenIds.has(node.id)) {
|
|
4340
|
+
errors.push(
|
|
4341
|
+
new SemanticValidationError(
|
|
4342
|
+
`Duplicate ID '${node.id}'`,
|
|
4343
|
+
nodePath,
|
|
4344
|
+
"Use unique IDs for each node in the tree"
|
|
4345
|
+
)
|
|
4346
|
+
);
|
|
4347
|
+
} else {
|
|
4348
|
+
seenIds.add(node.id);
|
|
4349
|
+
}
|
|
4350
|
+
}
|
|
4351
|
+
if (node.type === "SubTree") {
|
|
4352
|
+
const treeId = node.props?.treeId || "";
|
|
4353
|
+
if (!treeId) {
|
|
4354
|
+
errors.push(
|
|
4355
|
+
new SemanticValidationError(
|
|
4356
|
+
"SubTree node missing 'treeId' property",
|
|
4357
|
+
nodePath,
|
|
4358
|
+
"Specify which tree to reference with 'treeId' in props"
|
|
4359
|
+
)
|
|
4360
|
+
);
|
|
4361
|
+
} else {
|
|
4362
|
+
if (subTreePath.includes(treeId)) {
|
|
4363
|
+
errors.push(
|
|
4364
|
+
new SemanticValidationError(
|
|
4365
|
+
`Circular SubTree reference detected: ${subTreePath.join(" -> ")} -> ${treeId}`,
|
|
4366
|
+
nodePath,
|
|
4367
|
+
"Remove circular tree references"
|
|
4368
|
+
)
|
|
4369
|
+
);
|
|
4370
|
+
}
|
|
4371
|
+
if (registry.hasTree && !registry.hasTree(treeId)) {
|
|
4372
|
+
errors.push(
|
|
4373
|
+
new SemanticValidationError(
|
|
4374
|
+
`SubTree references unknown tree '${treeId}'`,
|
|
4375
|
+
nodePath,
|
|
4376
|
+
`Register the tree '${treeId}' before referencing it`
|
|
4377
|
+
)
|
|
4378
|
+
);
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
}
|
|
4382
|
+
const metadata = registry.getMetadata(node.type);
|
|
4383
|
+
if (metadata) {
|
|
4384
|
+
if (metadata.category === "decorator") {
|
|
4385
|
+
const childCount = node.children?.length || 0;
|
|
4386
|
+
if (childCount !== 1) {
|
|
4387
|
+
errors.push(
|
|
4388
|
+
new SemanticValidationError(
|
|
4389
|
+
`Decorator '${node.type}' must have exactly 1 child (has ${childCount})`,
|
|
4390
|
+
nodePath,
|
|
4391
|
+
"Decorators wrap a single child node"
|
|
4392
|
+
)
|
|
4393
|
+
);
|
|
4394
|
+
}
|
|
4395
|
+
}
|
|
4396
|
+
if (node.type === "While") {
|
|
4397
|
+
const childCount = node.children?.length || 0;
|
|
4398
|
+
if (childCount !== 2) {
|
|
4399
|
+
errors.push(
|
|
4400
|
+
new SemanticValidationError(
|
|
4401
|
+
`While node requires exactly 2 children: condition and body (has ${childCount})`,
|
|
4402
|
+
nodePath,
|
|
4403
|
+
"First child is the condition, second is the body to execute"
|
|
4404
|
+
)
|
|
4405
|
+
);
|
|
4406
|
+
}
|
|
4407
|
+
}
|
|
4408
|
+
if (node.type === "Conditional") {
|
|
4409
|
+
const childCount = node.children?.length || 0;
|
|
4410
|
+
if (childCount < 2 || childCount > 3) {
|
|
4411
|
+
errors.push(
|
|
4412
|
+
new SemanticValidationError(
|
|
4413
|
+
`Conditional node requires 2-3 children: condition, then, optional else (has ${childCount})`,
|
|
4414
|
+
nodePath,
|
|
4415
|
+
"First child is condition, second is 'then' branch, third is optional 'else' branch"
|
|
4416
|
+
)
|
|
4417
|
+
);
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
if (node.type === "ForEach") {
|
|
4421
|
+
const childCount = node.children?.length || 0;
|
|
4422
|
+
if (childCount === 0) {
|
|
4423
|
+
errors.push(
|
|
4424
|
+
new SemanticValidationError(
|
|
4425
|
+
"ForEach node requires at least 1 child (the body to execute for each item)",
|
|
4426
|
+
nodePath,
|
|
4427
|
+
"Add a child node to execute for each item in the collection"
|
|
4428
|
+
)
|
|
4429
|
+
);
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
if (node.children && Array.isArray(node.children)) {
|
|
4434
|
+
const newSubTreePath = node.type === "SubTree" && node.props?.treeId ? [...subTreePath, node.props.treeId] : subTreePath;
|
|
4435
|
+
node.children.forEach((child, index) => {
|
|
4436
|
+
this.validateNode(
|
|
4437
|
+
child,
|
|
4438
|
+
registry,
|
|
4439
|
+
seenIds,
|
|
4440
|
+
newSubTreePath,
|
|
4441
|
+
`${nodePath}.children[${index}]`,
|
|
4442
|
+
errors
|
|
4443
|
+
);
|
|
4444
|
+
});
|
|
4445
|
+
}
|
|
4446
|
+
}
|
|
4447
|
+
};
|
|
4448
|
+
var semanticValidator = new SemanticValidator();
|
|
4449
|
+
|
|
4450
|
+
// src/yaml/parser.ts
|
|
4451
|
+
function parseYaml(yamlString) {
|
|
4452
|
+
try {
|
|
4453
|
+
const parsed = yaml.load(yamlString);
|
|
4454
|
+
if (!parsed || typeof parsed !== "object") {
|
|
4455
|
+
throw new YamlSyntaxError(
|
|
4456
|
+
"Invalid YAML: expected an object",
|
|
4457
|
+
void 0,
|
|
4458
|
+
void 0,
|
|
4459
|
+
"Ensure your YAML defines a tree structure with 'type' field"
|
|
4460
|
+
);
|
|
4461
|
+
}
|
|
4462
|
+
return parsed;
|
|
4463
|
+
} catch (error) {
|
|
4464
|
+
if (error instanceof YamlSyntaxError) {
|
|
4465
|
+
throw error;
|
|
4466
|
+
}
|
|
4467
|
+
if (error instanceof yaml.YAMLException) {
|
|
4468
|
+
throw new YamlSyntaxError(
|
|
4469
|
+
error.message,
|
|
4470
|
+
error.mark?.line,
|
|
4471
|
+
error.mark?.column,
|
|
4472
|
+
"Check YAML syntax (indentation, colons, quotes)"
|
|
4473
|
+
);
|
|
4474
|
+
}
|
|
4475
|
+
throw new YamlSyntaxError(
|
|
4476
|
+
`Failed to parse YAML: ${error instanceof Error ? error.message : String(error)}`,
|
|
4477
|
+
void 0,
|
|
4478
|
+
void 0,
|
|
4479
|
+
"Ensure your YAML is well-formed"
|
|
4480
|
+
);
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
function validateStructure(definition) {
|
|
4484
|
+
try {
|
|
4485
|
+
return treeDefinitionSchema.parse(definition);
|
|
4486
|
+
} catch (error) {
|
|
4487
|
+
throw new StructureValidationError(
|
|
4488
|
+
`Invalid tree structure: ${error instanceof Error ? error.message : String(error)}`,
|
|
4489
|
+
void 0,
|
|
4490
|
+
"Ensure all nodes have 'type' field and children are arrays"
|
|
4491
|
+
);
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
function loadTreeFromYaml(yamlString, registry, options = {}) {
|
|
4495
|
+
const { validate = true, failFast = true, autoGenerateIds = true } = options;
|
|
4496
|
+
const parsed = parseYaml(yamlString);
|
|
4497
|
+
if (validate) {
|
|
4498
|
+
const definition = validateStructure(parsed);
|
|
4499
|
+
if (failFast) {
|
|
4500
|
+
const semanticErrors = semanticValidator.validate(definition, registry);
|
|
4501
|
+
if (semanticErrors.length > 0) {
|
|
4502
|
+
if (failFast) {
|
|
4503
|
+
throw semanticErrors[0];
|
|
4504
|
+
} else {
|
|
4505
|
+
throw new ValidationErrors(semanticErrors);
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
return registry.createTree(definition);
|
|
4510
|
+
}
|
|
4511
|
+
return registry.createTree(parsed);
|
|
4512
|
+
}
|
|
4513
|
+
function validateYaml(yamlString, registry, options = {}) {
|
|
4514
|
+
const { collectAllErrors = false, checkReferences = true } = options;
|
|
4515
|
+
const errors = [];
|
|
4516
|
+
try {
|
|
4517
|
+
const parsed = parseYaml(yamlString);
|
|
4518
|
+
const definition = validateStructure(parsed);
|
|
4519
|
+
if (checkReferences) {
|
|
4520
|
+
const semanticErrors = semanticValidator.validate(definition, registry);
|
|
4521
|
+
errors.push(...semanticErrors);
|
|
4522
|
+
}
|
|
4523
|
+
if (errors.length === 0 || collectAllErrors) {
|
|
4524
|
+
const result = registry.safeCreateTree(definition);
|
|
4525
|
+
if (!result.success) {
|
|
4526
|
+
errors.push(
|
|
4527
|
+
new StructureValidationError(
|
|
4528
|
+
result.error.message,
|
|
4529
|
+
void 0,
|
|
4530
|
+
"Check node configurations match their schema requirements"
|
|
4531
|
+
)
|
|
4532
|
+
);
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
return {
|
|
4536
|
+
valid: errors.length === 0,
|
|
4537
|
+
errors
|
|
4538
|
+
};
|
|
4539
|
+
} catch (error) {
|
|
4540
|
+
if (error instanceof ValidationErrors) {
|
|
4541
|
+
return {
|
|
4542
|
+
valid: false,
|
|
4543
|
+
errors: error.errors
|
|
4544
|
+
};
|
|
4545
|
+
}
|
|
4546
|
+
if (error instanceof ValidationError) {
|
|
4547
|
+
errors.push(error);
|
|
4548
|
+
} else {
|
|
4549
|
+
errors.push(
|
|
4550
|
+
new YamlSyntaxError(
|
|
4551
|
+
error instanceof Error ? error.message : String(error)
|
|
4552
|
+
)
|
|
4553
|
+
);
|
|
4554
|
+
}
|
|
4555
|
+
return {
|
|
4556
|
+
valid: false,
|
|
4557
|
+
errors
|
|
4558
|
+
};
|
|
4559
|
+
}
|
|
4560
|
+
}
|
|
4561
|
+
function toYaml(definition) {
|
|
4562
|
+
return yaml.dump(definition, {
|
|
4563
|
+
indent: 2,
|
|
4564
|
+
lineWidth: 80,
|
|
4565
|
+
noRefs: true
|
|
4566
|
+
});
|
|
4567
|
+
}
|
|
4568
|
+
|
|
4569
|
+
// src/yaml/loader.ts
|
|
4570
|
+
var import_promises = require("fs/promises");
|
|
4571
|
+
async function loadTreeFromFile(filePath, registry, options = {}) {
|
|
4572
|
+
try {
|
|
4573
|
+
const yamlContent = await (0, import_promises.readFile)(filePath, "utf-8");
|
|
4574
|
+
return loadTreeFromYaml(yamlContent, registry, options);
|
|
4575
|
+
} catch (error) {
|
|
4576
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") {
|
|
4577
|
+
throw new Error(`File not found: ${filePath}`);
|
|
4578
|
+
}
|
|
4579
|
+
throw error;
|
|
4580
|
+
}
|
|
4581
|
+
}
|
|
4582
|
+
|
|
4583
|
+
// src/templates/template-loader.ts
|
|
4584
|
+
var fs = __toESM(require("fs"), 1);
|
|
4585
|
+
var path = __toESM(require("path"), 1);
|
|
4586
|
+
async function loadTemplatesFromDirectory(registry, options) {
|
|
4587
|
+
const { templatesDir, idPrefix = "" } = options;
|
|
4588
|
+
const loadedIds = [];
|
|
4589
|
+
if (!fs.existsSync(templatesDir)) {
|
|
4590
|
+
console.warn(
|
|
4591
|
+
`[TemplateLoader] Templates directory not found: ${templatesDir}`
|
|
4592
|
+
);
|
|
4593
|
+
return loadedIds;
|
|
4594
|
+
}
|
|
4595
|
+
const files = fs.readdirSync(templatesDir).filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
4596
|
+
for (const file of files) {
|
|
4597
|
+
const filePath = path.join(templatesDir, file);
|
|
4598
|
+
try {
|
|
4599
|
+
const yamlContent = fs.readFileSync(filePath, "utf-8");
|
|
4600
|
+
const rootNode = loadTreeFromYaml(yamlContent, registry);
|
|
4601
|
+
const tree = new BehaviorTree(rootNode);
|
|
4602
|
+
const baseName = path.basename(file, path.extname(file));
|
|
4603
|
+
const templateId = idPrefix + baseName;
|
|
4604
|
+
registry.registerTree(templateId, tree, filePath);
|
|
4605
|
+
loadedIds.push(templateId);
|
|
4606
|
+
console.log(`[TemplateLoader] Registered template: ${templateId}`);
|
|
4607
|
+
} catch (error) {
|
|
4608
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
4609
|
+
throw new Error(
|
|
4610
|
+
`Failed to load template '${file}': ${errorMessage}`
|
|
4611
|
+
);
|
|
4612
|
+
}
|
|
4613
|
+
}
|
|
4614
|
+
console.log(
|
|
4615
|
+
`[TemplateLoader] Loaded ${loadedIds.length} template(s) from ${templatesDir}`
|
|
4616
|
+
);
|
|
4617
|
+
return loadedIds;
|
|
4618
|
+
}
|
|
4619
|
+
function loadTemplate(registry, filePath, templateId) {
|
|
4620
|
+
const yamlContent = fs.readFileSync(filePath, "utf-8");
|
|
4621
|
+
const rootNode = loadTreeFromYaml(yamlContent, registry);
|
|
4622
|
+
const tree = new BehaviorTree(rootNode);
|
|
4623
|
+
const id = templateId || path.basename(filePath, path.extname(filePath));
|
|
4624
|
+
registry.registerTree(id, tree, filePath);
|
|
4625
|
+
console.log(`[TemplateLoader] Registered template: ${id} from ${filePath}`);
|
|
4626
|
+
return id;
|
|
4627
|
+
}
|
|
4628
|
+
function hasTemplate(registry, templateId) {
|
|
4629
|
+
return registry.hasTree(templateId);
|
|
4630
|
+
}
|
|
4631
|
+
function getTemplateIds(registry) {
|
|
4632
|
+
return registry.getAllTreeIds();
|
|
4633
|
+
}
|
|
4634
|
+
|
|
4635
|
+
// src/observability/execution-tracker.ts
|
|
4636
|
+
var ExecutionTracker = class {
|
|
4637
|
+
nodeStates = /* @__PURE__ */ new Map();
|
|
4638
|
+
timeline = [];
|
|
4639
|
+
errors = [];
|
|
4640
|
+
pathTaken = [];
|
|
4641
|
+
startedAt = Date.now();
|
|
4642
|
+
currentNodeId = null;
|
|
4643
|
+
totalNodes;
|
|
4644
|
+
finished = false;
|
|
4645
|
+
constructor(totalNodes) {
|
|
4646
|
+
this.totalNodes = totalNodes;
|
|
4647
|
+
}
|
|
4648
|
+
/**
|
|
4649
|
+
* Process a node event and update internal state
|
|
4650
|
+
* Called by NodeEventEmitter subscription
|
|
4651
|
+
*/
|
|
4652
|
+
onNodeEvent(event) {
|
|
4653
|
+
const state = this.getOrCreateState(
|
|
4654
|
+
event.nodeId,
|
|
4655
|
+
event.nodeType,
|
|
4656
|
+
event.nodeName,
|
|
4657
|
+
event.nodePath
|
|
4658
|
+
);
|
|
4659
|
+
const eventType = event.type;
|
|
4660
|
+
switch (eventType) {
|
|
4661
|
+
case "tick_start" /* TICK_START */:
|
|
4662
|
+
this.handleTickStart(state, event);
|
|
4663
|
+
break;
|
|
4664
|
+
case "tick_end" /* TICK_END */:
|
|
4665
|
+
this.handleTickEnd(state, event);
|
|
4666
|
+
break;
|
|
4667
|
+
case "error" /* ERROR */:
|
|
4668
|
+
this.handleError(state, event);
|
|
4669
|
+
break;
|
|
4670
|
+
case "log" /* LOG */:
|
|
4671
|
+
break;
|
|
4672
|
+
case "halt" /* HALT */:
|
|
4673
|
+
case "reset" /* RESET */:
|
|
4674
|
+
break;
|
|
4675
|
+
}
|
|
4676
|
+
}
|
|
4677
|
+
handleTickStart(state, event) {
|
|
4678
|
+
state.status = "running";
|
|
4679
|
+
state.tickCount++;
|
|
4680
|
+
state.lastTickAt = event.timestamp;
|
|
4681
|
+
this.currentNodeId = event.nodeId;
|
|
4682
|
+
this.timeline.push({
|
|
4683
|
+
nodeId: event.nodeId,
|
|
4684
|
+
nodeType: event.nodeType,
|
|
4685
|
+
nodeName: event.nodeName,
|
|
4686
|
+
nodePath: event.nodePath ?? "",
|
|
4687
|
+
event: "start",
|
|
4688
|
+
timestamp: event.timestamp
|
|
4689
|
+
});
|
|
4690
|
+
}
|
|
4691
|
+
handleTickEnd(state, event) {
|
|
4692
|
+
const status = event.data?.status;
|
|
4693
|
+
const durationMs = event.data?.durationMs;
|
|
4694
|
+
if (status === "SUCCESS") {
|
|
4695
|
+
state.status = "success";
|
|
4696
|
+
if (!this.pathTaken.includes(event.nodeId)) {
|
|
4697
|
+
this.pathTaken.push(event.nodeId);
|
|
4698
|
+
}
|
|
4699
|
+
} else if (status === "FAILURE") {
|
|
4700
|
+
state.status = "failure";
|
|
4701
|
+
} else if (status === "RUNNING") {
|
|
4702
|
+
state.status = "running";
|
|
4703
|
+
} else {
|
|
4704
|
+
state.status = "idle";
|
|
4705
|
+
}
|
|
4706
|
+
if (durationMs !== void 0) {
|
|
4707
|
+
state.durationMs = durationMs;
|
|
4708
|
+
}
|
|
4709
|
+
this.timeline.push({
|
|
4710
|
+
nodeId: event.nodeId,
|
|
4711
|
+
nodeType: event.nodeType,
|
|
4712
|
+
nodeName: event.nodeName,
|
|
4713
|
+
nodePath: event.nodePath ?? "",
|
|
4714
|
+
event: "end",
|
|
4715
|
+
timestamp: event.timestamp,
|
|
4716
|
+
status: state.status,
|
|
4717
|
+
durationMs: state.durationMs
|
|
4718
|
+
});
|
|
4719
|
+
if (this.currentNodeId === event.nodeId && state.status !== "running") {
|
|
4720
|
+
this.currentNodeId = null;
|
|
4721
|
+
}
|
|
4722
|
+
}
|
|
4723
|
+
handleError(state, event) {
|
|
4724
|
+
state.status = "failure";
|
|
4725
|
+
const errorData = event.data?.error;
|
|
4726
|
+
state.lastError = errorData?.message ?? "Unknown error";
|
|
4727
|
+
const structuredError = {
|
|
4728
|
+
nodeId: event.nodeId,
|
|
4729
|
+
nodeType: event.nodeType,
|
|
4730
|
+
nodeName: event.nodeName,
|
|
4731
|
+
nodePath: event.nodePath ?? "",
|
|
4732
|
+
message: errorData?.message ?? "Unknown error",
|
|
4733
|
+
stack: errorData?.stack,
|
|
4734
|
+
timestamp: event.timestamp,
|
|
4735
|
+
blackboardSnapshot: event.data?.blackboard ?? {},
|
|
4736
|
+
nodeInput: errorData?.input,
|
|
4737
|
+
recoverable: this.isRecoverable(event.nodeType),
|
|
4738
|
+
suggestedFix: this.suggestFix(event)
|
|
4739
|
+
};
|
|
4740
|
+
this.errors.push(structuredError);
|
|
4741
|
+
this.timeline.push({
|
|
4742
|
+
nodeId: event.nodeId,
|
|
4743
|
+
nodeType: event.nodeType,
|
|
4744
|
+
nodeName: event.nodeName,
|
|
4745
|
+
nodePath: event.nodePath ?? "",
|
|
4746
|
+
event: "error",
|
|
4747
|
+
timestamp: event.timestamp,
|
|
4748
|
+
error: {
|
|
4749
|
+
message: structuredError.message,
|
|
4750
|
+
stack: structuredError.stack
|
|
4751
|
+
}
|
|
4752
|
+
});
|
|
4753
|
+
}
|
|
4754
|
+
/**
|
|
4755
|
+
* Mark execution as finished
|
|
4756
|
+
*/
|
|
4757
|
+
markFinished() {
|
|
4758
|
+
this.finished = true;
|
|
4759
|
+
this.currentNodeId = null;
|
|
4760
|
+
}
|
|
4761
|
+
/**
|
|
4762
|
+
* Get overall execution progress
|
|
4763
|
+
*/
|
|
4764
|
+
getProgress() {
|
|
4765
|
+
const states = [...this.nodeStates.values()];
|
|
4766
|
+
const completed = states.filter((n) => n.status === "success").length;
|
|
4767
|
+
const failed = states.filter((n) => n.status === "failure").length;
|
|
4768
|
+
let status;
|
|
4769
|
+
if (failed > 0) {
|
|
4770
|
+
status = "failed";
|
|
4771
|
+
} else if (this.finished) {
|
|
4772
|
+
status = "completed";
|
|
4773
|
+
} else if (this.currentNodeId) {
|
|
4774
|
+
status = "running";
|
|
4775
|
+
} else {
|
|
4776
|
+
status = "completed";
|
|
4777
|
+
}
|
|
4778
|
+
return {
|
|
4779
|
+
totalNodes: this.totalNodes,
|
|
4780
|
+
completedNodes: completed,
|
|
4781
|
+
failedNodes: failed,
|
|
4782
|
+
currentNodeId: this.currentNodeId,
|
|
4783
|
+
currentNodeType: this.nodeStates.get(this.currentNodeId ?? "")?.type ?? null,
|
|
4784
|
+
pathTaken: [...this.pathTaken],
|
|
4785
|
+
startedAt: this.startedAt,
|
|
4786
|
+
lastActivityAt: this.timeline[this.timeline.length - 1]?.timestamp ?? this.startedAt,
|
|
4787
|
+
status
|
|
4788
|
+
};
|
|
4789
|
+
}
|
|
4790
|
+
/**
|
|
4791
|
+
* Get all node states
|
|
4792
|
+
*/
|
|
4793
|
+
getNodeStates() {
|
|
4794
|
+
return new Map(this.nodeStates);
|
|
4795
|
+
}
|
|
4796
|
+
/**
|
|
4797
|
+
* Get node states as a plain object (for JSON serialization)
|
|
4798
|
+
*/
|
|
4799
|
+
getNodeStatesObject() {
|
|
4800
|
+
const result = {};
|
|
4801
|
+
for (const [id, state] of this.nodeStates) {
|
|
4802
|
+
result[id] = { ...state };
|
|
4803
|
+
}
|
|
4804
|
+
return result;
|
|
4805
|
+
}
|
|
4806
|
+
/**
|
|
4807
|
+
* Get all errors that occurred during execution
|
|
4808
|
+
*/
|
|
4809
|
+
getErrors() {
|
|
4810
|
+
return [...this.errors];
|
|
4811
|
+
}
|
|
4812
|
+
/**
|
|
4813
|
+
* Get the full execution timeline
|
|
4814
|
+
*/
|
|
4815
|
+
getTimeline() {
|
|
4816
|
+
return [...this.timeline];
|
|
4817
|
+
}
|
|
4818
|
+
/**
|
|
4819
|
+
* Get state for a specific node
|
|
4820
|
+
*/
|
|
4821
|
+
getNodeState(nodeId) {
|
|
4822
|
+
return this.nodeStates.get(nodeId);
|
|
4823
|
+
}
|
|
4824
|
+
getOrCreateState(id, type, name, path2) {
|
|
4825
|
+
if (!this.nodeStates.has(id)) {
|
|
4826
|
+
this.nodeStates.set(id, {
|
|
4827
|
+
id,
|
|
4828
|
+
type,
|
|
4829
|
+
name,
|
|
4830
|
+
path: path2 ?? "",
|
|
4831
|
+
status: "idle",
|
|
4832
|
+
tickCount: 0
|
|
4833
|
+
});
|
|
4834
|
+
}
|
|
4835
|
+
return this.nodeStates.get(id);
|
|
4836
|
+
}
|
|
4837
|
+
/**
|
|
4838
|
+
* Check if an error type is potentially recoverable with retry
|
|
4839
|
+
*/
|
|
4840
|
+
isRecoverable(nodeType) {
|
|
4841
|
+
const recoverableTypes = [
|
|
4842
|
+
"FetchUrl",
|
|
4843
|
+
"HttpRequest",
|
|
4844
|
+
"CodeExecution",
|
|
4845
|
+
"ParseFile",
|
|
4846
|
+
"GenerateFile",
|
|
4847
|
+
"IntegrationAction"
|
|
4848
|
+
];
|
|
4849
|
+
return recoverableTypes.includes(nodeType);
|
|
4850
|
+
}
|
|
4851
|
+
/**
|
|
4852
|
+
* Suggest a fix based on error patterns
|
|
4853
|
+
*/
|
|
4854
|
+
suggestFix(event) {
|
|
4855
|
+
const msg = event.data?.error?.message ?? "";
|
|
4856
|
+
const nodeType = event.nodeType;
|
|
4857
|
+
if (msg.includes("timeout") || msg.includes("TIMEOUT") || msg.includes("timed out")) {
|
|
4858
|
+
return "Consider increasing timeout or adding retry decorator";
|
|
4859
|
+
}
|
|
4860
|
+
if (msg.includes("undefined") || msg.includes("null") || msg.includes("Cannot read property")) {
|
|
4861
|
+
return "Check if required blackboard key exists before accessing";
|
|
4862
|
+
}
|
|
4863
|
+
if (msg.includes("network") || msg.includes("ECONNREFUSED") || msg.includes("fetch failed")) {
|
|
4864
|
+
return "Check URL accessibility, consider adding retry decorator";
|
|
4865
|
+
}
|
|
4866
|
+
if (msg.includes("401") || msg.includes("403") || msg.includes("unauthorized")) {
|
|
4867
|
+
return "Check authentication credentials and permissions";
|
|
4868
|
+
}
|
|
4869
|
+
if (msg.includes("not found") || msg.includes("ENOENT") || msg.includes("404")) {
|
|
4870
|
+
return "Verify file path or URL exists";
|
|
4871
|
+
}
|
|
4872
|
+
if (nodeType === "CodeExecution") {
|
|
4873
|
+
if (msg.includes("SyntaxError")) {
|
|
4874
|
+
return "Fix syntax error in code";
|
|
4875
|
+
}
|
|
4876
|
+
if (msg.includes("ReferenceError")) {
|
|
4877
|
+
return "Variable not defined - check getBB() keys";
|
|
4878
|
+
}
|
|
4879
|
+
}
|
|
4880
|
+
return void 0;
|
|
4881
|
+
}
|
|
4882
|
+
};
|
|
4883
|
+
|
|
4884
|
+
// src/observability/sinks.ts
|
|
4885
|
+
function createObservabilitySinkHandler(config = {}) {
|
|
4886
|
+
return {
|
|
4887
|
+
events: {
|
|
4888
|
+
push: {
|
|
4889
|
+
fn: (workflowInfo, event) => {
|
|
4890
|
+
const { workflowId, runId } = workflowInfo;
|
|
4891
|
+
if (config.logEvents) {
|
|
4892
|
+
console.log(
|
|
4893
|
+
`[Sink] [${workflowId}] ${event.type}: ${event.nodeId} (${event.nodeType})`
|
|
4894
|
+
);
|
|
4895
|
+
}
|
|
4896
|
+
if (config.onEvent) {
|
|
4897
|
+
Promise.resolve(config.onEvent(workflowId, runId, event)).catch(
|
|
4898
|
+
(err) => console.error("[Sink] Error in onEvent handler:", err)
|
|
4899
|
+
);
|
|
4900
|
+
}
|
|
4901
|
+
if (event.type === "error" && config.onError) {
|
|
4902
|
+
Promise.resolve(config.onError(workflowId, runId, event)).catch(
|
|
4903
|
+
(err) => console.error("[Sink] Error in onError handler:", err)
|
|
4904
|
+
);
|
|
4905
|
+
}
|
|
4906
|
+
},
|
|
4907
|
+
// Don't call during replay - events were already processed
|
|
4908
|
+
callDuringReplay: false
|
|
4909
|
+
}
|
|
4910
|
+
}
|
|
4911
|
+
};
|
|
4912
|
+
}
|
|
4913
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
4914
|
+
0 && (module.exports = {
|
|
4915
|
+
ActionNode,
|
|
4916
|
+
AlwaysCondition,
|
|
4917
|
+
BaseNode,
|
|
4918
|
+
BehaviorTree,
|
|
4919
|
+
CheckCondition,
|
|
4920
|
+
CodeExecution,
|
|
4921
|
+
CompositeNode,
|
|
4922
|
+
ConditionNode,
|
|
4923
|
+
Conditional,
|
|
4924
|
+
ConfigValidationError,
|
|
4925
|
+
ConfigurationError,
|
|
4926
|
+
CounterAction,
|
|
4927
|
+
DecoratorNode,
|
|
4928
|
+
Delay,
|
|
4929
|
+
ExecutionTracker,
|
|
4930
|
+
FailureNode,
|
|
4931
|
+
Fallback,
|
|
4932
|
+
ForEach,
|
|
4933
|
+
ForceFailure,
|
|
4934
|
+
ForceSuccess,
|
|
4935
|
+
GenerateFile,
|
|
4936
|
+
HttpRequest,
|
|
4937
|
+
IntegrationAction,
|
|
4938
|
+
Invert,
|
|
4939
|
+
KeepRunningUntilFailure,
|
|
4940
|
+
LogMessage,
|
|
4941
|
+
MemoryDataStore,
|
|
4942
|
+
MemorySequence,
|
|
4943
|
+
MockAction,
|
|
4944
|
+
NodeEventEmitter,
|
|
4945
|
+
NodeEventType,
|
|
4946
|
+
NodeStatus,
|
|
4947
|
+
Parallel,
|
|
4948
|
+
ParseFile,
|
|
4949
|
+
Precondition,
|
|
4950
|
+
PrintAction,
|
|
4951
|
+
PythonScript,
|
|
4952
|
+
ReactiveSequence,
|
|
4953
|
+
Recovery,
|
|
4954
|
+
RegexExtract,
|
|
4955
|
+
Registry,
|
|
4956
|
+
Repeat,
|
|
4957
|
+
ResumePoint,
|
|
4958
|
+
RunOnce,
|
|
4959
|
+
RunningNode,
|
|
4960
|
+
SchemaRegistry,
|
|
4961
|
+
ScopedBlackboard,
|
|
4962
|
+
Selector,
|
|
4963
|
+
SemanticValidationError,
|
|
4964
|
+
Sequence,
|
|
4965
|
+
SequenceWithMemory,
|
|
4966
|
+
SoftAssert,
|
|
4967
|
+
StructureValidationError,
|
|
4968
|
+
SubTree,
|
|
4969
|
+
SuccessNode,
|
|
4970
|
+
Timeout,
|
|
4971
|
+
ValidationError,
|
|
4972
|
+
ValidationErrors,
|
|
4973
|
+
WaitAction,
|
|
4974
|
+
While,
|
|
4975
|
+
YamlSyntaxError,
|
|
4976
|
+
clearPieceCache,
|
|
4977
|
+
createNodeSchema,
|
|
4978
|
+
createObservabilitySinkHandler,
|
|
4979
|
+
envTokenProvider,
|
|
4980
|
+
executePieceAction,
|
|
4981
|
+
extractVariables,
|
|
4982
|
+
getTemplateIds,
|
|
4983
|
+
hasTemplate,
|
|
4984
|
+
hasVariables,
|
|
4985
|
+
isDataRef,
|
|
4986
|
+
isPieceInstalled,
|
|
4987
|
+
listPieceActions,
|
|
4988
|
+
loadTemplate,
|
|
4989
|
+
loadTemplatesFromDirectory,
|
|
4990
|
+
loadTreeFromFile,
|
|
4991
|
+
loadTreeFromYaml,
|
|
4992
|
+
nodeConfigurationSchema,
|
|
4993
|
+
parseYaml,
|
|
4994
|
+
registerStandardNodes,
|
|
4995
|
+
resolveString,
|
|
4996
|
+
resolveValue,
|
|
4997
|
+
safeValidateConfiguration,
|
|
4998
|
+
schemaRegistry,
|
|
4999
|
+
semanticValidator,
|
|
5000
|
+
toYaml,
|
|
5001
|
+
treeDefinitionSchema,
|
|
5002
|
+
validateChildCount,
|
|
5003
|
+
validateChildCountRange,
|
|
5004
|
+
validateCompositeChildren,
|
|
5005
|
+
validateConfiguration,
|
|
5006
|
+
validateDecoratorChildren,
|
|
5007
|
+
validateTreeDefinition,
|
|
5008
|
+
validateYaml,
|
|
5009
|
+
validations,
|
|
5010
|
+
zodErrorToConfigurationError
|
|
5011
|
+
});
|