@duckflux/core 0.6.8 → 0.7.0

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.
@@ -26590,6 +26590,248 @@ var init_validate = __esm(() => {
26590
26590
  BUILTIN_ONERROR = new Set(["fail", "skip", "retry"]);
26591
26591
  });
26592
26592
 
26593
+ // src/tracer/writers/json.ts
26594
+ var exports_json = {};
26595
+ __export(exports_json, {
26596
+ JsonTraceWriter: () => JsonTraceWriter
26597
+ });
26598
+ import { writeFile } from "node:fs/promises";
26599
+ import { join } from "node:path";
26600
+
26601
+ class JsonTraceWriter {
26602
+ dir;
26603
+ filePath = "";
26604
+ trace = {
26605
+ execution: {
26606
+ id: "",
26607
+ startedAt: "",
26608
+ finishedAt: "",
26609
+ duration: 0,
26610
+ status: "running",
26611
+ inputs: null,
26612
+ output: null
26613
+ },
26614
+ steps: []
26615
+ };
26616
+ constructor(dir) {
26617
+ this.dir = dir;
26618
+ }
26619
+ async open(meta) {
26620
+ this.filePath = join(this.dir, `${meta.id}.json`);
26621
+ this.trace = {
26622
+ execution: {
26623
+ id: meta.id,
26624
+ workflowId: meta.workflowId,
26625
+ workflowName: meta.workflowName,
26626
+ workflowVersion: meta.workflowVersion,
26627
+ startedAt: meta.startedAt,
26628
+ finishedAt: "",
26629
+ duration: 0,
26630
+ status: "running",
26631
+ inputs: meta.inputs,
26632
+ output: null
26633
+ },
26634
+ steps: []
26635
+ };
26636
+ await this.flush();
26637
+ }
26638
+ writeStep(step) {
26639
+ this.trace.steps.push(step);
26640
+ this.flushSync();
26641
+ }
26642
+ async finalize(meta) {
26643
+ this.trace.execution.status = meta.status;
26644
+ this.trace.execution.output = meta.output;
26645
+ this.trace.execution.finishedAt = meta.finishedAt;
26646
+ this.trace.execution.duration = meta.duration;
26647
+ await this.flush();
26648
+ }
26649
+ flushSync() {
26650
+ this.flush().catch(() => {});
26651
+ }
26652
+ async flush() {
26653
+ if (!this.filePath)
26654
+ return;
26655
+ await writeFile(this.filePath, JSON.stringify(this.trace, null, 2), "utf-8");
26656
+ }
26657
+ }
26658
+ var init_json = () => {};
26659
+
26660
+ // src/tracer/writers/txt.ts
26661
+ var exports_txt = {};
26662
+ __export(exports_txt, {
26663
+ TxtTraceWriter: () => TxtTraceWriter
26664
+ });
26665
+ import { appendFile, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
26666
+ import { join as join2 } from "node:path";
26667
+ function serializeValue(value) {
26668
+ if (value === undefined || value === null)
26669
+ return "none";
26670
+ if (typeof value === "string")
26671
+ return value;
26672
+ return JSON.stringify(value, null, 2);
26673
+ }
26674
+ function formatStep(step) {
26675
+ const lines = [
26676
+ `## [${step.seq}] ${step.name} (${step.type})`,
26677
+ `startedAt: ${step.startedAt}`
26678
+ ];
26679
+ if (step.finishedAt)
26680
+ lines.push(`finishedAt: ${step.finishedAt}`);
26681
+ if (step.duration !== undefined)
26682
+ lines.push(`duration: ${step.duration}ms`);
26683
+ lines.push(`status: ${step.status}`);
26684
+ if (step.loopIndex !== undefined)
26685
+ lines.push(`loopIndex: ${step.loopIndex}`);
26686
+ if (step.retries !== undefined && step.retries > 0)
26687
+ lines.push(`retries: ${step.retries}`);
26688
+ lines.push(`input: ${serializeValue(step.input)}`);
26689
+ lines.push(`output: ${serializeValue(step.output)}`);
26690
+ if (step.error)
26691
+ lines.push(`error: ${step.error}`);
26692
+ lines.push("");
26693
+ return lines.join(`
26694
+ `);
26695
+ }
26696
+
26697
+ class TxtTraceWriter {
26698
+ dir;
26699
+ filePath = "";
26700
+ constructor(dir) {
26701
+ this.dir = dir;
26702
+ }
26703
+ async open(meta) {
26704
+ this.filePath = join2(this.dir, `${meta.id}.txt`);
26705
+ const versionStr = meta.workflowVersion !== undefined ? ` (v${meta.workflowVersion})` : "";
26706
+ const workflowLabel = meta.workflowName ?? meta.workflowId ?? "unnamed";
26707
+ const header = [
26708
+ "# execution",
26709
+ `id: ${meta.id}`,
26710
+ `workflow: ${workflowLabel}${versionStr}`,
26711
+ `startedAt: ${meta.startedAt}`,
26712
+ "status: running",
26713
+ "",
26714
+ "# inputs",
26715
+ serializeValue(meta.inputs),
26716
+ "",
26717
+ "# steps",
26718
+ ""
26719
+ ].join(`
26720
+ `);
26721
+ await writeFile2(this.filePath, header, "utf-8");
26722
+ }
26723
+ async writeStep(step) {
26724
+ if (!this.filePath)
26725
+ return;
26726
+ await appendFile(this.filePath, formatStep(step), "utf-8");
26727
+ }
26728
+ async finalize(meta) {
26729
+ if (!this.filePath)
26730
+ return;
26731
+ const outputSection = [
26732
+ "# output",
26733
+ serializeValue(meta.output),
26734
+ ""
26735
+ ].join(`
26736
+ `);
26737
+ await appendFile(this.filePath, outputSection, "utf-8");
26738
+ const content = await readFile2(this.filePath, "utf-8");
26739
+ const updated = content.replace(/^status: running$/m, `status: ${meta.status}
26740
+ finishedAt: ${meta.finishedAt}
26741
+ duration: ${meta.duration}ms`);
26742
+ await writeFile2(this.filePath, updated, "utf-8");
26743
+ }
26744
+ }
26745
+ var init_txt = () => {};
26746
+
26747
+ // src/tracer/writers/sqlite.ts
26748
+ var exports_sqlite = {};
26749
+ __export(exports_sqlite, {
26750
+ SqliteTraceWriter: () => SqliteTraceWriter
26751
+ });
26752
+ import { join as join3 } from "node:path";
26753
+ function toJson(value) {
26754
+ if (value === undefined || value === null)
26755
+ return null;
26756
+ if (typeof value === "string")
26757
+ return value;
26758
+ return JSON.stringify(value);
26759
+ }
26760
+
26761
+ class SqliteTraceWriter {
26762
+ dir;
26763
+ db = null;
26764
+ executionId = "";
26765
+ constructor(dir) {
26766
+ this.dir = dir;
26767
+ }
26768
+ async open(meta) {
26769
+ this.executionId = meta.id;
26770
+ const filePath = join3(this.dir, `${meta.id}.sqlite`);
26771
+ const { Database } = await import("bun:sqlite");
26772
+ this.db = new Database(filePath);
26773
+ this.db.exec(`
26774
+ CREATE TABLE IF NOT EXISTS executions (
26775
+ id TEXT PRIMARY KEY,
26776
+ workflow_id TEXT,
26777
+ workflow_name TEXT,
26778
+ workflow_version TEXT,
26779
+ started_at TEXT NOT NULL,
26780
+ finished_at TEXT,
26781
+ duration_ms INTEGER,
26782
+ status TEXT NOT NULL,
26783
+ inputs TEXT,
26784
+ output TEXT
26785
+ );
26786
+
26787
+ CREATE TABLE IF NOT EXISTS steps (
26788
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26789
+ execution_id TEXT NOT NULL REFERENCES executions(id),
26790
+ seq INTEGER NOT NULL,
26791
+ name TEXT NOT NULL,
26792
+ type TEXT NOT NULL,
26793
+ started_at TEXT,
26794
+ finished_at TEXT,
26795
+ duration_ms INTEGER,
26796
+ status TEXT NOT NULL,
26797
+ input TEXT,
26798
+ output TEXT,
26799
+ error TEXT,
26800
+ retries INTEGER,
26801
+ loop_index INTEGER
26802
+ );
26803
+ `);
26804
+ const insert = this.db.prepare(`
26805
+ INSERT INTO executions (id, workflow_id, workflow_name, workflow_version, started_at, status, inputs)
26806
+ VALUES (?, ?, ?, ?, ?, 'running', ?)
26807
+ `);
26808
+ insert.run(meta.id, meta.workflowId ?? null, meta.workflowName ?? null, meta.workflowVersion !== undefined ? String(meta.workflowVersion) : null, meta.startedAt, toJson(meta.inputs));
26809
+ }
26810
+ writeStep(step) {
26811
+ if (!this.db)
26812
+ return;
26813
+ const insert = this.db.prepare(`
26814
+ INSERT INTO steps
26815
+ (execution_id, seq, name, type, started_at, finished_at, duration_ms, status, input, output, error, retries, loop_index)
26816
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
26817
+ `);
26818
+ 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);
26819
+ }
26820
+ async finalize(meta) {
26821
+ if (!this.db)
26822
+ return;
26823
+ const update = this.db.prepare(`
26824
+ UPDATE executions
26825
+ SET status = ?, output = ?, finished_at = ?, duration_ms = ?
26826
+ WHERE id = ?
26827
+ `);
26828
+ update.run(meta.status, toJson(meta.output), meta.finishedAt, Math.round(meta.duration), this.executionId);
26829
+ this.db.close();
26830
+ this.db = null;
26831
+ }
26832
+ }
26833
+ var init_sqlite = () => {};
26834
+
26593
26835
  // src/engine/errors.ts
26594
26836
  function sleep(ms) {
26595
26837
  return new Promise((resolve2) => setTimeout(resolve2, ms));
@@ -27189,6 +27431,10 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27189
27431
  output: "",
27190
27432
  duration: 0
27191
27433
  });
27434
+ const loopIndex2 = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27435
+ const skippedSeq = state.tracer?.startStep(stepName, participant.type, undefined, loopIndex2);
27436
+ if (skippedSeq !== undefined)
27437
+ state.tracer?.endStep(skippedSeq, "skipped");
27192
27438
  }
27193
27439
  return chain;
27194
27440
  }
@@ -27197,6 +27443,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27197
27443
  const overrideInput = override?.input !== undefined ? resolveParticipantInput(override.input, state) : undefined;
27198
27444
  const mergedWithBase = mergeChainedInput(chain, baseInput);
27199
27445
  const mergedInput = mergeChainedInput(mergedWithBase, overrideInput);
27446
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27447
+ const traceSeq = state.tracer?.startStep(stepName ?? "<anonymous>", participant.type, mergedInput, loopIndex);
27200
27448
  state.currentInput = mergedInput;
27201
27449
  const strategy = resolveErrorStrategy(override ?? null, participant, workflow.defaults ?? null);
27202
27450
  const timeoutMs = resolveTimeout(override ?? null, participant, workflow.defaults ?? null);
@@ -27274,6 +27522,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27274
27522
  }
27275
27523
  const outputValue = result.parsedOutput ?? result.output;
27276
27524
  state.currentOutput = outputValue;
27525
+ if (traceSeq !== undefined)
27526
+ state.tracer?.endStep(traceSeq, result.status, outputValue, undefined, retries);
27277
27527
  return outputValue;
27278
27528
  } catch (error) {
27279
27529
  const message = String(error?.message ?? error);
@@ -27299,6 +27549,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27299
27549
  if (stepName) {
27300
27550
  state.setResult(stepName, skipResult);
27301
27551
  }
27552
+ if (traceSeq !== undefined)
27553
+ state.tracer?.endStep(traceSeq, "skipped", undefined, message);
27302
27554
  return chain;
27303
27555
  }
27304
27556
  if (strategy !== "fail" && strategy !== "retry") {
@@ -27317,6 +27569,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27317
27569
  ...httpMeta
27318
27570
  });
27319
27571
  }
27572
+ if (traceSeq !== undefined)
27573
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
27320
27574
  const fallbackResult = await executeStep(workflow, state, fallbackName, basePath, engineExecutor, [...fallbackStack, stepName ?? "<anonymous>"], chain, hub);
27321
27575
  return fallbackResult;
27322
27576
  }
@@ -27331,6 +27585,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
27331
27585
  ...httpMeta
27332
27586
  });
27333
27587
  }
27588
+ if (traceSeq !== undefined)
27589
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
27334
27590
  throw error;
27335
27591
  }
27336
27592
  }
@@ -27443,21 +27699,38 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27443
27699
  }
27444
27700
  const obj = step;
27445
27701
  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);
27702
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27703
+ const traceSeq = state.tracer?.startStep("wait", "wait", undefined, loopIndex);
27704
+ try {
27705
+ const { executeWait: executeWait2 } = await Promise.resolve().then(() => (init_wait(), exports_wait));
27706
+ const result = await executeWait2(state, obj.wait, chain, hub, signal);
27707
+ if (traceSeq !== undefined)
27708
+ state.tracer?.endStep(traceSeq, "success", result);
27709
+ return result;
27710
+ } catch (err) {
27711
+ if (traceSeq !== undefined)
27712
+ state.tracer?.endStep(traceSeq, "failure", undefined, String(err?.message ?? err));
27713
+ throw err;
27714
+ }
27448
27715
  }
27449
27716
  if ("set" in obj && Object.keys(obj).length === 1) {
27450
27717
  const setDef = obj.set;
27451
27718
  const ctx = state.toCelContext();
27719
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27720
+ const traceSeq = state.tracer?.startStep("set", "set", setDef, loopIndex);
27452
27721
  if (!state.executionMeta.context) {
27453
27722
  state.executionMeta.context = {};
27454
27723
  }
27455
27724
  for (const [key, expr] of Object.entries(setDef)) {
27456
27725
  if (RESERVED_SET_KEYS.has(key)) {
27726
+ if (traceSeq !== undefined)
27727
+ state.tracer?.endStep(traceSeq, "failure", undefined, `set key '${key}' uses a reserved name`);
27457
27728
  throw new Error(`set key '${key}' uses a reserved name`);
27458
27729
  }
27459
27730
  state.executionMeta.context[key] = evaluateCel(expr, ctx);
27460
27731
  }
27732
+ if (traceSeq !== undefined)
27733
+ state.tracer?.endStep(traceSeq, "success", state.executionMeta.context);
27461
27734
  return chain;
27462
27735
  }
27463
27736
  if ("loop" in obj && Object.keys(obj).length === 1) {
@@ -27474,6 +27747,8 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27474
27747
  maxIterations = loopDef.max ?? Number.POSITIVE_INFINITY;
27475
27748
  }
27476
27749
  const hasMax = loopDef.max !== undefined;
27750
+ const outerLoopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27751
+ const loopTraceSeq = state.tracer?.startStep(loopAs ?? "loop", "loop", undefined, outerLoopIndex);
27477
27752
  state.pushLoop(loopAs);
27478
27753
  let loopChain = chain;
27479
27754
  try {
@@ -27497,6 +27772,12 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27497
27772
  iterations += 1;
27498
27773
  state.incrementLoop();
27499
27774
  }
27775
+ if (loopTraceSeq !== undefined)
27776
+ state.tracer?.endStep(loopTraceSeq, "success", loopChain);
27777
+ } catch (err) {
27778
+ if (loopTraceSeq !== undefined)
27779
+ state.tracer?.endStep(loopTraceSeq, "failure", undefined, String(err?.message ?? err));
27780
+ throw err;
27500
27781
  } finally {
27501
27782
  state.popLoop();
27502
27783
  }
@@ -27505,29 +27786,54 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27505
27786
  if ("parallel" in obj && Object.keys(obj).length === 1) {
27506
27787
  const parallelSteps = obj.parallel;
27507
27788
  const controller = new AbortController;
27789
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27790
+ const parallelTraceSeq = state.tracer?.startStep("parallel", "parallel", undefined, loopIndex);
27508
27791
  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;
27792
+ try {
27793
+ const results = await Promise.all(parallelSteps.map(async (parallelStep) => {
27794
+ try {
27795
+ return await executeControlStep(workflow, state, parallelStep, basePath, engineExecutor, chain, hub, branchSignal);
27796
+ } catch (error) {
27797
+ controller.abort();
27798
+ throw error;
27799
+ }
27800
+ }));
27801
+ if (parallelTraceSeq !== undefined)
27802
+ state.tracer?.endStep(parallelTraceSeq, "success", results);
27803
+ return results;
27804
+ } catch (err) {
27805
+ if (parallelTraceSeq !== undefined)
27806
+ state.tracer?.endStep(parallelTraceSeq, "failure", undefined, String(err?.message ?? err));
27807
+ throw err;
27808
+ }
27518
27809
  }
27519
27810
  if ("if" in obj && Object.keys(obj).length === 1) {
27520
27811
  const ifDef = obj.if;
27521
27812
  const condition = evaluateCel(ifDef.condition, state.toCelContext());
27813
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27814
+ const ifTraceSeq = state.tracer?.startStep("if", "if", { condition: ifDef.condition }, loopIndex);
27522
27815
  if (typeof condition !== "boolean") {
27816
+ if (ifTraceSeq !== undefined)
27817
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, `if.condition must evaluate to boolean, got ${typeof condition}`);
27523
27818
  throw new Error(`if.condition must evaluate to boolean, got ${typeof condition}`);
27524
27819
  }
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);
27820
+ try {
27821
+ let result;
27822
+ if (condition) {
27823
+ result = await executeSequential(workflow, state, ifDef.then, basePath, engineExecutor, chain, hub, signal);
27824
+ } else if (ifDef.else) {
27825
+ result = await executeSequential(workflow, state, ifDef.else, basePath, engineExecutor, chain, hub, signal);
27826
+ } else {
27827
+ result = chain;
27828
+ }
27829
+ if (ifTraceSeq !== undefined)
27830
+ state.tracer?.endStep(ifTraceSeq, "success", result);
27831
+ return result;
27832
+ } catch (err) {
27833
+ if (ifTraceSeq !== undefined)
27834
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, String(err?.message ?? err));
27835
+ throw err;
27529
27836
  }
27530
- return chain;
27531
27837
  }
27532
27838
  return executeStep(workflow, state, step, basePath, engineExecutor, [], chain, hub, signal);
27533
27839
  }
@@ -27707,6 +28013,80 @@ function validateInputs(inputDefs, provided) {
27707
28013
  };
27708
28014
  }
27709
28015
 
28016
+ // src/tracer/index.ts
28017
+ import { mkdir } from "node:fs/promises";
28018
+
28019
+ class TraceCollector {
28020
+ openSteps = new Map;
28021
+ seq = 0;
28022
+ truncateAt;
28023
+ writer;
28024
+ constructor(truncateAt = 1e6) {
28025
+ this.truncateAt = truncateAt;
28026
+ }
28027
+ startStep(name, type3, input, loopIndex) {
28028
+ this.seq += 1;
28029
+ this.openSteps.set(this.seq, {
28030
+ name,
28031
+ type: type3,
28032
+ startedAt: new Date().toISOString(),
28033
+ startMs: performance.now(),
28034
+ input: input !== undefined ? this.truncate(input) : undefined,
28035
+ loopIndex
28036
+ });
28037
+ return this.seq;
28038
+ }
28039
+ endStep(seq, status, output, error, retries) {
28040
+ const open = this.openSteps.get(seq);
28041
+ if (!open)
28042
+ return;
28043
+ this.openSteps.delete(seq);
28044
+ const finishedAt = new Date().toISOString();
28045
+ const duration = Math.max(0, performance.now() - open.startMs);
28046
+ const step = {
28047
+ seq,
28048
+ name: open.name,
28049
+ type: open.type,
28050
+ startedAt: open.startedAt,
28051
+ finishedAt,
28052
+ duration: Math.round(duration),
28053
+ status,
28054
+ ...open.input !== undefined ? { input: open.input } : {},
28055
+ ...output !== undefined ? { output: this.truncate(output) } : {},
28056
+ ...error !== undefined ? { error } : {},
28057
+ ...retries !== undefined && retries > 0 ? { retries } : {},
28058
+ ...open.loopIndex !== undefined ? { loopIndex: open.loopIndex } : {}
28059
+ };
28060
+ this.writer?.writeStep(step);
28061
+ }
28062
+ truncate(value) {
28063
+ if (value == null)
28064
+ return value;
28065
+ const str = typeof value === "string" ? value : JSON.stringify(value);
28066
+ const bytes = new TextEncoder().encode(str);
28067
+ if (bytes.length <= this.truncateAt)
28068
+ return value;
28069
+ const cut = new TextDecoder().decode(bytes.slice(0, this.truncateAt));
28070
+ return cut + "...[truncated]";
28071
+ }
28072
+ }
28073
+ async function createTraceWriter(dir, format) {
28074
+ await mkdir(dir, { recursive: true });
28075
+ if (format === "json") {
28076
+ const { JsonTraceWriter: JsonTraceWriter2 } = await Promise.resolve().then(() => (init_json(), exports_json));
28077
+ return new JsonTraceWriter2(dir);
28078
+ }
28079
+ if (format === "txt") {
28080
+ const { TxtTraceWriter: TxtTraceWriter2 } = await Promise.resolve().then(() => (init_txt(), exports_txt));
28081
+ return new TxtTraceWriter2(dir);
28082
+ }
28083
+ if (format === "sqlite") {
28084
+ const { SqliteTraceWriter: SqliteTraceWriter2 } = await Promise.resolve().then(() => (init_sqlite(), exports_sqlite));
28085
+ return new SqliteTraceWriter2(dir);
28086
+ }
28087
+ throw new Error(`unknown trace format: ${format}`);
28088
+ }
28089
+
27710
28090
  // src/engine/engine.ts
27711
28091
  init_control();
27712
28092
 
@@ -27725,6 +28105,7 @@ class WorkflowState {
27725
28105
  chainValue;
27726
28106
  eventPayload;
27727
28107
  ancestorPaths;
28108
+ tracer;
27728
28109
  constructor(inputs = {}) {
27729
28110
  this.inputs = { ...inputs };
27730
28111
  this.workflowInputs = { ...inputs };
@@ -27775,6 +28156,9 @@ class WorkflowState {
27775
28156
  if (top)
27776
28157
  top.last = last2;
27777
28158
  }
28159
+ isInsideLoop() {
28160
+ return this.loopStack.length > 0;
28161
+ }
27778
28162
  currentLoopContext() {
27779
28163
  const top = this.loopStack[this.loopStack.length - 1];
27780
28164
  if (!top)
@@ -27865,38 +28249,66 @@ async function executeWorkflow(workflow, inputs = {}, basePath = process.cwd(),
27865
28249
  if (options.cwd) {
27866
28250
  state.executionMeta.cwd = options.cwd;
27867
28251
  }
27868
- const startedAt = performance.now();
28252
+ const startedAtMs = performance.now();
28253
+ const startedAtIso = state.executionMeta.startedAt;
27869
28254
  if (options._ancestorPaths) {
27870
28255
  state.ancestorPaths = options._ancestorPaths;
27871
28256
  }
28257
+ if (options.traceDir && !options._ancestorPaths) {
28258
+ const collector = new TraceCollector;
28259
+ const writer = await createTraceWriter(options.traceDir, options.traceFormat ?? "json");
28260
+ collector.writer = writer;
28261
+ state.tracer = collector;
28262
+ await writer.open({
28263
+ id: state.executionMeta.id,
28264
+ workflowId: workflow.id,
28265
+ workflowName: workflow.name,
28266
+ workflowVersion: workflow.version,
28267
+ startedAt: startedAtIso,
28268
+ inputs: resolved
28269
+ });
28270
+ }
27872
28271
  const engineExecutor = async (subWorkflow, subInputs, subBasePath) => {
27873
28272
  return executeWorkflow(subWorkflow, subInputs, subBasePath, {
27874
28273
  ...options,
27875
28274
  _ancestorPaths: state.ancestorPaths
27876
28275
  });
27877
28276
  };
27878
- let chain;
27879
- for (const step of workflow.flow) {
27880
- chain = await executeControlStep(workflow, state, step, basePath, engineExecutor, chain, options.hub);
27881
- }
27882
28277
  let output;
27883
- if (workflow.output !== undefined) {
27884
- output = state.resolveOutput(workflow.output, evaluateCel);
27885
- if (typeof workflow.output === "object" && "schema" in workflow.output && "map" in workflow.output && typeof output === "object" && output !== null) {
27886
- validateOutputSchema(workflow.output.schema, output);
28278
+ let success = false;
28279
+ try {
28280
+ let chain;
28281
+ for (const step of workflow.flow) {
28282
+ chain = await executeControlStep(workflow, state, step, basePath, engineExecutor, chain, options.hub);
28283
+ }
28284
+ if (workflow.output !== undefined) {
28285
+ output = state.resolveOutput(workflow.output, evaluateCel);
28286
+ if (typeof workflow.output === "object" && "schema" in workflow.output && "map" in workflow.output && typeof output === "object" && output !== null) {
28287
+ validateOutputSchema(workflow.output.schema, output);
28288
+ }
28289
+ } else {
28290
+ output = chain;
28291
+ }
28292
+ const steps = state.getAllResults();
28293
+ success = !Object.values(steps).some((step) => step.status === "failure");
28294
+ state.executionMeta.status = success ? "success" : "failure";
28295
+ return {
28296
+ success,
28297
+ output,
28298
+ steps,
28299
+ duration: Math.max(0, performance.now() - startedAtMs)
28300
+ };
28301
+ } finally {
28302
+ if (state.tracer?.writer) {
28303
+ const duration = Math.max(0, performance.now() - startedAtMs);
28304
+ await state.tracer.writer.finalize({
28305
+ status: success ? "success" : "failure",
28306
+ output: output ?? null,
28307
+ finishedAt: new Date().toISOString(),
28308
+ duration
28309
+ }).catch(() => {});
27887
28310
  }
27888
- } else {
27889
- output = chain;
27890
28311
  }
27891
- const steps = state.getAllResults();
27892
- const success = !Object.values(steps).some((step) => step.status === "failure");
27893
- state.executionMeta.status = success ? "success" : "failure";
27894
- return {
27895
- success,
27896
- output,
27897
- steps,
27898
- duration: Math.max(0, performance.now() - startedAt)
27899
- };
27900
28312
  }
27901
28313
  async function runWorkflowFromFile(filePath, inputs = {}, options = {}) {
27902
28314
  const workflow = await parseWorkflowFile(filePath);