@duckflux/core 0.6.8 → 0.7.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/dist/index.js CHANGED
@@ -26165,8 +26165,552 @@ var require_dist2 = __commonJS((exports, module) => {
26165
26165
  exports.default = formatsPlugin;
26166
26166
  });
26167
26167
 
26168
+ // src/parser/schema/duckflux.schema.json
26169
+ var duckflux_schema_default;
26170
+ var init_duckflux_schema = __esm(() => {
26171
+ duckflux_schema_default = {
26172
+ $schema: "https://json-schema.org/draft/2020-12/schema",
26173
+ $id: "https://raw.githubusercontent.com/duckflux/spec/main/duckflux.schema.json",
26174
+ title: "duckflux Workflow",
26175
+ description: "Schema for duckflux — a minimal, deterministic, runtime-agnostic workflow DSL.",
26176
+ type: "object",
26177
+ required: ["flow"],
26178
+ additionalProperties: false,
26179
+ properties: {
26180
+ id: {
26181
+ type: "string",
26182
+ description: "Unique identifier for the workflow."
26183
+ },
26184
+ name: {
26185
+ type: "string",
26186
+ description: "Human-readable workflow name."
26187
+ },
26188
+ version: {
26189
+ description: "Version identifier for the workflow definition.",
26190
+ oneOf: [
26191
+ { type: "string" },
26192
+ { type: "integer" }
26193
+ ]
26194
+ },
26195
+ defaults: {
26196
+ $ref: "#/$defs/defaults"
26197
+ },
26198
+ inputs: {
26199
+ $ref: "#/$defs/inputs"
26200
+ },
26201
+ participants: {
26202
+ type: "object",
26203
+ description: "Named steps that can be referenced in the flow.",
26204
+ additionalProperties: {
26205
+ $ref: "#/$defs/participant"
26206
+ },
26207
+ not: {
26208
+ anyOf: [
26209
+ { required: ["workflow"] },
26210
+ { required: ["execution"] },
26211
+ { required: ["input"] },
26212
+ { required: ["output"] },
26213
+ { required: ["env"] },
26214
+ { required: ["loop"] },
26215
+ { required: ["event"] }
26216
+ ]
26217
+ }
26218
+ },
26219
+ flow: {
26220
+ $ref: "#/$defs/flowSequence"
26221
+ },
26222
+ output: {
26223
+ $ref: "#/$defs/workflowOutput"
26224
+ }
26225
+ },
26226
+ $defs: {
26227
+ duration: {
26228
+ type: "string",
26229
+ pattern: "^[0-9]+(ms|s|m|h|d)$",
26230
+ description: "Duration string, e.g. '30s', '5m', '2h', '1d'."
26231
+ },
26232
+ celExpression: {
26233
+ type: "string",
26234
+ description: "A Google CEL expression."
26235
+ },
26236
+ defaults: {
26237
+ type: "object",
26238
+ description: "Global defaults applied to all participants.",
26239
+ additionalProperties: false,
26240
+ properties: {
26241
+ timeout: {
26242
+ $ref: "#/$defs/duration"
26243
+ },
26244
+ cwd: {
26245
+ type: "string",
26246
+ description: "Default working directory for exec participants. Supports CEL expressions."
26247
+ }
26248
+ }
26249
+ },
26250
+ inputs: {
26251
+ type: "object",
26252
+ description: "Workflow input parameters. Accessed in CEL as workflow.inputs.<field>. Each key is a parameter name. Value can be null (string, no schema), or a JSON Schema object for validation.",
26253
+ additionalProperties: {
26254
+ oneOf: [
26255
+ { type: "null" },
26256
+ { $ref: "#/$defs/inputField" }
26257
+ ]
26258
+ }
26259
+ },
26260
+ inputField: {
26261
+ type: "object",
26262
+ description: "JSON Schema field definition with optional 'required: true' shortcut.",
26263
+ properties: {
26264
+ type: {
26265
+ type: "string",
26266
+ enum: ["string", "integer", "number", "boolean", "array", "object"]
26267
+ },
26268
+ description: { type: "string" },
26269
+ default: {},
26270
+ required: { type: "boolean" },
26271
+ format: { type: "string" },
26272
+ enum: { type: "array" },
26273
+ minimum: { type: "number" },
26274
+ maximum: { type: "number" },
26275
+ minLength: { type: "integer" },
26276
+ maxLength: { type: "integer" },
26277
+ pattern: { type: "string" },
26278
+ items: {
26279
+ $ref: "#/$defs/inputField"
26280
+ }
26281
+ },
26282
+ additionalProperties: false
26283
+ },
26284
+ retryConfig: {
26285
+ type: "object",
26286
+ description: "Retry configuration. Only applies when onError is 'retry'.",
26287
+ additionalProperties: false,
26288
+ required: ["max"],
26289
+ properties: {
26290
+ max: {
26291
+ type: "integer",
26292
+ minimum: 1,
26293
+ description: "Maximum number of retry attempts."
26294
+ },
26295
+ backoff: {
26296
+ $ref: "#/$defs/duration",
26297
+ description: "Interval between attempts. Default: 0s."
26298
+ },
26299
+ factor: {
26300
+ type: "number",
26301
+ minimum: 1,
26302
+ description: "Backoff multiplier. Default: 1 (no escalation)."
26303
+ }
26304
+ }
26305
+ },
26306
+ onError: {
26307
+ type: "string",
26308
+ description: "Error handling strategy: 'fail' (default), 'skip', 'retry', or a participant name for redirect.",
26309
+ minLength: 1
26310
+ },
26311
+ onTimeout: {
26312
+ type: "string",
26313
+ description: "Timeout handling strategy: 'fail' (default), 'skip', or a participant name for redirect.",
26314
+ minLength: 1
26315
+ },
26316
+ participantInput: {
26317
+ description: "Explicit input mapping. String for direct passthrough, object for structured mapping. Values are CEL expressions. Merged with implicit I/O chain input at runtime.",
26318
+ oneOf: [
26319
+ { type: "string" },
26320
+ {
26321
+ type: "object",
26322
+ additionalProperties: {
26323
+ type: "string"
26324
+ }
26325
+ }
26326
+ ]
26327
+ },
26328
+ participantOutputSchema: {
26329
+ type: "object",
26330
+ description: "Output schema (JSON Schema fields). Opt-in validation.",
26331
+ additionalProperties: {
26332
+ $ref: "#/$defs/inputField"
26333
+ }
26334
+ },
26335
+ payload: {
26336
+ description: "Event payload. String (CEL expression) or object with CEL expressions as values.",
26337
+ oneOf: [
26338
+ { type: "string" },
26339
+ {
26340
+ type: "object",
26341
+ additionalProperties: {}
26342
+ }
26343
+ ]
26344
+ },
26345
+ participant: {
26346
+ type: "object",
26347
+ required: ["type"],
26348
+ description: "A named, reusable building block of the workflow.",
26349
+ properties: {
26350
+ type: {
26351
+ type: "string",
26352
+ enum: ["exec", "http", "mcp", "workflow", "emit"],
26353
+ description: "Participant type."
26354
+ },
26355
+ as: {
26356
+ type: "string",
26357
+ description: "Human-readable display name."
26358
+ },
26359
+ timeout: {
26360
+ $ref: "#/$defs/duration"
26361
+ },
26362
+ onError: {
26363
+ $ref: "#/$defs/onError"
26364
+ },
26365
+ retry: {
26366
+ $ref: "#/$defs/retryConfig"
26367
+ },
26368
+ input: {
26369
+ $ref: "#/$defs/participantInput"
26370
+ },
26371
+ output: {
26372
+ $ref: "#/$defs/participantOutputSchema"
26373
+ },
26374
+ run: {
26375
+ type: "string",
26376
+ description: "[exec] Shell command to execute."
26377
+ },
26378
+ cwd: {
26379
+ type: "string",
26380
+ description: "[exec] Working directory. Supports CEL expressions."
26381
+ },
26382
+ url: {
26383
+ type: "string",
26384
+ description: "[http] Target URL. Supports CEL expressions."
26385
+ },
26386
+ method: {
26387
+ type: "string",
26388
+ enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
26389
+ description: "[http] HTTP method."
26390
+ },
26391
+ headers: {
26392
+ type: "object",
26393
+ additionalProperties: { type: "string" },
26394
+ description: "[http] HTTP headers. Values support CEL expressions."
26395
+ },
26396
+ body: {
26397
+ description: "[http] Request body. String or object. Supports CEL expressions.",
26398
+ oneOf: [
26399
+ { type: "string" },
26400
+ { type: "object" }
26401
+ ]
26402
+ },
26403
+ path: {
26404
+ type: "string",
26405
+ description: "[workflow] Path to the sub-workflow YAML file."
26406
+ },
26407
+ server: {
26408
+ type: "string",
26409
+ description: "[mcp] MCP server identifier."
26410
+ },
26411
+ tool: {
26412
+ type: "string",
26413
+ description: "[mcp] MCP tool name to invoke."
26414
+ },
26415
+ event: {
26416
+ type: "string",
26417
+ description: "[emit] Event name to emit."
26418
+ },
26419
+ payload: {
26420
+ $ref: "#/$defs/payload",
26421
+ description: "[emit] Event payload. CEL expression or object with CEL expressions."
26422
+ },
26423
+ ack: {
26424
+ type: "boolean",
26425
+ description: "[emit] If true, wait for delivery acknowledgment. Default: false.",
26426
+ default: false
26427
+ }
26428
+ },
26429
+ additionalProperties: false
26430
+ },
26431
+ inlineParticipant: {
26432
+ type: "object",
26433
+ required: ["type"],
26434
+ description: "An inline participant definition in the flow. 'as' is optional — if omitted, the participant is anonymous and its output is only accessible via the implicit I/O chain.",
26435
+ properties: {
26436
+ as: {
26437
+ type: "string",
26438
+ description: "Optional name for inline participants. If provided, must be unique across all participant names. Enables output reference by name."
26439
+ },
26440
+ type: {
26441
+ type: "string",
26442
+ enum: ["exec", "http", "mcp", "workflow", "emit"],
26443
+ description: "Participant type."
26444
+ },
26445
+ when: {
26446
+ $ref: "#/$defs/celExpression",
26447
+ description: "Guard condition. If false, step is skipped."
26448
+ },
26449
+ timeout: {
26450
+ $ref: "#/$defs/duration"
26451
+ },
26452
+ onError: {
26453
+ $ref: "#/$defs/onError"
26454
+ },
26455
+ retry: {
26456
+ $ref: "#/$defs/retryConfig"
26457
+ },
26458
+ input: {
26459
+ $ref: "#/$defs/participantInput"
26460
+ },
26461
+ output: {
26462
+ $ref: "#/$defs/participantOutputSchema"
26463
+ },
26464
+ run: { type: "string" },
26465
+ cwd: { type: "string" },
26466
+ url: { type: "string" },
26467
+ method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
26468
+ headers: { type: "object", additionalProperties: { type: "string" } },
26469
+ body: { oneOf: [{ type: "string" }, { type: "object" }] },
26470
+ path: { type: "string" },
26471
+ server: { type: "string" },
26472
+ tool: { type: "string" },
26473
+ event: { type: "string" },
26474
+ payload: { $ref: "#/$defs/payload" },
26475
+ ack: { type: "boolean" }
26476
+ },
26477
+ additionalProperties: false
26478
+ },
26479
+ flowSequence: {
26480
+ type: "array",
26481
+ description: "Ordered list of flow steps. Each step's output is implicitly chained as input to the next step.",
26482
+ minItems: 1,
26483
+ items: {
26484
+ $ref: "#/$defs/flowStep"
26485
+ }
26486
+ },
26487
+ flowStep: {
26488
+ description: "A single step in the flow: participant reference (string), control construct, inline participant (named or anonymous), or participant override.",
26489
+ oneOf: [
26490
+ {
26491
+ type: "string",
26492
+ description: "Simple participant reference by name."
26493
+ },
26494
+ { $ref: "#/$defs/loopStep" },
26495
+ { $ref: "#/$defs/parallelStep" },
26496
+ { $ref: "#/$defs/ifStep" },
26497
+ { $ref: "#/$defs/setStep" },
26498
+ { $ref: "#/$defs/waitStep" },
26499
+ { $ref: "#/$defs/inlineParticipant" },
26500
+ { $ref: "#/$defs/participantOverrideStep" }
26501
+ ]
26502
+ },
26503
+ loopStep: {
26504
+ type: "object",
26505
+ required: ["loop"],
26506
+ additionalProperties: false,
26507
+ properties: {
26508
+ loop: {
26509
+ type: "object",
26510
+ additionalProperties: false,
26511
+ required: ["steps"],
26512
+ properties: {
26513
+ as: {
26514
+ type: "string",
26515
+ description: "Renames the loop context variable. Access as <as>.index, <as>.iteration, etc. instead of loop.*"
26516
+ },
26517
+ until: {
26518
+ $ref: "#/$defs/celExpression",
26519
+ description: "CEL condition to break out of the loop."
26520
+ },
26521
+ max: {
26522
+ description: "Maximum iterations. Integer or CEL expression.",
26523
+ oneOf: [
26524
+ { type: "integer", minimum: 1 },
26525
+ { type: "string" }
26526
+ ]
26527
+ },
26528
+ steps: {
26529
+ $ref: "#/$defs/flowSequence"
26530
+ }
26531
+ },
26532
+ anyOf: [
26533
+ { required: ["until"] },
26534
+ { required: ["max"] }
26535
+ ]
26536
+ }
26537
+ }
26538
+ },
26539
+ parallelStep: {
26540
+ type: "object",
26541
+ required: ["parallel"],
26542
+ additionalProperties: false,
26543
+ properties: {
26544
+ parallel: {
26545
+ $ref: "#/$defs/flowSequence",
26546
+ description: "Steps to run concurrently. The chained output after a parallel block is an array of all branch outputs in declaration order."
26547
+ }
26548
+ }
26549
+ },
26550
+ ifStep: {
26551
+ type: "object",
26552
+ required: ["if"],
26553
+ additionalProperties: false,
26554
+ properties: {
26555
+ if: {
26556
+ type: "object",
26557
+ required: ["condition", "then"],
26558
+ additionalProperties: false,
26559
+ properties: {
26560
+ condition: {
26561
+ $ref: "#/$defs/celExpression",
26562
+ description: "CEL expression that determines which branch to take."
26563
+ },
26564
+ then: {
26565
+ $ref: "#/$defs/flowSequence"
26566
+ },
26567
+ else: {
26568
+ $ref: "#/$defs/flowSequence"
26569
+ }
26570
+ }
26571
+ }
26572
+ }
26573
+ },
26574
+ setStep: {
26575
+ type: "object",
26576
+ required: ["set"],
26577
+ additionalProperties: false,
26578
+ properties: {
26579
+ set: {
26580
+ type: "object",
26581
+ description: "Writes values into execution.context. Each key becomes execution.context.<key>. Values are CEL expressions.",
26582
+ minProperties: 1,
26583
+ additionalProperties: {
26584
+ $ref: "#/$defs/celExpression"
26585
+ },
26586
+ not: {
26587
+ anyOf: [
26588
+ { required: ["workflow"] },
26589
+ { required: ["execution"] },
26590
+ { required: ["input"] },
26591
+ { required: ["output"] },
26592
+ { required: ["env"] },
26593
+ { required: ["loop"] },
26594
+ { required: ["event"] }
26595
+ ]
26596
+ }
26597
+ }
26598
+ }
26599
+ },
26600
+ waitStep: {
26601
+ type: "object",
26602
+ required: ["wait"],
26603
+ additionalProperties: false,
26604
+ properties: {
26605
+ wait: {
26606
+ type: "object",
26607
+ additionalProperties: false,
26608
+ properties: {
26609
+ event: {
26610
+ type: "string",
26611
+ description: "Event name to wait for (event mode)."
26612
+ },
26613
+ match: {
26614
+ $ref: "#/$defs/celExpression",
26615
+ description: "CEL condition to match against event payload. 'event' variable contains the payload."
26616
+ },
26617
+ until: {
26618
+ $ref: "#/$defs/celExpression",
26619
+ description: "CEL condition to wait for (polling mode)."
26620
+ },
26621
+ poll: {
26622
+ $ref: "#/$defs/duration",
26623
+ description: "Polling interval for 'until' condition. Default: runtime decides."
26624
+ },
26625
+ timeout: {
26626
+ $ref: "#/$defs/duration",
26627
+ description: "Maximum wait time."
26628
+ },
26629
+ onTimeout: {
26630
+ $ref: "#/$defs/onTimeout",
26631
+ description: "Strategy when timeout is reached: 'fail' (default), 'skip', or participant name."
26632
+ }
26633
+ }
26634
+ }
26635
+ }
26636
+ },
26637
+ participantOverrideStep: {
26638
+ type: "object",
26639
+ description: "A participant reference with flow-level overrides (timeout, onError, when, input, workflow, etc.).",
26640
+ minProperties: 1,
26641
+ maxProperties: 1,
26642
+ not: {
26643
+ anyOf: [
26644
+ { required: ["loop"] },
26645
+ { required: ["parallel"] },
26646
+ { required: ["if"] },
26647
+ { required: ["wait"] },
26648
+ { required: ["set"] },
26649
+ { required: ["type"] }
26650
+ ]
26651
+ },
26652
+ additionalProperties: {
26653
+ type: "object",
26654
+ properties: {
26655
+ when: {
26656
+ $ref: "#/$defs/celExpression",
26657
+ description: "Guard condition. If false, step is skipped."
26658
+ },
26659
+ timeout: {
26660
+ $ref: "#/$defs/duration"
26661
+ },
26662
+ onError: {
26663
+ $ref: "#/$defs/onError"
26664
+ },
26665
+ retry: {
26666
+ $ref: "#/$defs/retryConfig"
26667
+ },
26668
+ input: {
26669
+ $ref: "#/$defs/participantInput"
26670
+ },
26671
+ workflow: {
26672
+ type: "string",
26673
+ description: "Inline sub-workflow path (alternative to defining as participant)."
26674
+ }
26675
+ },
26676
+ additionalProperties: false
26677
+ }
26678
+ },
26679
+ workflowOutput: {
26680
+ description: "Workflow output. Accessed in CEL as workflow.output. String (single CEL expression), object (structured mapping), or object with schema+map.",
26681
+ oneOf: [
26682
+ {
26683
+ type: "string",
26684
+ description: "Single CEL expression mapping."
26685
+ },
26686
+ {
26687
+ type: "object",
26688
+ description: "Structured output mapping (key → CEL expression) or schema+map.",
26689
+ properties: {
26690
+ schema: {
26691
+ type: "object",
26692
+ additionalProperties: {
26693
+ $ref: "#/$defs/inputField"
26694
+ }
26695
+ },
26696
+ map: {
26697
+ type: "object",
26698
+ additionalProperties: {
26699
+ type: "string"
26700
+ }
26701
+ }
26702
+ },
26703
+ additionalProperties: {
26704
+ type: "string"
26705
+ }
26706
+ }
26707
+ ]
26708
+ }
26709
+ }
26710
+ };
26711
+ });
26712
+
26168
26713
  // src/parser/schema.ts
26169
- import { readFileSync } from "node:fs";
26170
26714
  function validateSchema(workflow) {
26171
26715
  const valid = validate(workflow);
26172
26716
  if (valid) {
@@ -26178,12 +26722,12 @@ function validateSchema(workflow) {
26178
26722
  }));
26179
26723
  return { valid: false, errors };
26180
26724
  }
26181
- var import__2020, import_ajv_formats, rawSchema, schema, ajv, validate;
26725
+ var import__2020, import_ajv_formats, schema, ajv, validate;
26182
26726
  var init_schema = __esm(() => {
26727
+ init_duckflux_schema();
26183
26728
  import__2020 = __toESM(require_2020(), 1);
26184
26729
  import_ajv_formats = __toESM(require_dist2(), 1);
26185
- rawSchema = readFileSync(new URL("./schema/duckflux.schema.json", import.meta.url), "utf-8");
26186
- schema = JSON.parse(rawSchema);
26730
+ schema = { ...duckflux_schema_default };
26187
26731
  delete schema.$schema;
26188
26732
  ajv = new import__2020.default({ allErrors: true, strict: false });
26189
26733
  import_ajv_formats.default(ajv);
@@ -26590,6 +27134,248 @@ var init_validate = __esm(() => {
26590
27134
  BUILTIN_ONERROR = new Set(["fail", "skip", "retry"]);
26591
27135
  });
26592
27136
 
27137
+ // src/tracer/writers/json.ts
27138
+ var exports_json = {};
27139
+ __export(exports_json, {
27140
+ JsonTraceWriter: () => JsonTraceWriter
27141
+ });
27142
+ import { writeFile } from "node:fs/promises";
27143
+ import { join } from "node:path";
27144
+
27145
+ class JsonTraceWriter {
27146
+ dir;
27147
+ filePath = "";
27148
+ trace = {
27149
+ execution: {
27150
+ id: "",
27151
+ startedAt: "",
27152
+ finishedAt: "",
27153
+ duration: 0,
27154
+ status: "running",
27155
+ inputs: null,
27156
+ output: null
27157
+ },
27158
+ steps: []
27159
+ };
27160
+ constructor(dir) {
27161
+ this.dir = dir;
27162
+ }
27163
+ async open(meta) {
27164
+ this.filePath = join(this.dir, `${meta.id}.json`);
27165
+ this.trace = {
27166
+ execution: {
27167
+ id: meta.id,
27168
+ workflowId: meta.workflowId,
27169
+ workflowName: meta.workflowName,
27170
+ workflowVersion: meta.workflowVersion,
27171
+ startedAt: meta.startedAt,
27172
+ finishedAt: "",
27173
+ duration: 0,
27174
+ status: "running",
27175
+ inputs: meta.inputs,
27176
+ output: null
27177
+ },
27178
+ steps: []
27179
+ };
27180
+ await this.flush();
27181
+ }
27182
+ writeStep(step) {
27183
+ this.trace.steps.push(step);
27184
+ this.flushSync();
27185
+ }
27186
+ async finalize(meta) {
27187
+ this.trace.execution.status = meta.status;
27188
+ this.trace.execution.output = meta.output;
27189
+ this.trace.execution.finishedAt = meta.finishedAt;
27190
+ this.trace.execution.duration = meta.duration;
27191
+ await this.flush();
27192
+ }
27193
+ flushSync() {
27194
+ this.flush().catch(() => {});
27195
+ }
27196
+ async flush() {
27197
+ if (!this.filePath)
27198
+ return;
27199
+ await writeFile(this.filePath, JSON.stringify(this.trace, null, 2), "utf-8");
27200
+ }
27201
+ }
27202
+ var init_json = () => {};
27203
+
27204
+ // src/tracer/writers/txt.ts
27205
+ var exports_txt = {};
27206
+ __export(exports_txt, {
27207
+ TxtTraceWriter: () => TxtTraceWriter
27208
+ });
27209
+ import { appendFile, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
27210
+ import { join as join2 } from "node:path";
27211
+ function serializeValue(value) {
27212
+ if (value === undefined || value === null)
27213
+ return "none";
27214
+ if (typeof value === "string")
27215
+ return value;
27216
+ return JSON.stringify(value, null, 2);
27217
+ }
27218
+ function formatStep(step) {
27219
+ const lines = [
27220
+ `## [${step.seq}] ${step.name} (${step.type})`,
27221
+ `startedAt: ${step.startedAt}`
27222
+ ];
27223
+ if (step.finishedAt)
27224
+ lines.push(`finishedAt: ${step.finishedAt}`);
27225
+ if (step.duration !== undefined)
27226
+ lines.push(`duration: ${step.duration}ms`);
27227
+ lines.push(`status: ${step.status}`);
27228
+ if (step.loopIndex !== undefined)
27229
+ lines.push(`loopIndex: ${step.loopIndex}`);
27230
+ if (step.retries !== undefined && step.retries > 0)
27231
+ lines.push(`retries: ${step.retries}`);
27232
+ lines.push(`input: ${serializeValue(step.input)}`);
27233
+ lines.push(`output: ${serializeValue(step.output)}`);
27234
+ if (step.error)
27235
+ lines.push(`error: ${step.error}`);
27236
+ lines.push("");
27237
+ return lines.join(`
27238
+ `);
27239
+ }
27240
+
27241
+ class TxtTraceWriter {
27242
+ dir;
27243
+ filePath = "";
27244
+ constructor(dir) {
27245
+ this.dir = dir;
27246
+ }
27247
+ async open(meta) {
27248
+ this.filePath = join2(this.dir, `${meta.id}.txt`);
27249
+ const versionStr = meta.workflowVersion !== undefined ? ` (v${meta.workflowVersion})` : "";
27250
+ const workflowLabel = meta.workflowName ?? meta.workflowId ?? "unnamed";
27251
+ const header = [
27252
+ "# execution",
27253
+ `id: ${meta.id}`,
27254
+ `workflow: ${workflowLabel}${versionStr}`,
27255
+ `startedAt: ${meta.startedAt}`,
27256
+ "status: running",
27257
+ "",
27258
+ "# inputs",
27259
+ serializeValue(meta.inputs),
27260
+ "",
27261
+ "# steps",
27262
+ ""
27263
+ ].join(`
27264
+ `);
27265
+ await writeFile2(this.filePath, header, "utf-8");
27266
+ }
27267
+ async writeStep(step) {
27268
+ if (!this.filePath)
27269
+ return;
27270
+ await appendFile(this.filePath, formatStep(step), "utf-8");
27271
+ }
27272
+ async finalize(meta) {
27273
+ if (!this.filePath)
27274
+ return;
27275
+ const outputSection = [
27276
+ "# output",
27277
+ serializeValue(meta.output),
27278
+ ""
27279
+ ].join(`
27280
+ `);
27281
+ await appendFile(this.filePath, outputSection, "utf-8");
27282
+ const content = await readFile2(this.filePath, "utf-8");
27283
+ const updated = content.replace(/^status: running$/m, `status: ${meta.status}
27284
+ finishedAt: ${meta.finishedAt}
27285
+ duration: ${meta.duration}ms`);
27286
+ await writeFile2(this.filePath, updated, "utf-8");
27287
+ }
27288
+ }
27289
+ var init_txt = () => {};
27290
+
27291
+ // src/tracer/writers/sqlite.ts
27292
+ var exports_sqlite = {};
27293
+ __export(exports_sqlite, {
27294
+ SqliteTraceWriter: () => SqliteTraceWriter
27295
+ });
27296
+ import { join as join3 } from "node:path";
27297
+ function toJson(value) {
27298
+ if (value === undefined || value === null)
27299
+ return null;
27300
+ if (typeof value === "string")
27301
+ return value;
27302
+ return JSON.stringify(value);
27303
+ }
27304
+
27305
+ class SqliteTraceWriter {
27306
+ dir;
27307
+ db = null;
27308
+ executionId = "";
27309
+ constructor(dir) {
27310
+ this.dir = dir;
27311
+ }
27312
+ async open(meta) {
27313
+ this.executionId = meta.id;
27314
+ const filePath = join3(this.dir, `${meta.id}.sqlite`);
27315
+ const { Database } = await import("bun:sqlite");
27316
+ this.db = new Database(filePath);
27317
+ this.db.exec(`
27318
+ CREATE TABLE IF NOT EXISTS executions (
27319
+ id TEXT PRIMARY KEY,
27320
+ workflow_id TEXT,
27321
+ workflow_name TEXT,
27322
+ workflow_version TEXT,
27323
+ started_at TEXT NOT NULL,
27324
+ finished_at TEXT,
27325
+ duration_ms INTEGER,
27326
+ status TEXT NOT NULL,
27327
+ inputs TEXT,
27328
+ output TEXT
27329
+ );
27330
+
27331
+ CREATE TABLE IF NOT EXISTS steps (
27332
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
27333
+ execution_id TEXT NOT NULL REFERENCES executions(id),
27334
+ seq INTEGER NOT NULL,
27335
+ name TEXT NOT NULL,
27336
+ type TEXT NOT NULL,
27337
+ started_at TEXT,
27338
+ finished_at TEXT,
27339
+ duration_ms INTEGER,
27340
+ status TEXT NOT NULL,
27341
+ input TEXT,
27342
+ output TEXT,
27343
+ error TEXT,
27344
+ retries INTEGER,
27345
+ loop_index INTEGER
27346
+ );
27347
+ `);
27348
+ const insert = this.db.prepare(`
27349
+ INSERT INTO executions (id, workflow_id, workflow_name, workflow_version, started_at, status, inputs)
27350
+ VALUES (?, ?, ?, ?, ?, 'running', ?)
27351
+ `);
27352
+ insert.run(meta.id, meta.workflowId ?? null, meta.workflowName ?? null, meta.workflowVersion !== undefined ? String(meta.workflowVersion) : null, meta.startedAt, toJson(meta.inputs));
27353
+ }
27354
+ writeStep(step) {
27355
+ if (!this.db)
27356
+ return;
27357
+ const insert = this.db.prepare(`
27358
+ INSERT INTO steps
27359
+ (execution_id, seq, name, type, started_at, finished_at, duration_ms, status, input, output, error, retries, loop_index)
27360
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
27361
+ `);
27362
+ insert.run(this.executionId, step.seq, step.name, step.type, step.startedAt ?? null, step.finishedAt ?? null, step.duration ?? null, step.status, toJson(step.input), toJson(step.output), step.error ?? null, step.retries ?? null, step.loopIndex ?? null);
27363
+ }
27364
+ async finalize(meta) {
27365
+ if (!this.db)
27366
+ return;
27367
+ const update = this.db.prepare(`
27368
+ UPDATE executions
27369
+ SET status = ?, output = ?, finished_at = ?, duration_ms = ?
27370
+ WHERE id = ?
27371
+ `);
27372
+ update.run(meta.status, toJson(meta.output), meta.finishedAt, Math.round(meta.duration), this.executionId);
27373
+ this.db.close();
27374
+ this.db = null;
27375
+ }
27376
+ }
27377
+ var init_sqlite = () => {};
27378
+
26593
27379
  // src/engine/errors.ts
26594
27380
  function sleep(ms) {
26595
27381
  return new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -27189,6 +27975,10 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27189
27975
  output: "",
27190
27976
  duration: 0
27191
27977
  });
27978
+ const loopIndex2 = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27979
+ const skippedSeq = state.tracer?.startStep(stepName, participant.type, undefined, loopIndex2);
27980
+ if (skippedSeq !== undefined)
27981
+ state.tracer?.endStep(skippedSeq, "skipped");
27192
27982
  }
27193
27983
  return chain;
27194
27984
  }
@@ -27197,6 +27987,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27197
27987
  const overrideInput = override?.input !== undefined ? resolveParticipantInput(override.input, state) : undefined;
27198
27988
  const mergedWithBase = mergeChainedInput(chain, baseInput);
27199
27989
  const mergedInput = mergeChainedInput(mergedWithBase, overrideInput);
27990
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27991
+ const traceSeq = state.tracer?.startStep(stepName ?? "<anonymous>", participant.type, mergedInput, loopIndex);
27200
27992
  state.currentInput = mergedInput;
27201
27993
  const strategy = resolveErrorStrategy(override ?? null, participant, workflow.defaults ?? null);
27202
27994
  const timeoutMs = resolveTimeout(override ?? null, participant, workflow.defaults ?? null);
@@ -27274,6 +28066,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27274
28066
  }
27275
28067
  const outputValue = result.parsedOutput ?? result.output;
27276
28068
  state.currentOutput = outputValue;
28069
+ if (traceSeq !== undefined)
28070
+ state.tracer?.endStep(traceSeq, result.status, outputValue, undefined, retries);
27277
28071
  return outputValue;
27278
28072
  } catch (error) {
27279
28073
  const message = String(error?.message ?? error);
@@ -27299,6 +28093,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27299
28093
  if (stepName) {
27300
28094
  state.setResult(stepName, skipResult);
27301
28095
  }
28096
+ if (traceSeq !== undefined)
28097
+ state.tracer?.endStep(traceSeq, "skipped", undefined, message);
27302
28098
  return chain;
27303
28099
  }
27304
28100
  if (strategy !== "fail" && strategy !== "retry") {
@@ -27317,6 +28113,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27317
28113
  ...httpMeta
27318
28114
  });
27319
28115
  }
28116
+ if (traceSeq !== undefined)
28117
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
27320
28118
  const fallbackResult = await executeStep(workflow, state, fallbackName, basePath, engineExecutor, [...fallbackStack, stepName ?? "<anonymous>"], chain, hub);
27321
28119
  return fallbackResult;
27322
28120
  }
@@ -27331,6 +28129,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27331
28129
  ...httpMeta
27332
28130
  });
27333
28131
  }
28132
+ if (traceSeq !== undefined)
28133
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
27334
28134
  throw error;
27335
28135
  }
27336
28136
  }
@@ -27443,21 +28243,38 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27443
28243
  }
27444
28244
  const obj = step;
27445
28245
  if ("wait" in obj && Object.keys(obj).length === 1) {
27446
- const { executeWait: executeWait2 } = await Promise.resolve().then(() => (init_wait(), exports_wait));
27447
- return executeWait2(state, obj.wait, chain, hub, signal);
28246
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
28247
+ const traceSeq = state.tracer?.startStep("wait", "wait", undefined, loopIndex);
28248
+ try {
28249
+ const { executeWait: executeWait2 } = await Promise.resolve().then(() => (init_wait(), exports_wait));
28250
+ const result = await executeWait2(state, obj.wait, chain, hub, signal);
28251
+ if (traceSeq !== undefined)
28252
+ state.tracer?.endStep(traceSeq, "success", result);
28253
+ return result;
28254
+ } catch (err) {
28255
+ if (traceSeq !== undefined)
28256
+ state.tracer?.endStep(traceSeq, "failure", undefined, String(err?.message ?? err));
28257
+ throw err;
28258
+ }
27448
28259
  }
27449
28260
  if ("set" in obj && Object.keys(obj).length === 1) {
27450
28261
  const setDef = obj.set;
27451
28262
  const ctx = state.toCelContext();
28263
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
28264
+ const traceSeq = state.tracer?.startStep("set", "set", setDef, loopIndex);
27452
28265
  if (!state.executionMeta.context) {
27453
28266
  state.executionMeta.context = {};
27454
28267
  }
27455
28268
  for (const [key, expr] of Object.entries(setDef)) {
27456
28269
  if (RESERVED_SET_KEYS.has(key)) {
28270
+ if (traceSeq !== undefined)
28271
+ state.tracer?.endStep(traceSeq, "failure", undefined, `set key '${key}' uses a reserved name`);
27457
28272
  throw new Error(`set key '${key}' uses a reserved name`);
27458
28273
  }
27459
28274
  state.executionMeta.context[key] = evaluateCel(expr, ctx);
27460
28275
  }
28276
+ if (traceSeq !== undefined)
28277
+ state.tracer?.endStep(traceSeq, "success", state.executionMeta.context);
27461
28278
  return chain;
27462
28279
  }
27463
28280
  if ("loop" in obj && Object.keys(obj).length === 1) {
@@ -27474,6 +28291,8 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27474
28291
  maxIterations = loopDef.max ?? Number.POSITIVE_INFINITY;
27475
28292
  }
27476
28293
  const hasMax = loopDef.max !== undefined;
28294
+ const outerLoopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
28295
+ const loopTraceSeq = state.tracer?.startStep(loopAs ?? "loop", "loop", undefined, outerLoopIndex);
27477
28296
  state.pushLoop(loopAs);
27478
28297
  let loopChain = chain;
27479
28298
  try {
@@ -27497,6 +28316,12 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27497
28316
  iterations += 1;
27498
28317
  state.incrementLoop();
27499
28318
  }
28319
+ if (loopTraceSeq !== undefined)
28320
+ state.tracer?.endStep(loopTraceSeq, "success", loopChain);
28321
+ } catch (err) {
28322
+ if (loopTraceSeq !== undefined)
28323
+ state.tracer?.endStep(loopTraceSeq, "failure", undefined, String(err?.message ?? err));
28324
+ throw err;
27500
28325
  } finally {
27501
28326
  state.popLoop();
27502
28327
  }
@@ -27505,29 +28330,54 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27505
28330
  if ("parallel" in obj && Object.keys(obj).length === 1) {
27506
28331
  const parallelSteps = obj.parallel;
27507
28332
  const controller = new AbortController;
28333
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
28334
+ const parallelTraceSeq = state.tracer?.startStep("parallel", "parallel", undefined, loopIndex);
27508
28335
  const branchSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
27509
- const results = await Promise.all(parallelSteps.map(async (parallelStep) => {
27510
- try {
27511
- return await executeControlStep(workflow, state, parallelStep, basePath, engineExecutor, chain, hub, branchSignal);
27512
- } catch (error) {
27513
- controller.abort();
27514
- throw error;
27515
- }
27516
- }));
27517
- return results;
28336
+ try {
28337
+ const results = await Promise.all(parallelSteps.map(async (parallelStep) => {
28338
+ try {
28339
+ return await executeControlStep(workflow, state, parallelStep, basePath, engineExecutor, chain, hub, branchSignal);
28340
+ } catch (error) {
28341
+ controller.abort();
28342
+ throw error;
28343
+ }
28344
+ }));
28345
+ if (parallelTraceSeq !== undefined)
28346
+ state.tracer?.endStep(parallelTraceSeq, "success", results);
28347
+ return results;
28348
+ } catch (err) {
28349
+ if (parallelTraceSeq !== undefined)
28350
+ state.tracer?.endStep(parallelTraceSeq, "failure", undefined, String(err?.message ?? err));
28351
+ throw err;
28352
+ }
27518
28353
  }
27519
28354
  if ("if" in obj && Object.keys(obj).length === 1) {
27520
28355
  const ifDef = obj.if;
27521
28356
  const condition = evaluateCel(ifDef.condition, state.toCelContext());
28357
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
28358
+ const ifTraceSeq = state.tracer?.startStep("if", "if", { condition: ifDef.condition }, loopIndex);
27522
28359
  if (typeof condition !== "boolean") {
28360
+ if (ifTraceSeq !== undefined)
28361
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, `if.condition must evaluate to boolean, got ${typeof condition}`);
27523
28362
  throw new Error(`if.condition must evaluate to boolean, got ${typeof condition}`);
27524
28363
  }
27525
- if (condition) {
27526
- return executeSequential(workflow, state, ifDef.then, basePath, engineExecutor, chain, hub, signal);
27527
- } else if (ifDef.else) {
27528
- return executeSequential(workflow, state, ifDef.else, basePath, engineExecutor, chain, hub, signal);
28364
+ try {
28365
+ let result;
28366
+ if (condition) {
28367
+ result = await executeSequential(workflow, state, ifDef.then, basePath, engineExecutor, chain, hub, signal);
28368
+ } else if (ifDef.else) {
28369
+ result = await executeSequential(workflow, state, ifDef.else, basePath, engineExecutor, chain, hub, signal);
28370
+ } else {
28371
+ result = chain;
28372
+ }
28373
+ if (ifTraceSeq !== undefined)
28374
+ state.tracer?.endStep(ifTraceSeq, "success", result);
28375
+ return result;
28376
+ } catch (err) {
28377
+ if (ifTraceSeq !== undefined)
28378
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, String(err?.message ?? err));
28379
+ throw err;
27529
28380
  }
27530
- return chain;
27531
28381
  }
27532
28382
  return executeStep(workflow, state, step, basePath, engineExecutor, [], chain, hub, signal);
27533
28383
  }
@@ -27814,6 +28664,82 @@ init_parser3();
27814
28664
  init_schema();
27815
28665
  init_validate();
27816
28666
  import { dirname as dirname2, resolve as resolve3 } from "node:path";
28667
+
28668
+ // src/tracer/index.ts
28669
+ import { mkdir } from "node:fs/promises";
28670
+
28671
+ class TraceCollector {
28672
+ openSteps = new Map;
28673
+ seq = 0;
28674
+ truncateAt;
28675
+ writer;
28676
+ constructor(truncateAt = 1e6) {
28677
+ this.truncateAt = truncateAt;
28678
+ }
28679
+ startStep(name, type3, input, loopIndex) {
28680
+ this.seq += 1;
28681
+ this.openSteps.set(this.seq, {
28682
+ name,
28683
+ type: type3,
28684
+ startedAt: new Date().toISOString(),
28685
+ startMs: performance.now(),
28686
+ input: input !== undefined ? this.truncate(input) : undefined,
28687
+ loopIndex
28688
+ });
28689
+ return this.seq;
28690
+ }
28691
+ endStep(seq, status, output, error, retries) {
28692
+ const open = this.openSteps.get(seq);
28693
+ if (!open)
28694
+ return;
28695
+ this.openSteps.delete(seq);
28696
+ const finishedAt = new Date().toISOString();
28697
+ const duration = Math.max(0, performance.now() - open.startMs);
28698
+ const step = {
28699
+ seq,
28700
+ name: open.name,
28701
+ type: open.type,
28702
+ startedAt: open.startedAt,
28703
+ finishedAt,
28704
+ duration: Math.round(duration),
28705
+ status,
28706
+ ...open.input !== undefined ? { input: open.input } : {},
28707
+ ...output !== undefined ? { output: this.truncate(output) } : {},
28708
+ ...error !== undefined ? { error } : {},
28709
+ ...retries !== undefined && retries > 0 ? { retries } : {},
28710
+ ...open.loopIndex !== undefined ? { loopIndex: open.loopIndex } : {}
28711
+ };
28712
+ this.writer?.writeStep(step);
28713
+ }
28714
+ truncate(value) {
28715
+ if (value == null)
28716
+ return value;
28717
+ const str = typeof value === "string" ? value : JSON.stringify(value);
28718
+ const bytes = new TextEncoder().encode(str);
28719
+ if (bytes.length <= this.truncateAt)
28720
+ return value;
28721
+ const cut = new TextDecoder().decode(bytes.slice(0, this.truncateAt));
28722
+ return cut + "...[truncated]";
28723
+ }
28724
+ }
28725
+ async function createTraceWriter(dir, format) {
28726
+ await mkdir(dir, { recursive: true });
28727
+ if (format === "json") {
28728
+ const { JsonTraceWriter: JsonTraceWriter2 } = await Promise.resolve().then(() => (init_json(), exports_json));
28729
+ return new JsonTraceWriter2(dir);
28730
+ }
28731
+ if (format === "txt") {
28732
+ const { TxtTraceWriter: TxtTraceWriter2 } = await Promise.resolve().then(() => (init_txt(), exports_txt));
28733
+ return new TxtTraceWriter2(dir);
28734
+ }
28735
+ if (format === "sqlite") {
28736
+ const { SqliteTraceWriter: SqliteTraceWriter2 } = await Promise.resolve().then(() => (init_sqlite(), exports_sqlite));
28737
+ return new SqliteTraceWriter2(dir);
28738
+ }
28739
+ throw new Error(`unknown trace format: ${format}`);
28740
+ }
28741
+
28742
+ // src/engine/engine.ts
27817
28743
  init_control();
27818
28744
 
27819
28745
  // src/engine/state.ts
@@ -27831,6 +28757,7 @@ class WorkflowState {
27831
28757
  chainValue;
27832
28758
  eventPayload;
27833
28759
  ancestorPaths;
28760
+ tracer;
27834
28761
  constructor(inputs = {}) {
27835
28762
  this.inputs = { ...inputs };
27836
28763
  this.workflowInputs = { ...inputs };
@@ -27881,6 +28808,9 @@ class WorkflowState {
27881
28808
  if (top)
27882
28809
  top.last = last2;
27883
28810
  }
28811
+ isInsideLoop() {
28812
+ return this.loopStack.length > 0;
28813
+ }
27884
28814
  currentLoopContext() {
27885
28815
  const top = this.loopStack[this.loopStack.length - 1];
27886
28816
  if (!top)
@@ -27971,38 +28901,66 @@ async function executeWorkflow(workflow, inputs = {}, basePath = process.cwd(),
27971
28901
  if (options.cwd) {
27972
28902
  state.executionMeta.cwd = options.cwd;
27973
28903
  }
27974
- const startedAt = performance.now();
28904
+ const startedAtMs = performance.now();
28905
+ const startedAtIso = state.executionMeta.startedAt;
27975
28906
  if (options._ancestorPaths) {
27976
28907
  state.ancestorPaths = options._ancestorPaths;
27977
28908
  }
28909
+ if (options.traceDir && !options._ancestorPaths) {
28910
+ const collector = new TraceCollector;
28911
+ const writer = await createTraceWriter(options.traceDir, options.traceFormat ?? "json");
28912
+ collector.writer = writer;
28913
+ state.tracer = collector;
28914
+ await writer.open({
28915
+ id: state.executionMeta.id,
28916
+ workflowId: workflow.id,
28917
+ workflowName: workflow.name,
28918
+ workflowVersion: workflow.version,
28919
+ startedAt: startedAtIso,
28920
+ inputs: resolved
28921
+ });
28922
+ }
27978
28923
  const engineExecutor = async (subWorkflow, subInputs, subBasePath) => {
27979
28924
  return executeWorkflow(subWorkflow, subInputs, subBasePath, {
27980
28925
  ...options,
27981
28926
  _ancestorPaths: state.ancestorPaths
27982
28927
  });
27983
28928
  };
27984
- let chain;
27985
- for (const step of workflow.flow) {
27986
- chain = await executeControlStep(workflow, state, step, basePath, engineExecutor, chain, options.hub);
27987
- }
27988
28929
  let output;
27989
- if (workflow.output !== undefined) {
27990
- output = state.resolveOutput(workflow.output, evaluateCel);
27991
- if (typeof workflow.output === "object" && "schema" in workflow.output && "map" in workflow.output && typeof output === "object" && output !== null) {
27992
- validateOutputSchema(workflow.output.schema, output);
28930
+ let success = false;
28931
+ try {
28932
+ let chain;
28933
+ for (const step of workflow.flow) {
28934
+ chain = await executeControlStep(workflow, state, step, basePath, engineExecutor, chain, options.hub);
28935
+ }
28936
+ if (workflow.output !== undefined) {
28937
+ output = state.resolveOutput(workflow.output, evaluateCel);
28938
+ if (typeof workflow.output === "object" && "schema" in workflow.output && "map" in workflow.output && typeof output === "object" && output !== null) {
28939
+ validateOutputSchema(workflow.output.schema, output);
28940
+ }
28941
+ } else {
28942
+ output = chain;
28943
+ }
28944
+ const steps = state.getAllResults();
28945
+ success = !Object.values(steps).some((step) => step.status === "failure");
28946
+ state.executionMeta.status = success ? "success" : "failure";
28947
+ return {
28948
+ success,
28949
+ output,
28950
+ steps,
28951
+ duration: Math.max(0, performance.now() - startedAtMs)
28952
+ };
28953
+ } finally {
28954
+ if (state.tracer?.writer) {
28955
+ const duration = Math.max(0, performance.now() - startedAtMs);
28956
+ await state.tracer.writer.finalize({
28957
+ status: success ? "success" : "failure",
28958
+ output: output ?? null,
28959
+ finishedAt: new Date().toISOString(),
28960
+ duration
28961
+ }).catch(() => {});
27993
28962
  }
27994
- } else {
27995
- output = chain;
27996
28963
  }
27997
- const steps = state.getAllResults();
27998
- const success = !Object.values(steps).some((step) => step.status === "failure");
27999
- state.executionMeta.status = success ? "success" : "failure";
28000
- return {
28001
- success,
28002
- output,
28003
- steps,
28004
- duration: Math.max(0, performance.now() - startedAt)
28005
- };
28006
28964
  }
28007
28965
  async function runWorkflowFromFile(filePath, inputs = {}, options = {}) {
28008
28966
  const workflow = await parseWorkflowFile(filePath);