@duckflux/runner 0.6.8 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/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":
@@ -126,25 +126,27 @@ async function createHub(config) {
126
126
  var init_eventhub = () => {};
127
127
 
128
128
  // src/main.ts
129
- import { readFileSync as readFileSync3 } from "fs";
129
+ import { readFileSync } 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
- import { readFileSync } from "node:fs";
140
139
  import { constants } from "node:fs";
141
140
  import { access } from "node:fs/promises";
142
141
  import { resolve } from "node:path";
142
+ import { writeFile } from "node:fs/promises";
143
+ import { join } from "node:path";
144
+ import { appendFile, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
145
+ import { join as join2 } from "node:path";
146
+ import { join as join3 } from "node:path";
143
147
  import { spawn } from "node:child_process";
144
148
  import { dirname, resolve as resolve2 } from "node:path";
145
149
  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
150
  var __create = Object.create;
149
151
  var __getProtoOf = Object.getPrototypeOf;
150
152
  var __defProp2 = Object.defineProperty;
@@ -21520,20 +21522,20 @@ var require_compile = __commonJS((exports) => {
21520
21522
  var validate_1 = require_validate();
21521
21523
 
21522
21524
  class SchemaEnv {
21523
- constructor(env2) {
21525
+ constructor(env) {
21524
21526
  var _a;
21525
21527
  this.refs = {};
21526
21528
  this.dynamicAnchors = {};
21527
21529
  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;
21530
+ if (typeof env.schema == "object")
21531
+ schema = env.schema;
21532
+ this.schema = env.schema;
21533
+ this.schemaId = env.schemaId;
21534
+ this.root = env.root || this;
21535
+ this.baseId = (_a = env.baseId) !== null && _a !== undefined ? _a : (0, resolve_1.normalizeId)(schema === null || schema === undefined ? undefined : schema[env.schemaId || "$id"]);
21536
+ this.schemaPath = env.schemaPath;
21537
+ this.localRefs = env.localRefs;
21538
+ this.meta = env.meta;
21537
21539
  this.$async = schema === null || schema === undefined ? undefined : schema.$async;
21538
21540
  this.refs = {};
21539
21541
  }
@@ -21630,7 +21632,7 @@ var require_compile = __commonJS((exports) => {
21630
21632
  const schOrFunc = root2.refs[ref];
21631
21633
  if (schOrFunc)
21632
21634
  return schOrFunc;
21633
- let _sch = resolve4.call(this, root2, ref);
21635
+ let _sch = resolve3.call(this, root2, ref);
21634
21636
  if (_sch === undefined) {
21635
21637
  const schema = (_a = root2.localRefs) === null || _a === undefined ? undefined : _a[ref];
21636
21638
  const { schemaId } = this.opts;
@@ -21657,7 +21659,7 @@ var require_compile = __commonJS((exports) => {
21657
21659
  function sameSchemaEnv(s1, s2) {
21658
21660
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
21659
21661
  }
21660
- function resolve4(root2, ref) {
21662
+ function resolve3(root2, ref) {
21661
21663
  let sch;
21662
21664
  while (typeof (sch = this.refs[ref]) == "string")
21663
21665
  ref = sch;
@@ -21716,15 +21718,15 @@ var require_compile = __commonJS((exports) => {
21716
21718
  baseId = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schId);
21717
21719
  }
21718
21720
  }
21719
- let env2;
21721
+ let env;
21720
21722
  if (typeof schema != "boolean" && schema.$ref && !(0, util_1.schemaHasRulesButRef)(schema, this.RULES)) {
21721
21723
  const $ref = (0, resolve_1.resolveUrl)(this.opts.uriResolver, baseId, schema.$ref);
21722
- env2 = resolveSchema.call(this, root2, $ref);
21724
+ env = resolveSchema.call(this, root2, $ref);
21723
21725
  }
21724
21726
  const { schemaId } = this.opts;
21725
- env2 = env2 || new SchemaEnv({ schema, schemaId, root: root2, baseId });
21726
- if (env2.schema !== env2.root.schema)
21727
- return env2;
21727
+ env = env || new SchemaEnv({ schema, schemaId, root: root2, baseId });
21728
+ if (env.schema !== env.root.schema)
21729
+ return env;
21728
21730
  return;
21729
21731
  }
21730
21732
  });
@@ -22179,7 +22181,7 @@ var require_fast_uri = __commonJS((exports, module) => {
22179
22181
  }
22180
22182
  return uri;
22181
22183
  }
22182
- function resolve4(baseURI, relativeURI, options) {
22184
+ function resolve3(baseURI, relativeURI, options) {
22183
22185
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
22184
22186
  const resolved = resolveComponent(parse2(baseURI, schemelessOptions), parse2(relativeURI, schemelessOptions), schemelessOptions, true);
22185
22187
  schemelessOptions.skipEscape = true;
@@ -22407,7 +22409,7 @@ var require_fast_uri = __commonJS((exports, module) => {
22407
22409
  var fastUri = {
22408
22410
  SCHEMES,
22409
22411
  normalize,
22410
- resolve: resolve4,
22412
+ resolve: resolve3,
22411
22413
  resolveComponent,
22412
22414
  equal,
22413
22415
  serialize,
@@ -23038,8 +23040,8 @@ var require_ref = __commonJS((exports) => {
23038
23040
  schemaType: "string",
23039
23041
  code(cxt) {
23040
23042
  const { gen, schema: $ref, it } = cxt;
23041
- const { baseId, schemaEnv: env2, validateName, opts, self: self2 } = it;
23042
- const { root: root2 } = env2;
23043
+ const { baseId, schemaEnv: env, validateName, opts, self: self2 } = it;
23044
+ const { root: root2 } = env;
23043
23045
  if (($ref === "#" || $ref === "#/") && baseId === root2.baseId)
23044
23046
  return callRootRef();
23045
23047
  const schOrEnv = compile_1.resolveRef.call(self2, root2, baseId, $ref);
@@ -23049,8 +23051,8 @@ var require_ref = __commonJS((exports) => {
23049
23051
  return callValidate(schOrEnv);
23050
23052
  return inlineRefSchema(schOrEnv);
23051
23053
  function callRootRef() {
23052
- if (env2 === root2)
23053
- return callRef(cxt, validateName, env2, env2.$async);
23054
+ if (env === root2)
23055
+ return callRef(cxt, validateName, env, env.$async);
23054
23056
  const rootName = gen.scopeValue("root", { ref: root2 });
23055
23057
  return callRef(cxt, (0, codegen_1._)`${rootName}.validate`, root2, root2.$async);
23056
23058
  }
@@ -23080,14 +23082,14 @@ var require_ref = __commonJS((exports) => {
23080
23082
  exports.getValidate = getValidate;
23081
23083
  function callRef(cxt, v, sch, $async) {
23082
23084
  const { gen, it } = cxt;
23083
- const { allErrors, schemaEnv: env2, opts } = it;
23085
+ const { allErrors, schemaEnv: env, opts } = it;
23084
23086
  const passCxt = opts.passContext ? names_1.default.this : codegen_1.nil;
23085
23087
  if ($async)
23086
23088
  callAsyncRef();
23087
23089
  else
23088
23090
  callSyncRef();
23089
23091
  function callAsyncRef() {
23090
- if (!env2.$async)
23092
+ if (!env.$async)
23091
23093
  throw new Error("async schema referenced by sync schema");
23092
23094
  const valid = gen.let("valid");
23093
23095
  gen.try(() => {
@@ -25796,6 +25798,549 @@ var require_dist2 = __commonJS((exports, module) => {
25796
25798
  Object.defineProperty(exports, "__esModule", { value: true });
25797
25799
  exports.default = formatsPlugin;
25798
25800
  });
25801
+ var duckflux_schema_default;
25802
+ var init_duckflux_schema = __esm2(() => {
25803
+ duckflux_schema_default = {
25804
+ $schema: "https://json-schema.org/draft/2020-12/schema",
25805
+ $id: "https://raw.githubusercontent.com/duckflux/spec/main/duckflux.schema.json",
25806
+ title: "duckflux Workflow",
25807
+ description: "Schema for duckflux — a minimal, deterministic, runtime-agnostic workflow DSL.",
25808
+ type: "object",
25809
+ required: ["flow"],
25810
+ additionalProperties: false,
25811
+ properties: {
25812
+ id: {
25813
+ type: "string",
25814
+ description: "Unique identifier for the workflow."
25815
+ },
25816
+ name: {
25817
+ type: "string",
25818
+ description: "Human-readable workflow name."
25819
+ },
25820
+ version: {
25821
+ description: "Version identifier for the workflow definition.",
25822
+ oneOf: [
25823
+ { type: "string" },
25824
+ { type: "integer" }
25825
+ ]
25826
+ },
25827
+ defaults: {
25828
+ $ref: "#/$defs/defaults"
25829
+ },
25830
+ inputs: {
25831
+ $ref: "#/$defs/inputs"
25832
+ },
25833
+ participants: {
25834
+ type: "object",
25835
+ description: "Named steps that can be referenced in the flow.",
25836
+ additionalProperties: {
25837
+ $ref: "#/$defs/participant"
25838
+ },
25839
+ not: {
25840
+ anyOf: [
25841
+ { required: ["workflow"] },
25842
+ { required: ["execution"] },
25843
+ { required: ["input"] },
25844
+ { required: ["output"] },
25845
+ { required: ["env"] },
25846
+ { required: ["loop"] },
25847
+ { required: ["event"] }
25848
+ ]
25849
+ }
25850
+ },
25851
+ flow: {
25852
+ $ref: "#/$defs/flowSequence"
25853
+ },
25854
+ output: {
25855
+ $ref: "#/$defs/workflowOutput"
25856
+ }
25857
+ },
25858
+ $defs: {
25859
+ duration: {
25860
+ type: "string",
25861
+ pattern: "^[0-9]+(ms|s|m|h|d)$",
25862
+ description: "Duration string, e.g. '30s', '5m', '2h', '1d'."
25863
+ },
25864
+ celExpression: {
25865
+ type: "string",
25866
+ description: "A Google CEL expression."
25867
+ },
25868
+ defaults: {
25869
+ type: "object",
25870
+ description: "Global defaults applied to all participants.",
25871
+ additionalProperties: false,
25872
+ properties: {
25873
+ timeout: {
25874
+ $ref: "#/$defs/duration"
25875
+ },
25876
+ cwd: {
25877
+ type: "string",
25878
+ description: "Default working directory for exec participants. Supports CEL expressions."
25879
+ }
25880
+ }
25881
+ },
25882
+ inputs: {
25883
+ type: "object",
25884
+ description: "Workflow input parameters. Accessed in CEL as workflow.inputs.<field>. Each key is a parameter name. Value can be null (string, no schema), or a JSON Schema object for validation.",
25885
+ additionalProperties: {
25886
+ oneOf: [
25887
+ { type: "null" },
25888
+ { $ref: "#/$defs/inputField" }
25889
+ ]
25890
+ }
25891
+ },
25892
+ inputField: {
25893
+ type: "object",
25894
+ description: "JSON Schema field definition with optional 'required: true' shortcut.",
25895
+ properties: {
25896
+ type: {
25897
+ type: "string",
25898
+ enum: ["string", "integer", "number", "boolean", "array", "object"]
25899
+ },
25900
+ description: { type: "string" },
25901
+ default: {},
25902
+ required: { type: "boolean" },
25903
+ format: { type: "string" },
25904
+ enum: { type: "array" },
25905
+ minimum: { type: "number" },
25906
+ maximum: { type: "number" },
25907
+ minLength: { type: "integer" },
25908
+ maxLength: { type: "integer" },
25909
+ pattern: { type: "string" },
25910
+ items: {
25911
+ $ref: "#/$defs/inputField"
25912
+ }
25913
+ },
25914
+ additionalProperties: false
25915
+ },
25916
+ retryConfig: {
25917
+ type: "object",
25918
+ description: "Retry configuration. Only applies when onError is 'retry'.",
25919
+ additionalProperties: false,
25920
+ required: ["max"],
25921
+ properties: {
25922
+ max: {
25923
+ type: "integer",
25924
+ minimum: 1,
25925
+ description: "Maximum number of retry attempts."
25926
+ },
25927
+ backoff: {
25928
+ $ref: "#/$defs/duration",
25929
+ description: "Interval between attempts. Default: 0s."
25930
+ },
25931
+ factor: {
25932
+ type: "number",
25933
+ minimum: 1,
25934
+ description: "Backoff multiplier. Default: 1 (no escalation)."
25935
+ }
25936
+ }
25937
+ },
25938
+ onError: {
25939
+ type: "string",
25940
+ description: "Error handling strategy: 'fail' (default), 'skip', 'retry', or a participant name for redirect.",
25941
+ minLength: 1
25942
+ },
25943
+ onTimeout: {
25944
+ type: "string",
25945
+ description: "Timeout handling strategy: 'fail' (default), 'skip', or a participant name for redirect.",
25946
+ minLength: 1
25947
+ },
25948
+ participantInput: {
25949
+ description: "Explicit input mapping. String for direct passthrough, object for structured mapping. Values are CEL expressions. Merged with implicit I/O chain input at runtime.",
25950
+ oneOf: [
25951
+ { type: "string" },
25952
+ {
25953
+ type: "object",
25954
+ additionalProperties: {
25955
+ type: "string"
25956
+ }
25957
+ }
25958
+ ]
25959
+ },
25960
+ participantOutputSchema: {
25961
+ type: "object",
25962
+ description: "Output schema (JSON Schema fields). Opt-in validation.",
25963
+ additionalProperties: {
25964
+ $ref: "#/$defs/inputField"
25965
+ }
25966
+ },
25967
+ payload: {
25968
+ description: "Event payload. String (CEL expression) or object with CEL expressions as values.",
25969
+ oneOf: [
25970
+ { type: "string" },
25971
+ {
25972
+ type: "object",
25973
+ additionalProperties: {}
25974
+ }
25975
+ ]
25976
+ },
25977
+ participant: {
25978
+ type: "object",
25979
+ required: ["type"],
25980
+ description: "A named, reusable building block of the workflow.",
25981
+ properties: {
25982
+ type: {
25983
+ type: "string",
25984
+ enum: ["exec", "http", "mcp", "workflow", "emit"],
25985
+ description: "Participant type."
25986
+ },
25987
+ as: {
25988
+ type: "string",
25989
+ description: "Human-readable display name."
25990
+ },
25991
+ timeout: {
25992
+ $ref: "#/$defs/duration"
25993
+ },
25994
+ onError: {
25995
+ $ref: "#/$defs/onError"
25996
+ },
25997
+ retry: {
25998
+ $ref: "#/$defs/retryConfig"
25999
+ },
26000
+ input: {
26001
+ $ref: "#/$defs/participantInput"
26002
+ },
26003
+ output: {
26004
+ $ref: "#/$defs/participantOutputSchema"
26005
+ },
26006
+ run: {
26007
+ type: "string",
26008
+ description: "[exec] Shell command to execute."
26009
+ },
26010
+ cwd: {
26011
+ type: "string",
26012
+ description: "[exec] Working directory. Supports CEL expressions."
26013
+ },
26014
+ url: {
26015
+ type: "string",
26016
+ description: "[http] Target URL. Supports CEL expressions."
26017
+ },
26018
+ method: {
26019
+ type: "string",
26020
+ enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
26021
+ description: "[http] HTTP method."
26022
+ },
26023
+ headers: {
26024
+ type: "object",
26025
+ additionalProperties: { type: "string" },
26026
+ description: "[http] HTTP headers. Values support CEL expressions."
26027
+ },
26028
+ body: {
26029
+ description: "[http] Request body. String or object. Supports CEL expressions.",
26030
+ oneOf: [
26031
+ { type: "string" },
26032
+ { type: "object" }
26033
+ ]
26034
+ },
26035
+ path: {
26036
+ type: "string",
26037
+ description: "[workflow] Path to the sub-workflow YAML file."
26038
+ },
26039
+ server: {
26040
+ type: "string",
26041
+ description: "[mcp] MCP server identifier."
26042
+ },
26043
+ tool: {
26044
+ type: "string",
26045
+ description: "[mcp] MCP tool name to invoke."
26046
+ },
26047
+ event: {
26048
+ type: "string",
26049
+ description: "[emit] Event name to emit."
26050
+ },
26051
+ payload: {
26052
+ $ref: "#/$defs/payload",
26053
+ description: "[emit] Event payload. CEL expression or object with CEL expressions."
26054
+ },
26055
+ ack: {
26056
+ type: "boolean",
26057
+ description: "[emit] If true, wait for delivery acknowledgment. Default: false.",
26058
+ default: false
26059
+ }
26060
+ },
26061
+ additionalProperties: false
26062
+ },
26063
+ inlineParticipant: {
26064
+ type: "object",
26065
+ required: ["type"],
26066
+ description: "An inline participant definition in the flow. 'as' is optional — if omitted, the participant is anonymous and its output is only accessible via the implicit I/O chain.",
26067
+ properties: {
26068
+ as: {
26069
+ type: "string",
26070
+ description: "Optional name for inline participants. If provided, must be unique across all participant names. Enables output reference by name."
26071
+ },
26072
+ type: {
26073
+ type: "string",
26074
+ enum: ["exec", "http", "mcp", "workflow", "emit"],
26075
+ description: "Participant type."
26076
+ },
26077
+ when: {
26078
+ $ref: "#/$defs/celExpression",
26079
+ description: "Guard condition. If false, step is skipped."
26080
+ },
26081
+ timeout: {
26082
+ $ref: "#/$defs/duration"
26083
+ },
26084
+ onError: {
26085
+ $ref: "#/$defs/onError"
26086
+ },
26087
+ retry: {
26088
+ $ref: "#/$defs/retryConfig"
26089
+ },
26090
+ input: {
26091
+ $ref: "#/$defs/participantInput"
26092
+ },
26093
+ output: {
26094
+ $ref: "#/$defs/participantOutputSchema"
26095
+ },
26096
+ run: { type: "string" },
26097
+ cwd: { type: "string" },
26098
+ url: { type: "string" },
26099
+ method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
26100
+ headers: { type: "object", additionalProperties: { type: "string" } },
26101
+ body: { oneOf: [{ type: "string" }, { type: "object" }] },
26102
+ path: { type: "string" },
26103
+ server: { type: "string" },
26104
+ tool: { type: "string" },
26105
+ event: { type: "string" },
26106
+ payload: { $ref: "#/$defs/payload" },
26107
+ ack: { type: "boolean" }
26108
+ },
26109
+ additionalProperties: false
26110
+ },
26111
+ flowSequence: {
26112
+ type: "array",
26113
+ description: "Ordered list of flow steps. Each step's output is implicitly chained as input to the next step.",
26114
+ minItems: 1,
26115
+ items: {
26116
+ $ref: "#/$defs/flowStep"
26117
+ }
26118
+ },
26119
+ flowStep: {
26120
+ description: "A single step in the flow: participant reference (string), control construct, inline participant (named or anonymous), or participant override.",
26121
+ oneOf: [
26122
+ {
26123
+ type: "string",
26124
+ description: "Simple participant reference by name."
26125
+ },
26126
+ { $ref: "#/$defs/loopStep" },
26127
+ { $ref: "#/$defs/parallelStep" },
26128
+ { $ref: "#/$defs/ifStep" },
26129
+ { $ref: "#/$defs/setStep" },
26130
+ { $ref: "#/$defs/waitStep" },
26131
+ { $ref: "#/$defs/inlineParticipant" },
26132
+ { $ref: "#/$defs/participantOverrideStep" }
26133
+ ]
26134
+ },
26135
+ loopStep: {
26136
+ type: "object",
26137
+ required: ["loop"],
26138
+ additionalProperties: false,
26139
+ properties: {
26140
+ loop: {
26141
+ type: "object",
26142
+ additionalProperties: false,
26143
+ required: ["steps"],
26144
+ properties: {
26145
+ as: {
26146
+ type: "string",
26147
+ description: "Renames the loop context variable. Access as <as>.index, <as>.iteration, etc. instead of loop.*"
26148
+ },
26149
+ until: {
26150
+ $ref: "#/$defs/celExpression",
26151
+ description: "CEL condition to break out of the loop."
26152
+ },
26153
+ max: {
26154
+ description: "Maximum iterations. Integer or CEL expression.",
26155
+ oneOf: [
26156
+ { type: "integer", minimum: 1 },
26157
+ { type: "string" }
26158
+ ]
26159
+ },
26160
+ steps: {
26161
+ $ref: "#/$defs/flowSequence"
26162
+ }
26163
+ },
26164
+ anyOf: [
26165
+ { required: ["until"] },
26166
+ { required: ["max"] }
26167
+ ]
26168
+ }
26169
+ }
26170
+ },
26171
+ parallelStep: {
26172
+ type: "object",
26173
+ required: ["parallel"],
26174
+ additionalProperties: false,
26175
+ properties: {
26176
+ parallel: {
26177
+ $ref: "#/$defs/flowSequence",
26178
+ description: "Steps to run concurrently. The chained output after a parallel block is an array of all branch outputs in declaration order."
26179
+ }
26180
+ }
26181
+ },
26182
+ ifStep: {
26183
+ type: "object",
26184
+ required: ["if"],
26185
+ additionalProperties: false,
26186
+ properties: {
26187
+ if: {
26188
+ type: "object",
26189
+ required: ["condition", "then"],
26190
+ additionalProperties: false,
26191
+ properties: {
26192
+ condition: {
26193
+ $ref: "#/$defs/celExpression",
26194
+ description: "CEL expression that determines which branch to take."
26195
+ },
26196
+ then: {
26197
+ $ref: "#/$defs/flowSequence"
26198
+ },
26199
+ else: {
26200
+ $ref: "#/$defs/flowSequence"
26201
+ }
26202
+ }
26203
+ }
26204
+ }
26205
+ },
26206
+ setStep: {
26207
+ type: "object",
26208
+ required: ["set"],
26209
+ additionalProperties: false,
26210
+ properties: {
26211
+ set: {
26212
+ type: "object",
26213
+ description: "Writes values into execution.context. Each key becomes execution.context.<key>. Values are CEL expressions.",
26214
+ minProperties: 1,
26215
+ additionalProperties: {
26216
+ $ref: "#/$defs/celExpression"
26217
+ },
26218
+ not: {
26219
+ anyOf: [
26220
+ { required: ["workflow"] },
26221
+ { required: ["execution"] },
26222
+ { required: ["input"] },
26223
+ { required: ["output"] },
26224
+ { required: ["env"] },
26225
+ { required: ["loop"] },
26226
+ { required: ["event"] }
26227
+ ]
26228
+ }
26229
+ }
26230
+ }
26231
+ },
26232
+ waitStep: {
26233
+ type: "object",
26234
+ required: ["wait"],
26235
+ additionalProperties: false,
26236
+ properties: {
26237
+ wait: {
26238
+ type: "object",
26239
+ additionalProperties: false,
26240
+ properties: {
26241
+ event: {
26242
+ type: "string",
26243
+ description: "Event name to wait for (event mode)."
26244
+ },
26245
+ match: {
26246
+ $ref: "#/$defs/celExpression",
26247
+ description: "CEL condition to match against event payload. 'event' variable contains the payload."
26248
+ },
26249
+ until: {
26250
+ $ref: "#/$defs/celExpression",
26251
+ description: "CEL condition to wait for (polling mode)."
26252
+ },
26253
+ poll: {
26254
+ $ref: "#/$defs/duration",
26255
+ description: "Polling interval for 'until' condition. Default: runtime decides."
26256
+ },
26257
+ timeout: {
26258
+ $ref: "#/$defs/duration",
26259
+ description: "Maximum wait time."
26260
+ },
26261
+ onTimeout: {
26262
+ $ref: "#/$defs/onTimeout",
26263
+ description: "Strategy when timeout is reached: 'fail' (default), 'skip', or participant name."
26264
+ }
26265
+ }
26266
+ }
26267
+ }
26268
+ },
26269
+ participantOverrideStep: {
26270
+ type: "object",
26271
+ description: "A participant reference with flow-level overrides (timeout, onError, when, input, workflow, etc.).",
26272
+ minProperties: 1,
26273
+ maxProperties: 1,
26274
+ not: {
26275
+ anyOf: [
26276
+ { required: ["loop"] },
26277
+ { required: ["parallel"] },
26278
+ { required: ["if"] },
26279
+ { required: ["wait"] },
26280
+ { required: ["set"] },
26281
+ { required: ["type"] }
26282
+ ]
26283
+ },
26284
+ additionalProperties: {
26285
+ type: "object",
26286
+ properties: {
26287
+ when: {
26288
+ $ref: "#/$defs/celExpression",
26289
+ description: "Guard condition. If false, step is skipped."
26290
+ },
26291
+ timeout: {
26292
+ $ref: "#/$defs/duration"
26293
+ },
26294
+ onError: {
26295
+ $ref: "#/$defs/onError"
26296
+ },
26297
+ retry: {
26298
+ $ref: "#/$defs/retryConfig"
26299
+ },
26300
+ input: {
26301
+ $ref: "#/$defs/participantInput"
26302
+ },
26303
+ workflow: {
26304
+ type: "string",
26305
+ description: "Inline sub-workflow path (alternative to defining as participant)."
26306
+ }
26307
+ },
26308
+ additionalProperties: false
26309
+ }
26310
+ },
26311
+ workflowOutput: {
26312
+ description: "Workflow output. Accessed in CEL as workflow.output. String (single CEL expression), object (structured mapping), or object with schema+map.",
26313
+ oneOf: [
26314
+ {
26315
+ type: "string",
26316
+ description: "Single CEL expression mapping."
26317
+ },
26318
+ {
26319
+ type: "object",
26320
+ description: "Structured output mapping (key → CEL expression) or schema+map.",
26321
+ properties: {
26322
+ schema: {
26323
+ type: "object",
26324
+ additionalProperties: {
26325
+ $ref: "#/$defs/inputField"
26326
+ }
26327
+ },
26328
+ map: {
26329
+ type: "object",
26330
+ additionalProperties: {
26331
+ type: "string"
26332
+ }
26333
+ }
26334
+ },
26335
+ additionalProperties: {
26336
+ type: "string"
26337
+ }
26338
+ }
26339
+ ]
26340
+ }
26341
+ }
26342
+ };
26343
+ });
25799
26344
  function validateSchema(workflow) {
25800
26345
  const valid = validate(workflow);
25801
26346
  if (valid) {
@@ -25809,15 +26354,14 @@ function validateSchema(workflow) {
25809
26354
  }
25810
26355
  var import__2020;
25811
26356
  var import_ajv_formats;
25812
- var rawSchema;
25813
26357
  var schema;
25814
26358
  var ajv;
25815
26359
  var validate;
25816
26360
  var init_schema = __esm2(() => {
26361
+ init_duckflux_schema();
25817
26362
  import__2020 = __toESM(require_2020(), 1);
25818
26363
  import_ajv_formats = __toESM(require_dist2(), 1);
25819
- rawSchema = readFileSync(new URL("./schema/duckflux.schema.json", import.meta.url), "utf-8");
25820
- schema = JSON.parse(rawSchema);
26364
+ schema = { ...duckflux_schema_default };
25821
26365
  delete schema.$schema;
25822
26366
  ajv = new import__2020.default({ allErrors: true, strict: false });
25823
26367
  import_ajv_formats.default(ajv);
@@ -26219,6 +26763,234 @@ var init_validate = __esm2(() => {
26219
26763
  RESERVED_NAMES = new Set(["workflow", "execution", "input", "output", "env", "loop", "event"]);
26220
26764
  BUILTIN_ONERROR = new Set(["fail", "skip", "retry"]);
26221
26765
  });
26766
+ var exports_json = {};
26767
+ __export2(exports_json, {
26768
+ JsonTraceWriter: () => JsonTraceWriter
26769
+ });
26770
+
26771
+ class JsonTraceWriter {
26772
+ dir;
26773
+ filePath = "";
26774
+ trace = {
26775
+ execution: {
26776
+ id: "",
26777
+ startedAt: "",
26778
+ finishedAt: "",
26779
+ duration: 0,
26780
+ status: "running",
26781
+ inputs: null,
26782
+ output: null
26783
+ },
26784
+ steps: []
26785
+ };
26786
+ constructor(dir) {
26787
+ this.dir = dir;
26788
+ }
26789
+ async open(meta) {
26790
+ this.filePath = join(this.dir, `${meta.id}.json`);
26791
+ this.trace = {
26792
+ execution: {
26793
+ id: meta.id,
26794
+ workflowId: meta.workflowId,
26795
+ workflowName: meta.workflowName,
26796
+ workflowVersion: meta.workflowVersion,
26797
+ startedAt: meta.startedAt,
26798
+ finishedAt: "",
26799
+ duration: 0,
26800
+ status: "running",
26801
+ inputs: meta.inputs,
26802
+ output: null
26803
+ },
26804
+ steps: []
26805
+ };
26806
+ await this.flush();
26807
+ }
26808
+ writeStep(step) {
26809
+ this.trace.steps.push(step);
26810
+ this.flushSync();
26811
+ }
26812
+ async finalize(meta) {
26813
+ this.trace.execution.status = meta.status;
26814
+ this.trace.execution.output = meta.output;
26815
+ this.trace.execution.finishedAt = meta.finishedAt;
26816
+ this.trace.execution.duration = meta.duration;
26817
+ await this.flush();
26818
+ }
26819
+ flushSync() {
26820
+ this.flush().catch(() => {});
26821
+ }
26822
+ async flush() {
26823
+ if (!this.filePath)
26824
+ return;
26825
+ await writeFile(this.filePath, JSON.stringify(this.trace, null, 2), "utf-8");
26826
+ }
26827
+ }
26828
+ var exports_txt = {};
26829
+ __export2(exports_txt, {
26830
+ TxtTraceWriter: () => TxtTraceWriter
26831
+ });
26832
+ function serializeValue(value) {
26833
+ if (value === undefined || value === null)
26834
+ return "none";
26835
+ if (typeof value === "string")
26836
+ return value;
26837
+ return JSON.stringify(value, null, 2);
26838
+ }
26839
+ function formatStep(step) {
26840
+ const lines = [
26841
+ `## [${step.seq}] ${step.name} (${step.type})`,
26842
+ `startedAt: ${step.startedAt}`
26843
+ ];
26844
+ if (step.finishedAt)
26845
+ lines.push(`finishedAt: ${step.finishedAt}`);
26846
+ if (step.duration !== undefined)
26847
+ lines.push(`duration: ${step.duration}ms`);
26848
+ lines.push(`status: ${step.status}`);
26849
+ if (step.loopIndex !== undefined)
26850
+ lines.push(`loopIndex: ${step.loopIndex}`);
26851
+ if (step.retries !== undefined && step.retries > 0)
26852
+ lines.push(`retries: ${step.retries}`);
26853
+ lines.push(`input: ${serializeValue(step.input)}`);
26854
+ lines.push(`output: ${serializeValue(step.output)}`);
26855
+ if (step.error)
26856
+ lines.push(`error: ${step.error}`);
26857
+ lines.push("");
26858
+ return lines.join(`
26859
+ `);
26860
+ }
26861
+
26862
+ class TxtTraceWriter {
26863
+ dir;
26864
+ filePath = "";
26865
+ constructor(dir) {
26866
+ this.dir = dir;
26867
+ }
26868
+ async open(meta) {
26869
+ this.filePath = join2(this.dir, `${meta.id}.txt`);
26870
+ const versionStr = meta.workflowVersion !== undefined ? ` (v${meta.workflowVersion})` : "";
26871
+ const workflowLabel = meta.workflowName ?? meta.workflowId ?? "unnamed";
26872
+ const header = [
26873
+ "# execution",
26874
+ `id: ${meta.id}`,
26875
+ `workflow: ${workflowLabel}${versionStr}`,
26876
+ `startedAt: ${meta.startedAt}`,
26877
+ "status: running",
26878
+ "",
26879
+ "# inputs",
26880
+ serializeValue(meta.inputs),
26881
+ "",
26882
+ "# steps",
26883
+ ""
26884
+ ].join(`
26885
+ `);
26886
+ await writeFile2(this.filePath, header, "utf-8");
26887
+ }
26888
+ async writeStep(step) {
26889
+ if (!this.filePath)
26890
+ return;
26891
+ await appendFile(this.filePath, formatStep(step), "utf-8");
26892
+ }
26893
+ async finalize(meta) {
26894
+ if (!this.filePath)
26895
+ return;
26896
+ const outputSection = [
26897
+ "# output",
26898
+ serializeValue(meta.output),
26899
+ ""
26900
+ ].join(`
26901
+ `);
26902
+ await appendFile(this.filePath, outputSection, "utf-8");
26903
+ const content = await readFile2(this.filePath, "utf-8");
26904
+ const updated = content.replace(/^status: running$/m, `status: ${meta.status}
26905
+ finishedAt: ${meta.finishedAt}
26906
+ duration: ${meta.duration}ms`);
26907
+ await writeFile2(this.filePath, updated, "utf-8");
26908
+ }
26909
+ }
26910
+ var exports_sqlite = {};
26911
+ __export2(exports_sqlite, {
26912
+ SqliteTraceWriter: () => SqliteTraceWriter
26913
+ });
26914
+ function toJson(value) {
26915
+ if (value === undefined || value === null)
26916
+ return null;
26917
+ if (typeof value === "string")
26918
+ return value;
26919
+ return JSON.stringify(value);
26920
+ }
26921
+
26922
+ class SqliteTraceWriter {
26923
+ dir;
26924
+ db = null;
26925
+ executionId = "";
26926
+ constructor(dir) {
26927
+ this.dir = dir;
26928
+ }
26929
+ async open(meta) {
26930
+ this.executionId = meta.id;
26931
+ const filePath = join3(this.dir, `${meta.id}.sqlite`);
26932
+ const { Database } = await import("bun:sqlite");
26933
+ this.db = new Database(filePath);
26934
+ this.db.exec(`
26935
+ CREATE TABLE IF NOT EXISTS executions (
26936
+ id TEXT PRIMARY KEY,
26937
+ workflow_id TEXT,
26938
+ workflow_name TEXT,
26939
+ workflow_version TEXT,
26940
+ started_at TEXT NOT NULL,
26941
+ finished_at TEXT,
26942
+ duration_ms INTEGER,
26943
+ status TEXT NOT NULL,
26944
+ inputs TEXT,
26945
+ output TEXT
26946
+ );
26947
+
26948
+ CREATE TABLE IF NOT EXISTS steps (
26949
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
26950
+ execution_id TEXT NOT NULL REFERENCES executions(id),
26951
+ seq INTEGER NOT NULL,
26952
+ name TEXT NOT NULL,
26953
+ type TEXT NOT NULL,
26954
+ started_at TEXT,
26955
+ finished_at TEXT,
26956
+ duration_ms INTEGER,
26957
+ status TEXT NOT NULL,
26958
+ input TEXT,
26959
+ output TEXT,
26960
+ error TEXT,
26961
+ retries INTEGER,
26962
+ loop_index INTEGER
26963
+ );
26964
+ `);
26965
+ const insert = this.db.prepare(`
26966
+ INSERT INTO executions (id, workflow_id, workflow_name, workflow_version, started_at, status, inputs)
26967
+ VALUES (?, ?, ?, ?, ?, 'running', ?)
26968
+ `);
26969
+ insert.run(meta.id, meta.workflowId ?? null, meta.workflowName ?? null, meta.workflowVersion !== undefined ? String(meta.workflowVersion) : null, meta.startedAt, toJson(meta.inputs));
26970
+ }
26971
+ writeStep(step) {
26972
+ if (!this.db)
26973
+ return;
26974
+ const insert = this.db.prepare(`
26975
+ INSERT INTO steps
26976
+ (execution_id, seq, name, type, started_at, finished_at, duration_ms, status, input, output, error, retries, loop_index)
26977
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
26978
+ `);
26979
+ 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);
26980
+ }
26981
+ async finalize(meta) {
26982
+ if (!this.db)
26983
+ return;
26984
+ const update = this.db.prepare(`
26985
+ UPDATE executions
26986
+ SET status = ?, output = ?, finished_at = ?, duration_ms = ?
26987
+ WHERE id = ?
26988
+ `);
26989
+ update.run(meta.status, toJson(meta.output), meta.finishedAt, Math.round(meta.duration), this.executionId);
26990
+ this.db.close();
26991
+ this.db = null;
26992
+ }
26993
+ }
26222
26994
  function sleep(ms) {
26223
26995
  return new Promise((resolve22) => setTimeout(resolve22, ms));
26224
26996
  }
@@ -26345,13 +27117,13 @@ function isMap3(value) {
26345
27117
  return typeof value === "object" && value !== null && !Array.isArray(value);
26346
27118
  }
26347
27119
  function mapToEnvVars(input) {
26348
- const env2 = {};
27120
+ const env = {};
26349
27121
  for (const [key, value] of Object.entries(input)) {
26350
- env2[key] = typeof value === "string" ? value : String(value);
27122
+ env[key] = typeof value === "string" ? value : String(value);
26351
27123
  }
26352
- return env2;
27124
+ return env;
26353
27125
  }
26354
- async function executeExec(participant, input, env2 = {}, signal) {
27126
+ async function executeExec(participant, input, env = {}, signal) {
26355
27127
  const command = participant.run ?? "";
26356
27128
  const participantEnv = participant.env ?? {};
26357
27129
  const cwd = participant.cwd ?? process.cwd();
@@ -26362,7 +27134,7 @@ async function executeExec(participant, input, env2 = {}, signal) {
26362
27134
  return new Promise((resolve22) => {
26363
27135
  try {
26364
27136
  const proc = spawn("sh", ["-c", command], {
26365
- env: { ...process.env, ...env2, ...participantEnv, ...inputEnvVars },
27137
+ env: { ...process.env, ...env, ...participantEnv, ...inputEnvVars },
26366
27138
  cwd,
26367
27139
  stdio: ["pipe", "pipe", "pipe"]
26368
27140
  });
@@ -26510,7 +27282,7 @@ async function executeHttp(participant, input) {
26510
27282
  };
26511
27283
  }
26512
27284
  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.");
27285
+ throw new Error(`mcp participant is not yet implemented (server: ${participant.server ?? "unspecified"}, tool: ${participant.tool ?? "unspecified"}). Use onError to handle this gracefully.`);
26514
27286
  }
26515
27287
  function toWorkflowInputs(input) {
26516
27288
  if (input && typeof input === "object" && !Array.isArray(input)) {
@@ -26555,12 +27327,12 @@ var init_workflow = __esm2(() => {
26555
27327
  init_schema();
26556
27328
  init_validate();
26557
27329
  });
26558
- async function executeParticipant(participant, input, env2 = {}, basePath, engineExecutor, hub, celContext, ancestorPaths) {
27330
+ async function executeParticipant(participant, input, env = {}, basePath, engineExecutor, hub, celContext, ancestorPaths) {
26559
27331
  const executor = executors[participant.type];
26560
27332
  if (!executor) {
26561
27333
  throw new Error(`participant type '${participant.type}' is not yet implemented`);
26562
27334
  }
26563
- return executor(participant, input, env2, basePath, engineExecutor, hub, celContext, ancestorPaths);
27335
+ return executor(participant, input, env, basePath, engineExecutor, hub, celContext, ancestorPaths);
26564
27336
  }
26565
27337
  var executors;
26566
27338
  var init_participant = __esm2(() => {
@@ -26568,7 +27340,7 @@ var init_participant = __esm2(() => {
26568
27340
  init_exec();
26569
27341
  init_workflow();
26570
27342
  executors = {
26571
- exec: async (participant, input, env2) => executeExec(participant, input, env2),
27343
+ exec: async (participant, input, env) => executeExec(participant, input, env),
26572
27344
  http: async (participant, input) => executeHttp(participant, input),
26573
27345
  workflow: async (participant, input, _env, basePath, engineExecutor, _hub, _celContext, ancestorPaths) => {
26574
27346
  if (!basePath) {
@@ -26796,6 +27568,10 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26796
27568
  output: "",
26797
27569
  duration: 0
26798
27570
  });
27571
+ const loopIndex2 = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27572
+ const skippedSeq = state.tracer?.startStep(stepName, participant.type, undefined, loopIndex2);
27573
+ if (skippedSeq !== undefined)
27574
+ state.tracer?.endStep(skippedSeq, "skipped");
26799
27575
  }
26800
27576
  return chain;
26801
27577
  }
@@ -26804,6 +27580,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26804
27580
  const overrideInput = override?.input !== undefined ? resolveParticipantInput(override.input, state) : undefined;
26805
27581
  const mergedWithBase = mergeChainedInput(chain, baseInput);
26806
27582
  const mergedInput = mergeChainedInput(mergedWithBase, overrideInput);
27583
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27584
+ const traceSeq = state.tracer?.startStep(stepName ?? "<anonymous>", participant.type, mergedInput, loopIndex);
26807
27585
  state.currentInput = mergedInput;
26808
27586
  const strategy = resolveErrorStrategy(override ?? null, participant, workflow.defaults ?? null);
26809
27587
  const timeoutMs = resolveTimeout(override ?? null, participant, workflow.defaults ?? null);
@@ -26881,6 +27659,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26881
27659
  }
26882
27660
  const outputValue = result.parsedOutput ?? result.output;
26883
27661
  state.currentOutput = outputValue;
27662
+ if (traceSeq !== undefined)
27663
+ state.tracer?.endStep(traceSeq, result.status, outputValue, undefined, retries);
26884
27664
  return outputValue;
26885
27665
  } catch (error) {
26886
27666
  const message = String(error?.message ?? error);
@@ -26906,6 +27686,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26906
27686
  if (stepName) {
26907
27687
  state.setResult(stepName, skipResult);
26908
27688
  }
27689
+ if (traceSeq !== undefined)
27690
+ state.tracer?.endStep(traceSeq, "skipped", undefined, message);
26909
27691
  return chain;
26910
27692
  }
26911
27693
  if (strategy !== "fail" && strategy !== "retry") {
@@ -26924,6 +27706,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26924
27706
  ...httpMeta
26925
27707
  });
26926
27708
  }
27709
+ if (traceSeq !== undefined)
27710
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
26927
27711
  const fallbackResult = await executeStep(workflow, state, fallbackName, basePath, engineExecutor, [...fallbackStack, stepName ?? "<anonymous>"], chain, hub);
26928
27712
  return fallbackResult;
26929
27713
  }
@@ -26938,6 +27722,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
26938
27722
  ...httpMeta
26939
27723
  });
26940
27724
  }
27725
+ if (traceSeq !== undefined)
27726
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
26941
27727
  throw error;
26942
27728
  }
26943
27729
  }
@@ -26960,7 +27746,7 @@ __export2(exports_wait, {
26960
27746
  executeWait: () => executeWait
26961
27747
  });
26962
27748
  function sleep2(ms) {
26963
- return new Promise((resolve32) => setTimeout(resolve32, ms));
27749
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
26964
27750
  }
26965
27751
  async function executeWait(state, waitDef, chain, hub, signal) {
26966
27752
  const timeoutMs = waitDef.timeout ? parseDuration(waitDef.timeout) : undefined;
@@ -27046,21 +27832,38 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27046
27832
  }
27047
27833
  const obj = step;
27048
27834
  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);
27835
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27836
+ const traceSeq = state.tracer?.startStep("wait", "wait", undefined, loopIndex);
27837
+ try {
27838
+ const { executeWait: executeWait2 } = await Promise.resolve().then(() => (init_wait(), exports_wait));
27839
+ const result = await executeWait2(state, obj.wait, chain, hub, signal);
27840
+ if (traceSeq !== undefined)
27841
+ state.tracer?.endStep(traceSeq, "success", result);
27842
+ return result;
27843
+ } catch (err) {
27844
+ if (traceSeq !== undefined)
27845
+ state.tracer?.endStep(traceSeq, "failure", undefined, String(err?.message ?? err));
27846
+ throw err;
27847
+ }
27051
27848
  }
27052
27849
  if ("set" in obj && Object.keys(obj).length === 1) {
27053
27850
  const setDef = obj.set;
27054
27851
  const ctx = state.toCelContext();
27852
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27853
+ const traceSeq = state.tracer?.startStep("set", "set", setDef, loopIndex);
27055
27854
  if (!state.executionMeta.context) {
27056
27855
  state.executionMeta.context = {};
27057
27856
  }
27058
27857
  for (const [key, expr] of Object.entries(setDef)) {
27059
27858
  if (RESERVED_SET_KEYS.has(key)) {
27859
+ if (traceSeq !== undefined)
27860
+ state.tracer?.endStep(traceSeq, "failure", undefined, `set key '${key}' uses a reserved name`);
27060
27861
  throw new Error(`set key '${key}' uses a reserved name`);
27061
27862
  }
27062
27863
  state.executionMeta.context[key] = evaluateCel(expr, ctx);
27063
27864
  }
27865
+ if (traceSeq !== undefined)
27866
+ state.tracer?.endStep(traceSeq, "success", state.executionMeta.context);
27064
27867
  return chain;
27065
27868
  }
27066
27869
  if ("loop" in obj && Object.keys(obj).length === 1) {
@@ -27077,6 +27880,8 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27077
27880
  maxIterations = loopDef.max ?? Number.POSITIVE_INFINITY;
27078
27881
  }
27079
27882
  const hasMax = loopDef.max !== undefined;
27883
+ const outerLoopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27884
+ const loopTraceSeq = state.tracer?.startStep(loopAs ?? "loop", "loop", undefined, outerLoopIndex);
27080
27885
  state.pushLoop(loopAs);
27081
27886
  let loopChain = chain;
27082
27887
  try {
@@ -27100,6 +27905,12 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27100
27905
  iterations += 1;
27101
27906
  state.incrementLoop();
27102
27907
  }
27908
+ if (loopTraceSeq !== undefined)
27909
+ state.tracer?.endStep(loopTraceSeq, "success", loopChain);
27910
+ } catch (err) {
27911
+ if (loopTraceSeq !== undefined)
27912
+ state.tracer?.endStep(loopTraceSeq, "failure", undefined, String(err?.message ?? err));
27913
+ throw err;
27103
27914
  } finally {
27104
27915
  state.popLoop();
27105
27916
  }
@@ -27108,29 +27919,54 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
27108
27919
  if ("parallel" in obj && Object.keys(obj).length === 1) {
27109
27920
  const parallelSteps = obj.parallel;
27110
27921
  const controller = new AbortController;
27922
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27923
+ const parallelTraceSeq = state.tracer?.startStep("parallel", "parallel", undefined, loopIndex);
27111
27924
  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;
27925
+ try {
27926
+ const results = await Promise.all(parallelSteps.map(async (parallelStep) => {
27927
+ try {
27928
+ return await executeControlStep(workflow, state, parallelStep, basePath, engineExecutor, chain, hub, branchSignal);
27929
+ } catch (error) {
27930
+ controller.abort();
27931
+ throw error;
27932
+ }
27933
+ }));
27934
+ if (parallelTraceSeq !== undefined)
27935
+ state.tracer?.endStep(parallelTraceSeq, "success", results);
27936
+ return results;
27937
+ } catch (err) {
27938
+ if (parallelTraceSeq !== undefined)
27939
+ state.tracer?.endStep(parallelTraceSeq, "failure", undefined, String(err?.message ?? err));
27940
+ throw err;
27941
+ }
27121
27942
  }
27122
27943
  if ("if" in obj && Object.keys(obj).length === 1) {
27123
27944
  const ifDef = obj.if;
27124
27945
  const condition = evaluateCel(ifDef.condition, state.toCelContext());
27946
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
27947
+ const ifTraceSeq = state.tracer?.startStep("if", "if", { condition: ifDef.condition }, loopIndex);
27125
27948
  if (typeof condition !== "boolean") {
27949
+ if (ifTraceSeq !== undefined)
27950
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, `if.condition must evaluate to boolean, got ${typeof condition}`);
27126
27951
  throw new Error(`if.condition must evaluate to boolean, got ${typeof condition}`);
27127
27952
  }
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);
27953
+ try {
27954
+ let result;
27955
+ if (condition) {
27956
+ result = await executeSequential(workflow, state, ifDef.then, basePath, engineExecutor, chain, hub, signal);
27957
+ } else if (ifDef.else) {
27958
+ result = await executeSequential(workflow, state, ifDef.else, basePath, engineExecutor, chain, hub, signal);
27959
+ } else {
27960
+ result = chain;
27961
+ }
27962
+ if (ifTraceSeq !== undefined)
27963
+ state.tracer?.endStep(ifTraceSeq, "success", result);
27964
+ return result;
27965
+ } catch (err) {
27966
+ if (ifTraceSeq !== undefined)
27967
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, String(err?.message ?? err));
27968
+ throw err;
27132
27969
  }
27133
- return chain;
27134
27970
  }
27135
27971
  return executeStep(workflow, state, step, basePath, engineExecutor, [], chain, hub, signal);
27136
27972
  }
@@ -27140,7 +27976,92 @@ var init_control = __esm2(() => {
27140
27976
  init_sequential();
27141
27977
  RESERVED_SET_KEYS = new Set(["workflow", "execution", "input", "output", "env", "loop", "event"]);
27142
27978
  });
27143
- init_cel();
27979
+
27980
+ class MemoryHub {
27981
+ listeners = new Map;
27982
+ buffer = new Map;
27983
+ closed = false;
27984
+ async publish(event, payload) {
27985
+ if (this.closed)
27986
+ throw new Error("hub is closed");
27987
+ const envelope = { name: event, payload };
27988
+ let buf = this.buffer.get(event);
27989
+ if (!buf) {
27990
+ buf = [];
27991
+ this.buffer.set(event, buf);
27992
+ }
27993
+ buf.push(envelope);
27994
+ const listeners = this.listeners.get(event);
27995
+ if (listeners) {
27996
+ for (const listener of listeners) {
27997
+ listener(envelope);
27998
+ }
27999
+ }
28000
+ }
28001
+ async publishAndWaitAck(event, payload, _timeoutMs) {
28002
+ await this.publish(event, payload);
28003
+ }
28004
+ async* subscribe(event, signal) {
28005
+ if (this.closed)
28006
+ return;
28007
+ const buffered = this.buffer.get(event);
28008
+ if (buffered) {
28009
+ for (const envelope of buffered) {
28010
+ if (signal?.aborted)
28011
+ return;
28012
+ yield envelope;
28013
+ }
28014
+ }
28015
+ const queue = [];
28016
+ let resolve3 = null;
28017
+ const listener = (envelope) => {
28018
+ queue.push(envelope);
28019
+ if (resolve3) {
28020
+ resolve3();
28021
+ resolve3 = null;
28022
+ }
28023
+ };
28024
+ let listeners = this.listeners.get(event);
28025
+ if (!listeners) {
28026
+ listeners = new Set;
28027
+ this.listeners.set(event, listeners);
28028
+ }
28029
+ listeners.add(listener);
28030
+ const onAbort = () => {
28031
+ listeners.delete(listener);
28032
+ if (resolve3) {
28033
+ resolve3();
28034
+ resolve3 = null;
28035
+ }
28036
+ };
28037
+ if (signal) {
28038
+ signal.addEventListener("abort", onAbort);
28039
+ }
28040
+ try {
28041
+ while (!this.closed && !signal?.aborted) {
28042
+ if (queue.length > 0) {
28043
+ yield queue.shift();
28044
+ } else {
28045
+ await new Promise((r) => {
28046
+ resolve3 = r;
28047
+ });
28048
+ }
28049
+ }
28050
+ } finally {
28051
+ listeners.delete(listener);
28052
+ if (signal) {
28053
+ signal.removeEventListener("abort", onAbort);
28054
+ }
28055
+ }
28056
+ }
28057
+ async close() {
28058
+ this.closed = true;
28059
+ for (const listeners of this.listeners.values()) {
28060
+ listeners.clear();
28061
+ }
28062
+ this.listeners.clear();
28063
+ }
28064
+ }
27144
28065
  init_parser3();
27145
28066
  init_schema();
27146
28067
  init_validate();
@@ -27304,357 +28225,219 @@ function validateInputs(inputDefs, provided) {
27304
28225
  resolved
27305
28226
  };
27306
28227
  }
27307
- init_control();
28228
+ init_cel();
28229
+ init_cel();
28230
+ init_parser3();
28231
+ init_schema();
28232
+ init_validate();
27308
28233
 
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(),
28234
+ class TraceCollector {
28235
+ openSteps = new Map;
28236
+ seq = 0;
28237
+ truncateAt;
28238
+ writer;
28239
+ constructor(truncateAt = 1e6) {
28240
+ this.truncateAt = truncateAt;
28241
+ }
28242
+ startStep(name, type3, input, loopIndex) {
28243
+ this.seq += 1;
28244
+ this.openSteps.set(this.seq, {
28245
+ name,
28246
+ type: type3,
27327
28247
  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
28248
+ startMs: performance.now(),
28249
+ input: input !== undefined ? this.truncate(input) : undefined,
28250
+ loopIndex
27467
28251
  });
27468
- };
27469
- let chain;
27470
- for (const step of workflow.flow) {
27471
- chain = await executeControlStep(workflow, state, step, basePath, engineExecutor, chain, options.hub);
27472
- }
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;
28252
+ return this.seq;
27481
28253
  }
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)}`);
28254
+ endStep(seq, status, output, error, retries) {
28255
+ const open = this.openSteps.get(seq);
28256
+ if (!open)
28257
+ return;
28258
+ this.openSteps.delete(seq);
28259
+ const finishedAt = new Date().toISOString();
28260
+ const duration = Math.max(0, performance.now() - open.startMs);
28261
+ const step = {
28262
+ seq,
28263
+ name: open.name,
28264
+ type: open.type,
28265
+ startedAt: open.startedAt,
28266
+ finishedAt,
28267
+ duration: Math.round(duration),
28268
+ status,
28269
+ ...open.input !== undefined ? { input: open.input } : {},
28270
+ ...output !== undefined ? { output: this.truncate(output) } : {},
28271
+ ...error !== undefined ? { error } : {},
28272
+ ...retries !== undefined && retries > 0 ? { retries } : {},
28273
+ ...open.loopIndex !== undefined ? { loopIndex: open.loopIndex } : {}
28274
+ };
28275
+ this.writer?.writeStep(step);
27497
28276
  }
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)}`);
28277
+ truncate(value) {
28278
+ if (value == null)
28279
+ return value;
28280
+ const str = typeof value === "string" ? value : JSON.stringify(value);
28281
+ const bytes = new TextEncoder().encode(str);
28282
+ if (bytes.length <= this.truncateAt)
28283
+ return value;
28284
+ const cut = new TextDecoder().decode(bytes.slice(0, this.truncateAt));
28285
+ return cut + "...[truncated]";
27502
28286
  }
27503
- return executeWorkflow(workflow, inputs, workflowBasePath, options);
27504
28287
  }
27505
28288
  init_control();
27506
28289
  init_sequential();
27507
- init_errors();
27508
- init_timeout();
27509
28290
  init_wait();
28291
+ init_emit();
27510
28292
 
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");
28293
+ // src/lint.ts
28294
+ function collectLintWarnings(workflow) {
28295
+ const warnings = [];
28296
+ const participants = workflow.participants ?? {};
28297
+ collectFlowWarnings(workflow.flow ?? [], participants, warnings);
28298
+ return warnings;
28299
+ }
28300
+ function collectFlowWarnings(flow, participants, warnings, basePath = "flow") {
28301
+ for (const [index, step] of flow.entries()) {
28302
+ const stepPath = `${basePath}[${index}]`;
28303
+ if (!step || typeof step !== "object")
28304
+ continue;
28305
+ const obj = step;
28306
+ if (obj.loop && Object.keys(obj).length === 1) {
28307
+ const loopDef = obj.loop;
28308
+ if (loopDef.until == null && loopDef.max == null) {
28309
+ warnings.push({
28310
+ path: `${stepPath}.loop`,
28311
+ message: "loop has no 'until' and no 'max' — this will be rejected at runtime"
28312
+ });
28313
+ }
28314
+ collectFlowWarnings(loopDef.steps ?? [], participants, warnings, `${stepPath}.loop.steps`);
28315
+ continue;
27523
28316
  }
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");
28317
+ if (obj.parallel && Object.keys(obj).length === 1) {
28318
+ const parallelSteps = obj.parallel;
28319
+ const branchSetKeys = new Map;
28320
+ for (const [branchIdx, branch] of parallelSteps.entries()) {
28321
+ const setKeys = collectSetKeys(branch);
28322
+ for (const key of setKeys) {
28323
+ const branches = branchSetKeys.get(key) ?? [];
28324
+ branches.push(branchIdx);
28325
+ branchSetKeys.set(key, branches);
28326
+ }
27532
28327
  }
27533
- throw err;
28328
+ for (const [key, branches] of branchSetKeys) {
28329
+ if (branches.length > 1) {
28330
+ warnings.push({
28331
+ path: `${stepPath}.parallel`,
28332
+ message: `branches [${branches.join(", ")}] both write to '${key}' via set — race condition risk`
28333
+ });
28334
+ }
28335
+ }
28336
+ collectFlowWarnings(parallelSteps, participants, warnings, `${stepPath}.parallel`);
28337
+ continue;
27534
28338
  }
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");
28339
+ if ("type" in obj && !obj.as) {
28340
+ warnings.push({
28341
+ path: stepPath,
28342
+ message: "inline participant without 'as' — its output cannot be referenced by name"
28343
+ });
28344
+ continue;
28345
+ }
28346
+ if (obj.if && Object.keys(obj).length === 1) {
28347
+ const ifDef = obj.if;
28348
+ collectFlowWarnings(ifDef.then ?? [], participants, warnings, `${stepPath}.if.then`);
28349
+ if (ifDef.else) {
28350
+ collectFlowWarnings(ifDef.else, participants, warnings, `${stepPath}.if.else`);
27546
28351
  }
27547
- throw err;
27548
28352
  }
27549
28353
  }
27550
- console.error(`Error: unknown event backend "${backend}". Supported: memory, nats, redis`);
27551
- throw new Error(`unknown event backend: ${backend}`);
27552
28354
  }
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;
28355
+ function collectSetKeys(step) {
28356
+ if (!step || typeof step !== "object")
28357
+ return [];
28358
+ const obj = step;
28359
+ if ("set" in obj && Object.keys(obj).length === 1) {
28360
+ return Object.keys(obj.set);
28361
+ }
28362
+ const keys3 = [];
28363
+ if (obj.loop) {
28364
+ const loopDef = obj.loop;
28365
+ for (const s of loopDef.steps ?? []) {
28366
+ keys3.push(...collectSetKeys(s));
28367
+ }
28368
+ }
28369
+ if (obj.if) {
28370
+ const ifDef = obj.if;
28371
+ for (const s of ifDef.then ?? []) {
28372
+ keys3.push(...collectSetKeys(s));
28373
+ }
28374
+ if (ifDef.else) {
28375
+ for (const s of ifDef.else) {
28376
+ keys3.push(...collectSetKeys(s));
27568
28377
  }
27569
28378
  }
27570
28379
  }
27571
- return out;
28380
+ return keys3;
27572
28381
  }
27573
- async function runCommand(filePath, cliValues) {
28382
+ async function lintCommand(filePath) {
27574
28383
  if (!filePath) {
27575
- console.error("Usage: duckflux run <workflow.yaml> [--input k=v] [--input-file file.json] [--cwd dir]");
28384
+ console.error("Usage: quack lint <workflow.yaml>");
27576
28385
  return 1;
27577
28386
  }
27578
- let inputs = {};
27579
28387
  try {
27580
- if (process.stdin && !process.stdin.isTTY) {
27581
- let stdin = "";
27582
- for await (const chunk of process.stdin) {
27583
- stdin += chunk;
27584
- }
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 {}
28388
+ const workflow = await parseWorkflowFile(filePath);
28389
+ const schemaRes = validateSchema(workflow);
28390
+ if (!schemaRes.valid) {
28391
+ console.error("Schema validation failed:");
28392
+ for (const e of schemaRes.errors) {
28393
+ console.error(` - ${e.path}: ${e.message}`);
27592
28394
  }
28395
+ return 1;
27593
28396
  }
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;
28397
+ const basePath = dirname2(filePath);
28398
+ const semanticRes = await validateSemantic(workflow, basePath);
28399
+ if (!semanticRes.valid) {
28400
+ console.error("Semantic validation failed:");
28401
+ for (const e of semanticRes.errors) {
28402
+ console.error(` - ${e.path}: ${e.message}`);
27605
28403
  }
28404
+ return 1;
27606
28405
  }
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));
28406
+ const warnings = collectLintWarnings(workflow);
28407
+ if (warnings.length > 0) {
28408
+ console.warn("Warnings:");
28409
+ for (const w of warnings) {
28410
+ console.warn(` - ${w.path}: ${w.message}`);
28411
+ }
27631
28412
  }
27632
- return res.success ? 0 : 2;
28413
+ console.log("valid");
28414
+ return 0;
27633
28415
  } 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
- }
28416
+ console.error("Error during lint:", err && err.message ? err.message : err);
27639
28417
  return 1;
27640
- } finally {
27641
- await hub?.close();
27642
28418
  }
27643
28419
  }
27644
28420
 
27645
- // src/lint.ts
27646
- import { dirname as dirname5 } from "node:path";
28421
+ // src/run.ts
28422
+ import { readFile as readFile5 } from "node:fs/promises";
27647
28423
 
27648
- // ../core/dist/index.js
28424
+ // ../core/dist/engine/index.js
27649
28425
  import { createRequire as createRequire3 } from "node:module";
27650
28426
  import { readFile as readFile3 } from "node:fs/promises";
27651
- import { readFileSync as readFileSync2 } from "node:fs";
27652
28427
  import { constants as constants2 } from "node:fs";
27653
28428
  import { access as access2 } from "node:fs/promises";
27654
- import { resolve as resolve4 } from "node:path";
28429
+ import { resolve as resolve3 } from "node:path";
28430
+ import { writeFile as writeFile3 } from "node:fs/promises";
28431
+ import { join as join4 } from "node:path";
28432
+ import { appendFile as appendFile2, readFile as readFile22, writeFile as writeFile22 } from "node:fs/promises";
28433
+ import { join as join22 } from "node:path";
28434
+ import { join as join32 } from "node:path";
27655
28435
  import { spawn as spawn2 } from "node:child_process";
27656
28436
  import { dirname as dirname3, resolve as resolve22 } from "node:path";
27657
28437
  import { resolve as resolvePath2, isAbsolute as isAbsolute2 } from "node:path";
28438
+ import { dirname as dirname22, resolve as resolve32 } from "node:path";
28439
+ import { mkdir } from "node:fs/promises";
28440
+ import { env } from "node:process";
27658
28441
  var __create2 = Object.create;
27659
28442
  var __getProtoOf2 = Object.getPrototypeOf;
27660
28443
  var __defProp3 = Object.defineProperty;
@@ -49140,7 +49923,7 @@ var require_compile2 = __commonJS2((exports) => {
49140
49923
  const schOrFunc = root22.refs[ref];
49141
49924
  if (schOrFunc)
49142
49925
  return schOrFunc;
49143
- let _sch = resolve5.call(this, root22, ref);
49926
+ let _sch = resolve4.call(this, root22, ref);
49144
49927
  if (_sch === undefined) {
49145
49928
  const schema2 = (_a = root22.localRefs) === null || _a === undefined ? undefined : _a[ref];
49146
49929
  const { schemaId } = this.opts;
@@ -49167,7 +49950,7 @@ var require_compile2 = __commonJS2((exports) => {
49167
49950
  function sameSchemaEnv(s1, s2) {
49168
49951
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
49169
49952
  }
49170
- function resolve5(root22, ref) {
49953
+ function resolve4(root22, ref) {
49171
49954
  let sch;
49172
49955
  while (typeof (sch = this.refs[ref]) == "string")
49173
49956
  ref = sch;
@@ -49689,7 +50472,7 @@ var require_fast_uri2 = __commonJS2((exports, module) => {
49689
50472
  }
49690
50473
  return uri;
49691
50474
  }
49692
- function resolve5(baseURI, relativeURI, options) {
50475
+ function resolve4(baseURI, relativeURI, options) {
49693
50476
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
49694
50477
  const resolved = resolveComponent(parse22(baseURI, schemelessOptions), parse22(relativeURI, schemelessOptions), schemelessOptions, true);
49695
50478
  schemelessOptions.skipEscape = true;
@@ -49917,7 +50700,7 @@ var require_fast_uri2 = __commonJS2((exports, module) => {
49917
50700
  var fastUri = {
49918
50701
  SCHEMES,
49919
50702
  normalize,
49920
- resolve: resolve5,
50703
+ resolve: resolve4,
49921
50704
  resolveComponent,
49922
50705
  equal,
49923
50706
  serialize,
@@ -53306,6 +54089,549 @@ var require_dist22 = __commonJS2((exports, module) => {
53306
54089
  Object.defineProperty(exports, "__esModule", { value: true });
53307
54090
  exports.default = formatsPlugin;
53308
54091
  });
54092
+ var duckflux_schema_default2;
54093
+ var init_duckflux_schema2 = __esm3(() => {
54094
+ duckflux_schema_default2 = {
54095
+ $schema: "https://json-schema.org/draft/2020-12/schema",
54096
+ $id: "https://raw.githubusercontent.com/duckflux/spec/main/duckflux.schema.json",
54097
+ title: "duckflux Workflow",
54098
+ description: "Schema for duckflux — a minimal, deterministic, runtime-agnostic workflow DSL.",
54099
+ type: "object",
54100
+ required: ["flow"],
54101
+ additionalProperties: false,
54102
+ properties: {
54103
+ id: {
54104
+ type: "string",
54105
+ description: "Unique identifier for the workflow."
54106
+ },
54107
+ name: {
54108
+ type: "string",
54109
+ description: "Human-readable workflow name."
54110
+ },
54111
+ version: {
54112
+ description: "Version identifier for the workflow definition.",
54113
+ oneOf: [
54114
+ { type: "string" },
54115
+ { type: "integer" }
54116
+ ]
54117
+ },
54118
+ defaults: {
54119
+ $ref: "#/$defs/defaults"
54120
+ },
54121
+ inputs: {
54122
+ $ref: "#/$defs/inputs"
54123
+ },
54124
+ participants: {
54125
+ type: "object",
54126
+ description: "Named steps that can be referenced in the flow.",
54127
+ additionalProperties: {
54128
+ $ref: "#/$defs/participant"
54129
+ },
54130
+ not: {
54131
+ anyOf: [
54132
+ { required: ["workflow"] },
54133
+ { required: ["execution"] },
54134
+ { required: ["input"] },
54135
+ { required: ["output"] },
54136
+ { required: ["env"] },
54137
+ { required: ["loop"] },
54138
+ { required: ["event"] }
54139
+ ]
54140
+ }
54141
+ },
54142
+ flow: {
54143
+ $ref: "#/$defs/flowSequence"
54144
+ },
54145
+ output: {
54146
+ $ref: "#/$defs/workflowOutput"
54147
+ }
54148
+ },
54149
+ $defs: {
54150
+ duration: {
54151
+ type: "string",
54152
+ pattern: "^[0-9]+(ms|s|m|h|d)$",
54153
+ description: "Duration string, e.g. '30s', '5m', '2h', '1d'."
54154
+ },
54155
+ celExpression: {
54156
+ type: "string",
54157
+ description: "A Google CEL expression."
54158
+ },
54159
+ defaults: {
54160
+ type: "object",
54161
+ description: "Global defaults applied to all participants.",
54162
+ additionalProperties: false,
54163
+ properties: {
54164
+ timeout: {
54165
+ $ref: "#/$defs/duration"
54166
+ },
54167
+ cwd: {
54168
+ type: "string",
54169
+ description: "Default working directory for exec participants. Supports CEL expressions."
54170
+ }
54171
+ }
54172
+ },
54173
+ inputs: {
54174
+ type: "object",
54175
+ description: "Workflow input parameters. Accessed in CEL as workflow.inputs.<field>. Each key is a parameter name. Value can be null (string, no schema), or a JSON Schema object for validation.",
54176
+ additionalProperties: {
54177
+ oneOf: [
54178
+ { type: "null" },
54179
+ { $ref: "#/$defs/inputField" }
54180
+ ]
54181
+ }
54182
+ },
54183
+ inputField: {
54184
+ type: "object",
54185
+ description: "JSON Schema field definition with optional 'required: true' shortcut.",
54186
+ properties: {
54187
+ type: {
54188
+ type: "string",
54189
+ enum: ["string", "integer", "number", "boolean", "array", "object"]
54190
+ },
54191
+ description: { type: "string" },
54192
+ default: {},
54193
+ required: { type: "boolean" },
54194
+ format: { type: "string" },
54195
+ enum: { type: "array" },
54196
+ minimum: { type: "number" },
54197
+ maximum: { type: "number" },
54198
+ minLength: { type: "integer" },
54199
+ maxLength: { type: "integer" },
54200
+ pattern: { type: "string" },
54201
+ items: {
54202
+ $ref: "#/$defs/inputField"
54203
+ }
54204
+ },
54205
+ additionalProperties: false
54206
+ },
54207
+ retryConfig: {
54208
+ type: "object",
54209
+ description: "Retry configuration. Only applies when onError is 'retry'.",
54210
+ additionalProperties: false,
54211
+ required: ["max"],
54212
+ properties: {
54213
+ max: {
54214
+ type: "integer",
54215
+ minimum: 1,
54216
+ description: "Maximum number of retry attempts."
54217
+ },
54218
+ backoff: {
54219
+ $ref: "#/$defs/duration",
54220
+ description: "Interval between attempts. Default: 0s."
54221
+ },
54222
+ factor: {
54223
+ type: "number",
54224
+ minimum: 1,
54225
+ description: "Backoff multiplier. Default: 1 (no escalation)."
54226
+ }
54227
+ }
54228
+ },
54229
+ onError: {
54230
+ type: "string",
54231
+ description: "Error handling strategy: 'fail' (default), 'skip', 'retry', or a participant name for redirect.",
54232
+ minLength: 1
54233
+ },
54234
+ onTimeout: {
54235
+ type: "string",
54236
+ description: "Timeout handling strategy: 'fail' (default), 'skip', or a participant name for redirect.",
54237
+ minLength: 1
54238
+ },
54239
+ participantInput: {
54240
+ description: "Explicit input mapping. String for direct passthrough, object for structured mapping. Values are CEL expressions. Merged with implicit I/O chain input at runtime.",
54241
+ oneOf: [
54242
+ { type: "string" },
54243
+ {
54244
+ type: "object",
54245
+ additionalProperties: {
54246
+ type: "string"
54247
+ }
54248
+ }
54249
+ ]
54250
+ },
54251
+ participantOutputSchema: {
54252
+ type: "object",
54253
+ description: "Output schema (JSON Schema fields). Opt-in validation.",
54254
+ additionalProperties: {
54255
+ $ref: "#/$defs/inputField"
54256
+ }
54257
+ },
54258
+ payload: {
54259
+ description: "Event payload. String (CEL expression) or object with CEL expressions as values.",
54260
+ oneOf: [
54261
+ { type: "string" },
54262
+ {
54263
+ type: "object",
54264
+ additionalProperties: {}
54265
+ }
54266
+ ]
54267
+ },
54268
+ participant: {
54269
+ type: "object",
54270
+ required: ["type"],
54271
+ description: "A named, reusable building block of the workflow.",
54272
+ properties: {
54273
+ type: {
54274
+ type: "string",
54275
+ enum: ["exec", "http", "mcp", "workflow", "emit"],
54276
+ description: "Participant type."
54277
+ },
54278
+ as: {
54279
+ type: "string",
54280
+ description: "Human-readable display name."
54281
+ },
54282
+ timeout: {
54283
+ $ref: "#/$defs/duration"
54284
+ },
54285
+ onError: {
54286
+ $ref: "#/$defs/onError"
54287
+ },
54288
+ retry: {
54289
+ $ref: "#/$defs/retryConfig"
54290
+ },
54291
+ input: {
54292
+ $ref: "#/$defs/participantInput"
54293
+ },
54294
+ output: {
54295
+ $ref: "#/$defs/participantOutputSchema"
54296
+ },
54297
+ run: {
54298
+ type: "string",
54299
+ description: "[exec] Shell command to execute."
54300
+ },
54301
+ cwd: {
54302
+ type: "string",
54303
+ description: "[exec] Working directory. Supports CEL expressions."
54304
+ },
54305
+ url: {
54306
+ type: "string",
54307
+ description: "[http] Target URL. Supports CEL expressions."
54308
+ },
54309
+ method: {
54310
+ type: "string",
54311
+ enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
54312
+ description: "[http] HTTP method."
54313
+ },
54314
+ headers: {
54315
+ type: "object",
54316
+ additionalProperties: { type: "string" },
54317
+ description: "[http] HTTP headers. Values support CEL expressions."
54318
+ },
54319
+ body: {
54320
+ description: "[http] Request body. String or object. Supports CEL expressions.",
54321
+ oneOf: [
54322
+ { type: "string" },
54323
+ { type: "object" }
54324
+ ]
54325
+ },
54326
+ path: {
54327
+ type: "string",
54328
+ description: "[workflow] Path to the sub-workflow YAML file."
54329
+ },
54330
+ server: {
54331
+ type: "string",
54332
+ description: "[mcp] MCP server identifier."
54333
+ },
54334
+ tool: {
54335
+ type: "string",
54336
+ description: "[mcp] MCP tool name to invoke."
54337
+ },
54338
+ event: {
54339
+ type: "string",
54340
+ description: "[emit] Event name to emit."
54341
+ },
54342
+ payload: {
54343
+ $ref: "#/$defs/payload",
54344
+ description: "[emit] Event payload. CEL expression or object with CEL expressions."
54345
+ },
54346
+ ack: {
54347
+ type: "boolean",
54348
+ description: "[emit] If true, wait for delivery acknowledgment. Default: false.",
54349
+ default: false
54350
+ }
54351
+ },
54352
+ additionalProperties: false
54353
+ },
54354
+ inlineParticipant: {
54355
+ type: "object",
54356
+ required: ["type"],
54357
+ description: "An inline participant definition in the flow. 'as' is optional — if omitted, the participant is anonymous and its output is only accessible via the implicit I/O chain.",
54358
+ properties: {
54359
+ as: {
54360
+ type: "string",
54361
+ description: "Optional name for inline participants. If provided, must be unique across all participant names. Enables output reference by name."
54362
+ },
54363
+ type: {
54364
+ type: "string",
54365
+ enum: ["exec", "http", "mcp", "workflow", "emit"],
54366
+ description: "Participant type."
54367
+ },
54368
+ when: {
54369
+ $ref: "#/$defs/celExpression",
54370
+ description: "Guard condition. If false, step is skipped."
54371
+ },
54372
+ timeout: {
54373
+ $ref: "#/$defs/duration"
54374
+ },
54375
+ onError: {
54376
+ $ref: "#/$defs/onError"
54377
+ },
54378
+ retry: {
54379
+ $ref: "#/$defs/retryConfig"
54380
+ },
54381
+ input: {
54382
+ $ref: "#/$defs/participantInput"
54383
+ },
54384
+ output: {
54385
+ $ref: "#/$defs/participantOutputSchema"
54386
+ },
54387
+ run: { type: "string" },
54388
+ cwd: { type: "string" },
54389
+ url: { type: "string" },
54390
+ method: { type: "string", enum: ["GET", "POST", "PUT", "PATCH", "DELETE"] },
54391
+ headers: { type: "object", additionalProperties: { type: "string" } },
54392
+ body: { oneOf: [{ type: "string" }, { type: "object" }] },
54393
+ path: { type: "string" },
54394
+ server: { type: "string" },
54395
+ tool: { type: "string" },
54396
+ event: { type: "string" },
54397
+ payload: { $ref: "#/$defs/payload" },
54398
+ ack: { type: "boolean" }
54399
+ },
54400
+ additionalProperties: false
54401
+ },
54402
+ flowSequence: {
54403
+ type: "array",
54404
+ description: "Ordered list of flow steps. Each step's output is implicitly chained as input to the next step.",
54405
+ minItems: 1,
54406
+ items: {
54407
+ $ref: "#/$defs/flowStep"
54408
+ }
54409
+ },
54410
+ flowStep: {
54411
+ description: "A single step in the flow: participant reference (string), control construct, inline participant (named or anonymous), or participant override.",
54412
+ oneOf: [
54413
+ {
54414
+ type: "string",
54415
+ description: "Simple participant reference by name."
54416
+ },
54417
+ { $ref: "#/$defs/loopStep" },
54418
+ { $ref: "#/$defs/parallelStep" },
54419
+ { $ref: "#/$defs/ifStep" },
54420
+ { $ref: "#/$defs/setStep" },
54421
+ { $ref: "#/$defs/waitStep" },
54422
+ { $ref: "#/$defs/inlineParticipant" },
54423
+ { $ref: "#/$defs/participantOverrideStep" }
54424
+ ]
54425
+ },
54426
+ loopStep: {
54427
+ type: "object",
54428
+ required: ["loop"],
54429
+ additionalProperties: false,
54430
+ properties: {
54431
+ loop: {
54432
+ type: "object",
54433
+ additionalProperties: false,
54434
+ required: ["steps"],
54435
+ properties: {
54436
+ as: {
54437
+ type: "string",
54438
+ description: "Renames the loop context variable. Access as <as>.index, <as>.iteration, etc. instead of loop.*"
54439
+ },
54440
+ until: {
54441
+ $ref: "#/$defs/celExpression",
54442
+ description: "CEL condition to break out of the loop."
54443
+ },
54444
+ max: {
54445
+ description: "Maximum iterations. Integer or CEL expression.",
54446
+ oneOf: [
54447
+ { type: "integer", minimum: 1 },
54448
+ { type: "string" }
54449
+ ]
54450
+ },
54451
+ steps: {
54452
+ $ref: "#/$defs/flowSequence"
54453
+ }
54454
+ },
54455
+ anyOf: [
54456
+ { required: ["until"] },
54457
+ { required: ["max"] }
54458
+ ]
54459
+ }
54460
+ }
54461
+ },
54462
+ parallelStep: {
54463
+ type: "object",
54464
+ required: ["parallel"],
54465
+ additionalProperties: false,
54466
+ properties: {
54467
+ parallel: {
54468
+ $ref: "#/$defs/flowSequence",
54469
+ description: "Steps to run concurrently. The chained output after a parallel block is an array of all branch outputs in declaration order."
54470
+ }
54471
+ }
54472
+ },
54473
+ ifStep: {
54474
+ type: "object",
54475
+ required: ["if"],
54476
+ additionalProperties: false,
54477
+ properties: {
54478
+ if: {
54479
+ type: "object",
54480
+ required: ["condition", "then"],
54481
+ additionalProperties: false,
54482
+ properties: {
54483
+ condition: {
54484
+ $ref: "#/$defs/celExpression",
54485
+ description: "CEL expression that determines which branch to take."
54486
+ },
54487
+ then: {
54488
+ $ref: "#/$defs/flowSequence"
54489
+ },
54490
+ else: {
54491
+ $ref: "#/$defs/flowSequence"
54492
+ }
54493
+ }
54494
+ }
54495
+ }
54496
+ },
54497
+ setStep: {
54498
+ type: "object",
54499
+ required: ["set"],
54500
+ additionalProperties: false,
54501
+ properties: {
54502
+ set: {
54503
+ type: "object",
54504
+ description: "Writes values into execution.context. Each key becomes execution.context.<key>. Values are CEL expressions.",
54505
+ minProperties: 1,
54506
+ additionalProperties: {
54507
+ $ref: "#/$defs/celExpression"
54508
+ },
54509
+ not: {
54510
+ anyOf: [
54511
+ { required: ["workflow"] },
54512
+ { required: ["execution"] },
54513
+ { required: ["input"] },
54514
+ { required: ["output"] },
54515
+ { required: ["env"] },
54516
+ { required: ["loop"] },
54517
+ { required: ["event"] }
54518
+ ]
54519
+ }
54520
+ }
54521
+ }
54522
+ },
54523
+ waitStep: {
54524
+ type: "object",
54525
+ required: ["wait"],
54526
+ additionalProperties: false,
54527
+ properties: {
54528
+ wait: {
54529
+ type: "object",
54530
+ additionalProperties: false,
54531
+ properties: {
54532
+ event: {
54533
+ type: "string",
54534
+ description: "Event name to wait for (event mode)."
54535
+ },
54536
+ match: {
54537
+ $ref: "#/$defs/celExpression",
54538
+ description: "CEL condition to match against event payload. 'event' variable contains the payload."
54539
+ },
54540
+ until: {
54541
+ $ref: "#/$defs/celExpression",
54542
+ description: "CEL condition to wait for (polling mode)."
54543
+ },
54544
+ poll: {
54545
+ $ref: "#/$defs/duration",
54546
+ description: "Polling interval for 'until' condition. Default: runtime decides."
54547
+ },
54548
+ timeout: {
54549
+ $ref: "#/$defs/duration",
54550
+ description: "Maximum wait time."
54551
+ },
54552
+ onTimeout: {
54553
+ $ref: "#/$defs/onTimeout",
54554
+ description: "Strategy when timeout is reached: 'fail' (default), 'skip', or participant name."
54555
+ }
54556
+ }
54557
+ }
54558
+ }
54559
+ },
54560
+ participantOverrideStep: {
54561
+ type: "object",
54562
+ description: "A participant reference with flow-level overrides (timeout, onError, when, input, workflow, etc.).",
54563
+ minProperties: 1,
54564
+ maxProperties: 1,
54565
+ not: {
54566
+ anyOf: [
54567
+ { required: ["loop"] },
54568
+ { required: ["parallel"] },
54569
+ { required: ["if"] },
54570
+ { required: ["wait"] },
54571
+ { required: ["set"] },
54572
+ { required: ["type"] }
54573
+ ]
54574
+ },
54575
+ additionalProperties: {
54576
+ type: "object",
54577
+ properties: {
54578
+ when: {
54579
+ $ref: "#/$defs/celExpression",
54580
+ description: "Guard condition. If false, step is skipped."
54581
+ },
54582
+ timeout: {
54583
+ $ref: "#/$defs/duration"
54584
+ },
54585
+ onError: {
54586
+ $ref: "#/$defs/onError"
54587
+ },
54588
+ retry: {
54589
+ $ref: "#/$defs/retryConfig"
54590
+ },
54591
+ input: {
54592
+ $ref: "#/$defs/participantInput"
54593
+ },
54594
+ workflow: {
54595
+ type: "string",
54596
+ description: "Inline sub-workflow path (alternative to defining as participant)."
54597
+ }
54598
+ },
54599
+ additionalProperties: false
54600
+ }
54601
+ },
54602
+ workflowOutput: {
54603
+ description: "Workflow output. Accessed in CEL as workflow.output. String (single CEL expression), object (structured mapping), or object with schema+map.",
54604
+ oneOf: [
54605
+ {
54606
+ type: "string",
54607
+ description: "Single CEL expression mapping."
54608
+ },
54609
+ {
54610
+ type: "object",
54611
+ description: "Structured output mapping (key → CEL expression) or schema+map.",
54612
+ properties: {
54613
+ schema: {
54614
+ type: "object",
54615
+ additionalProperties: {
54616
+ $ref: "#/$defs/inputField"
54617
+ }
54618
+ },
54619
+ map: {
54620
+ type: "object",
54621
+ additionalProperties: {
54622
+ type: "string"
54623
+ }
54624
+ }
54625
+ },
54626
+ additionalProperties: {
54627
+ type: "string"
54628
+ }
54629
+ }
54630
+ ]
54631
+ }
54632
+ }
54633
+ };
54634
+ });
53309
54635
  function validateSchema2(workflow) {
53310
54636
  const valid = validate2(workflow);
53311
54637
  if (valid) {
@@ -53319,15 +54645,14 @@ function validateSchema2(workflow) {
53319
54645
  }
53320
54646
  var import__20202;
53321
54647
  var import_ajv_formats2;
53322
- var rawSchema2;
53323
54648
  var schema2;
53324
54649
  var ajv2;
53325
54650
  var validate2;
53326
54651
  var init_schema2 = __esm3(() => {
54652
+ init_duckflux_schema2();
53327
54653
  import__20202 = __toESM2(require_20202(), 1);
53328
54654
  import_ajv_formats2 = __toESM2(require_dist22(), 1);
53329
- rawSchema2 = readFileSync2(new URL("./schema/duckflux.schema.json", import.meta.url), "utf-8");
53330
- schema2 = JSON.parse(rawSchema2);
54655
+ schema2 = { ...duckflux_schema_default2 };
53331
54656
  delete schema2.$schema;
53332
54657
  ajv2 = new import__20202.default({ allErrors: true, strict: false });
53333
54658
  import_ajv_formats2.default(ajv2);
@@ -53711,7 +55036,7 @@ async function validateSemantic2(workflow, basePath) {
53711
55036
  for (const [name, participant] of Object.entries(participants)) {
53712
55037
  if (participant.type !== "workflow")
53713
55038
  continue;
53714
- const resolvedPath = resolve4(basePath, participant.path);
55039
+ const resolvedPath = resolve3(basePath, participant.path);
53715
55040
  const exists = await access2(resolvedPath, constants2.F_OK).then(() => true, () => false);
53716
55041
  if (!exists) {
53717
55042
  errors.push({
@@ -53729,6 +55054,237 @@ var init_validate2 = __esm3(() => {
53729
55054
  RESERVED_NAMES2 = new Set(["workflow", "execution", "input", "output", "env", "loop", "event"]);
53730
55055
  BUILTIN_ONERROR2 = new Set(["fail", "skip", "retry"]);
53731
55056
  });
55057
+ var exports_json2 = {};
55058
+ __export3(exports_json2, {
55059
+ JsonTraceWriter: () => JsonTraceWriter2
55060
+ });
55061
+
55062
+ class JsonTraceWriter2 {
55063
+ dir;
55064
+ filePath = "";
55065
+ trace = {
55066
+ execution: {
55067
+ id: "",
55068
+ startedAt: "",
55069
+ finishedAt: "",
55070
+ duration: 0,
55071
+ status: "running",
55072
+ inputs: null,
55073
+ output: null
55074
+ },
55075
+ steps: []
55076
+ };
55077
+ constructor(dir) {
55078
+ this.dir = dir;
55079
+ }
55080
+ async open(meta) {
55081
+ this.filePath = join4(this.dir, `${meta.id}.json`);
55082
+ this.trace = {
55083
+ execution: {
55084
+ id: meta.id,
55085
+ workflowId: meta.workflowId,
55086
+ workflowName: meta.workflowName,
55087
+ workflowVersion: meta.workflowVersion,
55088
+ startedAt: meta.startedAt,
55089
+ finishedAt: "",
55090
+ duration: 0,
55091
+ status: "running",
55092
+ inputs: meta.inputs,
55093
+ output: null
55094
+ },
55095
+ steps: []
55096
+ };
55097
+ await this.flush();
55098
+ }
55099
+ writeStep(step) {
55100
+ this.trace.steps.push(step);
55101
+ this.flushSync();
55102
+ }
55103
+ async finalize(meta) {
55104
+ this.trace.execution.status = meta.status;
55105
+ this.trace.execution.output = meta.output;
55106
+ this.trace.execution.finishedAt = meta.finishedAt;
55107
+ this.trace.execution.duration = meta.duration;
55108
+ await this.flush();
55109
+ }
55110
+ flushSync() {
55111
+ this.flush().catch(() => {});
55112
+ }
55113
+ async flush() {
55114
+ if (!this.filePath)
55115
+ return;
55116
+ await writeFile3(this.filePath, JSON.stringify(this.trace, null, 2), "utf-8");
55117
+ }
55118
+ }
55119
+ var init_json = () => {};
55120
+ var exports_txt2 = {};
55121
+ __export3(exports_txt2, {
55122
+ TxtTraceWriter: () => TxtTraceWriter2
55123
+ });
55124
+ function serializeValue2(value) {
55125
+ if (value === undefined || value === null)
55126
+ return "none";
55127
+ if (typeof value === "string")
55128
+ return value;
55129
+ return JSON.stringify(value, null, 2);
55130
+ }
55131
+ function formatStep2(step) {
55132
+ const lines = [
55133
+ `## [${step.seq}] ${step.name} (${step.type})`,
55134
+ `startedAt: ${step.startedAt}`
55135
+ ];
55136
+ if (step.finishedAt)
55137
+ lines.push(`finishedAt: ${step.finishedAt}`);
55138
+ if (step.duration !== undefined)
55139
+ lines.push(`duration: ${step.duration}ms`);
55140
+ lines.push(`status: ${step.status}`);
55141
+ if (step.loopIndex !== undefined)
55142
+ lines.push(`loopIndex: ${step.loopIndex}`);
55143
+ if (step.retries !== undefined && step.retries > 0)
55144
+ lines.push(`retries: ${step.retries}`);
55145
+ lines.push(`input: ${serializeValue2(step.input)}`);
55146
+ lines.push(`output: ${serializeValue2(step.output)}`);
55147
+ if (step.error)
55148
+ lines.push(`error: ${step.error}`);
55149
+ lines.push("");
55150
+ return lines.join(`
55151
+ `);
55152
+ }
55153
+
55154
+ class TxtTraceWriter2 {
55155
+ dir;
55156
+ filePath = "";
55157
+ constructor(dir) {
55158
+ this.dir = dir;
55159
+ }
55160
+ async open(meta) {
55161
+ this.filePath = join22(this.dir, `${meta.id}.txt`);
55162
+ const versionStr = meta.workflowVersion !== undefined ? ` (v${meta.workflowVersion})` : "";
55163
+ const workflowLabel = meta.workflowName ?? meta.workflowId ?? "unnamed";
55164
+ const header = [
55165
+ "# execution",
55166
+ `id: ${meta.id}`,
55167
+ `workflow: ${workflowLabel}${versionStr}`,
55168
+ `startedAt: ${meta.startedAt}`,
55169
+ "status: running",
55170
+ "",
55171
+ "# inputs",
55172
+ serializeValue2(meta.inputs),
55173
+ "",
55174
+ "# steps",
55175
+ ""
55176
+ ].join(`
55177
+ `);
55178
+ await writeFile22(this.filePath, header, "utf-8");
55179
+ }
55180
+ async writeStep(step) {
55181
+ if (!this.filePath)
55182
+ return;
55183
+ await appendFile2(this.filePath, formatStep2(step), "utf-8");
55184
+ }
55185
+ async finalize(meta) {
55186
+ if (!this.filePath)
55187
+ return;
55188
+ const outputSection = [
55189
+ "# output",
55190
+ serializeValue2(meta.output),
55191
+ ""
55192
+ ].join(`
55193
+ `);
55194
+ await appendFile2(this.filePath, outputSection, "utf-8");
55195
+ const content = await readFile22(this.filePath, "utf-8");
55196
+ const updated = content.replace(/^status: running$/m, `status: ${meta.status}
55197
+ finishedAt: ${meta.finishedAt}
55198
+ duration: ${meta.duration}ms`);
55199
+ await writeFile22(this.filePath, updated, "utf-8");
55200
+ }
55201
+ }
55202
+ var init_txt = () => {};
55203
+ var exports_sqlite2 = {};
55204
+ __export3(exports_sqlite2, {
55205
+ SqliteTraceWriter: () => SqliteTraceWriter2
55206
+ });
55207
+ function toJson2(value) {
55208
+ if (value === undefined || value === null)
55209
+ return null;
55210
+ if (typeof value === "string")
55211
+ return value;
55212
+ return JSON.stringify(value);
55213
+ }
55214
+
55215
+ class SqliteTraceWriter2 {
55216
+ dir;
55217
+ db = null;
55218
+ executionId = "";
55219
+ constructor(dir) {
55220
+ this.dir = dir;
55221
+ }
55222
+ async open(meta) {
55223
+ this.executionId = meta.id;
55224
+ const filePath = join32(this.dir, `${meta.id}.sqlite`);
55225
+ const { Database } = await import("bun:sqlite");
55226
+ this.db = new Database(filePath);
55227
+ this.db.exec(`
55228
+ CREATE TABLE IF NOT EXISTS executions (
55229
+ id TEXT PRIMARY KEY,
55230
+ workflow_id TEXT,
55231
+ workflow_name TEXT,
55232
+ workflow_version TEXT,
55233
+ started_at TEXT NOT NULL,
55234
+ finished_at TEXT,
55235
+ duration_ms INTEGER,
55236
+ status TEXT NOT NULL,
55237
+ inputs TEXT,
55238
+ output TEXT
55239
+ );
55240
+
55241
+ CREATE TABLE IF NOT EXISTS steps (
55242
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
55243
+ execution_id TEXT NOT NULL REFERENCES executions(id),
55244
+ seq INTEGER NOT NULL,
55245
+ name TEXT NOT NULL,
55246
+ type TEXT NOT NULL,
55247
+ started_at TEXT,
55248
+ finished_at TEXT,
55249
+ duration_ms INTEGER,
55250
+ status TEXT NOT NULL,
55251
+ input TEXT,
55252
+ output TEXT,
55253
+ error TEXT,
55254
+ retries INTEGER,
55255
+ loop_index INTEGER
55256
+ );
55257
+ `);
55258
+ const insert = this.db.prepare(`
55259
+ INSERT INTO executions (id, workflow_id, workflow_name, workflow_version, started_at, status, inputs)
55260
+ VALUES (?, ?, ?, ?, ?, 'running', ?)
55261
+ `);
55262
+ insert.run(meta.id, meta.workflowId ?? null, meta.workflowName ?? null, meta.workflowVersion !== undefined ? String(meta.workflowVersion) : null, meta.startedAt, toJson2(meta.inputs));
55263
+ }
55264
+ writeStep(step) {
55265
+ if (!this.db)
55266
+ return;
55267
+ const insert = this.db.prepare(`
55268
+ INSERT INTO steps
55269
+ (execution_id, seq, name, type, started_at, finished_at, duration_ms, status, input, output, error, retries, loop_index)
55270
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
55271
+ `);
55272
+ 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);
55273
+ }
55274
+ async finalize(meta) {
55275
+ if (!this.db)
55276
+ return;
55277
+ const update = this.db.prepare(`
55278
+ UPDATE executions
55279
+ SET status = ?, output = ?, finished_at = ?, duration_ms = ?
55280
+ WHERE id = ?
55281
+ `);
55282
+ update.run(meta.status, toJson2(meta.output), meta.finishedAt, Math.round(meta.duration), this.executionId);
55283
+ this.db.close();
55284
+ this.db = null;
55285
+ }
55286
+ }
55287
+ var init_sqlite = () => {};
53732
55288
  function sleep3(ms) {
53733
55289
  return new Promise((resolve23) => setTimeout(resolve23, ms));
53734
55290
  }
@@ -54020,7 +55576,7 @@ async function executeHttp2(participant, input) {
54020
55576
  };
54021
55577
  }
54022
55578
  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.");
55579
+ throw new Error(`mcp participant is not yet implemented (server: ${participant.server ?? "unspecified"}, tool: ${participant.tool ?? "unspecified"}). Use onError to handle this gracefully.`);
54024
55580
  }
54025
55581
  function toWorkflowInputs2(input) {
54026
55582
  if (input && typeof input === "object" && !Array.isArray(input)) {
@@ -54306,6 +55862,10 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54306
55862
  output: "",
54307
55863
  duration: 0
54308
55864
  });
55865
+ const loopIndex2 = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
55866
+ const skippedSeq = state.tracer?.startStep(stepName, participant.type, undefined, loopIndex2);
55867
+ if (skippedSeq !== undefined)
55868
+ state.tracer?.endStep(skippedSeq, "skipped");
54309
55869
  }
54310
55870
  return chain;
54311
55871
  }
@@ -54314,6 +55874,8 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54314
55874
  const overrideInput = override?.input !== undefined ? resolveParticipantInput2(override.input, state) : undefined;
54315
55875
  const mergedWithBase = mergeChainedInput2(chain, baseInput);
54316
55876
  const mergedInput = mergeChainedInput2(mergedWithBase, overrideInput);
55877
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
55878
+ const traceSeq = state.tracer?.startStep(stepName ?? "<anonymous>", participant.type, mergedInput, loopIndex);
54317
55879
  state.currentInput = mergedInput;
54318
55880
  const strategy = resolveErrorStrategy2(override ?? null, participant, workflow.defaults ?? null);
54319
55881
  const timeoutMs = resolveTimeout2(override ?? null, participant, workflow.defaults ?? null);
@@ -54391,6 +55953,8 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54391
55953
  }
54392
55954
  const outputValue = result.parsedOutput ?? result.output;
54393
55955
  state.currentOutput = outputValue;
55956
+ if (traceSeq !== undefined)
55957
+ state.tracer?.endStep(traceSeq, result.status, outputValue, undefined, retries);
54394
55958
  return outputValue;
54395
55959
  } catch (error) {
54396
55960
  const message = String(error?.message ?? error);
@@ -54416,6 +55980,8 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54416
55980
  if (stepName) {
54417
55981
  state.setResult(stepName, skipResult);
54418
55982
  }
55983
+ if (traceSeq !== undefined)
55984
+ state.tracer?.endStep(traceSeq, "skipped", undefined, message);
54419
55985
  return chain;
54420
55986
  }
54421
55987
  if (strategy !== "fail" && strategy !== "retry") {
@@ -54434,6 +56000,8 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54434
56000
  ...httpMeta
54435
56001
  });
54436
56002
  }
56003
+ if (traceSeq !== undefined)
56004
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
54437
56005
  const fallbackResult = await executeStep2(workflow, state, fallbackName, basePath, engineExecutor, [...fallbackStack, stepName ?? "<anonymous>"], chain, hub);
54438
56006
  return fallbackResult;
54439
56007
  }
@@ -54448,6 +56016,8 @@ async function executeStep2(workflow, state, step, basePath = process.cwd(), eng
54448
56016
  ...httpMeta
54449
56017
  });
54450
56018
  }
56019
+ if (traceSeq !== undefined)
56020
+ state.tracer?.endStep(traceSeq, "failure", undefined, message);
54451
56021
  throw error;
54452
56022
  }
54453
56023
  }
@@ -54470,7 +56040,7 @@ __export3(exports_wait2, {
54470
56040
  executeWait: () => executeWait2
54471
56041
  });
54472
56042
  function sleep22(ms) {
54473
- return new Promise((resolve32) => setTimeout(resolve32, ms));
56043
+ return new Promise((resolve33) => setTimeout(resolve33, ms));
54474
56044
  }
54475
56045
  async function executeWait2(state, waitDef, chain, hub, signal) {
54476
56046
  const timeoutMs = waitDef.timeout ? parseDuration2(waitDef.timeout) : undefined;
@@ -54556,21 +56126,38 @@ async function executeControlStep2(workflow, state, step, basePath = process.cwd
54556
56126
  }
54557
56127
  const obj = step;
54558
56128
  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);
56129
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
56130
+ const traceSeq = state.tracer?.startStep("wait", "wait", undefined, loopIndex);
56131
+ try {
56132
+ const { executeWait: executeWait22 } = await Promise.resolve().then(() => (init_wait2(), exports_wait2));
56133
+ const result = await executeWait22(state, obj.wait, chain, hub, signal);
56134
+ if (traceSeq !== undefined)
56135
+ state.tracer?.endStep(traceSeq, "success", result);
56136
+ return result;
56137
+ } catch (err) {
56138
+ if (traceSeq !== undefined)
56139
+ state.tracer?.endStep(traceSeq, "failure", undefined, String(err?.message ?? err));
56140
+ throw err;
56141
+ }
54561
56142
  }
54562
56143
  if ("set" in obj && Object.keys(obj).length === 1) {
54563
56144
  const setDef = obj.set;
54564
56145
  const ctx = state.toCelContext();
56146
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
56147
+ const traceSeq = state.tracer?.startStep("set", "set", setDef, loopIndex);
54565
56148
  if (!state.executionMeta.context) {
54566
56149
  state.executionMeta.context = {};
54567
56150
  }
54568
56151
  for (const [key, expr] of Object.entries(setDef)) {
54569
56152
  if (RESERVED_SET_KEYS2.has(key)) {
56153
+ if (traceSeq !== undefined)
56154
+ state.tracer?.endStep(traceSeq, "failure", undefined, `set key '${key}' uses a reserved name`);
54570
56155
  throw new Error(`set key '${key}' uses a reserved name`);
54571
56156
  }
54572
56157
  state.executionMeta.context[key] = evaluateCel2(expr, ctx);
54573
56158
  }
56159
+ if (traceSeq !== undefined)
56160
+ state.tracer?.endStep(traceSeq, "success", state.executionMeta.context);
54574
56161
  return chain;
54575
56162
  }
54576
56163
  if ("loop" in obj && Object.keys(obj).length === 1) {
@@ -54587,6 +56174,8 @@ async function executeControlStep2(workflow, state, step, basePath = process.cwd
54587
56174
  maxIterations = loopDef.max ?? Number.POSITIVE_INFINITY;
54588
56175
  }
54589
56176
  const hasMax = loopDef.max !== undefined;
56177
+ const outerLoopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
56178
+ const loopTraceSeq = state.tracer?.startStep(loopAs ?? "loop", "loop", undefined, outerLoopIndex);
54590
56179
  state.pushLoop(loopAs);
54591
56180
  let loopChain = chain;
54592
56181
  try {
@@ -54610,6 +56199,12 @@ async function executeControlStep2(workflow, state, step, basePath = process.cwd
54610
56199
  iterations += 1;
54611
56200
  state.incrementLoop();
54612
56201
  }
56202
+ if (loopTraceSeq !== undefined)
56203
+ state.tracer?.endStep(loopTraceSeq, "success", loopChain);
56204
+ } catch (err) {
56205
+ if (loopTraceSeq !== undefined)
56206
+ state.tracer?.endStep(loopTraceSeq, "failure", undefined, String(err?.message ?? err));
56207
+ throw err;
54613
56208
  } finally {
54614
56209
  state.popLoop();
54615
56210
  }
@@ -54618,29 +56213,54 @@ async function executeControlStep2(workflow, state, step, basePath = process.cwd
54618
56213
  if ("parallel" in obj && Object.keys(obj).length === 1) {
54619
56214
  const parallelSteps = obj.parallel;
54620
56215
  const controller = new AbortController;
56216
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
56217
+ const parallelTraceSeq = state.tracer?.startStep("parallel", "parallel", undefined, loopIndex);
54621
56218
  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;
56219
+ try {
56220
+ const results = await Promise.all(parallelSteps.map(async (parallelStep) => {
56221
+ try {
56222
+ return await executeControlStep2(workflow, state, parallelStep, basePath, engineExecutor, chain, hub, branchSignal);
56223
+ } catch (error) {
56224
+ controller.abort();
56225
+ throw error;
56226
+ }
56227
+ }));
56228
+ if (parallelTraceSeq !== undefined)
56229
+ state.tracer?.endStep(parallelTraceSeq, "success", results);
56230
+ return results;
56231
+ } catch (err) {
56232
+ if (parallelTraceSeq !== undefined)
56233
+ state.tracer?.endStep(parallelTraceSeq, "failure", undefined, String(err?.message ?? err));
56234
+ throw err;
56235
+ }
54631
56236
  }
54632
56237
  if ("if" in obj && Object.keys(obj).length === 1) {
54633
56238
  const ifDef = obj.if;
54634
56239
  const condition = evaluateCel2(ifDef.condition, state.toCelContext());
56240
+ const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
56241
+ const ifTraceSeq = state.tracer?.startStep("if", "if", { condition: ifDef.condition }, loopIndex);
54635
56242
  if (typeof condition !== "boolean") {
56243
+ if (ifTraceSeq !== undefined)
56244
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, `if.condition must evaluate to boolean, got ${typeof condition}`);
54636
56245
  throw new Error(`if.condition must evaluate to boolean, got ${typeof condition}`);
54637
56246
  }
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);
56247
+ try {
56248
+ let result;
56249
+ if (condition) {
56250
+ result = await executeSequential2(workflow, state, ifDef.then, basePath, engineExecutor, chain, hub, signal);
56251
+ } else if (ifDef.else) {
56252
+ result = await executeSequential2(workflow, state, ifDef.else, basePath, engineExecutor, chain, hub, signal);
56253
+ } else {
56254
+ result = chain;
56255
+ }
56256
+ if (ifTraceSeq !== undefined)
56257
+ state.tracer?.endStep(ifTraceSeq, "success", result);
56258
+ return result;
56259
+ } catch (err) {
56260
+ if (ifTraceSeq !== undefined)
56261
+ state.tracer?.endStep(ifTraceSeq, "failure", undefined, String(err?.message ?? err));
56262
+ throw err;
54642
56263
  }
54643
- return chain;
54644
56264
  }
54645
56265
  return executeStep2(workflow, state, step, basePath, engineExecutor, [], chain, hub, signal);
54646
56266
  }
@@ -54650,92 +56270,7 @@ var init_control2 = __esm3(() => {
54650
56270
  init_sequential2();
54651
56271
  RESERVED_SET_KEYS2 = new Set(["workflow", "execution", "input", "output", "env", "loop", "event"]);
54652
56272
  });
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
- }
56273
+ init_cel2();
54739
56274
  init_parser32();
54740
56275
  init_schema2();
54741
56276
  init_validate2();
@@ -54899,147 +56434,514 @@ function validateInputs2(inputDefs, provided) {
54899
56434
  resolved
54900
56435
  };
54901
56436
  }
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
56437
 
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;
56438
+ class TraceCollector2 {
56439
+ openSteps = new Map;
56440
+ seq = 0;
56441
+ truncateAt;
56442
+ writer;
56443
+ constructor(truncateAt = 1e6) {
56444
+ this.truncateAt = truncateAt;
56445
+ }
56446
+ startStep(name, type3, input, loopIndex) {
56447
+ this.seq += 1;
56448
+ this.openSteps.set(this.seq, {
56449
+ name,
56450
+ type: type3,
56451
+ startedAt: new Date().toISOString(),
56452
+ startMs: performance.now(),
56453
+ input: input !== undefined ? this.truncate(input) : undefined,
56454
+ loopIndex
56455
+ });
56456
+ return this.seq;
56457
+ }
56458
+ endStep(seq, status, output, error, retries) {
56459
+ const open = this.openSteps.get(seq);
56460
+ if (!open)
56461
+ return;
56462
+ this.openSteps.delete(seq);
56463
+ const finishedAt = new Date().toISOString();
56464
+ const duration = Math.max(0, performance.now() - open.startMs);
56465
+ const step = {
56466
+ seq,
56467
+ name: open.name,
56468
+ type: open.type,
56469
+ startedAt: open.startedAt,
56470
+ finishedAt,
56471
+ duration: Math.round(duration),
56472
+ status,
56473
+ ...open.input !== undefined ? { input: open.input } : {},
56474
+ ...output !== undefined ? { output: this.truncate(output) } : {},
56475
+ ...error !== undefined ? { error } : {},
56476
+ ...retries !== undefined && retries > 0 ? { retries } : {},
56477
+ ...open.loopIndex !== undefined ? { loopIndex: open.loopIndex } : {}
56478
+ };
56479
+ this.writer?.writeStep(step);
56480
+ }
56481
+ truncate(value) {
56482
+ if (value == null)
56483
+ return value;
56484
+ const str = typeof value === "string" ? value : JSON.stringify(value);
56485
+ const bytes = new TextEncoder().encode(str);
56486
+ if (bytes.length <= this.truncateAt)
56487
+ return value;
56488
+ const cut = new TextDecoder().decode(bytes.slice(0, this.truncateAt));
56489
+ return cut + "...[truncated]";
56490
+ }
54918
56491
  }
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;
56492
+ async function createTraceWriter(dir, format) {
56493
+ await mkdir(dir, { recursive: true });
56494
+ if (format === "json") {
56495
+ const { JsonTraceWriter: JsonTraceWriter22 } = await Promise.resolve().then(() => (init_json(), exports_json2));
56496
+ return new JsonTraceWriter22(dir);
56497
+ }
56498
+ if (format === "txt") {
56499
+ const { TxtTraceWriter: TxtTraceWriter22 } = await Promise.resolve().then(() => (init_txt(), exports_txt2));
56500
+ return new TxtTraceWriter22(dir);
56501
+ }
56502
+ if (format === "sqlite") {
56503
+ const { SqliteTraceWriter: SqliteTraceWriter22 } = await Promise.resolve().then(() => (init_sqlite(), exports_sqlite2));
56504
+ return new SqliteTraceWriter22(dir);
56505
+ }
56506
+ throw new Error(`unknown trace format: ${format}`);
56507
+ }
56508
+ init_control2();
56509
+
56510
+ class WorkflowState {
56511
+ inputs;
56512
+ results;
56513
+ loopStack;
56514
+ workflowInputs;
56515
+ workflowMeta;
56516
+ executionMeta;
56517
+ currentInput;
56518
+ currentOutput;
56519
+ chainValue;
56520
+ eventPayload;
56521
+ ancestorPaths;
56522
+ tracer;
56523
+ constructor(inputs = {}) {
56524
+ this.inputs = { ...inputs };
56525
+ this.workflowInputs = { ...inputs };
56526
+ this.workflowMeta = {};
56527
+ this.executionMeta = {
56528
+ id: crypto.randomUUID(),
56529
+ startedAt: new Date().toISOString(),
56530
+ status: "running",
56531
+ cwd: process.cwd()
56532
+ };
56533
+ this.currentInput = undefined;
56534
+ this.currentOutput = undefined;
56535
+ this.chainValue = undefined;
56536
+ this.eventPayload = undefined;
56537
+ this.ancestorPaths = new Set;
56538
+ this.results = new Map;
56539
+ this.loopStack = [];
56540
+ }
56541
+ setResult(stepName, result) {
56542
+ this.results.set(stepName, result);
56543
+ }
56544
+ getResult(stepName) {
56545
+ return this.results.get(stepName);
56546
+ }
56547
+ getAllResults() {
56548
+ const out = {};
56549
+ for (const [k, v] of this.results.entries())
56550
+ out[k] = v;
56551
+ return out;
56552
+ }
56553
+ pushLoop(as) {
56554
+ this.loopStack.push({ index: 0, as });
56555
+ }
56556
+ incrementLoop() {
56557
+ const top = this.loopStack[this.loopStack.length - 1];
56558
+ if (top)
56559
+ top.index += 1;
56560
+ }
56561
+ popLoop() {
56562
+ this.loopStack.pop();
56563
+ }
56564
+ currentLoopIndex() {
56565
+ const top = this.loopStack[this.loopStack.length - 1];
56566
+ return top ? top.index : 0;
56567
+ }
56568
+ setLoopLast(last22) {
56569
+ const top = this.loopStack[this.loopStack.length - 1];
56570
+ if (top)
56571
+ top.last = last22;
56572
+ }
56573
+ isInsideLoop() {
56574
+ return this.loopStack.length > 0;
56575
+ }
56576
+ currentLoopContext() {
56577
+ const top = this.loopStack[this.loopStack.length - 1];
56578
+ if (!top)
56579
+ return { index: 0, iteration: 1, first: true, last: false };
56580
+ return {
56581
+ index: top.index,
56582
+ iteration: top.index + 1,
56583
+ first: top.index === 0,
56584
+ last: top.last ?? false,
56585
+ as: top.as
56586
+ };
56587
+ }
56588
+ toCelContext() {
56589
+ const ctx = {};
56590
+ for (const [name, res] of this.results.entries()) {
56591
+ ctx[name] = {
56592
+ output: res.parsedOutput ?? res.output,
56593
+ status: res.status,
56594
+ startedAt: res.startedAt,
56595
+ finishedAt: res.finishedAt,
56596
+ duration: res.duration,
56597
+ retries: res.retries ?? 0,
56598
+ error: res.error,
56599
+ cwd: res.cwd
56600
+ };
54935
56601
  }
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
- }
56602
+ ctx["workflow"] = {
56603
+ id: this.workflowMeta.id,
56604
+ name: this.workflowMeta.name,
56605
+ version: this.workflowMeta.version,
56606
+ inputs: this.workflowInputs,
56607
+ output: null
56608
+ };
56609
+ ctx["execution"] = { ...this.executionMeta };
56610
+ ctx["input"] = this.currentInput ?? {};
56611
+ ctx["output"] = this.currentOutput ?? {};
56612
+ ctx["env"] = { ...env };
56613
+ const loopCtx = this.currentLoopContext();
56614
+ const loopObj = {
56615
+ index: loopCtx.index,
56616
+ iteration: loopCtx.iteration,
56617
+ first: loopCtx.first,
56618
+ last: loopCtx.last
56619
+ };
56620
+ if (loopCtx.as) {
56621
+ ctx[`_${loopCtx.as}`] = loopObj;
56622
+ }
56623
+ ctx["_loop"] = loopObj;
56624
+ ctx["loop"] = loopObj;
56625
+ ctx["event"] = this.eventPayload ?? {};
56626
+ ctx["now"] = Math.floor(Date.now() / 1000);
56627
+ return ctx;
56628
+ }
56629
+ resolveOutput(outputDef, celEvaluator) {
56630
+ const ctx = this.toCelContext();
56631
+ if (typeof outputDef === "object" && "map" in outputDef && "schema" in outputDef) {
56632
+ const result2 = {};
56633
+ for (const [k, expr] of Object.entries(outputDef.map)) {
56634
+ result2[k] = celEvaluator(expr, ctx);
54954
56635
  }
54955
- collectFlowWarnings(parallelSteps, participants, warnings, `${stepPath}.parallel`);
54956
- continue;
56636
+ return result2;
54957
56637
  }
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;
56638
+ if (typeof outputDef === "string") {
56639
+ return celEvaluator(outputDef, ctx);
54964
56640
  }
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`);
56641
+ const result = {};
56642
+ for (const k of Object.keys(outputDef)) {
56643
+ const expr = outputDef[k];
56644
+ result[k] = celEvaluator(expr, ctx);
56645
+ }
56646
+ return result;
56647
+ }
56648
+ }
56649
+ async function executeWorkflow(workflow, inputs = {}, basePath = process.cwd(), options = {}) {
56650
+ const { result: inputResult, resolved } = validateInputs2(workflow.inputs, inputs);
56651
+ if (!inputResult.valid) {
56652
+ throw new Error(`input validation failed: ${JSON.stringify(inputResult.errors)}`);
56653
+ }
56654
+ const state = new WorkflowState(resolved);
56655
+ state.workflowMeta = {
56656
+ id: workflow.id,
56657
+ name: workflow.name,
56658
+ version: workflow.version
56659
+ };
56660
+ state.executionMeta.number = options.executionNumber ?? 1;
56661
+ if (options.cwd) {
56662
+ state.executionMeta.cwd = options.cwd;
56663
+ }
56664
+ const startedAtMs = performance.now();
56665
+ const startedAtIso = state.executionMeta.startedAt;
56666
+ if (options._ancestorPaths) {
56667
+ state.ancestorPaths = options._ancestorPaths;
56668
+ }
56669
+ if (options.traceDir && !options._ancestorPaths) {
56670
+ const collector = new TraceCollector2;
56671
+ const writer = await createTraceWriter(options.traceDir, options.traceFormat ?? "json");
56672
+ collector.writer = writer;
56673
+ state.tracer = collector;
56674
+ await writer.open({
56675
+ id: state.executionMeta.id,
56676
+ workflowId: workflow.id,
56677
+ workflowName: workflow.name,
56678
+ workflowVersion: workflow.version,
56679
+ startedAt: startedAtIso,
56680
+ inputs: resolved
56681
+ });
56682
+ }
56683
+ const engineExecutor = async (subWorkflow, subInputs, subBasePath) => {
56684
+ return executeWorkflow(subWorkflow, subInputs, subBasePath, {
56685
+ ...options,
56686
+ _ancestorPaths: state.ancestorPaths
56687
+ });
56688
+ };
56689
+ let output;
56690
+ let success = false;
56691
+ try {
56692
+ let chain;
56693
+ for (const step of workflow.flow) {
56694
+ chain = await executeControlStep2(workflow, state, step, basePath, engineExecutor, chain, options.hub);
56695
+ }
56696
+ if (workflow.output !== undefined) {
56697
+ output = state.resolveOutput(workflow.output, evaluateCel2);
56698
+ if (typeof workflow.output === "object" && "schema" in workflow.output && "map" in workflow.output && typeof output === "object" && output !== null) {
56699
+ validateOutputSchema2(workflow.output.schema, output);
54970
56700
  }
56701
+ } else {
56702
+ output = chain;
56703
+ }
56704
+ const steps = state.getAllResults();
56705
+ success = !Object.values(steps).some((step) => step.status === "failure");
56706
+ state.executionMeta.status = success ? "success" : "failure";
56707
+ return {
56708
+ success,
56709
+ output,
56710
+ steps,
56711
+ duration: Math.max(0, performance.now() - startedAtMs)
56712
+ };
56713
+ } finally {
56714
+ if (state.tracer?.writer) {
56715
+ const duration = Math.max(0, performance.now() - startedAtMs);
56716
+ await state.tracer.writer.finalize({
56717
+ status: success ? "success" : "failure",
56718
+ output: output ?? null,
56719
+ finishedAt: new Date().toISOString(),
56720
+ duration
56721
+ }).catch(() => {});
54971
56722
  }
54972
56723
  }
54973
56724
  }
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);
56725
+ async function runWorkflowFromFile(filePath, inputs = {}, options = {}) {
56726
+ const workflow = await parseWorkflowFile2(filePath);
56727
+ const schemaValidation = validateSchema2(workflow);
56728
+ if (!schemaValidation.valid) {
56729
+ throw new Error(`schema validation failed: ${JSON.stringify(schemaValidation.errors)}`);
54980
56730
  }
54981
- const keys4 = [];
54982
- if (obj.loop) {
54983
- const loopDef = obj.loop;
54984
- for (const s of loopDef.steps ?? []) {
54985
- keys4.push(...collectSetKeys(s));
56731
+ const workflowBasePath = dirname22(resolve32(filePath));
56732
+ const semanticValidation = await validateSemantic2(workflow, workflowBasePath);
56733
+ if (!semanticValidation.valid) {
56734
+ throw new Error(`semantic validation failed: ${JSON.stringify(semanticValidation.errors)}`);
56735
+ }
56736
+ return executeWorkflow(workflow, inputs, workflowBasePath, options);
56737
+ }
56738
+ init_control2();
56739
+ init_sequential2();
56740
+ init_errors2();
56741
+ init_timeout2();
56742
+ init_wait2();
56743
+
56744
+ // src/run.ts
56745
+ async function createHubFromFlags(values3) {
56746
+ const backend = values3?.["event-backend"] ?? "memory";
56747
+ if (backend === "memory") {
56748
+ const { MemoryHub: MemoryHub3 } = await Promise.resolve().then(() => (init_eventhub(), exports_eventhub));
56749
+ return new MemoryHub3;
56750
+ }
56751
+ if (backend === "nats") {
56752
+ const url = values3?.["nats-url"];
56753
+ if (!url) {
56754
+ console.error("Error: --nats-url is required when using the NATS backend");
56755
+ throw new Error("missing --nats-url");
56756
+ }
56757
+ const stream = values3?.["nats-stream"] ?? "duckflux-events";
56758
+ try {
56759
+ const { NatsHub } = await import("@duckflux/hub-nats");
56760
+ return await NatsHub.create({ url, stream });
56761
+ } catch (err) {
56762
+ if (err instanceof Error && (err.message.includes("Cannot find module") || err.message.includes("Cannot find package"))) {
56763
+ console.error("Error: install @duckflux/hub-nats to use the NATS backend");
56764
+ throw new Error("@duckflux/hub-nats not installed");
56765
+ }
56766
+ throw err;
54986
56767
  }
54987
56768
  }
54988
- if (obj.if) {
54989
- const ifDef = obj.if;
54990
- for (const s of ifDef.then ?? []) {
54991
- keys4.push(...collectSetKeys(s));
56769
+ if (backend === "redis") {
56770
+ const addr = values3?.["redis-addr"] ?? "localhost:6379";
56771
+ const db = Number(values3?.["redis-db"] ?? "0");
56772
+ try {
56773
+ const { RedisHub } = await import("@duckflux/hub-redis");
56774
+ return await RedisHub.create({ addr, db });
56775
+ } catch (err) {
56776
+ if (err instanceof Error && (err.message.includes("Cannot find module") || err.message.includes("Cannot find package"))) {
56777
+ console.error("Error: install @duckflux/hub-redis to use the Redis backend");
56778
+ throw new Error("@duckflux/hub-redis not installed");
56779
+ }
56780
+ throw err;
54992
56781
  }
54993
- if (ifDef.else) {
54994
- for (const s of ifDef.else) {
54995
- keys4.push(...collectSetKeys(s));
56782
+ }
56783
+ console.error(`Error: unknown event backend "${backend}". Supported: memory, nats, redis`);
56784
+ throw new Error(`unknown event backend: ${backend}`);
56785
+ }
56786
+ function parseInputFlags(arr) {
56787
+ const out = {};
56788
+ if (!arr)
56789
+ return out;
56790
+ for (const item of arr) {
56791
+ const idx = item.indexOf("=");
56792
+ if (idx === -1) {
56793
+ out[item] = true;
56794
+ } else {
56795
+ const k = item.slice(0, idx);
56796
+ const v = item.slice(idx + 1);
56797
+ try {
56798
+ out[k] = JSON.parse(v);
56799
+ } catch {
56800
+ out[k] = v;
54996
56801
  }
54997
56802
  }
54998
56803
  }
54999
- return keys4;
56804
+ return out;
55000
56805
  }
55001
- async function lintCommand(filePath) {
56806
+ async function runCommand(filePath, cliValues) {
55002
56807
  if (!filePath) {
55003
- console.error("Usage: duckflux lint <workflow.yaml>");
56808
+ console.error("Usage: quack run <workflow.yaml> [--input k=v] [--input-file file.json] [--cwd dir]");
55004
56809
  return 1;
55005
56810
  }
56811
+ let inputs = {};
55006
56812
  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}`);
56813
+ if (process.stdin && !process.stdin.isTTY) {
56814
+ let stdin = "";
56815
+ for await (const chunk of process.stdin) {
56816
+ stdin += chunk;
55013
56817
  }
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}`);
56818
+ stdin = stdin.trim();
56819
+ if (stdin.length > 0) {
56820
+ try {
56821
+ const parsed = JSON.parse(stdin);
56822
+ if (typeof parsed === "object" && parsed !== null)
56823
+ inputs = { ...inputs, ...parsed };
56824
+ } catch {}
55022
56825
  }
55023
- return 1;
55024
56826
  }
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}`);
56827
+ } catch {}
56828
+ if (cliValues) {
56829
+ if (cliValues["input-file"]) {
56830
+ try {
56831
+ const content = await readFile5(String(cliValues["input-file"]), "utf-8");
56832
+ const parsed = JSON.parse(content);
56833
+ if (typeof parsed === "object" && parsed !== null)
56834
+ inputs = { ...inputs, ...parsed };
56835
+ } catch (err) {
56836
+ console.error("Failed to read input file:", err);
56837
+ return 1;
55030
56838
  }
55031
56839
  }
55032
- console.log("valid");
55033
- return 0;
56840
+ if (cliValues.input) {
56841
+ const parsed = Array.isArray(cliValues.input) ? cliValues.input : [cliValues.input];
56842
+ inputs = { ...inputs, ...parseInputFlags(parsed) };
56843
+ }
56844
+ }
56845
+ let hub;
56846
+ try {
56847
+ hub = await createHubFromFlags(cliValues);
56848
+ } catch {
56849
+ return 1;
56850
+ }
56851
+ const options = {
56852
+ hub,
56853
+ cwd: cliValues?.cwd,
56854
+ verbose: cliValues?.verbose,
56855
+ quiet: cliValues?.quiet,
56856
+ traceDir: cliValues?.["trace-dir"],
56857
+ traceFormat: cliValues?.["trace-format"] ?? "json"
56858
+ };
56859
+ try {
56860
+ const res = await runWorkflowFromFile(filePath, inputs, options);
56861
+ const output = res.output;
56862
+ if (output === undefined || output === null) {} else if (typeof output === "string") {
56863
+ process.stdout.write(output);
56864
+ } else {
56865
+ console.log(JSON.stringify(output, null, 2));
56866
+ }
56867
+ return res.success ? 0 : 2;
55034
56868
  } catch (err) {
55035
- console.error("Error during lint:", err && err.message ? err.message : err);
56869
+ const msg = err instanceof Error ? err.message : String(err);
56870
+ console.error("Error:", msg);
56871
+ if (cliValues?.verbose && err instanceof Error && err.stack) {
56872
+ console.error(err.stack);
56873
+ }
55036
56874
  return 1;
56875
+ } finally {
56876
+ await hub?.close();
55037
56877
  }
55038
56878
  }
55039
56879
 
56880
+ // src/server.ts
56881
+ import { existsSync } from "node:fs";
56882
+ import { join as join6 } from "node:path";
56883
+ import { spawn as spawn3, execSync } from "node:child_process";
56884
+ import { createInterface } from "node:readline";
56885
+ async function ensureServerPackage(cwd) {
56886
+ const serverBin = join6(cwd, "node_modules", ".bin", "duckflux-server");
56887
+ const serverPkg = join6(cwd, "node_modules", "@duckflux", "server");
56888
+ if (existsSync(serverBin) || existsSync(serverPkg))
56889
+ return true;
56890
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
56891
+ const answer = await new Promise((resolve4) => {
56892
+ rl.question(`
56893
+ @duckflux/server is not installed. Install it now? [Y/n] `, resolve4);
56894
+ });
56895
+ rl.close();
56896
+ if (answer.trim().toLowerCase() === "n") {
56897
+ console.error("Cancelled. Run `bun add @duckflux/server -D` to install manually.");
56898
+ return false;
56899
+ }
56900
+ console.error("Installing @duckflux/server...");
56901
+ try {
56902
+ execSync("bun add @duckflux/server -D", { cwd, stdio: "inherit" });
56903
+ return true;
56904
+ } catch {
56905
+ try {
56906
+ execSync("npm install @duckflux/server --save-dev", { cwd, stdio: "inherit" });
56907
+ return true;
56908
+ } catch {
56909
+ console.error("Failed to install @duckflux/server. Please install it manually.");
56910
+ return false;
56911
+ }
56912
+ }
56913
+ }
56914
+ async function serverCommand(values3) {
56915
+ const cwd = values3["cwd"] ?? process.cwd();
56916
+ const installed = await ensureServerPackage(cwd);
56917
+ if (!installed)
56918
+ return 1;
56919
+ const args = [];
56920
+ if (values3["trace-dir"])
56921
+ args.push("--trace-dir", values3["trace-dir"]);
56922
+ if (values3["workflow-dir"])
56923
+ args.push("--workflow-dir", values3["workflow-dir"]);
56924
+ if (values3["port"])
56925
+ args.push("--port", values3["port"]);
56926
+ const child = spawn3("bunx", ["@duckflux/server", ...args], {
56927
+ cwd,
56928
+ stdio: "inherit",
56929
+ env: process.env
56930
+ });
56931
+ return new Promise((resolve4) => {
56932
+ child.on("error", (err) => {
56933
+ console.error("Failed to start duckflux server:", err.message);
56934
+ resolve4(1);
56935
+ });
56936
+ child.on("exit", (code) => resolve4(code ?? 0));
56937
+ process.on("SIGINT", () => child.kill("SIGINT"));
56938
+ process.on("SIGTERM", () => child.kill("SIGTERM"));
56939
+ });
56940
+ }
56941
+
55040
56942
  // src/validate.ts
55041
- import { readFile as readFile4 } from "node:fs/promises";
55042
- import { dirname as dirname6 } from "node:path";
56943
+ import { readFile as readFile6 } from "node:fs/promises";
56944
+ import { dirname as dirname4 } from "node:path";
55043
56945
 
55044
56946
  // src/run.ts
55045
56947
  function parseInputFlags2(arr) {
@@ -55066,7 +56968,7 @@ function parseInputFlags2(arr) {
55066
56968
  // src/validate.ts
55067
56969
  async function validateCommand(filePath, cliValues) {
55068
56970
  if (!filePath) {
55069
- console.error("Usage: duckflux validate <workflow.yaml> [--input k=v] [--input-file file.json]");
56971
+ console.error("Usage: quack validate <workflow.yaml> [--input k=v] [--input-file file.json]");
55070
56972
  return 1;
55071
56973
  }
55072
56974
  let inputs = {};
@@ -55077,7 +56979,7 @@ async function validateCommand(filePath, cliValues) {
55077
56979
  }
55078
56980
  if (cliValues["input-file"]) {
55079
56981
  try {
55080
- const content = await readFile4(String(cliValues["input-file"]), "utf-8");
56982
+ const content = await readFile6(String(cliValues["input-file"]), "utf-8");
55081
56983
  const parsed = JSON.parse(content);
55082
56984
  if (typeof parsed === "object" && parsed !== null)
55083
56985
  inputs = { ...inputs, ...parsed };
@@ -55104,8 +57006,8 @@ async function validateCommand(filePath, cliValues) {
55104
57006
  }
55105
57007
  } catch {}
55106
57008
  try {
55107
- const workflow = await parseWorkflowFile2(filePath);
55108
- const schemaRes = validateSchema2(workflow);
57009
+ const workflow = await parseWorkflowFile(filePath);
57010
+ const schemaRes = validateSchema(workflow);
55109
57011
  if (!schemaRes.valid) {
55110
57012
  console.error("Schema validation failed:");
55111
57013
  for (const e of schemaRes.errors) {
@@ -55113,8 +57015,8 @@ async function validateCommand(filePath, cliValues) {
55113
57015
  }
55114
57016
  return 1;
55115
57017
  }
55116
- const basePath = dirname6(filePath);
55117
- const semanticRes = await validateSemantic2(workflow, basePath);
57018
+ const basePath = dirname4(filePath);
57019
+ const semanticRes = await validateSemantic(workflow, basePath);
55118
57020
  if (!semanticRes.valid) {
55119
57021
  console.error("Semantic validation failed:");
55120
57022
  for (const e of semanticRes.errors) {
@@ -55122,7 +57024,7 @@ async function validateCommand(filePath, cliValues) {
55122
57024
  }
55123
57025
  return 1;
55124
57026
  }
55125
- const { result: inputsResult, resolved } = validateInputs2(workflow.inputs, inputs);
57027
+ const { result: inputsResult, resolved } = validateInputs(workflow.inputs, inputs);
55126
57028
  if (!inputsResult.valid) {
55127
57029
  console.error("Input validation failed:");
55128
57030
  for (const e of inputsResult.errors) {
@@ -55141,7 +57043,7 @@ async function validateCommand(filePath, cliValues) {
55141
57043
  // src/main.ts
55142
57044
  function getVersion() {
55143
57045
  try {
55144
- const pkg = JSON.parse(readFileSync3(resolve6(dirname7(new URL(import.meta.url).pathname), "../package.json"), "utf-8"));
57046
+ const pkg = JSON.parse(readFileSync(resolve5(dirname5(new URL(import.meta.url).pathname), "../package.json"), "utf-8"));
55145
57047
  return pkg.version ?? "0.0.0";
55146
57048
  } catch {
55147
57049
  return "0.0.0";
@@ -55161,7 +57063,11 @@ if (__require.main == __require.module) {
55161
57063
  "nats-url": { type: "string" },
55162
57064
  "nats-stream": { type: "string", default: "duckflux-events" },
55163
57065
  "redis-addr": { type: "string", default: "localhost:6379" },
55164
- "redis-db": { type: "string", default: "0" }
57066
+ "redis-db": { type: "string", default: "0" },
57067
+ "trace-dir": { type: "string" },
57068
+ "trace-format": { type: "string", default: "json" },
57069
+ "workflow-dir": { type: "string" },
57070
+ port: { type: "string", default: "3000" }
55165
57071
  },
55166
57072
  allowPositionals: true
55167
57073
  });
@@ -55183,9 +57089,13 @@ if (__require.main == __require.module) {
55183
57089
  const exitCode = await validateCommand(file, values3);
55184
57090
  if (typeof exitCode === "number" && exitCode !== 0)
55185
57091
  process.exit(exitCode);
57092
+ } else if (cmd === "server") {
57093
+ const exitCode = await serverCommand(values3);
57094
+ if (typeof exitCode === "number" && exitCode !== 0)
57095
+ process.exit(exitCode);
55186
57096
  } else {
55187
57097
  console.error("Unknown command:", cmd);
55188
- console.error("Available commands: run, lint, validate, version");
57098
+ console.error("Available commands: run, lint, validate, version, server");
55189
57099
  process.exit(1);
55190
57100
  }
55191
57101
  }