@duckflux/core 0.6.8 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/engine/index.js +448 -36
- package/dist/index.js +450 -36
- 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/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/index.js
CHANGED
|
@@ -26590,6 +26590,248 @@ var init_validate = __esm(() => {
|
|
|
26590
26590
|
BUILTIN_ONERROR = new Set(["fail", "skip", "retry"]);
|
|
26591
26591
|
});
|
|
26592
26592
|
|
|
26593
|
+
// src/tracer/writers/json.ts
|
|
26594
|
+
var exports_json = {};
|
|
26595
|
+
__export(exports_json, {
|
|
26596
|
+
JsonTraceWriter: () => JsonTraceWriter
|
|
26597
|
+
});
|
|
26598
|
+
import { writeFile } from "node:fs/promises";
|
|
26599
|
+
import { join } from "node:path";
|
|
26600
|
+
|
|
26601
|
+
class JsonTraceWriter {
|
|
26602
|
+
dir;
|
|
26603
|
+
filePath = "";
|
|
26604
|
+
trace = {
|
|
26605
|
+
execution: {
|
|
26606
|
+
id: "",
|
|
26607
|
+
startedAt: "",
|
|
26608
|
+
finishedAt: "",
|
|
26609
|
+
duration: 0,
|
|
26610
|
+
status: "running",
|
|
26611
|
+
inputs: null,
|
|
26612
|
+
output: null
|
|
26613
|
+
},
|
|
26614
|
+
steps: []
|
|
26615
|
+
};
|
|
26616
|
+
constructor(dir) {
|
|
26617
|
+
this.dir = dir;
|
|
26618
|
+
}
|
|
26619
|
+
async open(meta) {
|
|
26620
|
+
this.filePath = join(this.dir, `${meta.id}.json`);
|
|
26621
|
+
this.trace = {
|
|
26622
|
+
execution: {
|
|
26623
|
+
id: meta.id,
|
|
26624
|
+
workflowId: meta.workflowId,
|
|
26625
|
+
workflowName: meta.workflowName,
|
|
26626
|
+
workflowVersion: meta.workflowVersion,
|
|
26627
|
+
startedAt: meta.startedAt,
|
|
26628
|
+
finishedAt: "",
|
|
26629
|
+
duration: 0,
|
|
26630
|
+
status: "running",
|
|
26631
|
+
inputs: meta.inputs,
|
|
26632
|
+
output: null
|
|
26633
|
+
},
|
|
26634
|
+
steps: []
|
|
26635
|
+
};
|
|
26636
|
+
await this.flush();
|
|
26637
|
+
}
|
|
26638
|
+
writeStep(step) {
|
|
26639
|
+
this.trace.steps.push(step);
|
|
26640
|
+
this.flushSync();
|
|
26641
|
+
}
|
|
26642
|
+
async finalize(meta) {
|
|
26643
|
+
this.trace.execution.status = meta.status;
|
|
26644
|
+
this.trace.execution.output = meta.output;
|
|
26645
|
+
this.trace.execution.finishedAt = meta.finishedAt;
|
|
26646
|
+
this.trace.execution.duration = meta.duration;
|
|
26647
|
+
await this.flush();
|
|
26648
|
+
}
|
|
26649
|
+
flushSync() {
|
|
26650
|
+
this.flush().catch(() => {});
|
|
26651
|
+
}
|
|
26652
|
+
async flush() {
|
|
26653
|
+
if (!this.filePath)
|
|
26654
|
+
return;
|
|
26655
|
+
await writeFile(this.filePath, JSON.stringify(this.trace, null, 2), "utf-8");
|
|
26656
|
+
}
|
|
26657
|
+
}
|
|
26658
|
+
var init_json = () => {};
|
|
26659
|
+
|
|
26660
|
+
// src/tracer/writers/txt.ts
|
|
26661
|
+
var exports_txt = {};
|
|
26662
|
+
__export(exports_txt, {
|
|
26663
|
+
TxtTraceWriter: () => TxtTraceWriter
|
|
26664
|
+
});
|
|
26665
|
+
import { appendFile, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
|
|
26666
|
+
import { join as join2 } from "node:path";
|
|
26667
|
+
function serializeValue(value) {
|
|
26668
|
+
if (value === undefined || value === null)
|
|
26669
|
+
return "none";
|
|
26670
|
+
if (typeof value === "string")
|
|
26671
|
+
return value;
|
|
26672
|
+
return JSON.stringify(value, null, 2);
|
|
26673
|
+
}
|
|
26674
|
+
function formatStep(step) {
|
|
26675
|
+
const lines = [
|
|
26676
|
+
`## [${step.seq}] ${step.name} (${step.type})`,
|
|
26677
|
+
`startedAt: ${step.startedAt}`
|
|
26678
|
+
];
|
|
26679
|
+
if (step.finishedAt)
|
|
26680
|
+
lines.push(`finishedAt: ${step.finishedAt}`);
|
|
26681
|
+
if (step.duration !== undefined)
|
|
26682
|
+
lines.push(`duration: ${step.duration}ms`);
|
|
26683
|
+
lines.push(`status: ${step.status}`);
|
|
26684
|
+
if (step.loopIndex !== undefined)
|
|
26685
|
+
lines.push(`loopIndex: ${step.loopIndex}`);
|
|
26686
|
+
if (step.retries !== undefined && step.retries > 0)
|
|
26687
|
+
lines.push(`retries: ${step.retries}`);
|
|
26688
|
+
lines.push(`input: ${serializeValue(step.input)}`);
|
|
26689
|
+
lines.push(`output: ${serializeValue(step.output)}`);
|
|
26690
|
+
if (step.error)
|
|
26691
|
+
lines.push(`error: ${step.error}`);
|
|
26692
|
+
lines.push("");
|
|
26693
|
+
return lines.join(`
|
|
26694
|
+
`);
|
|
26695
|
+
}
|
|
26696
|
+
|
|
26697
|
+
class TxtTraceWriter {
|
|
26698
|
+
dir;
|
|
26699
|
+
filePath = "";
|
|
26700
|
+
constructor(dir) {
|
|
26701
|
+
this.dir = dir;
|
|
26702
|
+
}
|
|
26703
|
+
async open(meta) {
|
|
26704
|
+
this.filePath = join2(this.dir, `${meta.id}.txt`);
|
|
26705
|
+
const versionStr = meta.workflowVersion !== undefined ? ` (v${meta.workflowVersion})` : "";
|
|
26706
|
+
const workflowLabel = meta.workflowName ?? meta.workflowId ?? "unnamed";
|
|
26707
|
+
const header = [
|
|
26708
|
+
"# execution",
|
|
26709
|
+
`id: ${meta.id}`,
|
|
26710
|
+
`workflow: ${workflowLabel}${versionStr}`,
|
|
26711
|
+
`startedAt: ${meta.startedAt}`,
|
|
26712
|
+
"status: running",
|
|
26713
|
+
"",
|
|
26714
|
+
"# inputs",
|
|
26715
|
+
serializeValue(meta.inputs),
|
|
26716
|
+
"",
|
|
26717
|
+
"# steps",
|
|
26718
|
+
""
|
|
26719
|
+
].join(`
|
|
26720
|
+
`);
|
|
26721
|
+
await writeFile2(this.filePath, header, "utf-8");
|
|
26722
|
+
}
|
|
26723
|
+
async writeStep(step) {
|
|
26724
|
+
if (!this.filePath)
|
|
26725
|
+
return;
|
|
26726
|
+
await appendFile(this.filePath, formatStep(step), "utf-8");
|
|
26727
|
+
}
|
|
26728
|
+
async finalize(meta) {
|
|
26729
|
+
if (!this.filePath)
|
|
26730
|
+
return;
|
|
26731
|
+
const outputSection = [
|
|
26732
|
+
"# output",
|
|
26733
|
+
serializeValue(meta.output),
|
|
26734
|
+
""
|
|
26735
|
+
].join(`
|
|
26736
|
+
`);
|
|
26737
|
+
await appendFile(this.filePath, outputSection, "utf-8");
|
|
26738
|
+
const content = await readFile2(this.filePath, "utf-8");
|
|
26739
|
+
const updated = content.replace(/^status: running$/m, `status: ${meta.status}
|
|
26740
|
+
finishedAt: ${meta.finishedAt}
|
|
26741
|
+
duration: ${meta.duration}ms`);
|
|
26742
|
+
await writeFile2(this.filePath, updated, "utf-8");
|
|
26743
|
+
}
|
|
26744
|
+
}
|
|
26745
|
+
var init_txt = () => {};
|
|
26746
|
+
|
|
26747
|
+
// src/tracer/writers/sqlite.ts
|
|
26748
|
+
var exports_sqlite = {};
|
|
26749
|
+
__export(exports_sqlite, {
|
|
26750
|
+
SqliteTraceWriter: () => SqliteTraceWriter
|
|
26751
|
+
});
|
|
26752
|
+
import { join as join3 } from "node:path";
|
|
26753
|
+
function toJson(value) {
|
|
26754
|
+
if (value === undefined || value === null)
|
|
26755
|
+
return null;
|
|
26756
|
+
if (typeof value === "string")
|
|
26757
|
+
return value;
|
|
26758
|
+
return JSON.stringify(value);
|
|
26759
|
+
}
|
|
26760
|
+
|
|
26761
|
+
class SqliteTraceWriter {
|
|
26762
|
+
dir;
|
|
26763
|
+
db = null;
|
|
26764
|
+
executionId = "";
|
|
26765
|
+
constructor(dir) {
|
|
26766
|
+
this.dir = dir;
|
|
26767
|
+
}
|
|
26768
|
+
async open(meta) {
|
|
26769
|
+
this.executionId = meta.id;
|
|
26770
|
+
const filePath = join3(this.dir, `${meta.id}.sqlite`);
|
|
26771
|
+
const { Database } = await import("bun:sqlite");
|
|
26772
|
+
this.db = new Database(filePath);
|
|
26773
|
+
this.db.exec(`
|
|
26774
|
+
CREATE TABLE IF NOT EXISTS executions (
|
|
26775
|
+
id TEXT PRIMARY KEY,
|
|
26776
|
+
workflow_id TEXT,
|
|
26777
|
+
workflow_name TEXT,
|
|
26778
|
+
workflow_version TEXT,
|
|
26779
|
+
started_at TEXT NOT NULL,
|
|
26780
|
+
finished_at TEXT,
|
|
26781
|
+
duration_ms INTEGER,
|
|
26782
|
+
status TEXT NOT NULL,
|
|
26783
|
+
inputs TEXT,
|
|
26784
|
+
output TEXT
|
|
26785
|
+
);
|
|
26786
|
+
|
|
26787
|
+
CREATE TABLE IF NOT EXISTS steps (
|
|
26788
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
26789
|
+
execution_id TEXT NOT NULL REFERENCES executions(id),
|
|
26790
|
+
seq INTEGER NOT NULL,
|
|
26791
|
+
name TEXT NOT NULL,
|
|
26792
|
+
type TEXT NOT NULL,
|
|
26793
|
+
started_at TEXT,
|
|
26794
|
+
finished_at TEXT,
|
|
26795
|
+
duration_ms INTEGER,
|
|
26796
|
+
status TEXT NOT NULL,
|
|
26797
|
+
input TEXT,
|
|
26798
|
+
output TEXT,
|
|
26799
|
+
error TEXT,
|
|
26800
|
+
retries INTEGER,
|
|
26801
|
+
loop_index INTEGER
|
|
26802
|
+
);
|
|
26803
|
+
`);
|
|
26804
|
+
const insert = this.db.prepare(`
|
|
26805
|
+
INSERT INTO executions (id, workflow_id, workflow_name, workflow_version, started_at, status, inputs)
|
|
26806
|
+
VALUES (?, ?, ?, ?, ?, 'running', ?)
|
|
26807
|
+
`);
|
|
26808
|
+
insert.run(meta.id, meta.workflowId ?? null, meta.workflowName ?? null, meta.workflowVersion !== undefined ? String(meta.workflowVersion) : null, meta.startedAt, toJson(meta.inputs));
|
|
26809
|
+
}
|
|
26810
|
+
writeStep(step) {
|
|
26811
|
+
if (!this.db)
|
|
26812
|
+
return;
|
|
26813
|
+
const insert = this.db.prepare(`
|
|
26814
|
+
INSERT INTO steps
|
|
26815
|
+
(execution_id, seq, name, type, started_at, finished_at, duration_ms, status, input, output, error, retries, loop_index)
|
|
26816
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
26817
|
+
`);
|
|
26818
|
+
insert.run(this.executionId, step.seq, step.name, step.type, step.startedAt ?? null, step.finishedAt ?? null, step.duration ?? null, step.status, toJson(step.input), toJson(step.output), step.error ?? null, step.retries ?? null, step.loopIndex ?? null);
|
|
26819
|
+
}
|
|
26820
|
+
async finalize(meta) {
|
|
26821
|
+
if (!this.db)
|
|
26822
|
+
return;
|
|
26823
|
+
const update = this.db.prepare(`
|
|
26824
|
+
UPDATE executions
|
|
26825
|
+
SET status = ?, output = ?, finished_at = ?, duration_ms = ?
|
|
26826
|
+
WHERE id = ?
|
|
26827
|
+
`);
|
|
26828
|
+
update.run(meta.status, toJson(meta.output), meta.finishedAt, Math.round(meta.duration), this.executionId);
|
|
26829
|
+
this.db.close();
|
|
26830
|
+
this.db = null;
|
|
26831
|
+
}
|
|
26832
|
+
}
|
|
26833
|
+
var init_sqlite = () => {};
|
|
26834
|
+
|
|
26593
26835
|
// src/engine/errors.ts
|
|
26594
26836
|
function sleep(ms) {
|
|
26595
26837
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
@@ -27189,6 +27431,10 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
|
|
|
27189
27431
|
output: "",
|
|
27190
27432
|
duration: 0
|
|
27191
27433
|
});
|
|
27434
|
+
const loopIndex2 = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
|
|
27435
|
+
const skippedSeq = state.tracer?.startStep(stepName, participant.type, undefined, loopIndex2);
|
|
27436
|
+
if (skippedSeq !== undefined)
|
|
27437
|
+
state.tracer?.endStep(skippedSeq, "skipped");
|
|
27192
27438
|
}
|
|
27193
27439
|
return chain;
|
|
27194
27440
|
}
|
|
@@ -27197,6 +27443,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
|
|
|
27197
27443
|
const overrideInput = override?.input !== undefined ? resolveParticipantInput(override.input, state) : undefined;
|
|
27198
27444
|
const mergedWithBase = mergeChainedInput(chain, baseInput);
|
|
27199
27445
|
const mergedInput = mergeChainedInput(mergedWithBase, overrideInput);
|
|
27446
|
+
const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
|
|
27447
|
+
const traceSeq = state.tracer?.startStep(stepName ?? "<anonymous>", participant.type, mergedInput, loopIndex);
|
|
27200
27448
|
state.currentInput = mergedInput;
|
|
27201
27449
|
const strategy = resolveErrorStrategy(override ?? null, participant, workflow.defaults ?? null);
|
|
27202
27450
|
const timeoutMs = resolveTimeout(override ?? null, participant, workflow.defaults ?? null);
|
|
@@ -27274,6 +27522,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
|
|
|
27274
27522
|
}
|
|
27275
27523
|
const outputValue = result.parsedOutput ?? result.output;
|
|
27276
27524
|
state.currentOutput = outputValue;
|
|
27525
|
+
if (traceSeq !== undefined)
|
|
27526
|
+
state.tracer?.endStep(traceSeq, result.status, outputValue, undefined, retries);
|
|
27277
27527
|
return outputValue;
|
|
27278
27528
|
} catch (error) {
|
|
27279
27529
|
const message = String(error?.message ?? error);
|
|
@@ -27299,6 +27549,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
|
|
|
27299
27549
|
if (stepName) {
|
|
27300
27550
|
state.setResult(stepName, skipResult);
|
|
27301
27551
|
}
|
|
27552
|
+
if (traceSeq !== undefined)
|
|
27553
|
+
state.tracer?.endStep(traceSeq, "skipped", undefined, message);
|
|
27302
27554
|
return chain;
|
|
27303
27555
|
}
|
|
27304
27556
|
if (strategy !== "fail" && strategy !== "retry") {
|
|
@@ -27317,6 +27569,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
|
|
|
27317
27569
|
...httpMeta
|
|
27318
27570
|
});
|
|
27319
27571
|
}
|
|
27572
|
+
if (traceSeq !== undefined)
|
|
27573
|
+
state.tracer?.endStep(traceSeq, "failure", undefined, message);
|
|
27320
27574
|
const fallbackResult = await executeStep(workflow, state, fallbackName, basePath, engineExecutor, [...fallbackStack, stepName ?? "<anonymous>"], chain, hub);
|
|
27321
27575
|
return fallbackResult;
|
|
27322
27576
|
}
|
|
@@ -27331,6 +27585,8 @@ async function executeStep(workflow, state, step, basePath = process.cwd(), engi
|
|
|
27331
27585
|
...httpMeta
|
|
27332
27586
|
});
|
|
27333
27587
|
}
|
|
27588
|
+
if (traceSeq !== undefined)
|
|
27589
|
+
state.tracer?.endStep(traceSeq, "failure", undefined, message);
|
|
27334
27590
|
throw error;
|
|
27335
27591
|
}
|
|
27336
27592
|
}
|
|
@@ -27443,21 +27699,38 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
|
|
|
27443
27699
|
}
|
|
27444
27700
|
const obj = step;
|
|
27445
27701
|
if ("wait" in obj && Object.keys(obj).length === 1) {
|
|
27446
|
-
const
|
|
27447
|
-
|
|
27702
|
+
const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
|
|
27703
|
+
const traceSeq = state.tracer?.startStep("wait", "wait", undefined, loopIndex);
|
|
27704
|
+
try {
|
|
27705
|
+
const { executeWait: executeWait2 } = await Promise.resolve().then(() => (init_wait(), exports_wait));
|
|
27706
|
+
const result = await executeWait2(state, obj.wait, chain, hub, signal);
|
|
27707
|
+
if (traceSeq !== undefined)
|
|
27708
|
+
state.tracer?.endStep(traceSeq, "success", result);
|
|
27709
|
+
return result;
|
|
27710
|
+
} catch (err) {
|
|
27711
|
+
if (traceSeq !== undefined)
|
|
27712
|
+
state.tracer?.endStep(traceSeq, "failure", undefined, String(err?.message ?? err));
|
|
27713
|
+
throw err;
|
|
27714
|
+
}
|
|
27448
27715
|
}
|
|
27449
27716
|
if ("set" in obj && Object.keys(obj).length === 1) {
|
|
27450
27717
|
const setDef = obj.set;
|
|
27451
27718
|
const ctx = state.toCelContext();
|
|
27719
|
+
const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
|
|
27720
|
+
const traceSeq = state.tracer?.startStep("set", "set", setDef, loopIndex);
|
|
27452
27721
|
if (!state.executionMeta.context) {
|
|
27453
27722
|
state.executionMeta.context = {};
|
|
27454
27723
|
}
|
|
27455
27724
|
for (const [key, expr] of Object.entries(setDef)) {
|
|
27456
27725
|
if (RESERVED_SET_KEYS.has(key)) {
|
|
27726
|
+
if (traceSeq !== undefined)
|
|
27727
|
+
state.tracer?.endStep(traceSeq, "failure", undefined, `set key '${key}' uses a reserved name`);
|
|
27457
27728
|
throw new Error(`set key '${key}' uses a reserved name`);
|
|
27458
27729
|
}
|
|
27459
27730
|
state.executionMeta.context[key] = evaluateCel(expr, ctx);
|
|
27460
27731
|
}
|
|
27732
|
+
if (traceSeq !== undefined)
|
|
27733
|
+
state.tracer?.endStep(traceSeq, "success", state.executionMeta.context);
|
|
27461
27734
|
return chain;
|
|
27462
27735
|
}
|
|
27463
27736
|
if ("loop" in obj && Object.keys(obj).length === 1) {
|
|
@@ -27474,6 +27747,8 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
|
|
|
27474
27747
|
maxIterations = loopDef.max ?? Number.POSITIVE_INFINITY;
|
|
27475
27748
|
}
|
|
27476
27749
|
const hasMax = loopDef.max !== undefined;
|
|
27750
|
+
const outerLoopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
|
|
27751
|
+
const loopTraceSeq = state.tracer?.startStep(loopAs ?? "loop", "loop", undefined, outerLoopIndex);
|
|
27477
27752
|
state.pushLoop(loopAs);
|
|
27478
27753
|
let loopChain = chain;
|
|
27479
27754
|
try {
|
|
@@ -27497,6 +27772,12 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
|
|
|
27497
27772
|
iterations += 1;
|
|
27498
27773
|
state.incrementLoop();
|
|
27499
27774
|
}
|
|
27775
|
+
if (loopTraceSeq !== undefined)
|
|
27776
|
+
state.tracer?.endStep(loopTraceSeq, "success", loopChain);
|
|
27777
|
+
} catch (err) {
|
|
27778
|
+
if (loopTraceSeq !== undefined)
|
|
27779
|
+
state.tracer?.endStep(loopTraceSeq, "failure", undefined, String(err?.message ?? err));
|
|
27780
|
+
throw err;
|
|
27500
27781
|
} finally {
|
|
27501
27782
|
state.popLoop();
|
|
27502
27783
|
}
|
|
@@ -27505,29 +27786,54 @@ async function executeControlStep(workflow, state, step, basePath = process.cwd(
|
|
|
27505
27786
|
if ("parallel" in obj && Object.keys(obj).length === 1) {
|
|
27506
27787
|
const parallelSteps = obj.parallel;
|
|
27507
27788
|
const controller = new AbortController;
|
|
27789
|
+
const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
|
|
27790
|
+
const parallelTraceSeq = state.tracer?.startStep("parallel", "parallel", undefined, loopIndex);
|
|
27508
27791
|
const branchSignal = signal ? AbortSignal.any([signal, controller.signal]) : controller.signal;
|
|
27509
|
-
|
|
27510
|
-
|
|
27511
|
-
|
|
27512
|
-
|
|
27513
|
-
|
|
27514
|
-
|
|
27515
|
-
|
|
27516
|
-
|
|
27517
|
-
|
|
27792
|
+
try {
|
|
27793
|
+
const results = await Promise.all(parallelSteps.map(async (parallelStep) => {
|
|
27794
|
+
try {
|
|
27795
|
+
return await executeControlStep(workflow, state, parallelStep, basePath, engineExecutor, chain, hub, branchSignal);
|
|
27796
|
+
} catch (error) {
|
|
27797
|
+
controller.abort();
|
|
27798
|
+
throw error;
|
|
27799
|
+
}
|
|
27800
|
+
}));
|
|
27801
|
+
if (parallelTraceSeq !== undefined)
|
|
27802
|
+
state.tracer?.endStep(parallelTraceSeq, "success", results);
|
|
27803
|
+
return results;
|
|
27804
|
+
} catch (err) {
|
|
27805
|
+
if (parallelTraceSeq !== undefined)
|
|
27806
|
+
state.tracer?.endStep(parallelTraceSeq, "failure", undefined, String(err?.message ?? err));
|
|
27807
|
+
throw err;
|
|
27808
|
+
}
|
|
27518
27809
|
}
|
|
27519
27810
|
if ("if" in obj && Object.keys(obj).length === 1) {
|
|
27520
27811
|
const ifDef = obj.if;
|
|
27521
27812
|
const condition = evaluateCel(ifDef.condition, state.toCelContext());
|
|
27813
|
+
const loopIndex = state.isInsideLoop() ? state.currentLoopIndex() : undefined;
|
|
27814
|
+
const ifTraceSeq = state.tracer?.startStep("if", "if", { condition: ifDef.condition }, loopIndex);
|
|
27522
27815
|
if (typeof condition !== "boolean") {
|
|
27816
|
+
if (ifTraceSeq !== undefined)
|
|
27817
|
+
state.tracer?.endStep(ifTraceSeq, "failure", undefined, `if.condition must evaluate to boolean, got ${typeof condition}`);
|
|
27523
27818
|
throw new Error(`if.condition must evaluate to boolean, got ${typeof condition}`);
|
|
27524
27819
|
}
|
|
27525
|
-
|
|
27526
|
-
|
|
27527
|
-
|
|
27528
|
-
|
|
27820
|
+
try {
|
|
27821
|
+
let result;
|
|
27822
|
+
if (condition) {
|
|
27823
|
+
result = await executeSequential(workflow, state, ifDef.then, basePath, engineExecutor, chain, hub, signal);
|
|
27824
|
+
} else if (ifDef.else) {
|
|
27825
|
+
result = await executeSequential(workflow, state, ifDef.else, basePath, engineExecutor, chain, hub, signal);
|
|
27826
|
+
} else {
|
|
27827
|
+
result = chain;
|
|
27828
|
+
}
|
|
27829
|
+
if (ifTraceSeq !== undefined)
|
|
27830
|
+
state.tracer?.endStep(ifTraceSeq, "success", result);
|
|
27831
|
+
return result;
|
|
27832
|
+
} catch (err) {
|
|
27833
|
+
if (ifTraceSeq !== undefined)
|
|
27834
|
+
state.tracer?.endStep(ifTraceSeq, "failure", undefined, String(err?.message ?? err));
|
|
27835
|
+
throw err;
|
|
27529
27836
|
}
|
|
27530
|
-
return chain;
|
|
27531
27837
|
}
|
|
27532
27838
|
return executeStep(workflow, state, step, basePath, engineExecutor, [], chain, hub, signal);
|
|
27533
27839
|
}
|
|
@@ -27814,6 +28120,82 @@ init_parser3();
|
|
|
27814
28120
|
init_schema();
|
|
27815
28121
|
init_validate();
|
|
27816
28122
|
import { dirname as dirname2, resolve as resolve3 } from "node:path";
|
|
28123
|
+
|
|
28124
|
+
// src/tracer/index.ts
|
|
28125
|
+
import { mkdir } from "node:fs/promises";
|
|
28126
|
+
|
|
28127
|
+
class TraceCollector {
|
|
28128
|
+
openSteps = new Map;
|
|
28129
|
+
seq = 0;
|
|
28130
|
+
truncateAt;
|
|
28131
|
+
writer;
|
|
28132
|
+
constructor(truncateAt = 1e6) {
|
|
28133
|
+
this.truncateAt = truncateAt;
|
|
28134
|
+
}
|
|
28135
|
+
startStep(name, type3, input, loopIndex) {
|
|
28136
|
+
this.seq += 1;
|
|
28137
|
+
this.openSteps.set(this.seq, {
|
|
28138
|
+
name,
|
|
28139
|
+
type: type3,
|
|
28140
|
+
startedAt: new Date().toISOString(),
|
|
28141
|
+
startMs: performance.now(),
|
|
28142
|
+
input: input !== undefined ? this.truncate(input) : undefined,
|
|
28143
|
+
loopIndex
|
|
28144
|
+
});
|
|
28145
|
+
return this.seq;
|
|
28146
|
+
}
|
|
28147
|
+
endStep(seq, status, output, error, retries) {
|
|
28148
|
+
const open = this.openSteps.get(seq);
|
|
28149
|
+
if (!open)
|
|
28150
|
+
return;
|
|
28151
|
+
this.openSteps.delete(seq);
|
|
28152
|
+
const finishedAt = new Date().toISOString();
|
|
28153
|
+
const duration = Math.max(0, performance.now() - open.startMs);
|
|
28154
|
+
const step = {
|
|
28155
|
+
seq,
|
|
28156
|
+
name: open.name,
|
|
28157
|
+
type: open.type,
|
|
28158
|
+
startedAt: open.startedAt,
|
|
28159
|
+
finishedAt,
|
|
28160
|
+
duration: Math.round(duration),
|
|
28161
|
+
status,
|
|
28162
|
+
...open.input !== undefined ? { input: open.input } : {},
|
|
28163
|
+
...output !== undefined ? { output: this.truncate(output) } : {},
|
|
28164
|
+
...error !== undefined ? { error } : {},
|
|
28165
|
+
...retries !== undefined && retries > 0 ? { retries } : {},
|
|
28166
|
+
...open.loopIndex !== undefined ? { loopIndex: open.loopIndex } : {}
|
|
28167
|
+
};
|
|
28168
|
+
this.writer?.writeStep(step);
|
|
28169
|
+
}
|
|
28170
|
+
truncate(value) {
|
|
28171
|
+
if (value == null)
|
|
28172
|
+
return value;
|
|
28173
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
28174
|
+
const bytes = new TextEncoder().encode(str);
|
|
28175
|
+
if (bytes.length <= this.truncateAt)
|
|
28176
|
+
return value;
|
|
28177
|
+
const cut = new TextDecoder().decode(bytes.slice(0, this.truncateAt));
|
|
28178
|
+
return cut + "...[truncated]";
|
|
28179
|
+
}
|
|
28180
|
+
}
|
|
28181
|
+
async function createTraceWriter(dir, format) {
|
|
28182
|
+
await mkdir(dir, { recursive: true });
|
|
28183
|
+
if (format === "json") {
|
|
28184
|
+
const { JsonTraceWriter: JsonTraceWriter2 } = await Promise.resolve().then(() => (init_json(), exports_json));
|
|
28185
|
+
return new JsonTraceWriter2(dir);
|
|
28186
|
+
}
|
|
28187
|
+
if (format === "txt") {
|
|
28188
|
+
const { TxtTraceWriter: TxtTraceWriter2 } = await Promise.resolve().then(() => (init_txt(), exports_txt));
|
|
28189
|
+
return new TxtTraceWriter2(dir);
|
|
28190
|
+
}
|
|
28191
|
+
if (format === "sqlite") {
|
|
28192
|
+
const { SqliteTraceWriter: SqliteTraceWriter2 } = await Promise.resolve().then(() => (init_sqlite(), exports_sqlite));
|
|
28193
|
+
return new SqliteTraceWriter2(dir);
|
|
28194
|
+
}
|
|
28195
|
+
throw new Error(`unknown trace format: ${format}`);
|
|
28196
|
+
}
|
|
28197
|
+
|
|
28198
|
+
// src/engine/engine.ts
|
|
27817
28199
|
init_control();
|
|
27818
28200
|
|
|
27819
28201
|
// src/engine/state.ts
|
|
@@ -27831,6 +28213,7 @@ class WorkflowState {
|
|
|
27831
28213
|
chainValue;
|
|
27832
28214
|
eventPayload;
|
|
27833
28215
|
ancestorPaths;
|
|
28216
|
+
tracer;
|
|
27834
28217
|
constructor(inputs = {}) {
|
|
27835
28218
|
this.inputs = { ...inputs };
|
|
27836
28219
|
this.workflowInputs = { ...inputs };
|
|
@@ -27881,6 +28264,9 @@ class WorkflowState {
|
|
|
27881
28264
|
if (top)
|
|
27882
28265
|
top.last = last2;
|
|
27883
28266
|
}
|
|
28267
|
+
isInsideLoop() {
|
|
28268
|
+
return this.loopStack.length > 0;
|
|
28269
|
+
}
|
|
27884
28270
|
currentLoopContext() {
|
|
27885
28271
|
const top = this.loopStack[this.loopStack.length - 1];
|
|
27886
28272
|
if (!top)
|
|
@@ -27971,38 +28357,66 @@ async function executeWorkflow(workflow, inputs = {}, basePath = process.cwd(),
|
|
|
27971
28357
|
if (options.cwd) {
|
|
27972
28358
|
state.executionMeta.cwd = options.cwd;
|
|
27973
28359
|
}
|
|
27974
|
-
const
|
|
28360
|
+
const startedAtMs = performance.now();
|
|
28361
|
+
const startedAtIso = state.executionMeta.startedAt;
|
|
27975
28362
|
if (options._ancestorPaths) {
|
|
27976
28363
|
state.ancestorPaths = options._ancestorPaths;
|
|
27977
28364
|
}
|
|
28365
|
+
if (options.traceDir && !options._ancestorPaths) {
|
|
28366
|
+
const collector = new TraceCollector;
|
|
28367
|
+
const writer = await createTraceWriter(options.traceDir, options.traceFormat ?? "json");
|
|
28368
|
+
collector.writer = writer;
|
|
28369
|
+
state.tracer = collector;
|
|
28370
|
+
await writer.open({
|
|
28371
|
+
id: state.executionMeta.id,
|
|
28372
|
+
workflowId: workflow.id,
|
|
28373
|
+
workflowName: workflow.name,
|
|
28374
|
+
workflowVersion: workflow.version,
|
|
28375
|
+
startedAt: startedAtIso,
|
|
28376
|
+
inputs: resolved
|
|
28377
|
+
});
|
|
28378
|
+
}
|
|
27978
28379
|
const engineExecutor = async (subWorkflow, subInputs, subBasePath) => {
|
|
27979
28380
|
return executeWorkflow(subWorkflow, subInputs, subBasePath, {
|
|
27980
28381
|
...options,
|
|
27981
28382
|
_ancestorPaths: state.ancestorPaths
|
|
27982
28383
|
});
|
|
27983
28384
|
};
|
|
27984
|
-
let chain;
|
|
27985
|
-
for (const step of workflow.flow) {
|
|
27986
|
-
chain = await executeControlStep(workflow, state, step, basePath, engineExecutor, chain, options.hub);
|
|
27987
|
-
}
|
|
27988
28385
|
let output;
|
|
27989
|
-
|
|
27990
|
-
|
|
27991
|
-
|
|
27992
|
-
|
|
28386
|
+
let success = false;
|
|
28387
|
+
try {
|
|
28388
|
+
let chain;
|
|
28389
|
+
for (const step of workflow.flow) {
|
|
28390
|
+
chain = await executeControlStep(workflow, state, step, basePath, engineExecutor, chain, options.hub);
|
|
28391
|
+
}
|
|
28392
|
+
if (workflow.output !== undefined) {
|
|
28393
|
+
output = state.resolveOutput(workflow.output, evaluateCel);
|
|
28394
|
+
if (typeof workflow.output === "object" && "schema" in workflow.output && "map" in workflow.output && typeof output === "object" && output !== null) {
|
|
28395
|
+
validateOutputSchema(workflow.output.schema, output);
|
|
28396
|
+
}
|
|
28397
|
+
} else {
|
|
28398
|
+
output = chain;
|
|
28399
|
+
}
|
|
28400
|
+
const steps = state.getAllResults();
|
|
28401
|
+
success = !Object.values(steps).some((step) => step.status === "failure");
|
|
28402
|
+
state.executionMeta.status = success ? "success" : "failure";
|
|
28403
|
+
return {
|
|
28404
|
+
success,
|
|
28405
|
+
output,
|
|
28406
|
+
steps,
|
|
28407
|
+
duration: Math.max(0, performance.now() - startedAtMs)
|
|
28408
|
+
};
|
|
28409
|
+
} finally {
|
|
28410
|
+
if (state.tracer?.writer) {
|
|
28411
|
+
const duration = Math.max(0, performance.now() - startedAtMs);
|
|
28412
|
+
await state.tracer.writer.finalize({
|
|
28413
|
+
status: success ? "success" : "failure",
|
|
28414
|
+
output: output ?? null,
|
|
28415
|
+
finishedAt: new Date().toISOString(),
|
|
28416
|
+
duration
|
|
28417
|
+
}).catch(() => {});
|
|
27993
28418
|
}
|
|
27994
|
-
} else {
|
|
27995
|
-
output = chain;
|
|
27996
28419
|
}
|
|
27997
|
-
const steps = state.getAllResults();
|
|
27998
|
-
const success = !Object.values(steps).some((step) => step.status === "failure");
|
|
27999
|
-
state.executionMeta.status = success ? "success" : "failure";
|
|
28000
|
-
return {
|
|
28001
|
-
success,
|
|
28002
|
-
output,
|
|
28003
|
-
steps,
|
|
28004
|
-
duration: Math.max(0, performance.now() - startedAt)
|
|
28005
|
-
};
|
|
28006
28420
|
}
|
|
28007
28421
|
async function runWorkflowFromFile(filePath, inputs = {}, options = {}) {
|
|
28008
28422
|
const workflow = await parseWorkflowFile(filePath);
|