@kb-labs/workflow-daemon 2.96.0 → 2.98.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.js +66 -23
- package/dist/index.js.map +1 -1
- package/package.json +21 -21
package/dist/index.js
CHANGED
|
@@ -165,6 +165,7 @@ async function createWorkflowWorker(options) {
|
|
|
165
165
|
}
|
|
166
166
|
const jobPromise = (async () => {
|
|
167
167
|
try {
|
|
168
|
+
let wasCancelled = false;
|
|
168
169
|
for (const step of job.steps) {
|
|
169
170
|
if (step.status === "success") {
|
|
170
171
|
continue;
|
|
@@ -175,6 +176,7 @@ async function createWorkflowWorker(options) {
|
|
|
175
176
|
runId: run.id,
|
|
176
177
|
jobId: job.id
|
|
177
178
|
});
|
|
179
|
+
wasCancelled = true;
|
|
178
180
|
break;
|
|
179
181
|
}
|
|
180
182
|
const exprCtx = {
|
|
@@ -476,6 +478,14 @@ async function createWorkflowWorker(options) {
|
|
|
476
478
|
});
|
|
477
479
|
}
|
|
478
480
|
}
|
|
481
|
+
if (wasCancelled) {
|
|
482
|
+
await engine.markJobInterrupted(run.id, job.id);
|
|
483
|
+
jobLogger.info("Job interrupted due to run cancellation", {
|
|
484
|
+
runId: run.id,
|
|
485
|
+
jobId: job.id
|
|
486
|
+
});
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
479
489
|
await engine.markJobCompleted(run.id, job.id);
|
|
480
490
|
const jobDuration = Date.now() - jobStartTime;
|
|
481
491
|
jobLogger.info("Job completed successfully", {
|
|
@@ -716,8 +726,12 @@ async function applyGateSkip(engine, run, job, step, decision, stepLogger) {
|
|
|
716
726
|
pastGate = true;
|
|
717
727
|
continue;
|
|
718
728
|
}
|
|
719
|
-
if (!pastGate)
|
|
720
|
-
|
|
729
|
+
if (!pastGate) {
|
|
730
|
+
continue;
|
|
731
|
+
}
|
|
732
|
+
if (s.spec.id === skipTo || s.id === skipTo) {
|
|
733
|
+
break;
|
|
734
|
+
}
|
|
721
735
|
await stateStore.updateStep(run.id, job.id, s.id, (draft) => {
|
|
722
736
|
draft.status = "success";
|
|
723
737
|
draft.outputs = { skipped: true };
|
|
@@ -1830,6 +1844,9 @@ var WorkflowHostService = class {
|
|
|
1830
1844
|
if (!sourceRun) {
|
|
1831
1845
|
throw new Error("Run not found");
|
|
1832
1846
|
}
|
|
1847
|
+
if (request.failedOnly) {
|
|
1848
|
+
return this.rerunFailedOnly(sourceRun.id, sourceRun);
|
|
1849
|
+
}
|
|
1833
1850
|
const workflowId = sourceRun.metadata?.["workflowId"] ?? sourceRun.name;
|
|
1834
1851
|
const workflowService = this.requireWorkflowService();
|
|
1835
1852
|
const workflow = await workflowService.get(workflowId);
|
|
@@ -1837,26 +1854,9 @@ var WorkflowHostService = class {
|
|
|
1837
1854
|
throw new Error("Workflow not found");
|
|
1838
1855
|
}
|
|
1839
1856
|
const specInput = workflow.input;
|
|
1840
|
-
|
|
1857
|
+
const spec = {
|
|
1841
1858
|
...specInput
|
|
1842
1859
|
};
|
|
1843
|
-
if (request.failedOnly) {
|
|
1844
|
-
const failedJobNames = new Set(
|
|
1845
|
-
(sourceRun.jobs ?? []).filter((job) => job.status === "failed" || job.status === "interrupted").map((job) => job.jobName)
|
|
1846
|
-
);
|
|
1847
|
-
if (failedJobNames.size === 0) {
|
|
1848
|
-
throw new Error("No failed jobs to rerun");
|
|
1849
|
-
}
|
|
1850
|
-
const filteredJobs = {};
|
|
1851
|
-
for (const [name, jobSpec] of Object.entries(spec.jobs)) {
|
|
1852
|
-
if (failedJobNames.has(name)) {
|
|
1853
|
-
const js = jobSpec;
|
|
1854
|
-
const needs = Array.isArray(js["needs"]) ? js["needs"].filter((dep) => failedJobNames.has(dep)) : void 0;
|
|
1855
|
-
filteredJobs[name] = needs !== void 0 && needs.length < js["needs"].length ? { ...js, needs } : js;
|
|
1856
|
-
}
|
|
1857
|
-
}
|
|
1858
|
-
spec = { ...spec, jobs: filteredJobs };
|
|
1859
|
-
}
|
|
1860
1860
|
const resolvedInputs = sourceRun.inputs ?? {};
|
|
1861
1861
|
const run = await this.options.engine.runFromSpec(spec, {
|
|
1862
1862
|
trigger: {
|
|
@@ -1871,14 +1871,55 @@ var WorkflowHostService = class {
|
|
|
1871
1871
|
status: run.status
|
|
1872
1872
|
};
|
|
1873
1873
|
}
|
|
1874
|
+
/**
|
|
1875
|
+
* failedOnly reruns resume from the source run's snapshot instead of
|
|
1876
|
+
* starting a brand-new run (via runFromSpec + a job-filtered spec), so
|
|
1877
|
+
* completed step outputs from jobs that are not being rerun are carried
|
|
1878
|
+
* forward into the interpolation context instead of being discarded
|
|
1879
|
+
* (see issue #263: `rerun --failed-only` used to redo the whole run).
|
|
1880
|
+
*/
|
|
1881
|
+
async rerunFailedOnly(resolvedRunId, sourceRun) {
|
|
1882
|
+
const failedJobNames = new Set(
|
|
1883
|
+
(sourceRun.jobs ?? []).filter((job) => job.status === "failed" || job.status === "interrupted").map((job) => job.jobName)
|
|
1884
|
+
);
|
|
1885
|
+
if (failedJobNames.size === 0) {
|
|
1886
|
+
throw new Error("No failed jobs to rerun");
|
|
1887
|
+
}
|
|
1888
|
+
let fromStepId;
|
|
1889
|
+
for (const job of sourceRun.jobs ?? []) {
|
|
1890
|
+
if (!failedJobNames.has(job.jobName)) {
|
|
1891
|
+
continue;
|
|
1892
|
+
}
|
|
1893
|
+
const unfinishedStep = (job.steps ?? []).find(
|
|
1894
|
+
(s) => s.status !== "success" && s.status !== "skipped"
|
|
1895
|
+
);
|
|
1896
|
+
if (unfinishedStep) {
|
|
1897
|
+
fromStepId = unfinishedStep.id;
|
|
1898
|
+
break;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1901
|
+
if (!fromStepId) {
|
|
1902
|
+
throw new Error(`Cannot determine resume point: no failed step found in run ${sourceRun.id}`);
|
|
1903
|
+
}
|
|
1904
|
+
const run = await this.options.engine.replayRun(resolvedRunId, { fromStepId });
|
|
1905
|
+
if (!run) {
|
|
1906
|
+
throw new Error(
|
|
1907
|
+
`No replay snapshot for run ${resolvedRunId} \u2014 cannot rerun failed steps only. Snapshots are created when a run finishes; check the workflow daemon logs for "Failed to create run snapshot" around that time.`
|
|
1908
|
+
);
|
|
1909
|
+
}
|
|
1910
|
+
return {
|
|
1911
|
+
runId: run.id,
|
|
1912
|
+
status: run.status
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1874
1915
|
async restartRun(runId, request) {
|
|
1875
1916
|
const resolved = await this.resolveRunId(runId);
|
|
1876
1917
|
if (!resolved) {
|
|
1877
|
-
throw new Error(
|
|
1918
|
+
throw new Error(`Run not found: ${runId}`);
|
|
1878
1919
|
}
|
|
1879
1920
|
const currentRun = await this.options.engine.getRun(resolved);
|
|
1880
1921
|
if (!currentRun) {
|
|
1881
|
-
throw new Error(
|
|
1922
|
+
throw new Error(`Run not found: ${resolved}`);
|
|
1882
1923
|
}
|
|
1883
1924
|
if (currentRun.status === "running" || currentRun.status === "queued") {
|
|
1884
1925
|
throw new Error(
|
|
@@ -1890,7 +1931,9 @@ var WorkflowHostService = class {
|
|
|
1890
1931
|
env: request.env
|
|
1891
1932
|
});
|
|
1892
1933
|
if (!run) {
|
|
1893
|
-
throw new Error(
|
|
1934
|
+
throw new Error(
|
|
1935
|
+
`No replay snapshot for run ${resolved} (status: ${currentRun.status}, finished: ${currentRun.finishedAt ?? "n/a"}). Snapshots are created when a run finishes; if this run finished but has no snapshot, check the workflow daemon logs for "Failed to create run snapshot" around that time.`
|
|
1936
|
+
);
|
|
1894
1937
|
}
|
|
1895
1938
|
return {
|
|
1896
1939
|
runId: run.id,
|