@withone/cli 1.23.0 → 1.24.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.
@@ -447,6 +447,22 @@ var execAsync = promisify(exec);
447
447
  function sleep2(ms) {
448
448
  return new Promise((resolve) => setTimeout(resolve, ms));
449
449
  }
450
+ var StepTimeoutError = class extends Error {
451
+ errorCode = "TIMEOUT";
452
+ constructor(stepId, timeoutMs) {
453
+ super(`Step "${stepId}" exceeded timeout of ${timeoutMs}ms`);
454
+ this.name = "StepTimeoutError";
455
+ }
456
+ };
457
+ function withTimeout(promise, timeoutMs, stepId) {
458
+ let timer;
459
+ const timeoutPromise = new Promise((_, reject) => {
460
+ timer = setTimeout(() => reject(new StepTimeoutError(stepId, timeoutMs)), timeoutMs);
461
+ });
462
+ return Promise.race([promise, timeoutPromise]).finally(() => {
463
+ if (timer) clearTimeout(timer);
464
+ });
465
+ }
450
466
  function computeRetryDelay(onError, attempt) {
451
467
  const base = onError.retryDelayMs ?? 1e3;
452
468
  const max = onError.maxDelayMs ?? 3e4;
@@ -898,7 +914,7 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
898
914
  if (flowStack.includes(resolvedKey)) {
899
915
  throw new Error(`Circular flow detected: ${[...flowStack, resolvedKey].join(" \u2192 ")}`);
900
916
  }
901
- const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-DFUFU2LM.js");
917
+ const { loadFlowWithMeta: loadFlowWithMeta2 } = await import("./flow-runner-LCDHSLUT.js");
902
918
  const { flow: subFlow, rootDir: subRootDir } = loadFlowWithMeta2(resolvedKey);
903
919
  const subContext = await executeFlow(
904
920
  subFlow,
@@ -910,10 +926,35 @@ async function executeSubflowStep(step, context, api, permissions, allowedAction
910
926
  void 0,
911
927
  [...flowStack, resolvedKey]
912
928
  );
929
+ const finalStep = subFlow.steps[subFlow.steps.length - 1];
930
+ const finalOutput = finalStep ? subContext.steps[finalStep.id]?.output : void 0;
931
+ let flattenedOutput;
932
+ if (finalOutput && typeof finalOutput === "object" && !Array.isArray(finalOutput)) {
933
+ const collisions = Object.keys(finalOutput).filter(
934
+ (k) => k in subContext.steps
935
+ );
936
+ if (collisions.length > 0) {
937
+ options.onEvent?.({
938
+ event: "flow:warning",
939
+ message: `Sub-flow "${resolvedKey}" final step output fields [${collisions.join(", ")}] collide with sub-step ids \u2014 flattened fields take precedence.`
940
+ });
941
+ }
942
+ flattenedOutput = {
943
+ ...subContext.steps,
944
+ ...finalOutput,
945
+ _steps: subContext.steps
946
+ };
947
+ } else {
948
+ flattenedOutput = {
949
+ ...subContext.steps,
950
+ _steps: subContext.steps,
951
+ ...finalOutput !== void 0 ? { _finalOutput: finalOutput } : {}
952
+ };
953
+ }
913
954
  return {
914
955
  status: "success",
915
- output: subContext.steps,
916
- response: subContext.steps
956
+ output: flattenedOutput,
957
+ response: flattenedOutput
917
958
  };
918
959
  }
919
960
  async function executePaginateStep(step, context, api, permissions, allowedActionIds, options) {
@@ -1031,47 +1072,37 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
1031
1072
  context.steps[step.id] = result2;
1032
1073
  return result2;
1033
1074
  }
1034
- let result;
1035
- switch (step.type) {
1036
- case "action":
1037
- result = await executeActionStep(step, context, api, permissions, allowedActionIds);
1038
- break;
1039
- case "transform":
1040
- result = executeTransformStep(step, context);
1041
- break;
1042
- case "code":
1043
- result = await executeCodeStep(step, context, options);
1044
- break;
1045
- case "condition":
1046
- result = await executeConditionStep(step, context, api, permissions, allowedActionIds, options, flowStack);
1047
- break;
1048
- case "loop":
1049
- result = await executeLoopStep(step, context, api, permissions, allowedActionIds, options, flowStack);
1050
- break;
1051
- case "parallel":
1052
- result = await executeParallelStep(step, context, api, permissions, allowedActionIds, options, flowStack);
1053
- break;
1054
- case "file-read":
1055
- result = executeFileReadStep(step, context);
1056
- break;
1057
- case "file-write":
1058
- result = executeFileWriteStep(step, context);
1059
- break;
1060
- case "while":
1061
- result = await executeWhileStep(step, context, api, permissions, allowedActionIds, options, flowStack);
1062
- break;
1063
- case "flow":
1064
- result = await executeSubflowStep(step, context, api, permissions, allowedActionIds, options, flowStack);
1065
- break;
1066
- case "paginate":
1067
- result = await executePaginateStep(step, context, api, permissions, allowedActionIds, options);
1068
- break;
1069
- case "bash":
1070
- result = await executeBashStep(step, context, options);
1071
- break;
1072
- default:
1073
- throw new Error(`Unknown step type: ${step.type}`);
1074
- }
1075
+ const dispatch = async () => {
1076
+ switch (step.type) {
1077
+ case "action":
1078
+ return await executeActionStep(step, context, api, permissions, allowedActionIds);
1079
+ case "transform":
1080
+ return executeTransformStep(step, context);
1081
+ case "code":
1082
+ return await executeCodeStep(step, context, options);
1083
+ case "condition":
1084
+ return await executeConditionStep(step, context, api, permissions, allowedActionIds, options, flowStack);
1085
+ case "loop":
1086
+ return await executeLoopStep(step, context, api, permissions, allowedActionIds, options, flowStack);
1087
+ case "parallel":
1088
+ return await executeParallelStep(step, context, api, permissions, allowedActionIds, options, flowStack);
1089
+ case "file-read":
1090
+ return executeFileReadStep(step, context);
1091
+ case "file-write":
1092
+ return executeFileWriteStep(step, context);
1093
+ case "while":
1094
+ return await executeWhileStep(step, context, api, permissions, allowedActionIds, options, flowStack);
1095
+ case "flow":
1096
+ return await executeSubflowStep(step, context, api, permissions, allowedActionIds, options, flowStack);
1097
+ case "paginate":
1098
+ return await executePaginateStep(step, context, api, permissions, allowedActionIds, options);
1099
+ case "bash":
1100
+ return await executeBashStep(step, context, options);
1101
+ default:
1102
+ throw new Error(`Unknown step type: ${step.type}`);
1103
+ }
1104
+ };
1105
+ const result = step.timeoutMs ? await withTimeout(dispatch(), step.timeoutMs, step.id) : await dispatch();
1075
1106
  result.durationMs = Date.now() - startTime;
1076
1107
  if (attempt > 1) {
1077
1108
  result.retries = attempt - 1;
@@ -1093,10 +1124,13 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
1093
1124
  const errorMessage = lastError?.message || "Unknown error";
1094
1125
  const strategy = step.onError?.strategy || "fail";
1095
1126
  const retriesUsed = Math.max(0, maxAttempts - 1);
1127
+ const isTimeout = lastError instanceof StepTimeoutError;
1128
+ const errorCode = lastError?.errorCode;
1096
1129
  if (strategy === "continue") {
1097
1130
  const result = {
1098
- status: "failed",
1131
+ status: isTimeout ? "timeout" : "failed",
1099
1132
  error: errorMessage,
1133
+ ...errorCode ? { errorCode } : {},
1100
1134
  durationMs: Date.now() - startTime,
1101
1135
  retries: retriesUsed
1102
1136
  };
@@ -1105,8 +1139,9 @@ async function executeSingleStep(step, context, api, permissions, allowedActionI
1105
1139
  }
1106
1140
  if (strategy === "fallback" && step.onError?.fallbackStepId) {
1107
1141
  const result = {
1108
- status: "failed",
1142
+ status: isTimeout ? "timeout" : "failed",
1109
1143
  error: errorMessage,
1144
+ ...errorCode ? { errorCode } : {},
1110
1145
  durationMs: Date.now() - startTime,
1111
1146
  retries: retriesUsed
1112
1147
  };
@@ -1243,7 +1278,8 @@ var FLOW_SCHEMA = {
1243
1278
  name: { type: "string", required: true, description: "Human-readable step label" },
1244
1279
  type: { type: "string", required: true, description: "Step type (determines which config object is required)" },
1245
1280
  if: { type: "string", required: false, description: "JS expression \u2014 skip step if falsy" },
1246
- unless: { type: "string", required: false, description: "JS expression \u2014 skip step if truthy" }
1281
+ unless: { type: "string", required: false, description: "JS expression \u2014 skip step if truthy" },
1282
+ timeoutMs: { type: "number", required: false, description: 'Wall-clock timeout (ms). On expiry the step fails with errorCode:"TIMEOUT"; with onError:continue the result gets status:"timeout".' }
1247
1283
  },
1248
1284
  stepTypes: [
1249
1285
  {
@@ -1751,6 +1787,39 @@ Every completed step produces both \`output\` and \`response\`:
1751
1787
  - **Code/transform steps**: \`output\` is the return value. \`response\` is an alias for \`output\`.
1752
1788
  - **In practice**: Use \`$.steps.stepId.response\` for action steps (API data) and \`$.steps.stepId.output\` for code/transform steps (computed data). Both work interchangeably, but using the semantically correct one makes flows easier to read.
1753
1789
 
1790
+ ### Step result metadata (\`status\`, \`error\`, \`errorCode\`)
1791
+
1792
+ Every step result also exposes execution metadata that downstream steps can inspect:
1793
+
1794
+ | Field | Values | When set |
1795
+ |-------|--------|----------|
1796
+ | \`$.steps.X.status\` | \`"success"\` \\| \`"skipped"\` \\| \`"failed"\` \\| \`"timeout"\` | Always |
1797
+ | \`$.steps.X.error\` | error message string | When status is \`failed\` or \`timeout\` |
1798
+ | \`$.steps.X.errorCode\` | machine-readable code (e.g. \`"TIMEOUT"\`) | When the error has a code |
1799
+ | \`$.steps.X.durationMs\` | number | Always |
1800
+ | \`$.steps.X.retries\` | number | When the step was retried |
1801
+
1802
+ This lets downstream steps distinguish \`skipped\` (\`if\` condition false) from \`failed\` (error, \`onError:continue\`) from \`timeout\` (exceeded \`timeoutMs\`) \u2014 e.g. \`"if": "$.steps.enrichment.status === 'timeout'"\` to retry with a longer window.
1803
+
1804
+ ### Sub-flow output (flattened)
1805
+
1806
+ When a step has \`type: "flow"\`, the sub-flow's final step output is flattened onto the parent step's \`output\`:
1807
+
1808
+ \`\`\`jsonc
1809
+ // Sub-flow "sub-consts" has a final step "load" that returns { CHART_URL, API_KEY }
1810
+
1811
+ // Preferred (flattened):
1812
+ "{{$.steps.loadConfig.output.CHART_URL}}"
1813
+
1814
+ // Legacy nested path (still works for backward compatibility):
1815
+ "{{$.steps.loadConfig.output.load.output.CHART_URL}}"
1816
+
1817
+ // Escape hatch for programmatic access to the full sub-flow steps map:
1818
+ "{{$.steps.loadConfig.output._steps.load.output.CHART_URL}}"
1819
+ \`\`\`
1820
+
1821
+ If a sub-step id collides with a flattened field name, the flattened field wins and the engine emits a \`flow:warning\` event.
1822
+
1754
1823
  ## Error Handling
1755
1824
 
1756
1825
  \`\`\`json
@@ -11,7 +11,7 @@ import {
11
11
  saveFlow,
12
12
  summarizeFlowInputs,
13
13
  walkSteps
14
- } from "./chunk-Z45IBT5P.js";
14
+ } from "./chunk-AVOGNLC7.js";
15
15
  export {
16
16
  FlowRunner,
17
17
  collectStepTypes,
package/dist/index.js CHANGED
@@ -16,7 +16,7 @@ import {
16
16
  loadFlowWithMeta,
17
17
  resolveFlowPath,
18
18
  saveFlow
19
- } from "./chunk-Z45IBT5P.js";
19
+ } from "./chunk-AVOGNLC7.js";
20
20
 
21
21
  // src/index.ts
22
22
  import { createRequire as createRequire2 } from "module";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@withone/cli",
3
- "version": "1.23.0",
3
+ "version": "1.24.0",
4
4
  "description": "CLI for managing One",
5
5
  "type": "module",
6
6
  "files": [