@kb-labs/workflow-engine 2.94.0 → 2.96.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/index.d.ts +55 -143
- package/dist/index.js +309 -311
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { readFile, mkdir, writeFile, readdir } from 'fs/promises';
|
|
1
|
+
import { readFile, access, mkdir, writeFile, readdir } from 'fs/promises';
|
|
2
2
|
import { resolve, join, basename } from 'path';
|
|
3
3
|
import { parse, stringify } from 'yaml';
|
|
4
|
-
import { WorkflowSpecSchema } from '@kb-labs/workflow-contracts';
|
|
4
|
+
import { WorkflowSpecSchema, evaluateExpression } from '@kb-labs/workflow-contracts';
|
|
5
5
|
import { randomUUID } from 'crypto';
|
|
6
6
|
import { WORKFLOW_REDIS_CHANNEL, EVENT_NAMES, IDEMPOTENCY_TTL_ENV, CONCURRENCY_TTL_ENV } from '@kb-labs/workflow-constants';
|
|
7
7
|
import { createFileSystemArtifactClient } from '@kb-labs/workflow-artifacts';
|
|
@@ -317,7 +317,8 @@ var RunCoordinator = class {
|
|
|
317
317
|
needs,
|
|
318
318
|
pendingDependencies: [...needs],
|
|
319
319
|
blocked: needs.length > 0,
|
|
320
|
-
priority
|
|
320
|
+
priority,
|
|
321
|
+
if: jobSpec.if
|
|
321
322
|
});
|
|
322
323
|
}
|
|
323
324
|
const workflowRun = {
|
|
@@ -857,7 +858,38 @@ var WorkflowEngine = class {
|
|
|
857
858
|
this.logger.info("Job re-queued for retry", { runId, jobId });
|
|
858
859
|
}, backoffMs);
|
|
859
860
|
} else {
|
|
860
|
-
await this.
|
|
861
|
+
await this.deadLetterJob(runId, jobId, error);
|
|
862
|
+
await this.cancelDownstreamJobs(runId, job.jobName, `upstream job "${job.jobName}" failed`);
|
|
863
|
+
await this.checkRunCompletion(runId);
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
/**
|
|
867
|
+
* Recursively cancel jobs blocked on a failed/cancelled upstream job, so the
|
|
868
|
+
* DAG does not leave dependents stuck in 'queued'. Cancellation cascades:
|
|
869
|
+
* a cancelled job also cancels its own dependents.
|
|
870
|
+
*/
|
|
871
|
+
async cancelDownstreamJobs(runId, failedJobName, reason) {
|
|
872
|
+
const released = await this.stateStore.releaseBlockedJobs(runId, failedJobName);
|
|
873
|
+
for (const downstreamJob of released) {
|
|
874
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
875
|
+
await this.stateStore.updateJob(runId, downstreamJob.id, (draft) => {
|
|
876
|
+
draft.status = "cancelled";
|
|
877
|
+
draft.blocked = false;
|
|
878
|
+
draft.finishedAt = now;
|
|
879
|
+
draft.error = { message: reason, timestamp: now };
|
|
880
|
+
});
|
|
881
|
+
await this.events.publish({
|
|
882
|
+
type: EVENT_NAMES.job.cancelled,
|
|
883
|
+
runId,
|
|
884
|
+
jobId: downstreamJob.id,
|
|
885
|
+
payload: { jobName: downstreamJob.jobName, reason }
|
|
886
|
+
});
|
|
887
|
+
this.logger.info("Cancelled dependent job after upstream failure", {
|
|
888
|
+
runId,
|
|
889
|
+
jobId: downstreamJob.id,
|
|
890
|
+
unlockedBy: failedJobName
|
|
891
|
+
});
|
|
892
|
+
await this.cancelDownstreamJobs(runId, downstreamJob.jobName, reason);
|
|
861
893
|
}
|
|
862
894
|
}
|
|
863
895
|
/**
|
|
@@ -879,7 +911,15 @@ var WorkflowEngine = class {
|
|
|
879
911
|
draft.status = "running";
|
|
880
912
|
draft.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
881
913
|
});
|
|
882
|
-
this.
|
|
914
|
+
await this.stateStore.updateRun(runId, (draft) => {
|
|
915
|
+
if (draft.status === "queued") {
|
|
916
|
+
draft.status = "running";
|
|
917
|
+
if (!draft.startedAt) {
|
|
918
|
+
draft.startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
});
|
|
922
|
+
this.logger.info("Job started", { runId, jobId });
|
|
883
923
|
const run = await this.getRun(runId);
|
|
884
924
|
const job = run?.jobs.find((j) => j.id === jobId);
|
|
885
925
|
this.analytics?.track("workflow.job.started", {
|
|
@@ -924,14 +964,65 @@ var WorkflowEngine = class {
|
|
|
924
964
|
payload: { jobName: job?.jobName, durationMs: duration }
|
|
925
965
|
});
|
|
926
966
|
if (job?.jobName) {
|
|
967
|
+
const run2 = await this.stateStore.getRun(runId);
|
|
968
|
+
const exprCtx = this.buildExpressionContext(run2);
|
|
927
969
|
const released = await this.stateStore.releaseBlockedJobs(runId, job.jobName);
|
|
928
970
|
for (const releasedJob of released) {
|
|
929
|
-
|
|
930
|
-
|
|
971
|
+
if (releasedJob.if && !this.evaluateJobIf(releasedJob.if, exprCtx)) {
|
|
972
|
+
await this.skipJob(runId, releasedJob);
|
|
973
|
+
} else {
|
|
974
|
+
await this.scheduler.enqueueJob(runId, releasedJob, releasedJob.priority ?? "normal");
|
|
975
|
+
this.logger.info("Unblocked dependent job", { runId, jobId: releasedJob.id, unlockedBy: job.jobName });
|
|
976
|
+
}
|
|
931
977
|
}
|
|
932
978
|
}
|
|
933
979
|
await this.checkRunCompletion(runId);
|
|
934
980
|
}
|
|
981
|
+
/** Build a minimal ExpressionContext from run state (for job-level if evaluation). */
|
|
982
|
+
buildExpressionContext(run) {
|
|
983
|
+
const ctx = {
|
|
984
|
+
env: run?.env ?? {},
|
|
985
|
+
trigger: run?.trigger ?? { type: "manual" },
|
|
986
|
+
inputs: run?.inputs ?? {},
|
|
987
|
+
steps: {}
|
|
988
|
+
};
|
|
989
|
+
if (run) {
|
|
990
|
+
for (const j of run.jobs) {
|
|
991
|
+
for (const s of j.steps) {
|
|
992
|
+
if (s.status === "success" && s.spec.id) {
|
|
993
|
+
ctx.steps[s.spec.id] = { outputs: s.outputs ?? {} };
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
return ctx;
|
|
999
|
+
}
|
|
1000
|
+
/** Evaluate a job-level `if:` expression. Strips ${{ }} wrapper if present. */
|
|
1001
|
+
evaluateJobIf(condition, ctx) {
|
|
1002
|
+
const raw = condition.trim().replace(/^\$\{\{\s*/, "").replace(/\s*\}\}$/, "");
|
|
1003
|
+
return evaluateExpression(raw, ctx);
|
|
1004
|
+
}
|
|
1005
|
+
/** Mark a job as skipped (success) and release any jobs blocked on it. */
|
|
1006
|
+
async skipJob(runId, job) {
|
|
1007
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1008
|
+
await this.stateStore.updateJob(runId, job.id, (draft) => {
|
|
1009
|
+
draft.status = "success";
|
|
1010
|
+
draft.startedAt = now;
|
|
1011
|
+
draft.finishedAt = now;
|
|
1012
|
+
});
|
|
1013
|
+
this.logger.info("Job skipped: if condition false", { runId, jobId: job.id, condition: job.if });
|
|
1014
|
+
const downstream = await this.stateStore.releaseBlockedJobs(runId, job.jobName);
|
|
1015
|
+
const run = await this.stateStore.getRun(runId);
|
|
1016
|
+
const exprCtx = this.buildExpressionContext(run);
|
|
1017
|
+
for (const downstreamJob of downstream) {
|
|
1018
|
+
if (downstreamJob.if && !this.evaluateJobIf(downstreamJob.if, exprCtx)) {
|
|
1019
|
+
await this.skipJob(runId, downstreamJob);
|
|
1020
|
+
} else {
|
|
1021
|
+
await this.scheduler.enqueueJob(runId, downstreamJob, downstreamJob.priority ?? "normal");
|
|
1022
|
+
this.logger.info("Unblocked dependent job", { runId, jobId: downstreamJob.id, unlockedBy: job.jobName });
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
935
1026
|
/**
|
|
936
1027
|
* Check if all jobs in a run are completed and update run status accordingly.
|
|
937
1028
|
*/
|
|
@@ -966,9 +1057,20 @@ var WorkflowEngine = class {
|
|
|
966
1057
|
payload: { status: "success", name: run.name }
|
|
967
1058
|
});
|
|
968
1059
|
} else if (anyFailed) {
|
|
1060
|
+
const failedJob = run.jobs.find((j) => j.status === "failed");
|
|
969
1061
|
await this.stateStore.updateRun(runId, (draft) => {
|
|
970
1062
|
draft.status = "failed";
|
|
971
1063
|
draft.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1064
|
+
if (failedJob?.error) {
|
|
1065
|
+
draft.result = {
|
|
1066
|
+
status: "failed",
|
|
1067
|
+
summary: `Job ${failedJob.jobName} failed`,
|
|
1068
|
+
error: {
|
|
1069
|
+
message: failedJob.error.message,
|
|
1070
|
+
details: failedJob.error.stack ? { stack: failedJob.error.stack } : void 0
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
}
|
|
972
1074
|
return draft;
|
|
973
1075
|
});
|
|
974
1076
|
this.logger.info("Workflow run failed", { runId });
|
|
@@ -1215,22 +1317,14 @@ var WorkflowEngine = class {
|
|
|
1215
1317
|
/**
|
|
1216
1318
|
* Move permanently failed job to Dead Letter Queue.
|
|
1217
1319
|
*/
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
message: error.message,
|
|
1227
|
-
details: {
|
|
1228
|
-
stack: error.stack
|
|
1229
|
-
}
|
|
1230
|
-
}
|
|
1231
|
-
};
|
|
1232
|
-
draft.finishedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1233
|
-
});
|
|
1320
|
+
/**
|
|
1321
|
+
* Record a dead-letter entry for a job that exhausted its retries. This is an
|
|
1322
|
+
* observability artifact only — it does NOT change the run status. The run
|
|
1323
|
+
* outcome is decided by checkRunCompletion (→ 'failed'). 'dlq' as a run status
|
|
1324
|
+
* is reserved for infrastructure failures and is no longer set here (B-029).
|
|
1325
|
+
*/
|
|
1326
|
+
async deadLetterJob(runId, jobId, error) {
|
|
1327
|
+
this.logger.warn("Job dead-lettered after max retries", { runId, jobId });
|
|
1234
1328
|
const dlqKey = `workflow:dlq:${runId}:${jobId}`;
|
|
1235
1329
|
const run = await this.stateStore.getRun(runId);
|
|
1236
1330
|
const job = run?.jobs.find((j) => j.id === jobId);
|
|
@@ -1250,10 +1344,6 @@ var WorkflowEngine = class {
|
|
|
1250
1344
|
7 * 24 * 60 * 60 * 1e3
|
|
1251
1345
|
// TTL 7 days
|
|
1252
1346
|
);
|
|
1253
|
-
const updatedRun = await this.stateStore.getRun(runId);
|
|
1254
|
-
if (updatedRun) {
|
|
1255
|
-
await this.publishRunEvent(EVENT_NAMES.run.failed, updatedRun);
|
|
1256
|
-
}
|
|
1257
1347
|
}
|
|
1258
1348
|
async updateRun(runId, mutator) {
|
|
1259
1349
|
const updated = await this.stateStore.updateRun(runId, mutator);
|
|
@@ -1280,6 +1370,19 @@ var WorkflowEngine = class {
|
|
|
1280
1370
|
status === "failed" ? EVENT_NAMES.run.failed : status === "cancelled" ? EVENT_NAMES.run.cancelled : EVENT_NAMES.run.finished,
|
|
1281
1371
|
updated
|
|
1282
1372
|
);
|
|
1373
|
+
if (status === "success" || status === "failed") {
|
|
1374
|
+
const stepOutputs = {};
|
|
1375
|
+
for (const job of updated.jobs) {
|
|
1376
|
+
for (const step of job.steps) {
|
|
1377
|
+
if (step.outputs && Object.keys(step.outputs).length > 0) {
|
|
1378
|
+
stepOutputs[step.id] = step.outputs;
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
await this.snapshotStorage.createSnapshot(updated, stepOutputs, updated.env ?? {}).catch((err) => {
|
|
1383
|
+
this.logger.warn("Failed to create run snapshot", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
1384
|
+
});
|
|
1385
|
+
}
|
|
1283
1386
|
}
|
|
1284
1387
|
return updated;
|
|
1285
1388
|
}
|
|
@@ -1303,13 +1406,13 @@ var WorkflowEngine = class {
|
|
|
1303
1406
|
/**
|
|
1304
1407
|
* Publish a log entry for real-time streaming to Studio UI.
|
|
1305
1408
|
*/
|
|
1306
|
-
async publishLog(runId, jobId, stepId, entry) {
|
|
1409
|
+
async publishLog(runId, jobId, stepId, entry, stepName) {
|
|
1307
1410
|
await this.events.publish({
|
|
1308
1411
|
type: EVENT_NAMES.log.appended,
|
|
1309
1412
|
runId,
|
|
1310
1413
|
jobId,
|
|
1311
1414
|
stepId,
|
|
1312
|
-
payload: entry
|
|
1415
|
+
payload: stepName ? { ...entry, stepName } : entry
|
|
1313
1416
|
});
|
|
1314
1417
|
}
|
|
1315
1418
|
/**
|
|
@@ -1363,16 +1466,22 @@ var WorkflowEngine = class {
|
|
|
1363
1466
|
restoredRun.env = snapshot.env;
|
|
1364
1467
|
}
|
|
1365
1468
|
if (options.fromStepId) {
|
|
1469
|
+
const stepExists = restoredRun.jobs.some(
|
|
1470
|
+
(job) => job.steps.some((s) => s.id === options.fromStepId)
|
|
1471
|
+
);
|
|
1472
|
+
if (!stepExists) {
|
|
1473
|
+
throw new Error(`Step not found: ${options.fromStepId}`);
|
|
1474
|
+
}
|
|
1475
|
+
}
|
|
1476
|
+
if (options.fromStepId) {
|
|
1477
|
+
let foundStep = false;
|
|
1366
1478
|
for (const job of restoredRun.jobs) {
|
|
1367
|
-
let foundStep = false;
|
|
1368
1479
|
for (const step of job.steps) {
|
|
1369
1480
|
if (step.id === options.fromStepId) {
|
|
1370
1481
|
foundStep = true;
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
step.finishedAt = void 0;
|
|
1375
|
-
}
|
|
1482
|
+
step.status = "queued";
|
|
1483
|
+
step.startedAt = void 0;
|
|
1484
|
+
step.finishedAt = void 0;
|
|
1376
1485
|
continue;
|
|
1377
1486
|
}
|
|
1378
1487
|
if (!foundStep) {
|
|
@@ -1396,16 +1505,41 @@ var WorkflowEngine = class {
|
|
|
1396
1505
|
}
|
|
1397
1506
|
}
|
|
1398
1507
|
}
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1508
|
+
const isJobComplete = (job) => job.steps.every((s) => s.status === "success" || s.status === "skipped");
|
|
1509
|
+
for (const job of restoredRun.jobs) {
|
|
1510
|
+
if (!job.needs || job.needs.length === 0) {
|
|
1511
|
+
job.blocked = false;
|
|
1512
|
+
job.pendingDependencies = [];
|
|
1513
|
+
continue;
|
|
1514
|
+
}
|
|
1515
|
+
const pendingDeps = job.needs.filter((depName) => {
|
|
1516
|
+
const depJob = restoredRun.jobs.find((j) => j.jobName === depName);
|
|
1517
|
+
return depJob !== void 0 && !isJobComplete(depJob);
|
|
1518
|
+
});
|
|
1519
|
+
job.pendingDependencies = pendingDeps;
|
|
1520
|
+
job.blocked = pendingDeps.length > 0;
|
|
1521
|
+
}
|
|
1522
|
+
const newRunId = randomUUID();
|
|
1523
|
+
const newNow = (/* @__PURE__ */ new Date()).toISOString();
|
|
1524
|
+
const newRun = {
|
|
1525
|
+
...restoredRun,
|
|
1526
|
+
id: newRunId,
|
|
1527
|
+
status: "queued",
|
|
1528
|
+
createdAt: newNow,
|
|
1529
|
+
queuedAt: newNow,
|
|
1530
|
+
startedAt: void 0,
|
|
1531
|
+
finishedAt: void 0,
|
|
1532
|
+
durationMs: void 0,
|
|
1533
|
+
result: void 0
|
|
1534
|
+
};
|
|
1535
|
+
await this.stateStore.saveRun(newRun);
|
|
1536
|
+
await this.scheduler.scheduleRun(newRun);
|
|
1537
|
+
this.logger.info("Run replayed as new run from snapshot", {
|
|
1538
|
+
originalRunId: runId,
|
|
1539
|
+
newRunId,
|
|
1406
1540
|
fromStepId: options.fromStepId
|
|
1407
1541
|
});
|
|
1408
|
-
return
|
|
1542
|
+
return newRun;
|
|
1409
1543
|
}
|
|
1410
1544
|
/**
|
|
1411
1545
|
* Delete a snapshot
|
|
@@ -1688,11 +1822,82 @@ var ArtifactMerger = class {
|
|
|
1688
1822
|
}
|
|
1689
1823
|
};
|
|
1690
1824
|
|
|
1825
|
+
// src/manifest-converter.ts
|
|
1826
|
+
var ManifestConverter = class {
|
|
1827
|
+
convertWorkflowHandler(pluginId, handler, pluginRoot) {
|
|
1828
|
+
const id = `${pluginId}/${handler.id}`;
|
|
1829
|
+
return {
|
|
1830
|
+
kind: "workflow",
|
|
1831
|
+
id,
|
|
1832
|
+
source: "manifest",
|
|
1833
|
+
pluginId,
|
|
1834
|
+
manifestPath: pluginRoot,
|
|
1835
|
+
name: handler.describe ?? handler.id,
|
|
1836
|
+
description: handler.describe,
|
|
1837
|
+
tags: ["plugin", pluginId],
|
|
1838
|
+
triggers: [{ type: "manual" }],
|
|
1839
|
+
handler: handler.handler,
|
|
1840
|
+
status: "active",
|
|
1841
|
+
permissions: handler.permissions,
|
|
1842
|
+
input: handler.input,
|
|
1843
|
+
output: handler.output
|
|
1844
|
+
};
|
|
1845
|
+
}
|
|
1846
|
+
convertJobHandler(pluginId, handler, pluginRoot) {
|
|
1847
|
+
const id = `${pluginId}:job:${handler.id}`;
|
|
1848
|
+
return {
|
|
1849
|
+
kind: "job",
|
|
1850
|
+
id,
|
|
1851
|
+
source: "manifest",
|
|
1852
|
+
pluginId,
|
|
1853
|
+
manifestPath: pluginRoot,
|
|
1854
|
+
name: handler.describe ?? handler.id,
|
|
1855
|
+
description: handler.describe,
|
|
1856
|
+
tags: ["plugin", "job", pluginId],
|
|
1857
|
+
triggers: [{ type: "manual" }],
|
|
1858
|
+
handler: handler.handler,
|
|
1859
|
+
status: "active",
|
|
1860
|
+
permissions: handler.permissions,
|
|
1861
|
+
input: handler.input,
|
|
1862
|
+
output: handler.output
|
|
1863
|
+
};
|
|
1864
|
+
}
|
|
1865
|
+
convertCronSchedule(pluginId, cronDecl, pluginRoot) {
|
|
1866
|
+
const id = `${pluginId}:cron:${cronDecl.id}`;
|
|
1867
|
+
const schedule = {
|
|
1868
|
+
cron: cronDecl.schedule,
|
|
1869
|
+
enabled: cronDecl.enabled ?? true
|
|
1870
|
+
};
|
|
1871
|
+
return {
|
|
1872
|
+
kind: "cron",
|
|
1873
|
+
id,
|
|
1874
|
+
source: "manifest",
|
|
1875
|
+
pluginId,
|
|
1876
|
+
manifestPath: pluginRoot,
|
|
1877
|
+
name: cronDecl.describe ?? cronDecl.id,
|
|
1878
|
+
description: cronDecl.describe,
|
|
1879
|
+
tags: ["plugin", "cron", pluginId],
|
|
1880
|
+
triggers: [
|
|
1881
|
+
{
|
|
1882
|
+
type: "schedule",
|
|
1883
|
+
config: { cron: cronDecl.schedule, timezone: cronDecl.timezone }
|
|
1884
|
+
}
|
|
1885
|
+
],
|
|
1886
|
+
handler: void 0,
|
|
1887
|
+
schedule,
|
|
1888
|
+
status: cronDecl.enabled ?? true ? "active" : "disabled",
|
|
1889
|
+
permissions: cronDecl.permissions
|
|
1890
|
+
};
|
|
1891
|
+
}
|
|
1892
|
+
};
|
|
1893
|
+
|
|
1691
1894
|
// src/manifest-scanner.ts
|
|
1895
|
+
var CACHE_KEY_WORKFLOWS = "manifest-scanner:v1:workflows";
|
|
1692
1896
|
var ManifestScanner = class {
|
|
1693
1897
|
cliApi;
|
|
1694
1898
|
platform;
|
|
1695
1899
|
cacheTtlMs;
|
|
1900
|
+
converter = new ManifestConverter();
|
|
1696
1901
|
constructor(options) {
|
|
1697
1902
|
this.cliApi = options.cliApi;
|
|
1698
1903
|
this.platform = options.platform;
|
|
@@ -1700,13 +1905,14 @@ var ManifestScanner = class {
|
|
|
1700
1905
|
}
|
|
1701
1906
|
/**
|
|
1702
1907
|
* Scan all installed plugins for workflows and jobs.
|
|
1908
|
+
* Purpose: listing — populates the REST API GET /api/v1/workflows catalog.
|
|
1909
|
+
* (CronDiscovery in daemon handles scheduling — different output, different consumer.)
|
|
1703
1910
|
*
|
|
1704
1911
|
* Returns unified WorkflowRuntime representations.
|
|
1705
1912
|
*/
|
|
1706
1913
|
async scanPlugins() {
|
|
1707
|
-
const cacheKey = "manifest-scanner:workflows";
|
|
1708
1914
|
if (this.platform.cache) {
|
|
1709
|
-
const cached = await this.platform.cache.get(
|
|
1915
|
+
const cached = await this.platform.cache.get(CACHE_KEY_WORKFLOWS);
|
|
1710
1916
|
if (cached) {
|
|
1711
1917
|
this.platform.logger?.debug("ManifestScanner: Using cached workflows", { count: cached.length });
|
|
1712
1918
|
return cached;
|
|
@@ -1738,11 +1944,11 @@ var ManifestScanner = class {
|
|
|
1738
1944
|
}
|
|
1739
1945
|
try {
|
|
1740
1946
|
if (converter === "workflow") {
|
|
1741
|
-
workflows.push(this.convertWorkflowHandler(entity.ref.pluginId, entity.declaration, root));
|
|
1947
|
+
workflows.push(this.converter.convertWorkflowHandler(entity.ref.pluginId, entity.declaration, root));
|
|
1742
1948
|
} else if (converter === "job") {
|
|
1743
|
-
workflows.push(this.convertJobHandler(entity.ref.pluginId, entity.declaration, root));
|
|
1949
|
+
workflows.push(this.converter.convertJobHandler(entity.ref.pluginId, entity.declaration, root));
|
|
1744
1950
|
} else {
|
|
1745
|
-
workflows.push(this.convertCronSchedule(entity.ref.pluginId, entity.declaration, root));
|
|
1951
|
+
workflows.push(this.converter.convertCronSchedule(entity.ref.pluginId, entity.declaration, root));
|
|
1746
1952
|
}
|
|
1747
1953
|
} catch (err) {
|
|
1748
1954
|
this.platform.logger?.warn("ManifestScanner: Failed to convert entity", {
|
|
@@ -1781,91 +1987,10 @@ var ManifestScanner = class {
|
|
|
1781
1987
|
templates: templatesCount
|
|
1782
1988
|
});
|
|
1783
1989
|
if (this.platform.cache) {
|
|
1784
|
-
await this.platform.cache.set(
|
|
1990
|
+
await this.platform.cache.set(CACHE_KEY_WORKFLOWS, workflows, this.cacheTtlMs);
|
|
1785
1991
|
}
|
|
1786
1992
|
return workflows;
|
|
1787
1993
|
}
|
|
1788
|
-
/**
|
|
1789
|
-
* Convert workflow handler declaration to WorkflowRuntime.
|
|
1790
|
-
*/
|
|
1791
|
-
convertWorkflowHandler(pluginId, handler, pluginRoot) {
|
|
1792
|
-
const id = `${pluginId}/${handler.id}`;
|
|
1793
|
-
return {
|
|
1794
|
-
id,
|
|
1795
|
-
source: "manifest",
|
|
1796
|
-
pluginId,
|
|
1797
|
-
manifestPath: pluginRoot,
|
|
1798
|
-
name: handler.describe ?? handler.id,
|
|
1799
|
-
description: handler.describe,
|
|
1800
|
-
tags: ["plugin", pluginId],
|
|
1801
|
-
triggers: [
|
|
1802
|
-
{ type: "manual" }
|
|
1803
|
-
// Workflow handlers can always be triggered manually
|
|
1804
|
-
],
|
|
1805
|
-
handler: handler.handler,
|
|
1806
|
-
status: "active",
|
|
1807
|
-
permissions: handler.permissions,
|
|
1808
|
-
input: handler.input,
|
|
1809
|
-
output: handler.output
|
|
1810
|
-
};
|
|
1811
|
-
}
|
|
1812
|
-
/**
|
|
1813
|
-
* Convert job handler declaration to WorkflowRuntime.
|
|
1814
|
-
*/
|
|
1815
|
-
convertJobHandler(pluginId, handler, pluginRoot) {
|
|
1816
|
-
const id = `${pluginId}:job:${handler.id}`;
|
|
1817
|
-
return {
|
|
1818
|
-
id,
|
|
1819
|
-
source: "manifest",
|
|
1820
|
-
pluginId,
|
|
1821
|
-
manifestPath: pluginRoot,
|
|
1822
|
-
name: handler.describe ?? handler.id,
|
|
1823
|
-
description: handler.describe,
|
|
1824
|
-
tags: ["plugin", "job", pluginId],
|
|
1825
|
-
triggers: [
|
|
1826
|
-
{ type: "manual" }
|
|
1827
|
-
// Job handlers are invoked on-demand via ctx.api.jobs.submit()
|
|
1828
|
-
],
|
|
1829
|
-
handler: handler.handler,
|
|
1830
|
-
status: "active",
|
|
1831
|
-
permissions: handler.permissions,
|
|
1832
|
-
input: handler.input,
|
|
1833
|
-
output: handler.output
|
|
1834
|
-
};
|
|
1835
|
-
}
|
|
1836
|
-
/**
|
|
1837
|
-
* Convert cron schedule declaration to WorkflowRuntime.
|
|
1838
|
-
*/
|
|
1839
|
-
convertCronSchedule(pluginId, cronDecl, pluginRoot) {
|
|
1840
|
-
const id = `${pluginId}:cron:${cronDecl.id}`;
|
|
1841
|
-
const schedule = {
|
|
1842
|
-
cron: cronDecl.schedule,
|
|
1843
|
-
enabled: cronDecl.enabled ?? true
|
|
1844
|
-
};
|
|
1845
|
-
return {
|
|
1846
|
-
id,
|
|
1847
|
-
source: "manifest",
|
|
1848
|
-
pluginId,
|
|
1849
|
-
manifestPath: pluginRoot,
|
|
1850
|
-
name: cronDecl.describe ?? cronDecl.id,
|
|
1851
|
-
description: cronDecl.describe,
|
|
1852
|
-
tags: ["plugin", "cron", pluginId],
|
|
1853
|
-
triggers: [
|
|
1854
|
-
{
|
|
1855
|
-
type: "schedule",
|
|
1856
|
-
config: { cron: cronDecl.schedule, timezone: cronDecl.timezone }
|
|
1857
|
-
}
|
|
1858
|
-
],
|
|
1859
|
-
// Note: Cron schedules reference a job type to execute
|
|
1860
|
-
// The actual handler path comes from the job declaration
|
|
1861
|
-
handler: void 0,
|
|
1862
|
-
// Will be resolved at execution time via job type
|
|
1863
|
-
schedule,
|
|
1864
|
-
status: cronDecl.enabled ?? true ? "active" : "disabled",
|
|
1865
|
-
permissions: cronDecl.permissions
|
|
1866
|
-
};
|
|
1867
|
-
}
|
|
1868
|
-
// Legacy convertLegacyJob method removed - use cron schedules instead
|
|
1869
1994
|
/**
|
|
1870
1995
|
* Scan all installed plugins for job handlers only.
|
|
1871
1996
|
*
|
|
@@ -1895,7 +2020,7 @@ var ManifestScanner = class {
|
|
|
1895
2020
|
*/
|
|
1896
2021
|
async clearCache() {
|
|
1897
2022
|
if (this.platform.cache) {
|
|
1898
|
-
await this.platform.cache.delete(
|
|
2023
|
+
await this.platform.cache.delete(CACHE_KEY_WORKFLOWS);
|
|
1899
2024
|
this.platform.logger?.debug("ManifestScanner: Cache cleared");
|
|
1900
2025
|
}
|
|
1901
2026
|
}
|
|
@@ -2007,7 +2132,10 @@ var WorkflowRepository = class {
|
|
|
2007
2132
|
* Delete workflow.
|
|
2008
2133
|
*/
|
|
2009
2134
|
async delete(id) {
|
|
2010
|
-
const path = this.getWorkflowPath(id);
|
|
2135
|
+
const path = await this.getWorkflowPath(id);
|
|
2136
|
+
if (!path) {
|
|
2137
|
+
throw new Error(`Invalid workflow ID for deletion: ${id}`);
|
|
2138
|
+
}
|
|
2011
2139
|
try {
|
|
2012
2140
|
await this.platform.storage.delete(path);
|
|
2013
2141
|
this.platform.logger?.info("WorkflowRepository: Deleted workflow", { id });
|
|
@@ -2078,21 +2206,22 @@ var WorkflowRepository = class {
|
|
|
2078
2206
|
status
|
|
2079
2207
|
});
|
|
2080
2208
|
}
|
|
2081
|
-
getWorkflowPath(id) {
|
|
2209
|
+
async getWorkflowPath(id) {
|
|
2210
|
+
if (id.includes("/") || id.includes("\\") || id.includes("..") || id.startsWith(".")) {
|
|
2211
|
+
return null;
|
|
2212
|
+
}
|
|
2082
2213
|
const ymlPath = join(this.absoluteStorageDir, `${id}.yml`);
|
|
2083
2214
|
const yamlPath = join(this.absoluteStorageDir, `${id}.yaml`);
|
|
2084
|
-
|
|
2085
|
-
return ymlPath;
|
|
2086
|
-
}
|
|
2087
|
-
return yamlPath;
|
|
2215
|
+
return access(ymlPath).then(() => ymlPath).catch(() => yamlPath);
|
|
2088
2216
|
}
|
|
2089
2217
|
async saveWorkflow(id, workflow) {
|
|
2090
|
-
const path = this.getWorkflowPath(id);
|
|
2218
|
+
const path = await this.getWorkflowPath(id);
|
|
2219
|
+
if (!path) {
|
|
2220
|
+
throw new Error(`Invalid workflow ID for storage: ${id}`);
|
|
2221
|
+
}
|
|
2091
2222
|
const yaml = stringify(workflow, { indent: 2 });
|
|
2092
2223
|
try {
|
|
2093
|
-
|
|
2094
|
-
await mkdir(this.absoluteStorageDir, { recursive: true });
|
|
2095
|
-
}
|
|
2224
|
+
await mkdir(this.absoluteStorageDir, { recursive: true });
|
|
2096
2225
|
await writeFile(path, yaml, "utf-8");
|
|
2097
2226
|
} catch (error) {
|
|
2098
2227
|
this.platform.logger?.error(
|
|
@@ -2104,17 +2233,17 @@ var WorkflowRepository = class {
|
|
|
2104
2233
|
}
|
|
2105
2234
|
}
|
|
2106
2235
|
async loadWorkflow(id) {
|
|
2107
|
-
const path = this.getWorkflowPath(id);
|
|
2236
|
+
const path = await this.getWorkflowPath(id);
|
|
2237
|
+
if (!path) {
|
|
2238
|
+
return null;
|
|
2239
|
+
}
|
|
2108
2240
|
try {
|
|
2109
|
-
if (!existsSync(path)) {
|
|
2110
|
-
return null;
|
|
2111
|
-
}
|
|
2112
2241
|
const content = await readFile(path, "utf-8");
|
|
2113
2242
|
const parsed = parse(content);
|
|
2114
2243
|
if (parsed.id && parsed.spec && parsed.createdAt) {
|
|
2115
|
-
return parsed;
|
|
2244
|
+
return { ...parsed, id };
|
|
2116
2245
|
}
|
|
2117
|
-
const
|
|
2246
|
+
const candidateSpec = {
|
|
2118
2247
|
name: parsed.name,
|
|
2119
2248
|
version: parsed.version || "1.0.0",
|
|
2120
2249
|
description: parsed.description,
|
|
@@ -2125,8 +2254,20 @@ var WorkflowRepository = class {
|
|
|
2125
2254
|
env: parsed.env,
|
|
2126
2255
|
secrets: parsed.secrets
|
|
2127
2256
|
};
|
|
2257
|
+
const validation = WorkflowSpecSchema.safeParse(candidateSpec);
|
|
2258
|
+
if (!validation.success) {
|
|
2259
|
+
this.platform.logger?.warn(
|
|
2260
|
+
`WorkflowRepository: skipping invalid workflow "${id}" \u2014 ${validation.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ")}`,
|
|
2261
|
+
{ id, path }
|
|
2262
|
+
);
|
|
2263
|
+
return null;
|
|
2264
|
+
}
|
|
2265
|
+
const spec = validation.data;
|
|
2128
2266
|
const stored = {
|
|
2129
|
-
id
|
|
2267
|
+
// The filename is the canonical id for file-based workflows. Using the
|
|
2268
|
+
// inner `id:` here made list() report an id that get()/getWorkflowPath()
|
|
2269
|
+
// (which resolve by filename) could not find → POST /runs 404 (B-016).
|
|
2270
|
+
id,
|
|
2130
2271
|
spec,
|
|
2131
2272
|
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2132
2273
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -2147,9 +2288,6 @@ var WorkflowRepository = class {
|
|
|
2147
2288
|
}
|
|
2148
2289
|
async listWorkflowFiles() {
|
|
2149
2290
|
try {
|
|
2150
|
-
if (!existsSync(this.absoluteStorageDir)) {
|
|
2151
|
-
return [];
|
|
2152
|
-
}
|
|
2153
2291
|
const files = await readdir(this.absoluteStorageDir);
|
|
2154
2292
|
return files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml"));
|
|
2155
2293
|
} catch (error) {
|
|
@@ -2212,7 +2350,9 @@ var WorkflowRepository = class {
|
|
|
2212
2350
|
// Store full spec for execution
|
|
2213
2351
|
input: spec,
|
|
2214
2352
|
// Expose declared input schema for REST API / Studio UI
|
|
2215
|
-
inputSchema: spec.inputs
|
|
2353
|
+
inputSchema: spec.inputs,
|
|
2354
|
+
version: spec.version,
|
|
2355
|
+
updatedAt: stored.updatedAt
|
|
2216
2356
|
};
|
|
2217
2357
|
}
|
|
2218
2358
|
};
|
|
@@ -2265,15 +2405,30 @@ var WorkflowService = class {
|
|
|
2265
2405
|
}
|
|
2266
2406
|
/**
|
|
2267
2407
|
* Get workflow by ID (from either source).
|
|
2408
|
+
*
|
|
2409
|
+
* Lookup order:
|
|
2410
|
+
* 1. Standalone by filename-derived id (canonical, fast path)
|
|
2411
|
+
* 2. Standalone by `name:` field — allows `--workflow-id=dev-cycle` even
|
|
2412
|
+
* when the file is named `03-dev-cycle.yml` (F8 ergonomics fix)
|
|
2413
|
+
* 3. Manifest-based by id
|
|
2414
|
+
* 4. Manifest-based by name field
|
|
2268
2415
|
*/
|
|
2269
2416
|
async get(id) {
|
|
2270
2417
|
const standalone = await this.repository.get(id);
|
|
2271
2418
|
if (standalone) {
|
|
2272
2419
|
return standalone;
|
|
2273
2420
|
}
|
|
2421
|
+
const allStandalone = await this.repository.list();
|
|
2422
|
+
const byName = allStandalone.find((w) => w.name === id);
|
|
2423
|
+
if (byName) {
|
|
2424
|
+
return byName;
|
|
2425
|
+
}
|
|
2274
2426
|
const manifestWorkflows = await this.scanner.scanPlugins();
|
|
2275
2427
|
const manifest = manifestWorkflows.find((w) => w.id === id);
|
|
2276
|
-
|
|
2428
|
+
if (manifest) {
|
|
2429
|
+
return manifest;
|
|
2430
|
+
}
|
|
2431
|
+
return manifestWorkflows.find((w) => w.name === id) ?? null;
|
|
2277
2432
|
}
|
|
2278
2433
|
/**
|
|
2279
2434
|
* Create standalone workflow.
|
|
@@ -2417,163 +2572,6 @@ var WorkflowService = class {
|
|
|
2417
2572
|
this.platform.logger?.info("WorkflowService: Manifest cache cleared");
|
|
2418
2573
|
}
|
|
2419
2574
|
};
|
|
2420
|
-
|
|
2421
|
-
// src/workflow-schedule-manager.ts
|
|
2422
|
-
var WorkflowScheduleManager = class {
|
|
2423
|
-
cronManager;
|
|
2424
|
-
workflowService;
|
|
2425
|
-
executor;
|
|
2426
|
-
platform;
|
|
2427
|
-
constructor(options) {
|
|
2428
|
-
this.cronManager = options.cronManager;
|
|
2429
|
-
this.workflowService = options.workflowService;
|
|
2430
|
-
this.executor = options.executor;
|
|
2431
|
-
this.platform = options.platform;
|
|
2432
|
-
}
|
|
2433
|
-
/**
|
|
2434
|
-
* Register all scheduled workflows with CronManager.
|
|
2435
|
-
*
|
|
2436
|
-
* Scans both manifest-based jobs and standalone workflows with schedules.
|
|
2437
|
-
*/
|
|
2438
|
-
async registerAll() {
|
|
2439
|
-
const workflows = await this.workflowService.listAll();
|
|
2440
|
-
const scheduled = workflows.filter(
|
|
2441
|
-
(w) => w.schedule && w.schedule.enabled && w.status === "active"
|
|
2442
|
-
);
|
|
2443
|
-
this.platform.logger?.info("WorkflowScheduleManager: Registering scheduled workflows", {
|
|
2444
|
-
total: workflows.length,
|
|
2445
|
-
scheduled: scheduled.length
|
|
2446
|
-
});
|
|
2447
|
-
await Promise.all(scheduled.map((workflow) => this.register(workflow)));
|
|
2448
|
-
}
|
|
2449
|
-
/**
|
|
2450
|
-
* Register single workflow schedule.
|
|
2451
|
-
*/
|
|
2452
|
-
async register(workflow) {
|
|
2453
|
-
if (!workflow.schedule || !workflow.schedule.enabled) {
|
|
2454
|
-
this.platform.logger?.warn("WorkflowScheduleManager: Cannot register workflow without schedule", {
|
|
2455
|
-
id: workflow.id
|
|
2456
|
-
});
|
|
2457
|
-
return;
|
|
2458
|
-
}
|
|
2459
|
-
const cronId = this.getCronId(workflow.id);
|
|
2460
|
-
const schedule = workflow.schedule.cron;
|
|
2461
|
-
this.cronManager.register(cronId, schedule, async (context) => {
|
|
2462
|
-
this.platform.logger?.info("WorkflowScheduleManager: Executing scheduled workflow", {
|
|
2463
|
-
workflowId: workflow.id,
|
|
2464
|
-
workflowName: workflow.name,
|
|
2465
|
-
runCount: context.runCount
|
|
2466
|
-
});
|
|
2467
|
-
try {
|
|
2468
|
-
const result = await this.executor.execute({
|
|
2469
|
-
workflowId: workflow.id,
|
|
2470
|
-
trigger: "schedule",
|
|
2471
|
-
input: {}
|
|
2472
|
-
});
|
|
2473
|
-
this.platform.logger?.info("WorkflowScheduleManager: Workflow execution started", {
|
|
2474
|
-
workflowId: workflow.id,
|
|
2475
|
-
runId: result.runId
|
|
2476
|
-
});
|
|
2477
|
-
} catch (error) {
|
|
2478
|
-
this.platform.logger?.error(
|
|
2479
|
-
"WorkflowScheduleManager: Workflow execution failed",
|
|
2480
|
-
error instanceof Error ? error : void 0,
|
|
2481
|
-
{
|
|
2482
|
-
workflowId: workflow.id,
|
|
2483
|
-
workflowName: workflow.name
|
|
2484
|
-
}
|
|
2485
|
-
);
|
|
2486
|
-
}
|
|
2487
|
-
});
|
|
2488
|
-
this.platform.logger?.debug("WorkflowScheduleManager: Registered workflow", {
|
|
2489
|
-
workflowId: workflow.id,
|
|
2490
|
-
schedule
|
|
2491
|
-
});
|
|
2492
|
-
}
|
|
2493
|
-
/**
|
|
2494
|
-
* Unregister workflow schedule.
|
|
2495
|
-
*/
|
|
2496
|
-
async unregister(workflowId) {
|
|
2497
|
-
const cronId = this.getCronId(workflowId);
|
|
2498
|
-
this.cronManager.unregister(cronId);
|
|
2499
|
-
this.platform.logger?.debug("WorkflowScheduleManager: Unregistered workflow", {
|
|
2500
|
-
workflowId
|
|
2501
|
-
});
|
|
2502
|
-
}
|
|
2503
|
-
/**
|
|
2504
|
-
* Re-register all schedules (refresh).
|
|
2505
|
-
*
|
|
2506
|
-
* Useful after workflow changes or service restart.
|
|
2507
|
-
*/
|
|
2508
|
-
async refresh() {
|
|
2509
|
-
const allJobs = this.cronManager.list();
|
|
2510
|
-
for (const job of allJobs) {
|
|
2511
|
-
if (job.id.startsWith("workflow:")) {
|
|
2512
|
-
this.cronManager.unregister(job.id);
|
|
2513
|
-
}
|
|
2514
|
-
}
|
|
2515
|
-
await this.registerAll();
|
|
2516
|
-
this.platform.logger?.info("WorkflowScheduleManager: Refreshed all schedules");
|
|
2517
|
-
}
|
|
2518
|
-
/**
|
|
2519
|
-
* Get next run time for scheduled workflow.
|
|
2520
|
-
*/
|
|
2521
|
-
getNextRun(workflowId) {
|
|
2522
|
-
const cronId = this.getCronId(workflowId);
|
|
2523
|
-
const job = this.cronManager.list().find((j) => j.id === cronId);
|
|
2524
|
-
return job?.nextRun ?? null;
|
|
2525
|
-
}
|
|
2526
|
-
/**
|
|
2527
|
-
* Get last run time for scheduled workflow.
|
|
2528
|
-
*/
|
|
2529
|
-
getLastRun(workflowId) {
|
|
2530
|
-
const cronId = this.getCronId(workflowId);
|
|
2531
|
-
const job = this.cronManager.list().find((j) => j.id === cronId);
|
|
2532
|
-
return job?.lastRun ?? null;
|
|
2533
|
-
}
|
|
2534
|
-
/**
|
|
2535
|
-
* Pause scheduled workflow.
|
|
2536
|
-
*/
|
|
2537
|
-
pause(workflowId) {
|
|
2538
|
-
const cronId = this.getCronId(workflowId);
|
|
2539
|
-
this.cronManager.pause(cronId);
|
|
2540
|
-
this.platform.logger?.info("WorkflowScheduleManager: Paused workflow schedule", {
|
|
2541
|
-
workflowId
|
|
2542
|
-
});
|
|
2543
|
-
}
|
|
2544
|
-
/**
|
|
2545
|
-
* Resume paused workflow schedule.
|
|
2546
|
-
*/
|
|
2547
|
-
resume(workflowId) {
|
|
2548
|
-
const cronId = this.getCronId(workflowId);
|
|
2549
|
-
this.cronManager.resume(cronId);
|
|
2550
|
-
this.platform.logger?.info("WorkflowScheduleManager: Resumed workflow schedule", {
|
|
2551
|
-
workflowId
|
|
2552
|
-
});
|
|
2553
|
-
}
|
|
2554
|
-
/**
|
|
2555
|
-
* List all scheduled workflows.
|
|
2556
|
-
*/
|
|
2557
|
-
listScheduled() {
|
|
2558
|
-
return this.cronManager.list().filter((job) => job.id.startsWith("workflow:")).map((job) => ({
|
|
2559
|
-
workflowId: this.getWorkflowId(job.id),
|
|
2560
|
-
schedule: job.schedule,
|
|
2561
|
-
status: job.status,
|
|
2562
|
-
lastRun: job.lastRun,
|
|
2563
|
-
nextRun: job.nextRun,
|
|
2564
|
-
runCount: job.runCount
|
|
2565
|
-
}));
|
|
2566
|
-
}
|
|
2567
|
-
// ============================================================================
|
|
2568
|
-
// Private helpers
|
|
2569
|
-
// ============================================================================
|
|
2570
|
-
getCronId(workflowId) {
|
|
2571
|
-
return `workflow:${workflowId}`;
|
|
2572
|
-
}
|
|
2573
|
-
getWorkflowId(cronId) {
|
|
2574
|
-
return cronId.replace("workflow:", "");
|
|
2575
|
-
}
|
|
2576
|
-
};
|
|
2577
2575
|
var WorkflowRegistry = class {
|
|
2578
2576
|
constructor(options) {
|
|
2579
2577
|
this.options = options;
|
|
@@ -3121,6 +3119,6 @@ var JobManager = class {
|
|
|
3121
3119
|
}
|
|
3122
3120
|
};
|
|
3123
3121
|
|
|
3124
|
-
export { ArtifactMerger, ConcurrencyManager, EnvSecretProvider, EventBusBridge, JobManager, ManifestScanner, RunCoordinator, RunSnapshotStorage, Scheduler, StateStore, WorkflowEngine, WorkflowLoader, WorkflowRegistry, WorkflowRepository,
|
|
3122
|
+
export { ArtifactMerger, ConcurrencyManager, EnvSecretProvider, EventBusBridge, JobManager, ManifestConverter, ManifestScanner, RunCoordinator, RunSnapshotStorage, Scheduler, StateStore, WorkflowEngine, WorkflowLoader, WorkflowRegistry, WorkflowRepository, WorkflowService, calculateBackoff, createDefaultSecretProvider, shouldRetry };
|
|
3125
3123
|
//# sourceMappingURL=index.js.map
|
|
3126
3124
|
//# sourceMappingURL=index.js.map
|