@kb-labs/workflow-daemon 2.96.0 → 2.100.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 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) continue;
720
- if (s.spec.id === skipTo || s.id === skipTo) break;
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
- let spec = {
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("Run not found or snapshot not available");
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("Run not found or snapshot not available");
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("Run not found or snapshot not available");
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,