@duckflux/runner 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.
package/dist/main.js CHANGED
@@ -23,10 +23,10 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
23
23
  var exports_eventhub = {};
24
24
  __export(exports_eventhub, {
25
25
  createHub: () => createHub,
26
- MemoryHub: () => MemoryHub
26
+ MemoryHub: () => MemoryHub2
27
27
  });
28
28
 
29
- class MemoryHub {
29
+ class MemoryHub2 {
30
30
  listeners = new Map;
31
31
  buffer = new Map;
32
32
  closed = false;
@@ -114,7 +114,7 @@ class MemoryHub {
114
114
  async function createHub(config) {
115
115
  switch (config.backend) {
116
116
  case "memory":
117
- return new MemoryHub;
117
+ return new MemoryHub2;
118
118
  case "nats":
119
119
  throw new Error("NATS backend has been moved to @duckflux/hub-nats. " + "Install it and pass a NatsHub instance via ExecuteOptions.hub instead.");
120
120
  case "redis":
@@ -128,23 +128,26 @@ var init_eventhub = () => {};
128
128
  // src/main.ts
129
129
  import { readFileSync as readFileSync3 } from "fs";
130
130
  import { parseArgs } from "util";
131
- import { dirname as dirname7, resolve as resolve6 } from "path";
131
+ import { dirname as dirname5, resolve as resolve5 } from "path";
132
132
 
133
- // src/run.ts
134
- import { readFile as readFile2 } from "node:fs/promises";
133
+ // src/lint.ts
134
+ import { dirname as dirname2 } from "node:path";
135
135
 
136
- // ../core/dist/engine/index.js
136
+ // ../core/dist/index.js
137
137
  import { createRequire as createRequire2 } from "node:module";
138
138
  import { readFile } from "node:fs/promises";
139
139
  import { readFileSync } from "node:fs";
140
140
  import { constants } from "node:fs";
141
141
  import { access } from "node:fs/promises";
142
142
  import { resolve } from "node:path";
143
+ import { writeFile } from "node:fs/promises";
144
+ import { join } from "node:path";
145
+ import { appendFile, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
146
+ import { join as join2 } from "node:path";
147
+ import { join as join3 } from "node:path";
143
148
  import { spawn } from "node:child_process";
144
149
  import { dirname, resolve as resolve2 } from "node:path";
145
150
  import { resolve as resolvePath, isAbsolute } from "node:path";
146
- import { dirname as dirname2, resolve as resolve3 } from "node:path";
147
- import { env } from "node:process";
148
151
  var __create = Object.create;
149
152
  var __getProtoOf = Object.getPrototypeOf;
150
153
  var __defProp2 = Object.defineProperty;
@@ -21520,20 +21523,20 @@ var require_compile = __commonJS((exports) => {
21520
21523
  var validate_1 = require_validate();
21521
21524
 
21522
21525
  class SchemaEnv {
21523
- constructor(env2) {
21526
+ constructor(env) {
21524
21527
  var _a;
21525
21528
  this.refs = {};
21526
21529
  this.dynamicAnchors = {};
21527
21530
  let schema;
21528
- if (typeof env2.schema == "object")
21529
- schema = env2.schema;
21530
- this.schema = env2.schema;
21531
- this.schemaId = env2.schemaId;
21532
- this.root = env2.root || this;
21533
- this.baseId = (_a = env2.baseId) !== null && _a !== undefined ? _a : (0, resolve_1.normalizeId)(schema === null || schema === undefined ? undefined : schema[env2.schemaId || "$id"]);
21534
- this.schemaPath = env2.schemaPath;
21535
- this.localRefs = env2.localRefs;
21536
- this.meta = env2.meta;
21531
+ if (typeof env.schema == "object")
21532
+ schema = env.schema;
21533
+ this.schema = env.schema;
21534
+ this.schemaId = env.schemaId;
21535
+ this.root = env.root || this;
21536
+ this.baseId = (_a = env.baseId) !== null && _a !== undefined ? _a : (0, resolve_1.normalizeId)(schema === null || schema === undefined ? undefined : schema[env.schemaId || "$id"]);
21537
+ this.schemaPath = env.schemaPath;
21538
+ this.localRefs = env.localRefs;
21539
+ this.meta = env.meta;
21537
21540
  this.$async = schema === null || schema === undefined ? undefined : schema.$async;
21538
21541
  this.refs = {};
21539
21542
  }
@@ -21630,7 +21633,7 @@ var require_compile = __commonJS((exports) => {
21630
21633
  const schOrFunc = root2.refs[ref];
21631
21634
  if (schOrFunc)
21632
21635
  return schOrFunc;
21633
- let _sch = resolve4.call(this, root2, ref);
21636
+ let _sch = resolve3.call(this, root2, ref);
21634
21637
  if (_sch === undefined) {
21635
21638
  const schema = (_a = root2.localRefs) === null || _a === undefined ? undefined : _a[ref];
21636
21639
  const { schemaId } = this.opts;
@@ -21657,7 +21660,7 @@ var require_compile = __commonJS((exports) => {
21657
21660
  function sameSchemaEnv(s1, s2) {
21658
21661
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
21659
21662
  }
21660
- function resolve4(root2, ref) {
21663
+ function resolve3(root2, ref) {
21661
21664
  let sch;
21662
21665
  while (typeof (sch = this.refs[ref]) == "string")
21663
21666
  ref = sch;
@@ -21716,15 +21719,15 @@ var require_compile = __commonJS((exports) => {
21716
21719
  baseId = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schId);
21717
21720
  }
21718
21721
  }
21719
- let env2;
21722
+ let env;
21720
21723
  if (typeof schema != "boolean" && schema.$ref && !(0, util_1.schemaHasRulesButRef)(schema, this.RULES)) {
21721
21724
  const $ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schema.$ref);
21722
- env2 = resolveSchema.call(this, root2, $ref);
21725
+ env = resolveSchema.call(this, root2, $ref);
21723
21726
  }
21724
21727
  const { schemaId } = this.opts;
21725
- env2 = env2 || new SchemaEnv({ schema, schemaId, root: root2, baseId });
21726
- if (env2.schema !== env2.root.schema)
21727
- return env2;
21728
+ env = env || new SchemaEnv({ schema, schemaId, root: root2, baseId });
21729
+ if (env.schema !== env.root.schema)
21730
+ return env;
21728
21731
  return;
21729
21732
  }
21730
21733
  });
@@ -22179,7 +22182,7 @@ var require_fast_uri = __commonJS((exports, module) => {
22179
22182
  }
22180
22183
  return uri;
22181
22184
  }
22182
- function resolve4(baseURI, relativeURI, options) {
22185
+ function resolve3(baseURI, relativeURI, options) {
22183
22186
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
22184
22187
  const resolved = resolveComponent(parse2(baseURI, schemelessOptions), parse2(relativeURI, schemelessOptions), schemelessOptions, true);
22185
22188
  schemelessOptions.skipEscape = true;
@@ -22407,7 +22410,7 @@ var require_fast_uri = __commonJS((exports, module) => {
22407
22410
  var fastUri = {
22408
22411
  SCHEMES,
22409
22412
  normalize,
22410
- resolve: resolve4,
22413
+ resolve: resolve3,
22411
22414
  resolveComponent,
22412
22415
  equal,
22413
22416
  serialize,
@@ -23038,8 +23041,8 @@ var require_ref = __commonJS((exports) => {
23038
23041
  schemaType: "string",
23039
23042
  code(cxt) {
23040
23043
  const { gen, schema: $ref, it } = cxt;
23041
- const { baseId, schemaEnv: env2, validateName, opts, self: self2 } = it;
23042
- const { root: root2 } = env2;
23044
+ const { baseId, schemaEnv: env, validateName, opts, self: self2 } = it;
23045
+ const { root: root2 } = env;
23043
23046
  if (($ref === "#" || $ref === "#/") && baseId === root2.baseId)
23044
23047
  return callRootRef();
23045
23048
  const schOrEnv = compile_1.resolveRef.call(self2, root2, baseId, $ref);
@@ -23049,8 +23052,8 @@ var require_ref = __commonJS((exports) => {
23049
23052
  return callValidate(schOrEnv);
23050
23053
  return inlineRefSchema(schOrEnv);
23051
23054
  function callRootRef() {
23052
- if (env2 === root2)
23053
- return callRef(cxt, validateName, env2, env2.$async);
23055
+ if (env === root2)
23056
+ return callRef(cxt, validateName, env, env.$async);
23054
23057
  const rootName = gen.scopeValue("root", { ref: root2 });
23055
23058
  return callRef(cxt, (0, codegen_1._)`${rootName}.validate`, root2, root2.$async);
23056
23059
  }
@@ -23080,14 +23083,14 @@ var require_ref = __commonJS((exports) => {
23080
23083
  exports.getValidate = getValidate;
23081
23084
  function callRef(cxt, v, sch, $async) {
23082
23085
  const { gen, it } = cxt;
23083
- const { allErrors, schemaEnv: env2, opts } = it;
23086
+ const { allErrors, schemaEnv: env, opts } = it;
23084
23087
  const passCxt = opts.passContext ? names_1.default.this : codegen_1.nil;
23085
23088
  if ($async)
23086
23089
  callAsyncRef();
23087
23090
  else
23088
23091
  callSyncRef();
23089
23092
  function callAsyncRef() {
23090
- if (!env2.$async)
23093
+ if (!env.$async)
23091
23094
  throw new Error("async schema referenced by sync schema");
23092
23095
  const valid = gen.let("valid");
23093
23096
  gen.try(() => {
@@ -26219,6 +26222,234 @@ var init_validate = __esm2(() => {
26219
26222
  RESERVED_NAMES = new Set(["workflow", "execution", "input", "output", "env", "loop", "event"]);
26220
26223
  BUILTIN_ONERROR = new Set(["fail", "skip", "retry"]);
26221
26224
  });
26225
+ var exports_json = {};
26226
+ __export2(exports_json, {
26227
+ JsonTraceWriter: () => JsonTraceWriter
26228
+ });
26229
+
26230
+ class JsonTraceWriter {
26231
+ dir;
26232
+ filePath = "";
26233
+ trace = {
26234
+ execution: {
26235
+ id: "",
26236
+ startedAt: "",
26237
+ finishedAt: "",
26238
+ duration: 0,
26239
+ status: "running",
26240
+ inputs: null,
26241
+ output: null
26242
+ },
26243
+ steps: []
26244
+ };
26245
+ constructor(dir) {
26246
+ this.dir = dir;
26247
+ }
26248
+ async open(meta) {
26249
+ this.filePath = join(this.dir, `${meta.id}.json`);
26250
+ this.trace = {
26251
+ execution: {
26252
+ id: meta.id,
26253
+ workflowId: meta.workflowId,
26254
+ workflowName: meta.workflowName,
26255
+ workflowVersion: meta.workflowVersion,
26256
+ startedAt: meta.startedAt,
26257
+ finishedAt: "",
26258
+ duration: 0,
26259
+ status: "running",
26260
+ inputs: meta.inputs,
26261
+ output: null
26262
+ },
26263
+ steps: []
26264
+ };
26265
+ await this.flush();
26266
+ }
26267
+ writeStep(step) {
26268
+ this.trace.steps.push(step);
26269
+ this.flushSync();
26270
+ }
26271
+ async finalize(meta) {
26272
+ this.trace.execution.status = meta.status;
26273
+ this.trace.execution.output = meta.output;
26274
+ this.trace.execution.finishedAt = meta.finishedAt;
26275
+ this.trace.execution.duration = meta.duration;
26276
+ await this.flush();
26277
+ }
26278
+ flushSync() {
26279
+ this.flush().catch(() => {});
26280
+ }
26281
+ async flush() {
26282
+ if (!this.filePath)
26283
+ return;
26284
+ await writeFile(this.filePath, JSON.stringify(this.trace, null, 2), "utf-8");
26285
+ }
26286
+ }
26287
+ var exports_txt = {};
26288
+ __export2(exports_txt, {
26289
+ TxtTraceWriter: () => TxtTraceWriter
26290
+ });
26291
+ function serializeValue(value) {
26292
+ if (value === undefined || value === null)
26293
+ return "none";
26294
+ if (typeof value === "string")
26295
+ return value;
26296
+ return JSON.stringify(value, null, 2);
26297
+ }
26298
+ function formatStep(step) {
26299
+ const lines = [
26300
+ `## [${step.seq}] ${step.name} (${step.type})`,
26301
+ `startedAt: ${step.startedAt}`
26302
+ ];
26303
+ if (step.finishedAt)
26304
+ lines.push(`finishedAt: ${step.finishedAt}`);
26305
+ if (step.duration !== undefined)
26306
+ lines.push(`duration: ${step.duration}ms`);
26307
+ lines.push(`status: ${step.status}`);
26308
+ if (step.loopIndex !== undefined)
26309
+ lines.push(`loopIndex: ${step.loopIndex}`);
26310
+ if (step.retries !== undefined && step.retries > 0)
26311
+ lines.push(`retries: ${step.retries}`);
26312
+ lines.push(`input: ${serializeValue(step.input)}`);
26313
+ lines.push(`output: ${serializeValue(step.output)}`);
26314
+ if (step.error)
26315
+ lines.push(`error: ${step.error}`);
26316
+ lines.push("");
26317
+ return lines.join(`
26318
+ `);
26319
+ }
26320
+
26321
+ class TxtTraceWriter {
26322
+ dir;
26323
+ filePath = "";
26324
+ constructor(dir) {
26325
+ this.dir = dir;
26326
+ }
26327
+ async open(meta) {
26328
+ this.filePath = join2(this.dir, `${meta.id}.txt`);
26329
+ const versionStr = meta.workflowVersion !== undefined ? ` (v${meta.workflowVersion})` : "";
26330
+ const workflowLabel = meta.workflowName ?? meta.workflowId ?? "unnamed";
26331
+ const header = [
26332
+ "# execution",
26333
+ `id: ${meta.id}`,
26334
+ `workflow: ${workflowLabel}${versionStr}`,
26335
+ `startedAt: ${meta.startedAt}`,
26336
+ "status: running",
26337
+ "",
26338
+ "# inputs",
26339
+ serializeValue(meta.inputs),
26340
+ "",
26341
+ "# steps",
26342
+ ""
26343
+ ].join(`
26344
+ `);
26345
+ await writeFile2(this.filePath, header, "utf-8");
26346
+ }
26347
+ async writeStep(step) {
26348
+ if (!this.filePath)
26349
+ return;
26350
+ await appendFile(this.filePath, formatStep(step), "utf-8");
26351
+ }
26352
+ async finalize(meta) {
26353
+ if (!this.filePath)
26354
+ return;
26355
+ const outputSection = [
26356
+ "# output",
26357
+ serializeValue(meta.output),
26358
+ ""
26359
+ ].join(`
26360
+ `);
26361
+ await appendFile(this.filePath, outputSection, "utf-8");
26362
+ const content = await readFile2(this.filePath, "utf-8");
26363
+ const updated = content.replace(/^status: running$/m, `status: ${meta.status}
26364
+ finishedAt: ${meta.finishedAt}
26365
+ duration: ${meta.duration}ms`);
26366
+ await writeFile2(this.filePath, updated, "utf-8");
26367
+ }
26368
+ }
26369
+ var exports_sqlite = {};
26370
+ __export2(exports_sqlite, {
26371
+ SqliteTraceWriter: () => SqliteTraceWriter
26372
+ });
26373
+ function toJson(value) {
26374
+ if (value === undefined || value === null)
26375
+ return null;
26376
+ if (typeof value === "string")
26377
+ return value;
26378
+ return JSON.stringify(value);
26379
+ }
26380
+
26381
+ class SqliteTraceWriter {
26382
+ dir;
26383
+ db = null;
26384
+ executionId = "";
26385
+ constructor(dir) {
26386
+ this.dir = dir;
26387
+ }
26388
+ async open(meta) {
26389
+ this.executionId = meta.id;
26390
+ const filePath = join3(this.dir, `${meta.id}.sqlite`);
26391
+ const { Database } = await import("bun:sqlite");
26392
+ this.db = new Database(filePath);
26393
+ this.db.exec(`
26394
+ CREATE TABLE IF NOT EXISTS executions (
26395
+ id TEXT PRIMARY KEY,
26396
+ workflow_id TEXT,
26397
+ workflow_name TEXT,
26398
+ workflow_version TEXT,
26399
+ started_at TEXT NOT NULL,
26400
+ finished_at TEXT,
26401
+ duration_ms INTEGER,
26402
+ status TEXT NOT NULL,
26403
+ inputs TEXT,
26404
+ output TEXT
26405
+ );
26406
+
26407
+ CREATE TABLE IF NOT EXISTS steps (
26408
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26409
+ execution_id TEXT NOT NULL REFERENCES executions(id),
26410
+ seq INTEGER NOT NULL,
26411
+ name TEXT NOT NULL,
26412
+ type TEXT NOT NULL,
26413
+ started_at TEXT,
26414
+ finished_at TEXT,
26415
+ duration_ms INTEGER,
26416
+ status TEXT NOT NULL,
26417
+ input TEXT,
26418
+ output TEXT,
26419
+ error TEXT,
26420
+ retries INTEGER,
26421
+ loop_index INTEGER
26422
+ );
26423
+ `);
26424
+ const insert = this.db.prepare(`
26425
+ INSERT INTO executions (id, workflow_id, workflow_name, workflow_version, started_at, status, inputs)
26426
+ VALUES (?, ?, ?, ?, ?, 'running', ?)
26427
+ `);
26428
+ insert.run(meta.id, meta.workflowId ?? null, meta.workflowName ?? null, meta.workflowVersion !== undefined ? String(meta.workflowVersion) : null, meta.startedAt, toJson(meta.inputs));
26429
+ }
26430
+ writeStep(step) {
26431
+ if (!this.db)
26432
+ return;
26433
+ const insert = this.db.prepare(`
26434
+ INSERT INTO steps
26435
+ (execution_id, seq, name, type, started_at, finished_at, duration_ms, status, input, output, error, retries, loop_index)
26436
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
26437
+ `);
26438
+ 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);
26439
+ }
26440
+ async finalize(meta) {
26441
+ if (!this.db)
26442
+ return;
26443
+ const update = this.db.prepare(`
26444
+ UPDATE executions
26445
+ SET status = ?, output = ?, finished_at = ?, duration_ms = ?
26446
+ WHERE id = ?
26447
+ `);
26448
+ update.run(meta.status, toJson(meta.output), meta.finishedAt, Math.round(meta.duration), this.executionId);
26449
+ this.db.close();
26450
+ this.db = null;
26451
+ }
26452
+ }
26222
26453
  function sleep(ms) {
26223
26454
  return new Promise((resolve22) => setTimeout(resolve22, ms));
26224
26455
  }
@@ -26345,13 +26576,13 @@ function isMap3(value) {
26345
26576
  return typeof value === "object" && value !== null && !Array.isArray(value);
26346
26577
  }
26347
26578
  function mapToEnvVars(input) {
26348
- const env2 = {};
26579
+ const env = {};
26349
26580
  for (const [key, value] of Object.entries(input)) {
26350
- env2[key] = typeof value === "string" ? value : String(value);
26581
+ env[key] = typeof value === "string" ? value : String(value);
26351
26582
  }
26352
- return env2;
26583
+ return env;
26353
26584
  }
26354
- async function executeExec(participant, input, env2 = {}, signal) {
26585
+ async function executeExec(participant, input, env = {}, signal) {
26355
26586
  const command = participant.run ?? "";
26356
26587
  const participantEnv = participant.env ?? {};
26357
26588
  const cwd = participant.cwd ?? process.cwd();
@@ -26362,7 +26593,7 @@ async function executeExec(participant, input, env2 = {}, signal) {
26362
26593
  return new Promise((resolve22) => {
26363
26594
  try {
26364
26595
  const proc = spawn("sh", ["-c", command], {
26365
- env: { ...process.env, ...env2, ...participantEnv, ...inputEnvVars },
26596
+ env: { ...process.env, ...env, ...participantEnv, ...inputEnvVars },
26366
26597
  cwd,
26367
26598
  stdio: ["pipe", "pipe", "pipe"]
26368
26599
  });
@@ -26510,7 +26741,7 @@ async function executeHttp(participant, input) {
26510
26741
  };
26511
26742
  }
26512
26743
  async function executeMcp(participant, _input) {
26513
- throw new Error(`mcp participant is not yet implemented (server: ${participant.server ?? "unspecified"}, tool: ${participant.tool ?? "unspecified"}). ` + "Use onError to handle this gracefully.");
26744
+ throw new Error(`mcp participant is not yet implemented (server: ${participant.server ?? "unspecified"}, tool: ${participant.tool ?? "unspecified"}). Use onError to handle this gracefully.`);
26514
26745
  }
26515
26746
  function toWorkflowInputs(input) {
26516
26747
  if (input && typeof input === "object" && !Array.isArray(input)) {
@@ -26555,12 +26786,12 @@ var init_workflow = __esm2(() => {
26555
26786
  init_schema();
26556
26787
  init_validate();
26557
26788
  });
26558
- async function executeParticipant(participant, input, env2 = {}, basePath, engineExecutor, hub, celContext, ancestorPaths) {
26789
+ async function executeParticipant(participant, input, env = {}, basePath, engineExecutor, hub, celContext, ancestorPaths) {
26559
26790
  const executor = executors[participant.type];
26560
26791
  if (!executor) {
26561
26792
  throw new Error(`participant type '${participant.type}' is not yet implemented`);
26562
26793
  }
26563
- return executor(participant, input, env2, basePath, engineExecutor, hub, celContext, ancestorPaths);
26794
+ return executor(participant, input, env, basePath, engineExecutor, hub, celContext, ancestorPaths);
26564
26795
  }
26565
26796
  var executors;
26566
26797
  var init_participant = __esm2(() => {
@@ -26568,7 +26799,7 @@ var init_participant = __esm2(() => {
26568
26799
  init_exec();
26569
26800
  init_workflow();
26570
26801
  executors = {
26571
- exec: async (participant, input, env2) => executeExec(participant, input, env2),
26802
+ exec: async (participant, input, env) => executeExec(participant, input, env),
26572
26803
  http: async (participant, input) => executeHttp(participant, input),
26573
26804
  workflow: async (participant, input, _env, basePath, engineExecutor, _hub, _celContext, ancestorPaths) => {
26574
26805
  if (!basePath) {
@@ -26796,6 +27027,10 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26796
27027
  output: "",
26797
27028
  duration: 0
26798
27029
  });
27030
+ const loopIndex2 = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27031
+ const skippedSeq = state.tracer?.startStep(stepName, participant.type, undefined, loopIndex2);
27032
+ if (skippedSeq !== undefined)
27033
+ state.tracer?.endStep(skippedSeq, "skipped");
26799
27034
  }
26800
27035
  return chain;
26801
27036
  }
@@ -26804,6 +27039,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26804
27039
  const overrideInput = override?.input !== undefined ? resolveParticipantInput(override.input, state) : undefined;
26805
27040
  const mergedWithBase = mergeChainedInput(chain, baseInput);
26806
27041
  const mergedInput = mergeChainedInput(mergedWithBase, overrideInput);
27042
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27043
+ const traceSeq = state.tracer?.startStep(stepName ?? "<anonymous>", participant.type, mergedInput, loopIndex);
26807
27044
  state.currentInput = mergedInput;
26808
27045
  const strategy = resolveErrorStrategy(override ?? null, participant, workflow.defaults ?? null);
26809
27046
  const timeoutMs = resolveTimeout(override ?? null, participant, workflow.defaults ?? null);
@@ -26881,6 +27118,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26881
27118
  }
26882
27119
  const outputValue = result.parsedOutput ?? result.output;
26883
27120
  state.currentOutput = outputValue;
27121
+ if (traceSeq !== undefined)
27122
+ state.tracer?.endStep(traceSeq, result.status, outputValue, undefined, retries);
26884
27123
  return outputValue;
26885
27124
  } catch (error) {
26886
27125
  const message = String(error?.message ?? error);
@@ -26906,6 +27145,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26906
27145
  if (stepName) {
26907
27146
  state.setResult(stepName, skipResult);
26908
27147
  }
27148
+ if (traceSeq !== undefined)
27149
+ state.tracer?.endStep(traceSeq, "skipped", undefined, message);
26909
27150
  return chain;
26910
27151
  }
26911
27152
  if (strategy !== "fail" && strategy !== "retry") {
@@ -26924,6 +27165,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26924
27165
  ...httpMeta
26925
27166
  });
26926
27167
  }
27168
+ if (traceSeq !== undefined)
27169
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
26927
27170
  const fallbackResult = await executeStep(workflow, state, fallbackName, basePath, engineExecutor, [...fallbackStack, stepName ?? "<anonymous>"], chain, hub);
26928
27171
  return fallbackResult;
26929
27172
  }
@@ -26938,6 +27181,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26938
27181
  ...httpMeta
26939
27182
  });
26940
27183
  }
27184
+ if (traceSeq !== undefined)
27185
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
26941
27186
  throw error;
26942
27187
  }
26943
27188
  }
@@ -26960,7 +27205,7 @@ __export2(exports_wait, {
26960
27205
  executeWait: () => executeWait
26961
27206
  });
26962
27207
  function sleep2(ms) {
26963
- return new Promise((resolve32) => setTimeout(resolve32, ms));
27208
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
26964
27209
  }
26965
27210
  async function executeWait(state, waitDef, chain, hub, signal) {
26966
27211
  const timeoutMs = waitDef.timeout ? parseDuration(waitDef.timeout) : undefined;
@@ -27046,21 +27291,38 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27046
27291
  }
27047
27292
  const obj = step;
27048
27293
  if ("wait" in obj && Object.keys(obj).length === 1) {
27049
- const { executeWait: executeWait2 } = await Promise.resolve().then(() => (init_wait(), exports_wait));
27050
- return executeWait2(state, obj.wait, chain, hub, signal);
27294
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27295
+ const traceSeq = state.tracer?.startStep("wait", "wait", undefined, loopIndex);
27296
+ try {
27297
+ const { executeWait: executeWait2 } = await Promise.resolve().then(() => (init_wait(), exports_wait));
27298
+ const result = await executeWait2(state, obj.wait, chain, hub, signal);
27299
+ if (traceSeq !== undefined)
27300
+ state.tracer?.endStep(traceSeq, "success", result);
27301
+ return result;
27302
+ } catch (err) {
27303
+ if (traceSeq !== undefined)
27304
+ state.tracer?.endStep(traceSeq, "failure", undefined, String(err?.message ?? err));
27305
+ throw err;
27306
+ }
27051
27307
  }
27052
27308
  if ("set" in obj && Object.keys(obj).length === 1) {
27053
27309
  const setDef = obj.set;
27054
27310
  const ctx = state.toCelContext();
27311
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27312
+ const traceSeq = state.tracer?.startStep("set", "set", setDef, loopIndex);
27055
27313
  if (!state.executionMeta.context) {
27056
27314
  state.executionMeta.context = {};
27057
27315
  }
27058
27316
  for (const [key, expr] of Object.entries(setDef)) {
27059
27317
  if (RESERVED_SET_KEYS.has(key)) {
27318
+ if (traceSeq !== undefined)
27319
+ state.tracer?.endStep(traceSeq, "failure", undefined, `set key '${key}' uses a reserved name`);
27060
27320
  throw new Error(`set key '${key}' uses a reserved name`);
27061
27321
  }
27062
27322
  state.executionMeta.context[key] = evaluateCel(expr, ctx);
27063
27323
  }
27324
+ if (traceSeq !== undefined)
27325
+ state.tracer?.endStep(traceSeq, "success", state.executionMeta.context);
27064
27326
  return chain;
27065
27327
  }
27066
27328
  if ("loop" in obj && Object.keys(obj).length === 1) {
@@ -27077,6 +27339,8 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27077
27339
  maxIterations = loopDef.max ?? Number.POSITIVE_INFINITY;
27078
27340
  }
27079
27341
  const hasMax = loopDef.max !== undefined;
27342
+ const outerLoopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27343
+ const loopTraceSeq = state.tracer?.startStep(loopAs ?? "loop", "loop", undefined, outerLoopIndex);
27080
27344
  state.pushLoop(loopAs);
27081
27345
  let loopChain = chain;
27082
27346
  try {
@@ -27100,6 +27364,12 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27100
27364
  iterations += 1;
27101
27365
  state.incrementLoop();
27102
27366
  }
27367
+ if (loopTraceSeq !== undefined)
27368
+ state.tracer?.endStep(loopTraceSeq, "success", loopChain);
27369
+ } catch (err) {
27370
+ if (loopTraceSeq !== undefined)
27371
+ state.tracer?.endStep(loopTraceSeq, "failure", undefined, String(err?.message ?? err));
27372
+ throw err;
27103
27373
  } finally {
27104
27374
  state.popLoop();
27105
27375
  }
@@ -27108,29 +27378,54 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27108
27378
  if ("parallel" in obj && Object.keys(obj).length === 1) {
27109
27379
  const parallelSteps = obj.parallel;
27110
27380
  const controller = new AbortController;
27381
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27382
+ const parallelTraceSeq = state.tracer?.startStep("parallel", "parallel", undefined, loopIndex);
27111
27383
  const branchSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
27112
- const results = await Promise.all(parallelSteps.map(async (parallelStep) => {
27113
- try {
27114
- return await executeControlStep(workflow, state, parallelStep, basePath, engineExecutor, chain, hub, branchSignal);
27115
- } catch (error) {
27116
- controller.abort();
27117
- throw error;
27118
- }
27119
- }));
27120
- return results;
27384
+ try {
27385
+ const results = await Promise.all(parallelSteps.map(async (parallelStep) => {
27386
+ try {
27387
+ return await executeControlStep(workflow, state, parallelStep, basePath, engineExecutor, chain, hub, branchSignal);
27388
+ } catch (error) {
27389
+ controller.abort();
27390
+ throw error;
27391
+ }
27392
+ }));
27393
+ if (parallelTraceSeq !== undefined)
27394
+ state.tracer?.endStep(parallelTraceSeq, "success", results);
27395
+ return results;
27396
+ } catch (err) {
27397
+ if (parallelTraceSeq !== undefined)
27398
+ state.tracer?.endStep(parallelTraceSeq, "failure", undefined, String(err?.message ?? err));
27399
+ throw err;
27400
+ }
27121
27401
  }
27122
27402
  if ("if" in obj && Object.keys(obj).length === 1) {
27123
27403
  const ifDef = obj.if;
27124
27404
  const condition = evaluateCel(ifDef.condition, state.toCelContext());
27405
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27406
+ const ifTraceSeq = state.tracer?.startStep("if", "if", { condition: ifDef.condition }, loopIndex);
27125
27407
  if (typeof condition !== "boolean") {
27408
+ if (ifTraceSeq !== undefined)
27409
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, `if.condition must evaluate to boolean, got ${typeof condition}`);
27126
27410
  throw new Error(`if.condition must evaluate to boolean, got ${typeof condition}`);
27127
27411
  }
27128
- if (condition) {
27129
- return executeSequential(workflow, state, ifDef.then, basePath, engineExecutor, chain, hub, signal);
27130
- } else if (ifDef.else) {
27131
- return executeSequential(workflow, state, ifDef.else, basePath, engineExecutor, chain, hub, signal);
27412
+ try {
27413
+ let result;
27414
+ if (condition) {
27415
+ result = await executeSequential(workflow, state, ifDef.then, basePath, engineExecutor, chain, hub, signal);
27416
+ } else if (ifDef.else) {
27417
+ result = await executeSequential(workflow, state, ifDef.else, basePath, engineExecutor, chain, hub, signal);
27418
+ } else {
27419
+ result = chain;
27420
+ }
27421
+ if (ifTraceSeq !== undefined)
27422
+ state.tracer?.endStep(ifTraceSeq, "success", result);
27423
+ return result;
27424
+ } catch (err) {
27425
+ if (ifTraceSeq !== undefined)
27426
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, String(err?.message ?? err));
27427
+ throw err;
27132
27428
  }
27133
- return chain;
27134
27429
  }
27135
27430
  return executeStep(workflow, state, step, basePath, engineExecutor, [], chain, hub, signal);
27136
27431
  }
@@ -27140,7 +27435,92 @@ var init_control = __esm2(() => {
27140
27435
  init_sequential();
27141
27436
  RESERVED_SET_KEYS = new Set(["workflow", "execution", "input", "output", "env", "loop", "event"]);
27142
27437
  });
27143
- init_cel();
27438
+
27439
+ class MemoryHub {
27440
+ listeners = new Map;
27441
+ buffer = new Map;
27442
+ closed = false;
27443
+ async publish(event, payload) {
27444
+ if (this.closed)
27445
+ throw new Error("hub is closed");
27446
+ const envelope = { name: event, payload };
27447
+ let buf = this.buffer.get(event);
27448
+ if (!buf) {
27449
+ buf = [];
27450
+ this.buffer.set(event, buf);
27451
+ }
27452
+ buf.push(envelope);
27453
+ const listeners = this.listeners.get(event);
27454
+ if (listeners) {
27455
+ for (const listener of listeners) {
27456
+ listener(envelope);
27457
+ }
27458
+ }
27459
+ }
27460
+ async publishAndWaitAck(event, payload, _timeoutMs) {
27461
+ await this.publish(event, payload);
27462
+ }
27463
+ async* subscribe(event, signal) {
27464
+ if (this.closed)
27465
+ return;
27466
+ const buffered = this.buffer.get(event);
27467
+ if (buffered) {
27468
+ for (const envelope of buffered) {
27469
+ if (signal?.aborted)
27470
+ return;
27471
+ yield envelope;
27472
+ }
27473
+ }
27474
+ const queue = [];
27475
+ let resolve3 = null;
27476
+ const listener = (envelope) => {
27477
+ queue.push(envelope);
27478
+ if (resolve3) {
27479
+ resolve3();
27480
+ resolve3 = null;
27481
+ }
27482
+ };
27483
+ let listeners = this.listeners.get(event);
27484
+ if (!listeners) {
27485
+ listeners = new Set;
27486
+ this.listeners.set(event, listeners);
27487
+ }
27488
+ listeners.add(listener);
27489
+ const onAbort = () => {
27490
+ listeners.delete(listener);
27491
+ if (resolve3) {
27492
+ resolve3();
27493
+ resolve3 = null;
27494
+ }
27495
+ };
27496
+ if (signal) {
27497
+ signal.addEventListener("abort", onAbort);
27498
+ }
27499
+ try {
27500
+ while (!this.closed && !signal?.aborted) {
27501
+ if (queue.length > 0) {
27502
+ yield queue.shift();
27503
+ } else {
27504
+ await new Promise((r) => {
27505
+ resolve3 = r;
27506
+ });
27507
+ }
27508
+ }
27509
+ } finally {
27510
+ listeners.delete(listener);
27511
+ if (signal) {
27512
+ signal.removeEventListener("abort", onAbort);
27513
+ }
27514
+ }
27515
+ }
27516
+ async close() {
27517
+ this.closed = true;
27518
+ for (const listeners of this.listeners.values()) {
27519
+ listeners.clear();
27520
+ }
27521
+ this.listeners.clear();
27522
+ }
27523
+ }
27144
27524
  init_parser3();
27145
27525
  init_schema();
27146
27526
  init_validate();
@@ -27304,357 +27684,220 @@ function validateInputs(inputDefs, provided) {
27304
27684
  resolved
27305
27685
  };
27306
27686
  }
27307
- init_control();
27687
+ init_cel();
27688
+ init_cel();
27689
+ init_parser3();
27690
+ init_schema();
27691
+ init_validate();
27308
27692
 
27309
- class WorkflowState {
27310
- inputs;
27311
- results;
27312
- loopStack;
27313
- workflowInputs;
27314
- workflowMeta;
27315
- executionMeta;
27316
- currentInput;
27317
- currentOutput;
27318
- chainValue;
27319
- eventPayload;
27320
- ancestorPaths;
27321
- constructor(inputs = {}) {
27322
- this.inputs = { ...inputs };
27323
- this.workflowInputs = { ...inputs };
27324
- this.workflowMeta = {};
27325
- this.executionMeta = {
27326
- id: crypto.randomUUID(),
27693
+ class TraceCollector {
27694
+ openSteps = new Map;
27695
+ seq = 0;
27696
+ truncateAt;
27697
+ writer;
27698
+ constructor(truncateAt = 1e6) {
27699
+ this.truncateAt = truncateAt;
27700
+ }
27701
+ startStep(name, type3, input, loopIndex) {
27702
+ this.seq += 1;
27703
+ this.openSteps.set(this.seq, {
27704
+ name,
27705
+ type: type3,
27327
27706
  startedAt: new Date().toISOString(),
27328
- status: "running",
27329
- cwd: process.cwd()
27330
- };
27331
- this.currentInput = undefined;
27332
- this.currentOutput = undefined;
27333
- this.chainValue = undefined;
27334
- this.eventPayload = undefined;
27335
- this.ancestorPaths = new Set;
27336
- this.results = new Map;
27337
- this.loopStack = [];
27338
- }
27339
- setResult(stepName, result) {
27340
- this.results.set(stepName, result);
27341
- }
27342
- getResult(stepName) {
27343
- return this.results.get(stepName);
27344
- }
27345
- getAllResults() {
27346
- const out = {};
27347
- for (const [k, v] of this.results.entries())
27348
- out[k] = v;
27349
- return out;
27350
- }
27351
- pushLoop(as) {
27352
- this.loopStack.push({ index: 0, as });
27353
- }
27354
- incrementLoop() {
27355
- const top = this.loopStack[this.loopStack.length - 1];
27356
- if (top)
27357
- top.index += 1;
27358
- }
27359
- popLoop() {
27360
- this.loopStack.pop();
27361
- }
27362
- currentLoopIndex() {
27363
- const top = this.loopStack[this.loopStack.length - 1];
27364
- return top ? top.index : 0;
27365
- }
27366
- setLoopLast(last2) {
27367
- const top = this.loopStack[this.loopStack.length - 1];
27368
- if (top)
27369
- top.last = last2;
27370
- }
27371
- currentLoopContext() {
27372
- const top = this.loopStack[this.loopStack.length - 1];
27373
- if (!top)
27374
- return { index: 0, iteration: 1, first: true, last: false };
27375
- return {
27376
- index: top.index,
27377
- iteration: top.index + 1,
27378
- first: top.index === 0,
27379
- last: top.last ?? false,
27380
- as: top.as
27381
- };
27382
- }
27383
- toCelContext() {
27384
- const ctx = {};
27385
- for (const [name, res] of this.results.entries()) {
27386
- ctx[name] = {
27387
- output: res.parsedOutput ?? res.output,
27388
- status: res.status,
27389
- startedAt: res.startedAt,
27390
- finishedAt: res.finishedAt,
27391
- duration: res.duration,
27392
- retries: res.retries ?? 0,
27393
- error: res.error,
27394
- cwd: res.cwd
27395
- };
27396
- }
27397
- ctx["workflow"] = {
27398
- id: this.workflowMeta.id,
27399
- name: this.workflowMeta.name,
27400
- version: this.workflowMeta.version,
27401
- inputs: this.workflowInputs,
27402
- output: null
27403
- };
27404
- ctx["execution"] = { ...this.executionMeta };
27405
- ctx["input"] = this.currentInput ?? {};
27406
- ctx["output"] = this.currentOutput ?? {};
27407
- ctx["env"] = { ...env };
27408
- const loopCtx = this.currentLoopContext();
27409
- const loopObj = {
27410
- index: loopCtx.index,
27411
- iteration: loopCtx.iteration,
27412
- first: loopCtx.first,
27413
- last: loopCtx.last
27414
- };
27415
- if (loopCtx.as) {
27416
- ctx[`_${loopCtx.as}`] = loopObj;
27417
- }
27418
- ctx["_loop"] = loopObj;
27419
- ctx["loop"] = loopObj;
27420
- ctx["event"] = this.eventPayload ?? {};
27421
- ctx["now"] = Math.floor(Date.now() / 1000);
27422
- return ctx;
27423
- }
27424
- resolveOutput(outputDef, celEvaluator) {
27425
- const ctx = this.toCelContext();
27426
- if (typeof outputDef === "object" && "map" in outputDef && "schema" in outputDef) {
27427
- const result2 = {};
27428
- for (const [k, expr] of Object.entries(outputDef.map)) {
27429
- result2[k] = celEvaluator(expr, ctx);
27430
- }
27431
- return result2;
27432
- }
27433
- if (typeof outputDef === "string") {
27434
- return celEvaluator(outputDef, ctx);
27435
- }
27436
- const result = {};
27437
- for (const k of Object.keys(outputDef)) {
27438
- const expr = outputDef[k];
27439
- result[k] = celEvaluator(expr, ctx);
27440
- }
27441
- return result;
27442
- }
27443
- }
27444
- async function executeWorkflow(workflow, inputs = {}, basePath = process.cwd(), options = {}) {
27445
- const { result: inputResult, resolved } = validateInputs(workflow.inputs, inputs);
27446
- if (!inputResult.valid) {
27447
- throw new Error(`input validation failed: ${JSON.stringify(inputResult.errors)}`);
27448
- }
27449
- const state = new WorkflowState(resolved);
27450
- state.workflowMeta = {
27451
- id: workflow.id,
27452
- name: workflow.name,
27453
- version: workflow.version
27454
- };
27455
- state.executionMeta.number = options.executionNumber ?? 1;
27456
- if (options.cwd) {
27457
- state.executionMeta.cwd = options.cwd;
27458
- }
27459
- const startedAt = performance.now();
27460
- if (options._ancestorPaths) {
27461
- state.ancestorPaths = options._ancestorPaths;
27462
- }
27463
- const engineExecutor = async (subWorkflow, subInputs, subBasePath) => {
27464
- return executeWorkflow(subWorkflow, subInputs, subBasePath, {
27465
- ...options,
27466
- _ancestorPaths: state.ancestorPaths
27707
+ startMs: performance.now(),
27708
+ input: input !== undefined ? this.truncate(input) : undefined,
27709
+ loopIndex
27467
27710
  });
27468
- };
27469
- let chain;
27470
- for (const step of workflow.flow) {
27471
- chain = await executeControlStep(workflow, state, step, basePath, engineExecutor, chain, options.hub);
27711
+ return this.seq;
27472
27712
  }
27473
- let output;
27474
- if (workflow.output !== undefined) {
27475
- output = state.resolveOutput(workflow.output, evaluateCel);
27476
- if (typeof workflow.output === "object" && "schema" in workflow.output && "map" in workflow.output && typeof output === "object" && output !== null) {
27477
- validateOutputSchema(workflow.output.schema, output);
27478
- }
27479
- } else {
27480
- output = chain;
27481
- }
27482
- const steps = state.getAllResults();
27483
- const success = !Object.values(steps).some((step) => step.status === "failure");
27484
- state.executionMeta.status = success ? "success" : "failure";
27485
- return {
27486
- success,
27487
- output,
27488
- steps,
27489
- duration: Math.max(0, performance.now() - startedAt)
27490
- };
27491
- }
27492
- async function runWorkflowFromFile(filePath, inputs = {}, options = {}) {
27493
- const workflow = await parseWorkflowFile(filePath);
27494
- const schemaValidation = validateSchema(workflow);
27495
- if (!schemaValidation.valid) {
27496
- throw new Error(`schema validation failed: ${JSON.stringify(schemaValidation.errors)}`);
27713
+ endStep(seq, status, output, error, retries) {
27714
+ const open = this.openSteps.get(seq);
27715
+ if (!open)
27716
+ return;
27717
+ this.openSteps.delete(seq);
27718
+ const finishedAt = new Date().toISOString();
27719
+ const duration = Math.max(0, performance.now() - open.startMs);
27720
+ const step = {
27721
+ seq,
27722
+ name: open.name,
27723
+ type: open.type,
27724
+ startedAt: open.startedAt,
27725
+ finishedAt,
27726
+ duration: Math.round(duration),
27727
+ status,
27728
+ ...open.input !== undefined ? { input: open.input } : {},
27729
+ ...output !== undefined ? { output: this.truncate(output) } : {},
27730
+ ...error !== undefined ? { error } : {},
27731
+ ...retries !== undefined && retries > 0 ? { retries } : {},
27732
+ ...open.loopIndex !== undefined ? { loopIndex: open.loopIndex } : {}
27733
+ };
27734
+ this.writer?.writeStep(step);
27497
27735
  }
27498
- const workflowBasePath = dirname2(resolve3(filePath));
27499
- const semanticValidation = await validateSemantic(workflow, workflowBasePath);
27500
- if (!semanticValidation.valid) {
27501
- throw new Error(`semantic validation failed: ${JSON.stringify(semanticValidation.errors)}`);
27736
+ truncate(value) {
27737
+ if (value == null)
27738
+ return value;
27739
+ const str = typeof value === "string" ? value : JSON.stringify(value);
27740
+ const bytes = new TextEncoder().encode(str);
27741
+ if (bytes.length <= this.truncateAt)
27742
+ return value;
27743
+ const cut = new TextDecoder().decode(bytes.slice(0, this.truncateAt));
27744
+ return cut + "...[truncated]";
27502
27745
  }
27503
- return executeWorkflow(workflow, inputs, workflowBasePath, options);
27504
27746
  }
27505
27747
  init_control();
27506
27748
  init_sequential();
27507
- init_errors();
27508
- init_timeout();
27509
27749
  init_wait();
27750
+ init_emit();
27510
27751
 
27511
- // src/run.ts
27512
- async function createHubFromFlags(values2) {
27513
- const backend = values2?.["event-backend"] ?? "memory";
27514
- if (backend === "memory") {
27515
- const { MemoryHub: MemoryHub2 } = await Promise.resolve().then(() => (init_eventhub(), exports_eventhub));
27516
- return new MemoryHub2;
27517
- }
27518
- if (backend === "nats") {
27519
- const url = values2?.["nats-url"];
27520
- if (!url) {
27521
- console.error("Error: --nats-url is required when using the NATS backend");
27522
- throw new Error("missing --nats-url");
27752
+ // src/lint.ts
27753
+ function collectLintWarnings(workflow) {
27754
+ const warnings = [];
27755
+ const participants = workflow.participants ?? {};
27756
+ collectFlowWarnings(workflow.flow ?? [], participants, warnings);
27757
+ return warnings;
27758
+ }
27759
+ function collectFlowWarnings(flow, participants, warnings, basePath = "flow") {
27760
+ for (const [index, step] of flow.entries()) {
27761
+ const stepPath = `${basePath}[${index}]`;
27762
+ if (!step || typeof step !== "object")
27763
+ continue;
27764
+ const obj = step;
27765
+ if (obj.loop && Object.keys(obj).length === 1) {
27766
+ const loopDef = obj.loop;
27767
+ if (loopDef.until == null && loopDef.max == null) {
27768
+ warnings.push({
27769
+ path: `${stepPath}.loop`,
27770
+ message: "loop has no 'until' and no 'max' — this will be rejected at runtime"
27771
+ });
27772
+ }
27773
+ collectFlowWarnings(loopDef.steps ?? [], participants, warnings, `${stepPath}.loop.steps`);
27774
+ continue;
27523
27775
  }
27524
- const stream = values2?.["nats-stream"] ?? "duckflux-events";
27525
- try {
27526
- const { NatsHub } = await import("@duckflux/hub-nats");
27527
- return await NatsHub.create({ url, stream });
27528
- } catch (err) {
27529
- if (err instanceof Error && (err.message.includes("Cannot find module") || err.message.includes("Cannot find package"))) {
27530
- console.error("Error: install @duckflux/hub-nats to use the NATS backend");
27531
- throw new Error("@duckflux/hub-nats not installed");
27776
+ if (obj.parallel && Object.keys(obj).length === 1) {
27777
+ const parallelSteps = obj.parallel;
27778
+ const branchSetKeys = new Map;
27779
+ for (const [branchIdx, branch] of parallelSteps.entries()) {
27780
+ const setKeys = collectSetKeys(branch);
27781
+ for (const key of setKeys) {
27782
+ const branches = branchSetKeys.get(key) ?? [];
27783
+ branches.push(branchIdx);
27784
+ branchSetKeys.set(key, branches);
27785
+ }
27532
27786
  }
27533
- throw err;
27787
+ for (const [key, branches] of branchSetKeys) {
27788
+ if (branches.length > 1) {
27789
+ warnings.push({
27790
+ path: `${stepPath}.parallel`,
27791
+ message: `branches [${branches.join(", ")}] both write to '${key}' via set — race condition risk`
27792
+ });
27793
+ }
27794
+ }
27795
+ collectFlowWarnings(parallelSteps, participants, warnings, `${stepPath}.parallel`);
27796
+ continue;
27534
27797
  }
27535
- }
27536
- if (backend === "redis") {
27537
- const addr = values2?.["redis-addr"] ?? "localhost:6379";
27538
- const db = Number(values2?.["redis-db"] ?? "0");
27539
- try {
27540
- const { RedisHub } = await import("@duckflux/hub-redis");
27541
- return await RedisHub.create({ addr, db });
27542
- } catch (err) {
27543
- if (err instanceof Error && (err.message.includes("Cannot find module") || err.message.includes("Cannot find package"))) {
27544
- console.error("Error: install @duckflux/hub-redis to use the Redis backend");
27545
- throw new Error("@duckflux/hub-redis not installed");
27798
+ if ("type" in obj && !obj.as) {
27799
+ warnings.push({
27800
+ path: stepPath,
27801
+ message: "inline participant without 'as' — its output cannot be referenced by name"
27802
+ });
27803
+ continue;
27804
+ }
27805
+ if (obj.if && Object.keys(obj).length === 1) {
27806
+ const ifDef = obj.if;
27807
+ collectFlowWarnings(ifDef.then ?? [], participants, warnings, `${stepPath}.if.then`);
27808
+ if (ifDef.else) {
27809
+ collectFlowWarnings(ifDef.else, participants, warnings, `${stepPath}.if.else`);
27546
27810
  }
27547
- throw err;
27548
27811
  }
27549
27812
  }
27550
- console.error(`Error: unknown event backend "${backend}". Supported: memory, nats, redis`);
27551
- throw new Error(`unknown event backend: ${backend}`);
27552
27813
  }
27553
- function parseInputFlags(arr) {
27554
- const out = {};
27555
- if (!arr)
27556
- return out;
27557
- for (const item of arr) {
27558
- const idx = item.indexOf("=");
27559
- if (idx === -1) {
27560
- out[item] = true;
27561
- } else {
27562
- const k = item.slice(0, idx);
27563
- const v = item.slice(idx + 1);
27564
- try {
27565
- out[k] = JSON.parse(v);
27566
- } catch {
27567
- out[k] = v;
27814
+ function collectSetKeys(step) {
27815
+ if (!step || typeof step !== "object")
27816
+ return [];
27817
+ const obj = step;
27818
+ if ("set" in obj && Object.keys(obj).length === 1) {
27819
+ return Object.keys(obj.set);
27820
+ }
27821
+ const keys3 = [];
27822
+ if (obj.loop) {
27823
+ const loopDef = obj.loop;
27824
+ for (const s of loopDef.steps ?? []) {
27825
+ keys3.push(...collectSetKeys(s));
27826
+ }
27827
+ }
27828
+ if (obj.if) {
27829
+ const ifDef = obj.if;
27830
+ for (const s of ifDef.then ?? []) {
27831
+ keys3.push(...collectSetKeys(s));
27832
+ }
27833
+ if (ifDef.else) {
27834
+ for (const s of ifDef.else) {
27835
+ keys3.push(...collectSetKeys(s));
27568
27836
  }
27569
27837
  }
27570
27838
  }
27571
- return out;
27839
+ return keys3;
27572
27840
  }
27573
- async function runCommand(filePath, cliValues) {
27841
+ async function lintCommand(filePath) {
27574
27842
  if (!filePath) {
27575
- console.error("Usage: duckflux run <workflow.yaml> [--input k=v] [--input-file file.json] [--cwd dir]");
27843
+ console.error("Usage: quack lint <workflow.yaml>");
27576
27844
  return 1;
27577
27845
  }
27578
- let inputs = {};
27579
27846
  try {
27580
- if (process.stdin && !process.stdin.isTTY) {
27581
- let stdin = "";
27582
- for await (const chunk of process.stdin) {
27583
- stdin += chunk;
27847
+ const workflow = await parseWorkflowFile(filePath);
27848
+ const schemaRes = validateSchema(workflow);
27849
+ if (!schemaRes.valid) {
27850
+ console.error("Schema validation failed:");
27851
+ for (const e of schemaRes.errors) {
27852
+ console.error(` - ${e.path}: ${e.message}`);
27584
27853
  }
27585
- stdin = stdin.trim();
27586
- if (stdin.length > 0) {
27587
- try {
27588
- const parsed = JSON.parse(stdin);
27589
- if (typeof parsed === "object" && parsed !== null)
27590
- inputs = { ...inputs, ...parsed };
27591
- } catch {}
27854
+ return 1;
27855
+ }
27856
+ const basePath = dirname2(filePath);
27857
+ const semanticRes = await validateSemantic(workflow, basePath);
27858
+ if (!semanticRes.valid) {
27859
+ console.error("Semantic validation failed:");
27860
+ for (const e of semanticRes.errors) {
27861
+ console.error(` - ${e.path}: ${e.message}`);
27592
27862
  }
27863
+ return 1;
27593
27864
  }
27594
- } catch {}
27595
- if (cliValues) {
27596
- if (cliValues["input-file"]) {
27597
- try {
27598
- const content = await readFile2(String(cliValues["input-file"]), "utf-8");
27599
- const parsed = JSON.parse(content);
27600
- if (typeof parsed === "object" && parsed !== null)
27601
- inputs = { ...inputs, ...parsed };
27602
- } catch (err) {
27603
- console.error("Failed to read input file:", err);
27604
- return 1;
27865
+ const warnings = collectLintWarnings(workflow);
27866
+ if (warnings.length > 0) {
27867
+ console.warn("Warnings:");
27868
+ for (const w of warnings) {
27869
+ console.warn(` - ${w.path}: ${w.message}`);
27605
27870
  }
27606
27871
  }
27607
- if (cliValues.input) {
27608
- const parsed = Array.isArray(cliValues.input) ? cliValues.input : [cliValues.input];
27609
- inputs = { ...inputs, ...parseInputFlags(parsed) };
27610
- }
27611
- }
27612
- let hub;
27613
- try {
27614
- hub = await createHubFromFlags(cliValues);
27615
- } catch {
27616
- return 1;
27617
- }
27618
- const options = {
27619
- hub,
27620
- cwd: cliValues?.cwd,
27621
- verbose: cliValues?.verbose,
27622
- quiet: cliValues?.quiet
27623
- };
27624
- try {
27625
- const res = await runWorkflowFromFile(filePath, inputs, options);
27626
- const output = res.output;
27627
- if (output === undefined || output === null) {} else if (typeof output === "string") {
27628
- process.stdout.write(output);
27629
- } else {
27630
- console.log(JSON.stringify(output, null, 2));
27631
- }
27632
- return res.success ? 0 : 2;
27872
+ console.log("valid");
27873
+ return 0;
27633
27874
  } catch (err) {
27634
- const msg = err instanceof Error ? err.message : String(err);
27635
- console.error("Error:", msg);
27636
- if (cliValues?.verbose && err instanceof Error && err.stack) {
27637
- console.error(err.stack);
27638
- }
27875
+ console.error("Error during lint:", err && err.message ? err.message : err);
27639
27876
  return 1;
27640
- } finally {
27641
- await hub?.close();
27642
27877
  }
27643
27878
  }
27644
27879
 
27645
- // src/lint.ts
27646
- import { dirname as dirname5 } from "node:path";
27880
+ // src/run.ts
27881
+ import { readFile as readFile5 } from "node:fs/promises";
27647
27882
 
27648
- // ../core/dist/index.js
27883
+ // ../core/dist/engine/index.js
27649
27884
  import { createRequire as createRequire3 } from "node:module";
27650
27885
  import { readFile as readFile3 } from "node:fs/promises";
27651
27886
  import { readFileSync as readFileSync2 } from "node:fs";
27652
27887
  import { constants as constants2 } from "node:fs";
27653
27888
  import { access as access2 } from "node:fs/promises";
27654
- import { resolve as resolve4 } from "node:path";
27889
+ import { resolve as resolve3 } from "node:path";
27890
+ import { writeFile as writeFile3 } from "node:fs/promises";
27891
+ import { join as join4 } from "node:path";
27892
+ import { appendFile as appendFile2, readFile as readFile22, writeFile as writeFile22 } from "node:fs/promises";
27893
+ import { join as join22 } from "node:path";
27894
+ import { join as join32 } from "node:path";
27655
27895
  import { spawn as spawn2 } from "node:child_process";
27656
27896
  import { dirname as dirname3, resolve as resolve22 } from "node:path";
27657
27897
  import { resolve as resolvePath2, isAbsolute as isAbsolute2 } from "node:path";
27898
+ import { dirname as dirname22, resolve as resolve32 } from "node:path";
27899
+ import { mkdir } from "node:fs/promises";
27900
+ import { env } from "node:process";
27658
27901
  var __create2 = Object.create;
27659
27902
  var __getProtoOf2 = Object.getPrototypeOf;
27660
27903
  var __defProp3 = Object.defineProperty;
@@ -49140,7 +49383,7 @@ var require_compile2 = __commonJS2((exports) => {
49140
49383
  const schOrFunc = root22.refs[ref];
49141
49384
  if (schOrFunc)
49142
49385
  return schOrFunc;
49143
- let _sch = resolve5.call(this, root22, ref);
49386
+ let _sch = resolve4.call(this, root22, ref);
49144
49387
  if (_sch === undefined) {
49145
49388
  const schema2 = (_a = root22.localRefs) === null || _a === undefined ? undefined : _a[ref];
49146
49389
  const { schemaId } = this.opts;
@@ -49167,7 +49410,7 @@ var require_compile2 = __commonJS2((exports) => {
49167
49410
  function sameSchemaEnv(s1, s2) {
49168
49411
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
49169
49412
  }
49170
- function resolve5(root22, ref) {
49413
+ function resolve4(root22, ref) {
49171
49414
  let sch;
49172
49415
  while (typeof (sch = this.refs[ref]) == "string")
49173
49416
  ref = sch;
@@ -49689,7 +49932,7 @@ var require_fast_uri2 = __commonJS2((exports, module) => {
49689
49932
  }
49690
49933
  return uri;
49691
49934
  }
49692
- function resolve5(baseURI, relativeURI, options) {
49935
+ function resolve4(baseURI, relativeURI, options) {
49693
49936
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
49694
49937
  const resolved = resolveComponent(parse22(baseURI, schemelessOptions), parse22(relativeURI, schemelessOptions), schemelessOptions, true);
49695
49938
  schemelessOptions.skipEscape = true;
@@ -49917,7 +50160,7 @@ var require_fast_uri2 = __commonJS2((exports, module) => {
49917
50160
  var fastUri = {
49918
50161
  SCHEMES,
49919
50162
  normalize,
49920
- resolve: resolve5,
50163
+ resolve: resolve4,
49921
50164
  resolveComponent,
49922
50165
  equal,
49923
50166
  serialize,
@@ -53711,7 +53954,7 @@ async function validateSemantic2(workflow, basePath) {
53711
53954
  for (const [name, participant] of Object.entries(participants)) {
53712
53955
  if (participant.type !== "workflow")
53713
53956
  continue;
53714
- const resolvedPath = resolve4(basePath, participant.path);
53957
+ const resolvedPath = resolve3(basePath, participant.path);
53715
53958
  const exists = await access2(resolvedPath, constants2.F_OK).then(() => true, () => false);
53716
53959
  if (!exists) {
53717
53960
  errors.push({
@@ -53729,6 +53972,237 @@ var init_validate2 = __esm3(() => {
53729
53972
  RESERVED_NAMES2 = new Set(["workflow", "execution", "input", "output", "env", "loop", "event"]);
53730
53973
  BUILTIN_ONERROR2 = new Set(["fail", "skip", "retry"]);
53731
53974
  });
53975
+ var exports_json2 = {};
53976
+ __export3(exports_json2, {
53977
+ JsonTraceWriter: () => JsonTraceWriter2
53978
+ });
53979
+
53980
+ class JsonTraceWriter2 {
53981
+ dir;
53982
+ filePath = "";
53983
+ trace = {
53984
+ execution: {
53985
+ id: "",
53986
+ startedAt: "",
53987
+ finishedAt: "",
53988
+ duration: 0,
53989
+ status: "running",
53990
+ inputs: null,
53991
+ output: null
53992
+ },
53993
+ steps: []
53994
+ };
53995
+ constructor(dir) {
53996
+ this.dir = dir;
53997
+ }
53998
+ async open(meta) {
53999
+ this.filePath = join4(this.dir, `${meta.id}.json`);
54000
+ this.trace = {
54001
+ execution: {
54002
+ id: meta.id,
54003
+ workflowId: meta.workflowId,
54004
+ workflowName: meta.workflowName,
54005
+ workflowVersion: meta.workflowVersion,
54006
+ startedAt: meta.startedAt,
54007
+ finishedAt: "",
54008
+ duration: 0,
54009
+ status: "running",
54010
+ inputs: meta.inputs,
54011
+ output: null
54012
+ },
54013
+ steps: []
54014
+ };
54015
+ await this.flush();
54016
+ }
54017
+ writeStep(step) {
54018
+ this.trace.steps.push(step);
54019
+ this.flushSync();
54020
+ }
54021
+ async finalize(meta) {
54022
+ this.trace.execution.status = meta.status;
54023
+ this.trace.execution.output = meta.output;
54024
+ this.trace.execution.finishedAt = meta.finishedAt;
54025
+ this.trace.execution.duration = meta.duration;
54026
+ await this.flush();
54027
+ }
54028
+ flushSync() {
54029
+ this.flush().catch(() => {});
54030
+ }
54031
+ async flush() {
54032
+ if (!this.filePath)
54033
+ return;
54034
+ await writeFile3(this.filePath, JSON.stringify(this.trace, null, 2), "utf-8");
54035
+ }
54036
+ }
54037
+ var init_json = () => {};
54038
+ var exports_txt2 = {};
54039
+ __export3(exports_txt2, {
54040
+ TxtTraceWriter: () => TxtTraceWriter2
54041
+ });
54042
+ function serializeValue2(value) {
54043
+ if (value === undefined || value === null)
54044
+ return "none";
54045
+ if (typeof value === "string")
54046
+ return value;
54047
+ return JSON.stringify(value, null, 2);
54048
+ }
54049
+ function formatStep2(step) {
54050
+ const lines = [
54051
+ `## [${step.seq}] ${step.name} (${step.type})`,
54052
+ `startedAt: ${step.startedAt}`
54053
+ ];
54054
+ if (step.finishedAt)
54055
+ lines.push(`finishedAt: ${step.finishedAt}`);
54056
+ if (step.duration !== undefined)
54057
+ lines.push(`duration: ${step.duration}ms`);
54058
+ lines.push(`status: ${step.status}`);
54059
+ if (step.loopIndex !== undefined)
54060
+ lines.push(`loopIndex: ${step.loopIndex}`);
54061
+ if (step.retries !== undefined && step.retries > 0)
54062
+ lines.push(`retries: ${step.retries}`);
54063
+ lines.push(`input: ${serializeValue2(step.input)}`);
54064
+ lines.push(`output: ${serializeValue2(step.output)}`);
54065
+ if (step.error)
54066
+ lines.push(`error: ${step.error}`);
54067
+ lines.push("");
54068
+ return lines.join(`
54069
+ `);
54070
+ }
54071
+
54072
+ class TxtTraceWriter2 {
54073
+ dir;
54074
+ filePath = "";
54075
+ constructor(dir) {
54076
+ this.dir = dir;
54077
+ }
54078
+ async open(meta) {
54079
+ this.filePath = join22(this.dir, `${meta.id}.txt`);
54080
+ const versionStr = meta.workflowVersion !== undefined ? ` (v${meta.workflowVersion})` : "";
54081
+ const workflowLabel = meta.workflowName ?? meta.workflowId ?? "unnamed";
54082
+ const header = [
54083
+ "# execution",
54084
+ `id: ${meta.id}`,
54085
+ `workflow: ${workflowLabel}${versionStr}`,
54086
+ `startedAt: ${meta.startedAt}`,
54087
+ "status: running",
54088
+ "",
54089
+ "# inputs",
54090
+ serializeValue2(meta.inputs),
54091
+ "",
54092
+ "# steps",
54093
+ ""
54094
+ ].join(`
54095
+ `);
54096
+ await writeFile22(this.filePath, header, "utf-8");
54097
+ }
54098
+ async writeStep(step) {
54099
+ if (!this.filePath)
54100
+ return;
54101
+ await appendFile2(this.filePath, formatStep2(step), "utf-8");
54102
+ }
54103
+ async finalize(meta) {
54104
+ if (!this.filePath)
54105
+ return;
54106
+ const outputSection = [
54107
+ "# output",
54108
+ serializeValue2(meta.output),
54109
+ ""
54110
+ ].join(`
54111
+ `);
54112
+ await appendFile2(this.filePath, outputSection, "utf-8");
54113
+ const content = await readFile22(this.filePath, "utf-8");
54114
+ const updated = content.replace(/^status: running$/m, `status: ${meta.status}
54115
+ finishedAt: ${meta.finishedAt}
54116
+ duration: ${meta.duration}ms`);
54117
+ await writeFile22(this.filePath, updated, "utf-8");
54118
+ }
54119
+ }
54120
+ var init_txt = () => {};
54121
+ var exports_sqlite2 = {};
54122
+ __export3(exports_sqlite2, {
54123
+ SqliteTraceWriter: () => SqliteTraceWriter2
54124
+ });
54125
+ function toJson2(value) {
54126
+ if (value === undefined || value === null)
54127
+ return null;
54128
+ if (typeof value === "string")
54129
+ return value;
54130
+ return JSON.stringify(value);
54131
+ }
54132
+
54133
+ class SqliteTraceWriter2 {
54134
+ dir;
54135
+ db = null;
54136
+ executionId = "";
54137
+ constructor(dir) {
54138
+ this.dir = dir;
54139
+ }
54140
+ async open(meta) {
54141
+ this.executionId = meta.id;
54142
+ const filePath = join32(this.dir, `${meta.id}.sqlite`);
54143
+ const { Database } = await import("bun:sqlite");
54144
+ this.db = new Database(filePath);
54145
+ this.db.exec(`
54146
+ CREATE TABLE IF NOT EXISTS executions (
54147
+ id TEXT PRIMARY KEY,
54148
+ workflow_id TEXT,
54149
+ workflow_name TEXT,
54150
+ workflow_version TEXT,
54151
+ started_at TEXT NOT NULL,
54152
+ finished_at TEXT,
54153
+ duration_ms INTEGER,
54154
+ status TEXT NOT NULL,
54155
+ inputs TEXT,
54156
+ output TEXT
54157
+ );
54158
+
54159
+ CREATE TABLE IF NOT EXISTS steps (
54160
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
54161
+ execution_id TEXT NOT NULL REFERENCES executions(id),
54162
+ seq INTEGER NOT NULL,
54163
+ name TEXT NOT NULL,
54164
+ type TEXT NOT NULL,
54165
+ started_at TEXT,
54166
+ finished_at TEXT,
54167
+ duration_ms INTEGER,
54168
+ status TEXT NOT NULL,
54169
+ input TEXT,
54170
+ output TEXT,
54171
+ error TEXT,
54172
+ retries INTEGER,
54173
+ loop_index INTEGER
54174
+ );
54175
+ `);
54176
+ const insert = this.db.prepare(`
54177
+ INSERT INTO executions (id, workflow_id, workflow_name, workflow_version, started_at, status, inputs)
54178
+ VALUES (?, ?, ?, ?, ?, 'running', ?)
54179
+ `);
54180
+ insert.run(meta.id, meta.workflowId ?? null, meta.workflowName ?? null, meta.workflowVersion !== undefined ? String(meta.workflowVersion) : null, meta.startedAt, toJson2(meta.inputs));
54181
+ }
54182
+ writeStep(step) {
54183
+ if (!this.db)
54184
+ return;
54185
+ const insert = this.db.prepare(`
54186
+ INSERT INTO steps
54187
+ (execution_id, seq, name, type, started_at, finished_at, duration_ms, status, input, output, error, retries, loop_index)
54188
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
54189
+ `);
54190
+ insert.run(this.executionId, step.seq, step.name, step.type, step.startedAt ?? null, step.finishedAt ?? null, step.duration ?? null, step.status, toJson2(step.input), toJson2(step.output), step.error ?? null, step.retries ?? null, step.loopIndex ?? null);
54191
+ }
54192
+ async finalize(meta) {
54193
+ if (!this.db)
54194
+ return;
54195
+ const update = this.db.prepare(`
54196
+ UPDATE executions
54197
+ SET status = ?, output = ?, finished_at = ?, duration_ms = ?
54198
+ WHERE id = ?
54199
+ `);
54200
+ update.run(meta.status, toJson2(meta.output), meta.finishedAt, Math.round(meta.duration), this.executionId);
54201
+ this.db.close();
54202
+ this.db = null;
54203
+ }
54204
+ }
54205
+ var init_sqlite = () => {};
53732
54206
  function sleep3(ms) {
53733
54207
  return new Promise((resolve23) => setTimeout(resolve23, ms));
53734
54208
  }
@@ -54020,7 +54494,7 @@ async function executeHttp2(participant, input) {
54020
54494
  };
54021
54495
  }
54022
54496
  async function executeMcp2(participant, _input) {
54023
- throw new Error(`mcp participant is not yet implemented (server: ${participant.server ?? "unspecified"}, tool: ${participant.tool ?? "unspecified"}). ` + "Use onError to handle this gracefully.");
54497
+ throw new Error(`mcp participant is not yet implemented (server: ${participant.server ?? "unspecified"}, tool: ${participant.tool ?? "unspecified"}). Use onError to handle this gracefully.`);
54024
54498
  }
54025
54499
  function toWorkflowInputs2(input) {
54026
54500
  if (input && typeof input === "object" && !Array.isArray(input)) {
@@ -54306,6 +54780,10 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54306
54780
  output: "",
54307
54781
  duration: 0
54308
54782
  });
54783
+ const loopIndex2 = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
54784
+ const skippedSeq = state.tracer?.startStep(stepName, participant.type, undefined, loopIndex2);
54785
+ if (skippedSeq !== undefined)
54786
+ state.tracer?.endStep(skippedSeq, "skipped");
54309
54787
  }
54310
54788
  return chain;
54311
54789
  }
@@ -54314,6 +54792,8 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54314
54792
  const overrideInput = override?.input !== undefined ? resolveParticipantInput2(override.input, state) : undefined;
54315
54793
  const mergedWithBase = mergeChainedInput2(chain, baseInput);
54316
54794
  const mergedInput = mergeChainedInput2(mergedWithBase, overrideInput);
54795
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
54796
+ const traceSeq = state.tracer?.startStep(stepName ?? "<anonymous>", participant.type, mergedInput, loopIndex);
54317
54797
  state.currentInput = mergedInput;
54318
54798
  const strategy = resolveErrorStrategy2(override ?? null, participant, workflow.defaults ?? null);
54319
54799
  const timeoutMs = resolveTimeout2(override ?? null, participant, workflow.defaults ?? null);
@@ -54391,6 +54871,8 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54391
54871
  }
54392
54872
  const outputValue = result.parsedOutput ?? result.output;
54393
54873
  state.currentOutput = outputValue;
54874
+ if (traceSeq !== undefined)
54875
+ state.tracer?.endStep(traceSeq, result.status, outputValue, undefined, retries);
54394
54876
  return outputValue;
54395
54877
  } catch (error) {
54396
54878
  const message = String(error?.message ?? error);
@@ -54416,6 +54898,8 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54416
54898
  if (stepName) {
54417
54899
  state.setResult(stepName, skipResult);
54418
54900
  }
54901
+ if (traceSeq !== undefined)
54902
+ state.tracer?.endStep(traceSeq, "skipped", undefined, message);
54419
54903
  return chain;
54420
54904
  }
54421
54905
  if (strategy !== "fail" && strategy !== "retry") {
@@ -54434,6 +54918,8 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54434
54918
  ...httpMeta
54435
54919
  });
54436
54920
  }
54921
+ if (traceSeq !== undefined)
54922
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
54437
54923
  const fallbackResult = await executeStep2(workflow, state, fallbackName, basePath, engineExecutor, [...fallbackStack, stepName ?? "<anonymous>"], chain, hub);
54438
54924
  return fallbackResult;
54439
54925
  }
@@ -54448,6 +54934,8 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54448
54934
  ...httpMeta
54449
54935
  });
54450
54936
  }
54937
+ if (traceSeq !== undefined)
54938
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
54451
54939
  throw error;
54452
54940
  }
54453
54941
  }
@@ -54470,7 +54958,7 @@ __export3(exports_wait2, {
54470
54958
  executeWait: () => executeWait2
54471
54959
  });
54472
54960
  function sleep22(ms) {
54473
- return new Promise((resolve32) => setTimeout(resolve32, ms));
54961
+ return new Promise((resolve33) => setTimeout(resolve33, ms));
54474
54962
  }
54475
54963
  async function executeWait2(state, waitDef, chain, hub, signal) {
54476
54964
  const timeoutMs = waitDef.timeout ? parseDuration2(waitDef.timeout) : undefined;
@@ -54556,21 +55044,38 @@ async function executeControlStep2(workflow, state, step, basePath = process.cwd
54556
55044
  }
54557
55045
  const obj = step;
54558
55046
  if ("wait" in obj && Object.keys(obj).length === 1) {
54559
- const { executeWait: executeWait22 } = await Promise.resolve().then(() => (init_wait2(), exports_wait2));
54560
- return executeWait22(state, obj.wait, chain, hub, signal);
55047
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
55048
+ const traceSeq = state.tracer?.startStep("wait", "wait", undefined, loopIndex);
55049
+ try {
55050
+ const { executeWait: executeWait22 } = await Promise.resolve().then(() => (init_wait2(), exports_wait2));
55051
+ const result = await executeWait22(state, obj.wait, chain, hub, signal);
55052
+ if (traceSeq !== undefined)
55053
+ state.tracer?.endStep(traceSeq, "success", result);
55054
+ return result;
55055
+ } catch (err) {
55056
+ if (traceSeq !== undefined)
55057
+ state.tracer?.endStep(traceSeq, "failure", undefined, String(err?.message ?? err));
55058
+ throw err;
55059
+ }
54561
55060
  }
54562
55061
  if ("set" in obj && Object.keys(obj).length === 1) {
54563
55062
  const setDef = obj.set;
54564
55063
  const ctx = state.toCelContext();
55064
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
55065
+ const traceSeq = state.tracer?.startStep("set", "set", setDef, loopIndex);
54565
55066
  if (!state.executionMeta.context) {
54566
55067
  state.executionMeta.context = {};
54567
55068
  }
54568
55069
  for (const [key, expr] of Object.entries(setDef)) {
54569
55070
  if (RESERVED_SET_KEYS2.has(key)) {
55071
+ if (traceSeq !== undefined)
55072
+ state.tracer?.endStep(traceSeq, "failure", undefined, `set key '${key}' uses a reserved name`);
54570
55073
  throw new Error(`set key '${key}' uses a reserved name`);
54571
55074
  }
54572
55075
  state.executionMeta.context[key] = evaluateCel2(expr, ctx);
54573
55076
  }
55077
+ if (traceSeq !== undefined)
55078
+ state.tracer?.endStep(traceSeq, "success", state.executionMeta.context);
54574
55079
  return chain;
54575
55080
  }
54576
55081
  if ("loop" in obj && Object.keys(obj).length === 1) {
@@ -54587,6 +55092,8 @@ async function executeControlStep2(workflow, state, step, basePath = process.cwd
54587
55092
  maxIterations = loopDef.max ?? Number.POSITIVE_INFINITY;
54588
55093
  }
54589
55094
  const hasMax = loopDef.max !== undefined;
55095
+ const outerLoopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
55096
+ const loopTraceSeq = state.tracer?.startStep(loopAs ?? "loop", "loop", undefined, outerLoopIndex);
54590
55097
  state.pushLoop(loopAs);
54591
55098
  let loopChain = chain;
54592
55099
  try {
@@ -54610,6 +55117,12 @@ async function executeControlStep2(workflow, state, step, basePath = process.cwd
54610
55117
  iterations += 1;
54611
55118
  state.incrementLoop();
54612
55119
  }
55120
+ if (loopTraceSeq !== undefined)
55121
+ state.tracer?.endStep(loopTraceSeq, "success", loopChain);
55122
+ } catch (err) {
55123
+ if (loopTraceSeq !== undefined)
55124
+ state.tracer?.endStep(loopTraceSeq, "failure", undefined, String(err?.message ?? err));
55125
+ throw err;
54613
55126
  } finally {
54614
55127
  state.popLoop();
54615
55128
  }
@@ -54618,29 +55131,54 @@ async function executeControlStep2(workflow, state, step, basePath = process.cwd
54618
55131
  if ("parallel" in obj && Object.keys(obj).length === 1) {
54619
55132
  const parallelSteps = obj.parallel;
54620
55133
  const controller = new AbortController;
55134
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
55135
+ const parallelTraceSeq = state.tracer?.startStep("parallel", "parallel", undefined, loopIndex);
54621
55136
  const branchSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
54622
- const results = await Promise.all(parallelSteps.map(async (parallelStep) => {
54623
- try {
54624
- return await executeControlStep2(workflow, state, parallelStep, basePath, engineExecutor, chain, hub, branchSignal);
54625
- } catch (error) {
54626
- controller.abort();
54627
- throw error;
54628
- }
54629
- }));
54630
- return results;
55137
+ try {
55138
+ const results = await Promise.all(parallelSteps.map(async (parallelStep) => {
55139
+ try {
55140
+ return await executeControlStep2(workflow, state, parallelStep, basePath, engineExecutor, chain, hub, branchSignal);
55141
+ } catch (error) {
55142
+ controller.abort();
55143
+ throw error;
55144
+ }
55145
+ }));
55146
+ if (parallelTraceSeq !== undefined)
55147
+ state.tracer?.endStep(parallelTraceSeq, "success", results);
55148
+ return results;
55149
+ } catch (err) {
55150
+ if (parallelTraceSeq !== undefined)
55151
+ state.tracer?.endStep(parallelTraceSeq, "failure", undefined, String(err?.message ?? err));
55152
+ throw err;
55153
+ }
54631
55154
  }
54632
55155
  if ("if" in obj && Object.keys(obj).length === 1) {
54633
55156
  const ifDef = obj.if;
54634
55157
  const condition = evaluateCel2(ifDef.condition, state.toCelContext());
55158
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
55159
+ const ifTraceSeq = state.tracer?.startStep("if", "if", { condition: ifDef.condition }, loopIndex);
54635
55160
  if (typeof condition !== "boolean") {
55161
+ if (ifTraceSeq !== undefined)
55162
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, `if.condition must evaluate to boolean, got ${typeof condition}`);
54636
55163
  throw new Error(`if.condition must evaluate to boolean, got ${typeof condition}`);
54637
55164
  }
54638
- if (condition) {
54639
- return executeSequential2(workflow, state, ifDef.then, basePath, engineExecutor, chain, hub, signal);
54640
- } else if (ifDef.else) {
54641
- return executeSequential2(workflow, state, ifDef.else, basePath, engineExecutor, chain, hub, signal);
55165
+ try {
55166
+ let result;
55167
+ if (condition) {
55168
+ result = await executeSequential2(workflow, state, ifDef.then, basePath, engineExecutor, chain, hub, signal);
55169
+ } else if (ifDef.else) {
55170
+ result = await executeSequential2(workflow, state, ifDef.else, basePath, engineExecutor, chain, hub, signal);
55171
+ } else {
55172
+ result = chain;
55173
+ }
55174
+ if (ifTraceSeq !== undefined)
55175
+ state.tracer?.endStep(ifTraceSeq, "success", result);
55176
+ return result;
55177
+ } catch (err) {
55178
+ if (ifTraceSeq !== undefined)
55179
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, String(err?.message ?? err));
55180
+ throw err;
54642
55181
  }
54643
- return chain;
54644
55182
  }
54645
55183
  return executeStep2(workflow, state, step, basePath, engineExecutor, [], chain, hub, signal);
54646
55184
  }
@@ -54650,92 +55188,7 @@ var init_control2 = __esm3(() => {
54650
55188
  init_sequential2();
54651
55189
  RESERVED_SET_KEYS2 = new Set(["workflow", "execution", "input", "output", "env", "loop", "event"]);
54652
55190
  });
54653
-
54654
- class MemoryHub2 {
54655
- listeners = new Map;
54656
- buffer = new Map;
54657
- closed = false;
54658
- async publish(event, payload) {
54659
- if (this.closed)
54660
- throw new Error("hub is closed");
54661
- const envelope = { name: event, payload };
54662
- let buf = this.buffer.get(event);
54663
- if (!buf) {
54664
- buf = [];
54665
- this.buffer.set(event, buf);
54666
- }
54667
- buf.push(envelope);
54668
- const listeners = this.listeners.get(event);
54669
- if (listeners) {
54670
- for (const listener of listeners) {
54671
- listener(envelope);
54672
- }
54673
- }
54674
- }
54675
- async publishAndWaitAck(event, payload, _timeoutMs) {
54676
- await this.publish(event, payload);
54677
- }
54678
- async* subscribe(event, signal) {
54679
- if (this.closed)
54680
- return;
54681
- const buffered = this.buffer.get(event);
54682
- if (buffered) {
54683
- for (const envelope of buffered) {
54684
- if (signal?.aborted)
54685
- return;
54686
- yield envelope;
54687
- }
54688
- }
54689
- const queue = [];
54690
- let resolve5 = null;
54691
- const listener = (envelope) => {
54692
- queue.push(envelope);
54693
- if (resolve5) {
54694
- resolve5();
54695
- resolve5 = null;
54696
- }
54697
- };
54698
- let listeners = this.listeners.get(event);
54699
- if (!listeners) {
54700
- listeners = new Set;
54701
- this.listeners.set(event, listeners);
54702
- }
54703
- listeners.add(listener);
54704
- const onAbort = () => {
54705
- listeners.delete(listener);
54706
- if (resolve5) {
54707
- resolve5();
54708
- resolve5 = null;
54709
- }
54710
- };
54711
- if (signal) {
54712
- signal.addEventListener("abort", onAbort);
54713
- }
54714
- try {
54715
- while (!this.closed && !signal?.aborted) {
54716
- if (queue.length > 0) {
54717
- yield queue.shift();
54718
- } else {
54719
- await new Promise((r) => {
54720
- resolve5 = r;
54721
- });
54722
- }
54723
- }
54724
- } finally {
54725
- listeners.delete(listener);
54726
- if (signal) {
54727
- signal.removeEventListener("abort", onAbort);
54728
- }
54729
- }
54730
- }
54731
- async close() {
54732
- this.closed = true;
54733
- for (const listeners of this.listeners.values()) {
54734
- listeners.clear();
54735
- }
54736
- this.listeners.clear();
54737
- }
54738
- }
55191
+ init_cel2();
54739
55192
  init_parser32();
54740
55193
  init_schema2();
54741
55194
  init_validate2();
@@ -54899,147 +55352,514 @@ function validateInputs2(inputDefs, provided) {
54899
55352
  resolved
54900
55353
  };
54901
55354
  }
54902
- init_cel2();
54903
- init_cel2();
54904
- init_parser32();
54905
- init_schema2();
54906
- init_validate2();
54907
- init_control2();
54908
- init_sequential2();
54909
- init_wait2();
54910
- init_emit2();
54911
55355
 
54912
- // src/lint.ts
54913
- function collectLintWarnings(workflow) {
54914
- const warnings = [];
54915
- const participants = workflow.participants ?? {};
54916
- collectFlowWarnings(workflow.flow ?? [], participants, warnings);
54917
- return warnings;
55356
+ class TraceCollector2 {
55357
+ openSteps = new Map;
55358
+ seq = 0;
55359
+ truncateAt;
55360
+ writer;
55361
+ constructor(truncateAt = 1e6) {
55362
+ this.truncateAt = truncateAt;
55363
+ }
55364
+ startStep(name, type3, input, loopIndex) {
55365
+ this.seq += 1;
55366
+ this.openSteps.set(this.seq, {
55367
+ name,
55368
+ type: type3,
55369
+ startedAt: new Date().toISOString(),
55370
+ startMs: performance.now(),
55371
+ input: input !== undefined ? this.truncate(input) : undefined,
55372
+ loopIndex
55373
+ });
55374
+ return this.seq;
55375
+ }
55376
+ endStep(seq, status, output, error, retries) {
55377
+ const open = this.openSteps.get(seq);
55378
+ if (!open)
55379
+ return;
55380
+ this.openSteps.delete(seq);
55381
+ const finishedAt = new Date().toISOString();
55382
+ const duration = Math.max(0, performance.now() - open.startMs);
55383
+ const step = {
55384
+ seq,
55385
+ name: open.name,
55386
+ type: open.type,
55387
+ startedAt: open.startedAt,
55388
+ finishedAt,
55389
+ duration: Math.round(duration),
55390
+ status,
55391
+ ...open.input !== undefined ? { input: open.input } : {},
55392
+ ...output !== undefined ? { output: this.truncate(output) } : {},
55393
+ ...error !== undefined ? { error } : {},
55394
+ ...retries !== undefined && retries > 0 ? { retries } : {},
55395
+ ...open.loopIndex !== undefined ? { loopIndex: open.loopIndex } : {}
55396
+ };
55397
+ this.writer?.writeStep(step);
55398
+ }
55399
+ truncate(value) {
55400
+ if (value == null)
55401
+ return value;
55402
+ const str = typeof value === "string" ? value : JSON.stringify(value);
55403
+ const bytes = new TextEncoder().encode(str);
55404
+ if (bytes.length <= this.truncateAt)
55405
+ return value;
55406
+ const cut = new TextDecoder().decode(bytes.slice(0, this.truncateAt));
55407
+ return cut + "...[truncated]";
55408
+ }
54918
55409
  }
54919
- function collectFlowWarnings(flow, participants, warnings, basePath = "flow") {
54920
- for (const [index, step] of flow.entries()) {
54921
- const stepPath = `${basePath}[${index}]`;
54922
- if (!step || typeof step !== "object")
54923
- continue;
54924
- const obj = step;
54925
- if (obj.loop && Object.keys(obj).length === 1) {
54926
- const loopDef = obj.loop;
54927
- if (loopDef.until == null && loopDef.max == null) {
54928
- warnings.push({
54929
- path: `${stepPath}.loop`,
54930
- message: "loop has no 'until' and no 'max' this will be rejected at runtime"
54931
- });
54932
- }
54933
- collectFlowWarnings(loopDef.steps ?? [], participants, warnings, `${stepPath}.loop.steps`);
54934
- continue;
55410
+ async function createTraceWriter(dir, format) {
55411
+ await mkdir(dir, { recursive: true });
55412
+ if (format === "json") {
55413
+ const { JsonTraceWriter: JsonTraceWriter22 } = await Promise.resolve().then(() => (init_json(), exports_json2));
55414
+ return new JsonTraceWriter22(dir);
55415
+ }
55416
+ if (format === "txt") {
55417
+ const { TxtTraceWriter: TxtTraceWriter22 } = await Promise.resolve().then(() => (init_txt(), exports_txt2));
55418
+ return new TxtTraceWriter22(dir);
55419
+ }
55420
+ if (format === "sqlite") {
55421
+ const { SqliteTraceWriter: SqliteTraceWriter22 } = await Promise.resolve().then(() => (init_sqlite(), exports_sqlite2));
55422
+ return new SqliteTraceWriter22(dir);
55423
+ }
55424
+ throw new Error(`unknown trace format: ${format}`);
55425
+ }
55426
+ init_control2();
55427
+
55428
+ class WorkflowState {
55429
+ inputs;
55430
+ results;
55431
+ loopStack;
55432
+ workflowInputs;
55433
+ workflowMeta;
55434
+ executionMeta;
55435
+ currentInput;
55436
+ currentOutput;
55437
+ chainValue;
55438
+ eventPayload;
55439
+ ancestorPaths;
55440
+ tracer;
55441
+ constructor(inputs = {}) {
55442
+ this.inputs = { ...inputs };
55443
+ this.workflowInputs = { ...inputs };
55444
+ this.workflowMeta = {};
55445
+ this.executionMeta = {
55446
+ id: crypto.randomUUID(),
55447
+ startedAt: new Date().toISOString(),
55448
+ status: "running",
55449
+ cwd: process.cwd()
55450
+ };
55451
+ this.currentInput = undefined;
55452
+ this.currentOutput = undefined;
55453
+ this.chainValue = undefined;
55454
+ this.eventPayload = undefined;
55455
+ this.ancestorPaths = new Set;
55456
+ this.results = new Map;
55457
+ this.loopStack = [];
55458
+ }
55459
+ setResult(stepName, result) {
55460
+ this.results.set(stepName, result);
55461
+ }
55462
+ getResult(stepName) {
55463
+ return this.results.get(stepName);
55464
+ }
55465
+ getAllResults() {
55466
+ const out = {};
55467
+ for (const [k, v] of this.results.entries())
55468
+ out[k] = v;
55469
+ return out;
55470
+ }
55471
+ pushLoop(as) {
55472
+ this.loopStack.push({ index: 0, as });
55473
+ }
55474
+ incrementLoop() {
55475
+ const top = this.loopStack[this.loopStack.length - 1];
55476
+ if (top)
55477
+ top.index += 1;
55478
+ }
55479
+ popLoop() {
55480
+ this.loopStack.pop();
55481
+ }
55482
+ currentLoopIndex() {
55483
+ const top = this.loopStack[this.loopStack.length - 1];
55484
+ return top ? top.index : 0;
55485
+ }
55486
+ setLoopLast(last22) {
55487
+ const top = this.loopStack[this.loopStack.length - 1];
55488
+ if (top)
55489
+ top.last = last22;
55490
+ }
55491
+ isInsideLoop() {
55492
+ return this.loopStack.length > 0;
55493
+ }
55494
+ currentLoopContext() {
55495
+ const top = this.loopStack[this.loopStack.length - 1];
55496
+ if (!top)
55497
+ return { index: 0, iteration: 1, first: true, last: false };
55498
+ return {
55499
+ index: top.index,
55500
+ iteration: top.index + 1,
55501
+ first: top.index === 0,
55502
+ last: top.last ?? false,
55503
+ as: top.as
55504
+ };
55505
+ }
55506
+ toCelContext() {
55507
+ const ctx = {};
55508
+ for (const [name, res] of this.results.entries()) {
55509
+ ctx[name] = {
55510
+ output: res.parsedOutput ?? res.output,
55511
+ status: res.status,
55512
+ startedAt: res.startedAt,
55513
+ finishedAt: res.finishedAt,
55514
+ duration: res.duration,
55515
+ retries: res.retries ?? 0,
55516
+ error: res.error,
55517
+ cwd: res.cwd
55518
+ };
54935
55519
  }
54936
- if (obj.parallel && Object.keys(obj).length === 1) {
54937
- const parallelSteps = obj.parallel;
54938
- const branchSetKeys = new Map;
54939
- for (const [branchIdx, branch] of parallelSteps.entries()) {
54940
- const setKeys = collectSetKeys(branch);
54941
- for (const key of setKeys) {
54942
- const branches = branchSetKeys.get(key) ?? [];
54943
- branches.push(branchIdx);
54944
- branchSetKeys.set(key, branches);
54945
- }
54946
- }
54947
- for (const [key, branches] of branchSetKeys) {
54948
- if (branches.length > 1) {
54949
- warnings.push({
54950
- path: `${stepPath}.parallel`,
54951
- message: `branches [${branches.join(", ")}] both write to '${key}' via set — race condition risk`
54952
- });
54953
- }
55520
+ ctx["workflow"] = {
55521
+ id: this.workflowMeta.id,
55522
+ name: this.workflowMeta.name,
55523
+ version: this.workflowMeta.version,
55524
+ inputs: this.workflowInputs,
55525
+ output: null
55526
+ };
55527
+ ctx["execution"] = { ...this.executionMeta };
55528
+ ctx["input"] = this.currentInput ?? {};
55529
+ ctx["output"] = this.currentOutput ?? {};
55530
+ ctx["env"] = { ...env };
55531
+ const loopCtx = this.currentLoopContext();
55532
+ const loopObj = {
55533
+ index: loopCtx.index,
55534
+ iteration: loopCtx.iteration,
55535
+ first: loopCtx.first,
55536
+ last: loopCtx.last
55537
+ };
55538
+ if (loopCtx.as) {
55539
+ ctx[`_${loopCtx.as}`] = loopObj;
55540
+ }
55541
+ ctx["_loop"] = loopObj;
55542
+ ctx["loop"] = loopObj;
55543
+ ctx["event"] = this.eventPayload ?? {};
55544
+ ctx["now"] = Math.floor(Date.now() / 1000);
55545
+ return ctx;
55546
+ }
55547
+ resolveOutput(outputDef, celEvaluator) {
55548
+ const ctx = this.toCelContext();
55549
+ if (typeof outputDef === "object" && "map" in outputDef && "schema" in outputDef) {
55550
+ const result2 = {};
55551
+ for (const [k, expr] of Object.entries(outputDef.map)) {
55552
+ result2[k] = celEvaluator(expr, ctx);
54954
55553
  }
54955
- collectFlowWarnings(parallelSteps, participants, warnings, `${stepPath}.parallel`);
54956
- continue;
55554
+ return result2;
54957
55555
  }
54958
- if ("type" in obj && !obj.as) {
54959
- warnings.push({
54960
- path: stepPath,
54961
- message: "inline participant without 'as' — its output cannot be referenced by name"
54962
- });
54963
- continue;
55556
+ if (typeof outputDef === "string") {
55557
+ return celEvaluator(outputDef, ctx);
54964
55558
  }
54965
- if (obj.if && Object.keys(obj).length === 1) {
54966
- const ifDef = obj.if;
54967
- collectFlowWarnings(ifDef.then ?? [], participants, warnings, `${stepPath}.if.then`);
54968
- if (ifDef.else) {
54969
- collectFlowWarnings(ifDef.else, participants, warnings, `${stepPath}.if.else`);
55559
+ const result = {};
55560
+ for (const k of Object.keys(outputDef)) {
55561
+ const expr = outputDef[k];
55562
+ result[k] = celEvaluator(expr, ctx);
55563
+ }
55564
+ return result;
55565
+ }
55566
+ }
55567
+ async function executeWorkflow(workflow, inputs = {}, basePath = process.cwd(), options = {}) {
55568
+ const { result: inputResult, resolved } = validateInputs2(workflow.inputs, inputs);
55569
+ if (!inputResult.valid) {
55570
+ throw new Error(`input validation failed: ${JSON.stringify(inputResult.errors)}`);
55571
+ }
55572
+ const state = new WorkflowState(resolved);
55573
+ state.workflowMeta = {
55574
+ id: workflow.id,
55575
+ name: workflow.name,
55576
+ version: workflow.version
55577
+ };
55578
+ state.executionMeta.number = options.executionNumber ?? 1;
55579
+ if (options.cwd) {
55580
+ state.executionMeta.cwd = options.cwd;
55581
+ }
55582
+ const startedAtMs = performance.now();
55583
+ const startedAtIso = state.executionMeta.startedAt;
55584
+ if (options._ancestorPaths) {
55585
+ state.ancestorPaths = options._ancestorPaths;
55586
+ }
55587
+ if (options.traceDir && !options._ancestorPaths) {
55588
+ const collector = new TraceCollector2;
55589
+ const writer = await createTraceWriter(options.traceDir, options.traceFormat ?? "json");
55590
+ collector.writer = writer;
55591
+ state.tracer = collector;
55592
+ await writer.open({
55593
+ id: state.executionMeta.id,
55594
+ workflowId: workflow.id,
55595
+ workflowName: workflow.name,
55596
+ workflowVersion: workflow.version,
55597
+ startedAt: startedAtIso,
55598
+ inputs: resolved
55599
+ });
55600
+ }
55601
+ const engineExecutor = async (subWorkflow, subInputs, subBasePath) => {
55602
+ return executeWorkflow(subWorkflow, subInputs, subBasePath, {
55603
+ ...options,
55604
+ _ancestorPaths: state.ancestorPaths
55605
+ });
55606
+ };
55607
+ let output;
55608
+ let success = false;
55609
+ try {
55610
+ let chain;
55611
+ for (const step of workflow.flow) {
55612
+ chain = await executeControlStep2(workflow, state, step, basePath, engineExecutor, chain, options.hub);
55613
+ }
55614
+ if (workflow.output !== undefined) {
55615
+ output = state.resolveOutput(workflow.output, evaluateCel2);
55616
+ if (typeof workflow.output === "object" && "schema" in workflow.output && "map" in workflow.output && typeof output === "object" && output !== null) {
55617
+ validateOutputSchema2(workflow.output.schema, output);
54970
55618
  }
55619
+ } else {
55620
+ output = chain;
55621
+ }
55622
+ const steps = state.getAllResults();
55623
+ success = !Object.values(steps).some((step) => step.status === "failure");
55624
+ state.executionMeta.status = success ? "success" : "failure";
55625
+ return {
55626
+ success,
55627
+ output,
55628
+ steps,
55629
+ duration: Math.max(0, performance.now() - startedAtMs)
55630
+ };
55631
+ } finally {
55632
+ if (state.tracer?.writer) {
55633
+ const duration = Math.max(0, performance.now() - startedAtMs);
55634
+ await state.tracer.writer.finalize({
55635
+ status: success ? "success" : "failure",
55636
+ output: output ?? null,
55637
+ finishedAt: new Date().toISOString(),
55638
+ duration
55639
+ }).catch(() => {});
54971
55640
  }
54972
55641
  }
54973
55642
  }
54974
- function collectSetKeys(step) {
54975
- if (!step || typeof step !== "object")
54976
- return [];
54977
- const obj = step;
54978
- if ("set" in obj && Object.keys(obj).length === 1) {
54979
- return Object.keys(obj.set);
55643
+ async function runWorkflowFromFile(filePath, inputs = {}, options = {}) {
55644
+ const workflow = await parseWorkflowFile2(filePath);
55645
+ const schemaValidation = validateSchema2(workflow);
55646
+ if (!schemaValidation.valid) {
55647
+ throw new Error(`schema validation failed: ${JSON.stringify(schemaValidation.errors)}`);
54980
55648
  }
54981
- const keys4 = [];
54982
- if (obj.loop) {
54983
- const loopDef = obj.loop;
54984
- for (const s of loopDef.steps ?? []) {
54985
- keys4.push(...collectSetKeys(s));
55649
+ const workflowBasePath = dirname22(resolve32(filePath));
55650
+ const semanticValidation = await validateSemantic2(workflow, workflowBasePath);
55651
+ if (!semanticValidation.valid) {
55652
+ throw new Error(`semantic validation failed: ${JSON.stringify(semanticValidation.errors)}`);
55653
+ }
55654
+ return executeWorkflow(workflow, inputs, workflowBasePath, options);
55655
+ }
55656
+ init_control2();
55657
+ init_sequential2();
55658
+ init_errors2();
55659
+ init_timeout2();
55660
+ init_wait2();
55661
+
55662
+ // src/run.ts
55663
+ async function createHubFromFlags(values3) {
55664
+ const backend = values3?.["event-backend"] ?? "memory";
55665
+ if (backend === "memory") {
55666
+ const { MemoryHub: MemoryHub3 } = await Promise.resolve().then(() => (init_eventhub(), exports_eventhub));
55667
+ return new MemoryHub3;
55668
+ }
55669
+ if (backend === "nats") {
55670
+ const url = values3?.["nats-url"];
55671
+ if (!url) {
55672
+ console.error("Error: --nats-url is required when using the NATS backend");
55673
+ throw new Error("missing --nats-url");
55674
+ }
55675
+ const stream = values3?.["nats-stream"] ?? "duckflux-events";
55676
+ try {
55677
+ const { NatsHub } = await import("@duckflux/hub-nats");
55678
+ return await NatsHub.create({ url, stream });
55679
+ } catch (err) {
55680
+ if (err instanceof Error && (err.message.includes("Cannot find module") || err.message.includes("Cannot find package"))) {
55681
+ console.error("Error: install @duckflux/hub-nats to use the NATS backend");
55682
+ throw new Error("@duckflux/hub-nats not installed");
55683
+ }
55684
+ throw err;
54986
55685
  }
54987
55686
  }
54988
- if (obj.if) {
54989
- const ifDef = obj.if;
54990
- for (const s of ifDef.then ?? []) {
54991
- keys4.push(...collectSetKeys(s));
55687
+ if (backend === "redis") {
55688
+ const addr = values3?.["redis-addr"] ?? "localhost:6379";
55689
+ const db = Number(values3?.["redis-db"] ?? "0");
55690
+ try {
55691
+ const { RedisHub } = await import("@duckflux/hub-redis");
55692
+ return await RedisHub.create({ addr, db });
55693
+ } catch (err) {
55694
+ if (err instanceof Error && (err.message.includes("Cannot find module") || err.message.includes("Cannot find package"))) {
55695
+ console.error("Error: install @duckflux/hub-redis to use the Redis backend");
55696
+ throw new Error("@duckflux/hub-redis not installed");
55697
+ }
55698
+ throw err;
54992
55699
  }
54993
- if (ifDef.else) {
54994
- for (const s of ifDef.else) {
54995
- keys4.push(...collectSetKeys(s));
55700
+ }
55701
+ console.error(`Error: unknown event backend "${backend}". Supported: memory, nats, redis`);
55702
+ throw new Error(`unknown event backend: ${backend}`);
55703
+ }
55704
+ function parseInputFlags(arr) {
55705
+ const out = {};
55706
+ if (!arr)
55707
+ return out;
55708
+ for (const item of arr) {
55709
+ const idx = item.indexOf("=");
55710
+ if (idx === -1) {
55711
+ out[item] = true;
55712
+ } else {
55713
+ const k = item.slice(0, idx);
55714
+ const v = item.slice(idx + 1);
55715
+ try {
55716
+ out[k] = JSON.parse(v);
55717
+ } catch {
55718
+ out[k] = v;
54996
55719
  }
54997
55720
  }
54998
55721
  }
54999
- return keys4;
55722
+ return out;
55000
55723
  }
55001
- async function lintCommand(filePath) {
55724
+ async function runCommand(filePath, cliValues) {
55002
55725
  if (!filePath) {
55003
- console.error("Usage: duckflux lint <workflow.yaml>");
55726
+ console.error("Usage: quack run <workflow.yaml> [--input k=v] [--input-file file.json] [--cwd dir]");
55004
55727
  return 1;
55005
55728
  }
55729
+ let inputs = {};
55006
55730
  try {
55007
- const workflow = await parseWorkflowFile2(filePath);
55008
- const schemaRes = validateSchema2(workflow);
55009
- if (!schemaRes.valid) {
55010
- console.error("Schema validation failed:");
55011
- for (const e of schemaRes.errors) {
55012
- console.error(` - ${e.path}: ${e.message}`);
55731
+ if (process.stdin && !process.stdin.isTTY) {
55732
+ let stdin = "";
55733
+ for await (const chunk of process.stdin) {
55734
+ stdin += chunk;
55013
55735
  }
55014
- return 1;
55015
- }
55016
- const basePath = dirname5(filePath);
55017
- const semanticRes = await validateSemantic2(workflow, basePath);
55018
- if (!semanticRes.valid) {
55019
- console.error("Semantic validation failed:");
55020
- for (const e of semanticRes.errors) {
55021
- console.error(` - ${e.path}: ${e.message}`);
55736
+ stdin = stdin.trim();
55737
+ if (stdin.length > 0) {
55738
+ try {
55739
+ const parsed = JSON.parse(stdin);
55740
+ if (typeof parsed === "object" && parsed !== null)
55741
+ inputs = { ...inputs, ...parsed };
55742
+ } catch {}
55022
55743
  }
55023
- return 1;
55024
55744
  }
55025
- const warnings = collectLintWarnings(workflow);
55026
- if (warnings.length > 0) {
55027
- console.warn("Warnings:");
55028
- for (const w of warnings) {
55029
- console.warn(` - ${w.path}: ${w.message}`);
55745
+ } catch {}
55746
+ if (cliValues) {
55747
+ if (cliValues["input-file"]) {
55748
+ try {
55749
+ const content = await readFile5(String(cliValues["input-file"]), "utf-8");
55750
+ const parsed = JSON.parse(content);
55751
+ if (typeof parsed === "object" && parsed !== null)
55752
+ inputs = { ...inputs, ...parsed };
55753
+ } catch (err) {
55754
+ console.error("Failed to read input file:", err);
55755
+ return 1;
55030
55756
  }
55031
55757
  }
55032
- console.log("valid");
55033
- return 0;
55758
+ if (cliValues.input) {
55759
+ const parsed = Array.isArray(cliValues.input) ? cliValues.input : [cliValues.input];
55760
+ inputs = { ...inputs, ...parseInputFlags(parsed) };
55761
+ }
55762
+ }
55763
+ let hub;
55764
+ try {
55765
+ hub = await createHubFromFlags(cliValues);
55766
+ } catch {
55767
+ return 1;
55768
+ }
55769
+ const options = {
55770
+ hub,
55771
+ cwd: cliValues?.cwd,
55772
+ verbose: cliValues?.verbose,
55773
+ quiet: cliValues?.quiet,
55774
+ traceDir: cliValues?.["trace-dir"],
55775
+ traceFormat: cliValues?.["trace-format"] ?? "json"
55776
+ };
55777
+ try {
55778
+ const res = await runWorkflowFromFile(filePath, inputs, options);
55779
+ const output = res.output;
55780
+ if (output === undefined || output === null) {} else if (typeof output === "string") {
55781
+ process.stdout.write(output);
55782
+ } else {
55783
+ console.log(JSON.stringify(output, null, 2));
55784
+ }
55785
+ return res.success ? 0 : 2;
55034
55786
  } catch (err) {
55035
- console.error("Error during lint:", err && err.message ? err.message : err);
55787
+ const msg = err instanceof Error ? err.message : String(err);
55788
+ console.error("Error:", msg);
55789
+ if (cliValues?.verbose && err instanceof Error && err.stack) {
55790
+ console.error(err.stack);
55791
+ }
55036
55792
  return 1;
55793
+ } finally {
55794
+ await hub?.close();
55037
55795
  }
55038
55796
  }
55039
55797
 
55798
+ // src/server.ts
55799
+ import { existsSync } from "node:fs";
55800
+ import { join as join6 } from "node:path";
55801
+ import { spawn as spawn3, execSync } from "node:child_process";
55802
+ import { createInterface } from "node:readline";
55803
+ async function ensureServerPackage(cwd) {
55804
+ const serverBin = join6(cwd, "node_modules", ".bin", "duckflux-server");
55805
+ const serverPkg = join6(cwd, "node_modules", "@duckflux", "server");
55806
+ if (existsSync(serverBin) || existsSync(serverPkg))
55807
+ return true;
55808
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
55809
+ const answer = await new Promise((resolve4) => {
55810
+ rl.question(`
55811
+ @duckflux/server is not installed. Install it now? [Y/n] `, resolve4);
55812
+ });
55813
+ rl.close();
55814
+ if (answer.trim().toLowerCase() === "n") {
55815
+ console.error("Cancelled. Run `bun add @duckflux/server -D` to install manually.");
55816
+ return false;
55817
+ }
55818
+ console.error("Installing @duckflux/server...");
55819
+ try {
55820
+ execSync("bun add @duckflux/server -D", { cwd, stdio: "inherit" });
55821
+ return true;
55822
+ } catch {
55823
+ try {
55824
+ execSync("npm install @duckflux/server --save-dev", { cwd, stdio: "inherit" });
55825
+ return true;
55826
+ } catch {
55827
+ console.error("Failed to install @duckflux/server. Please install it manually.");
55828
+ return false;
55829
+ }
55830
+ }
55831
+ }
55832
+ async function serverCommand(values3) {
55833
+ const cwd = values3["cwd"] ?? process.cwd();
55834
+ const installed = await ensureServerPackage(cwd);
55835
+ if (!installed)
55836
+ return 1;
55837
+ const args = [];
55838
+ if (values3["trace-dir"])
55839
+ args.push("--trace-dir", values3["trace-dir"]);
55840
+ if (values3["workflow-dir"])
55841
+ args.push("--workflow-dir", values3["workflow-dir"]);
55842
+ if (values3["port"])
55843
+ args.push("--port", values3["port"]);
55844
+ const child = spawn3("bunx", ["@duckflux/server", ...args], {
55845
+ cwd,
55846
+ stdio: "inherit",
55847
+ env: process.env
55848
+ });
55849
+ return new Promise((resolve4) => {
55850
+ child.on("error", (err) => {
55851
+ console.error("Failed to start duckflux server:", err.message);
55852
+ resolve4(1);
55853
+ });
55854
+ child.on("exit", (code) => resolve4(code ?? 0));
55855
+ process.on("SIGINT", () => child.kill("SIGINT"));
55856
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
55857
+ });
55858
+ }
55859
+
55040
55860
  // src/validate.ts
55041
- import { readFile as readFile4 } from "node:fs/promises";
55042
- import { dirname as dirname6 } from "node:path";
55861
+ import { readFile as readFile6 } from "node:fs/promises";
55862
+ import { dirname as dirname4 } from "node:path";
55043
55863
 
55044
55864
  // src/run.ts
55045
55865
  function parseInputFlags2(arr) {
@@ -55066,7 +55886,7 @@ function parseInputFlags2(arr) {
55066
55886
  // src/validate.ts
55067
55887
  async function validateCommand(filePath, cliValues) {
55068
55888
  if (!filePath) {
55069
- console.error("Usage: duckflux validate <workflow.yaml> [--input k=v] [--input-file file.json]");
55889
+ console.error("Usage: quack validate <workflow.yaml> [--input k=v] [--input-file file.json]");
55070
55890
  return 1;
55071
55891
  }
55072
55892
  let inputs = {};
@@ -55077,7 +55897,7 @@ async function validateCommand(filePath, cliValues) {
55077
55897
  }
55078
55898
  if (cliValues["input-file"]) {
55079
55899
  try {
55080
- const content = await readFile4(String(cliValues["input-file"]), "utf-8");
55900
+ const content = await readFile6(String(cliValues["input-file"]), "utf-8");
55081
55901
  const parsed = JSON.parse(content);
55082
55902
  if (typeof parsed === "object" && parsed !== null)
55083
55903
  inputs = { ...inputs, ...parsed };
@@ -55104,8 +55924,8 @@ async function validateCommand(filePath, cliValues) {
55104
55924
  }
55105
55925
  } catch {}
55106
55926
  try {
55107
- const workflow = await parseWorkflowFile2(filePath);
55108
- const schemaRes = validateSchema2(workflow);
55927
+ const workflow = await parseWorkflowFile(filePath);
55928
+ const schemaRes = validateSchema(workflow);
55109
55929
  if (!schemaRes.valid) {
55110
55930
  console.error("Schema validation failed:");
55111
55931
  for (const e of schemaRes.errors) {
@@ -55113,8 +55933,8 @@ async function validateCommand(filePath, cliValues) {
55113
55933
  }
55114
55934
  return 1;
55115
55935
  }
55116
- const basePath = dirname6(filePath);
55117
- const semanticRes = await validateSemantic2(workflow, basePath);
55936
+ const basePath = dirname4(filePath);
55937
+ const semanticRes = await validateSemantic(workflow, basePath);
55118
55938
  if (!semanticRes.valid) {
55119
55939
  console.error("Semantic validation failed:");
55120
55940
  for (const e of semanticRes.errors) {
@@ -55122,7 +55942,7 @@ async function validateCommand(filePath, cliValues) {
55122
55942
  }
55123
55943
  return 1;
55124
55944
  }
55125
- const { result: inputsResult, resolved } = validateInputs2(workflow.inputs, inputs);
55945
+ const { result: inputsResult, resolved } = validateInputs(workflow.inputs, inputs);
55126
55946
  if (!inputsResult.valid) {
55127
55947
  console.error("Input validation failed:");
55128
55948
  for (const e of inputsResult.errors) {
@@ -55141,7 +55961,7 @@ async function validateCommand(filePath, cliValues) {
55141
55961
  // src/main.ts
55142
55962
  function getVersion() {
55143
55963
  try {
55144
- const pkg = JSON.parse(readFileSync3(resolve6(dirname7(new URL(import.meta.url).pathname), "../package.json"), "utf-8"));
55964
+ const pkg = JSON.parse(readFileSync3(resolve5(dirname5(new URL(import.meta.url).pathname), "../package.json"), "utf-8"));
55145
55965
  return pkg.version ?? "0.0.0";
55146
55966
  } catch {
55147
55967
  return "0.0.0";
@@ -55161,7 +55981,11 @@ if (__require.main == __require.module) {
55161
55981
  "nats-url": { type: "string" },
55162
55982
  "nats-stream": { type: "string", default: "duckflux-events" },
55163
55983
  "redis-addr": { type: "string", default: "localhost:6379" },
55164
- "redis-db": { type: "string", default: "0" }
55984
+ "redis-db": { type: "string", default: "0" },
55985
+ "trace-dir": { type: "string" },
55986
+ "trace-format": { type: "string", default: "json" },
55987
+ "workflow-dir": { type: "string" },
55988
+ port: { type: "string", default: "3000" }
55165
55989
  },
55166
55990
  allowPositionals: true
55167
55991
  });
@@ -55183,9 +56007,13 @@ if (__require.main == __require.module) {
55183
56007
  const exitCode = await validateCommand(file, values3);
55184
56008
  if (typeof exitCode === "number" && exitCode !== 0)
55185
56009
  process.exit(exitCode);
56010
+ } else if (cmd === "server") {
56011
+ const exitCode = await serverCommand(values3);
56012
+ if (typeof exitCode === "number" && exitCode !== 0)
56013
+ process.exit(exitCode);
55186
56014
  } else {
55187
56015
  console.error("Unknown command:", cmd);
55188
- console.error("Available commands: run, lint, validate, version");
56016
+ console.error("Available commands: run, lint, validate, version, server");
55189
56017
  process.exit(1);
55190
56018
  }
55191
56019
  }