@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/engine/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
|
}
|
|
@@ -27707,6 +28013,80 @@ function validateInputs(inputDefs, provided) {
|
|
|
27707
28013
|
};
|
|
27708
28014
|
}
|
|
27709
28015
|
|
|
28016
|
+
// src/tracer/index.ts
|
|
28017
|
+
import { mkdir } from "node:fs/promises";
|
|
28018
|
+
|
|
28019
|
+
class TraceCollector {
|
|
28020
|
+
openSteps = new Map;
|
|
28021
|
+
seq = 0;
|
|
28022
|
+
truncateAt;
|
|
28023
|
+
writer;
|
|
28024
|
+
constructor(truncateAt = 1e6) {
|
|
28025
|
+
this.truncateAt = truncateAt;
|
|
28026
|
+
}
|
|
28027
|
+
startStep(name, type3, input, loopIndex) {
|
|
28028
|
+
this.seq += 1;
|
|
28029
|
+
this.openSteps.set(this.seq, {
|
|
28030
|
+
name,
|
|
28031
|
+
type: type3,
|
|
28032
|
+
startedAt: new Date().toISOString(),
|
|
28033
|
+
startMs: performance.now(),
|
|
28034
|
+
input: input !== undefined ? this.truncate(input) : undefined,
|
|
28035
|
+
loopIndex
|
|
28036
|
+
});
|
|
28037
|
+
return this.seq;
|
|
28038
|
+
}
|
|
28039
|
+
endStep(seq, status, output, error, retries) {
|
|
28040
|
+
const open = this.openSteps.get(seq);
|
|
28041
|
+
if (!open)
|
|
28042
|
+
return;
|
|
28043
|
+
this.openSteps.delete(seq);
|
|
28044
|
+
const finishedAt = new Date().toISOString();
|
|
28045
|
+
const duration = Math.max(0, performance.now() - open.startMs);
|
|
28046
|
+
const step = {
|
|
28047
|
+
seq,
|
|
28048
|
+
name: open.name,
|
|
28049
|
+
type: open.type,
|
|
28050
|
+
startedAt: open.startedAt,
|
|
28051
|
+
finishedAt,
|
|
28052
|
+
duration: Math.round(duration),
|
|
28053
|
+
status,
|
|
28054
|
+
...open.input !== undefined ? { input: open.input } : {},
|
|
28055
|
+
...output !== undefined ? { output: this.truncate(output) } : {},
|
|
28056
|
+
...error !== undefined ? { error } : {},
|
|
28057
|
+
...retries !== undefined && retries > 0 ? { retries } : {},
|
|
28058
|
+
...open.loopIndex !== undefined ? { loopIndex: open.loopIndex } : {}
|
|
28059
|
+
};
|
|
28060
|
+
this.writer?.writeStep(step);
|
|
28061
|
+
}
|
|
28062
|
+
truncate(value) {
|
|
28063
|
+
if (value == null)
|
|
28064
|
+
return value;
|
|
28065
|
+
const str = typeof value === "string" ? value : JSON.stringify(value);
|
|
28066
|
+
const bytes = new TextEncoder().encode(str);
|
|
28067
|
+
if (bytes.length <= this.truncateAt)
|
|
28068
|
+
return value;
|
|
28069
|
+
const cut = new TextDecoder().decode(bytes.slice(0, this.truncateAt));
|
|
28070
|
+
return cut + "...[truncated]";
|
|
28071
|
+
}
|
|
28072
|
+
}
|
|
28073
|
+
async function createTraceWriter(dir, format) {
|
|
28074
|
+
await mkdir(dir, { recursive: true });
|
|
28075
|
+
if (format === "json") {
|
|
28076
|
+
const { JsonTraceWriter: JsonTraceWriter2 } = await Promise.resolve().then(() => (init_json(), exports_json));
|
|
28077
|
+
return new JsonTraceWriter2(dir);
|
|
28078
|
+
}
|
|
28079
|
+
if (format === "txt") {
|
|
28080
|
+
const { TxtTraceWriter: TxtTraceWriter2 } = await Promise.resolve().then(() => (init_txt(), exports_txt));
|
|
28081
|
+
return new TxtTraceWriter2(dir);
|
|
28082
|
+
}
|
|
28083
|
+
if (format === "sqlite") {
|
|
28084
|
+
const { SqliteTraceWriter: SqliteTraceWriter2 } = await Promise.resolve().then(() => (init_sqlite(), exports_sqlite));
|
|
28085
|
+
return new SqliteTraceWriter2(dir);
|
|
28086
|
+
}
|
|
28087
|
+
throw new Error(`unknown trace format: ${format}`);
|
|
28088
|
+
}
|
|
28089
|
+
|
|
27710
28090
|
// src/engine/engine.ts
|
|
27711
28091
|
init_control();
|
|
27712
28092
|
|
|
@@ -27725,6 +28105,7 @@ class WorkflowState {
|
|
|
27725
28105
|
chainValue;
|
|
27726
28106
|
eventPayload;
|
|
27727
28107
|
ancestorPaths;
|
|
28108
|
+
tracer;
|
|
27728
28109
|
constructor(inputs = {}) {
|
|
27729
28110
|
this.inputs = { ...inputs };
|
|
27730
28111
|
this.workflowInputs = { ...inputs };
|
|
@@ -27775,6 +28156,9 @@ class WorkflowState {
|
|
|
27775
28156
|
if (top)
|
|
27776
28157
|
top.last = last2;
|
|
27777
28158
|
}
|
|
28159
|
+
isInsideLoop() {
|
|
28160
|
+
return this.loopStack.length > 0;
|
|
28161
|
+
}
|
|
27778
28162
|
currentLoopContext() {
|
|
27779
28163
|
const top = this.loopStack[this.loopStack.length - 1];
|
|
27780
28164
|
if (!top)
|
|
@@ -27865,38 +28249,66 @@ async function executeWorkflow(workflow, inputs = {}, basePath = process.cwd(),
|
|
|
27865
28249
|
if (options.cwd) {
|
|
27866
28250
|
state.executionMeta.cwd = options.cwd;
|
|
27867
28251
|
}
|
|
27868
|
-
const
|
|
28252
|
+
const startedAtMs = performance.now();
|
|
28253
|
+
const startedAtIso = state.executionMeta.startedAt;
|
|
27869
28254
|
if (options._ancestorPaths) {
|
|
27870
28255
|
state.ancestorPaths = options._ancestorPaths;
|
|
27871
28256
|
}
|
|
28257
|
+
if (options.traceDir && !options._ancestorPaths) {
|
|
28258
|
+
const collector = new TraceCollector;
|
|
28259
|
+
const writer = await createTraceWriter(options.traceDir, options.traceFormat ?? "json");
|
|
28260
|
+
collector.writer = writer;
|
|
28261
|
+
state.tracer = collector;
|
|
28262
|
+
await writer.open({
|
|
28263
|
+
id: state.executionMeta.id,
|
|
28264
|
+
workflowId: workflow.id,
|
|
28265
|
+
workflowName: workflow.name,
|
|
28266
|
+
workflowVersion: workflow.version,
|
|
28267
|
+
startedAt: startedAtIso,
|
|
28268
|
+
inputs: resolved
|
|
28269
|
+
});
|
|
28270
|
+
}
|
|
27872
28271
|
const engineExecutor = async (subWorkflow, subInputs, subBasePath) => {
|
|
27873
28272
|
return executeWorkflow(subWorkflow, subInputs, subBasePath, {
|
|
27874
28273
|
...options,
|
|
27875
28274
|
_ancestorPaths: state.ancestorPaths
|
|
27876
28275
|
});
|
|
27877
28276
|
};
|
|
27878
|
-
let chain;
|
|
27879
|
-
for (const step of workflow.flow) {
|
|
27880
|
-
chain = await executeControlStep(workflow, state, step, basePath, engineExecutor, chain, options.hub);
|
|
27881
|
-
}
|
|
27882
28277
|
let output;
|
|
27883
|
-
|
|
27884
|
-
|
|
27885
|
-
|
|
27886
|
-
|
|
28278
|
+
let success = false;
|
|
28279
|
+
try {
|
|
28280
|
+
let chain;
|
|
28281
|
+
for (const step of workflow.flow) {
|
|
28282
|
+
chain = await executeControlStep(workflow, state, step, basePath, engineExecutor, chain, options.hub);
|
|
28283
|
+
}
|
|
28284
|
+
if (workflow.output !== undefined) {
|
|
28285
|
+
output = state.resolveOutput(workflow.output, evaluateCel);
|
|
28286
|
+
if (typeof workflow.output === "object" && "schema" in workflow.output && "map" in workflow.output && typeof output === "object" && output !== null) {
|
|
28287
|
+
validateOutputSchema(workflow.output.schema, output);
|
|
28288
|
+
}
|
|
28289
|
+
} else {
|
|
28290
|
+
output = chain;
|
|
28291
|
+
}
|
|
28292
|
+
const steps = state.getAllResults();
|
|
28293
|
+
success = !Object.values(steps).some((step) => step.status === "failure");
|
|
28294
|
+
state.executionMeta.status = success ? "success" : "failure";
|
|
28295
|
+
return {
|
|
28296
|
+
success,
|
|
28297
|
+
output,
|
|
28298
|
+
steps,
|
|
28299
|
+
duration: Math.max(0, performance.now() - startedAtMs)
|
|
28300
|
+
};
|
|
28301
|
+
} finally {
|
|
28302
|
+
if (state.tracer?.writer) {
|
|
28303
|
+
const duration = Math.max(0, performance.now() - startedAtMs);
|
|
28304
|
+
await state.tracer.writer.finalize({
|
|
28305
|
+
status: success ? "success" : "failure",
|
|
28306
|
+
output: output ?? null,
|
|
28307
|
+
finishedAt: new Date().toISOString(),
|
|
28308
|
+
duration
|
|
28309
|
+
}).catch(() => {});
|
|
27887
28310
|
}
|
|
27888
|
-
} else {
|
|
27889
|
-
output = chain;
|
|
27890
28311
|
}
|
|
27891
|
-
const steps = state.getAllResults();
|
|
27892
|
-
const success = !Object.values(steps).some((step) => step.status === "failure");
|
|
27893
|
-
state.executionMeta.status = success ? "success" : "failure";
|
|
27894
|
-
return {
|
|
27895
|
-
success,
|
|
27896
|
-
output,
|
|
27897
|
-
steps,
|
|
27898
|
-
duration: Math.max(0, performance.now() - startedAt)
|
|
27899
|
-
};
|
|
27900
28312
|
}
|
|
27901
28313
|
async function runWorkflowFromFile(filePath, inputs = {}, options = {}) {
|
|
27902
28314
|
const workflow = await parseWorkflowFile(filePath);
|