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